Skip to content

Commit c863b6c

Browse files
feat: otel instrumentation configuration (#1127)
* Introduced the OpenTelemetry configuration for self-instrumentation * Refactored the previous logging config and moved it to the new instrumentation module --------- Co-authored-by: Christian Felipe Álvarez <calvarez@newrelic.com>
1 parent dc30816 commit c863b6c

File tree

24 files changed

+1151
-356
lines changed

24 files changed

+1151
-356
lines changed

Cargo.lock

Lines changed: 397 additions & 227 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

THIRD_PARTY_NOTICES.md

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,11 +1279,36 @@ Distributed under the following license(s):
12791279
Distributed under the following license(s):
12801280

12811281
* MIT
1282+
* Apache-2.0
1283+
## opentelemetry <https://crates.io/crates/opentelemetry>
1284+
1285+
Distributed under the following license(s):
1286+
1287+
* Apache-2.0
1288+
## opentelemetry-http <https://crates.io/crates/opentelemetry-http>
1289+
1290+
Distributed under the following license(s):
1291+
1292+
* Apache-2.0
1293+
## opentelemetry-otlp <https://crates.io/crates/opentelemetry-otlp>
1294+
1295+
Distributed under the following license(s):
1296+
1297+
* Apache-2.0
1298+
## opentelemetry-proto <https://crates.io/crates/opentelemetry-proto>
1299+
1300+
Distributed under the following license(s):
1301+
12821302
* Apache-2.0
12831303
## opentelemetry-semantic-conventions <https://crates.io/crates/opentelemetry-semantic-conventions>
12841304

12851305
Distributed under the following license(s):
12861306

1307+
* Apache-2.0
1308+
## opentelemetry_sdk <https://crates.io/crates/opentelemetry_sdk>
1309+
1310+
Distributed under the following license(s):
1311+
12871312
* Apache-2.0
12881313
## ordered-float <https://crates.io/crates/ordered-float>
12891314

@@ -1464,6 +1489,13 @@ Distributed under the following license(s):
14641489

14651490
* MIT
14661491
* Apache-2.0
1492+
## r-efi <https://crates.io/crates/r-efi>
1493+
1494+
Distributed under the following license(s):
1495+
1496+
* MIT
1497+
* Apache-2.0
1498+
* LGPL-2.1-or-later
14671499
## rand <https://crates.io/crates/rand>
14681500

14691501
Distributed under the following license(s):
@@ -1934,6 +1966,11 @@ Distributed under the following license(s):
19341966

19351967
Distributed under the following license(s):
19361968

1969+
* MIT
1970+
## tracing-opentelemetry <https://crates.io/crates/tracing-opentelemetry>
1971+
1972+
Distributed under the following license(s):
1973+
19371974
* MIT
19381975
## tracing-subscriber <https://crates.io/crates/tracing-subscriber>
19391976

@@ -2090,6 +2127,12 @@ Distributed under the following license(s):
20902127

20912128
Distributed under the following license(s):
20922129

2130+
* MIT
2131+
* Apache-2.0
2132+
## web-time <https://crates.io/crates/web-time>
2133+
2134+
Distributed under the following license(s):
2135+
20932136
* MIT
20942137
* Apache-2.0
20952138
## webpki <https://crates.io/crates/webpki>
@@ -2270,13 +2313,6 @@ Distributed under the following license(s):
22702313

22712314
Distributed under the following license(s):
22722315

2273-
* BSD-2-Clause
2274-
* Apache-2.0
2275-
* MIT
2276-
## zerocopy-derive <https://crates.io/crates/zerocopy-derive>
2277-
2278-
Distributed under the following license(s):
2279-
22802316
* BSD-2-Clause
22812317
* Apache-2.0
22822318
* MIT

agent-control/Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ serde = { workspace = true, features = ["derive"] }
1717
thiserror = { workspace = true }
1818
nix = { workspace = true, features = ["signal", "user", "hostname"] }
1919
tracing-subscriber = { workspace = true, features = ["env-filter", "chrono"] }
20-
tracing = { workspace = true }
20+
tracing = { workspace = true, features = ["attributes"] }
2121
ctrlc = { version = "3.4.5", features = ["termination"] }
2222
serde_yaml = { workspace = true }
2323
regex = { workspace = true }
@@ -67,6 +67,17 @@ rustls = { version = "0.23.23", features = ["ring"] }
6767
webpki = { version = "0.22.4", features = ["alloc"] }
6868
x509-parser = "0.17.0"
6969
ring = "0.17.13"
70+
bytes="1.10.1"
71+
opentelemetry = { version = "0.28.0", features = [
72+
"trace",
73+
] }
74+
opentelemetry_sdk = { version = "0.28.0", features = [
75+
"trace",
76+
] }
77+
tracing-opentelemetry = "0.29.0"
78+
opentelemetry-otlp = { version = "0.28.0", features = ["reqwest-rustls"] }
79+
opentelemetry-http = { version = "0.28.0"}
80+
async-trait = "0.1.86"
7081

7182
[dev-dependencies]
7283
assert_cmd = { workspace = true }

agent-control/src/agent_control/config.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use super::agent_id::AgentID;
22
use super::http_server::config::ServerConfig;
3-
use crate::agent_type::agent_type_id::AgentTypeID;
43
use crate::http::config::ProxyConfig;
5-
use crate::logging::config::LoggingConfig;
4+
use crate::instrumentation::config::logs::config::LoggingConfig;
65
use crate::opamp::auth::config::AuthConfig;
76
use crate::opamp::remote_config::validators::signature::validator::SignatureValidatorConfig;
87
use crate::opamp::remote_config::RemoteConfigError;
98
use crate::values::yaml_config::YAMLConfig;
9+
use crate::{
10+
agent_type::agent_type_id::AgentTypeID, instrumentation::config::InstrumentationConfig,
11+
};
1012
use http::HeaderMap;
1113
#[cfg(feature = "k8s")]
1214
use kube::api::TypeMeta;
@@ -42,6 +44,9 @@ pub struct AgentControlConfig {
4244

4345
#[serde(default)]
4446
pub proxy: ProxyConfig,
47+
48+
#[serde(default)]
49+
pub self_instrumentation: InstrumentationConfig,
4550
}
4651

4752
#[derive(Error, Debug)]
@@ -238,7 +243,7 @@ pub(crate) mod tests {
238243

239244
use std::path::PathBuf;
240245

241-
use crate::logging::{
246+
use crate::instrumentation::config::logs::{
242247
file_logging::{FileLoggingConfig, LogFilePath},
243248
format::{LoggingFormat, TimestampFormat},
244249
};

agent-control/src/bin/config_migrate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ use newrelic_agent_control::config_migrate::migration::defaults::NEWRELIC_INFRA_
1010
use newrelic_agent_control::config_migrate::migration::migrator::{ConfigMigrator, MigratorError};
1111
use newrelic_agent_control::config_migrate::migration::persister::legacy_config_renamer::LegacyConfigRenamer;
1212
use newrelic_agent_control::config_migrate::migration::persister::values_persister_file::ValuesPersisterFile;
13-
use newrelic_agent_control::logging::config::LoggingConfig;
13+
use newrelic_agent_control::instrumentation::tracing::{try_init_tracing, TracingConfig};
1414
use newrelic_agent_control::values::file::YAMLConfigRepositoryFile;
1515
use std::error::Error;
1616
use std::path::PathBuf;
1717
use std::sync::Arc;
1818
use tracing::{debug, info, warn};
1919

2020
fn main() -> Result<(), Box<dyn Error>> {
21-
// init logging singleton
22-
LoggingConfig::default().try_init(PathBuf::from(AGENT_CONTROL_LOG_DIR))?;
21+
let tracing_config = TracingConfig::from_logging_path(PathBuf::from(AGENT_CONTROL_LOG_DIR));
22+
let _tracer = try_init_tracing(tracing_config);
2323

2424
info!("Starting config conversion tool...");
2525

agent-control/src/bin/main.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use newrelic_agent_control::cli::{AgentControlCliConfig, Cli, CliCommand};
1111
use newrelic_agent_control::event::channel::{pub_sub, EventPublisher};
1212
use newrelic_agent_control::event::ApplicationEvent;
1313
use newrelic_agent_control::http::tls::install_rustls_default_crypto_provider;
14-
use newrelic_agent_control::logging::config::FileLoggerGuard;
14+
use newrelic_agent_control::instrumentation::tracing::TracingGuardBox;
1515
use std::error::Error;
1616
use std::process::ExitCode;
1717
use tracing::{error, info, trace};
@@ -29,9 +29,9 @@ fn main() -> ExitCode {
2929
return ExitCode::FAILURE;
3030
};
3131

32-
let agent_control_config = match cli_command {
32+
let (agent_control_config, tracer) = match cli_command {
3333
// Agent Control command call instructs normal operation. Continue with required data.
34-
CliCommand::InitAgentControl(cli) => cli,
34+
CliCommand::InitAgentControl(cli, tracer) => (cli, tracer),
3535

3636
// Agent Control command call was a "one-shot" operation. Exit successfully after performing.
3737
CliCommand::OneShot(op) => {
@@ -40,7 +40,7 @@ fn main() -> ExitCode {
4040
}
4141
};
4242

43-
match _main(agent_control_config) {
43+
match _main(agent_control_config, tracer) {
4444
Err(e) => {
4545
error!("The agent control main process exited with an error: {e}");
4646
ExitCode::FAILURE
@@ -62,11 +62,10 @@ fn main() -> ExitCode {
6262
/// Could not read Agent Control config from /invalid/path: error loading the agent control config: \`error retrieving config: \`missing field \`agents\`\`\`
6363
/// Error: ConfigRead(LoadConfigError(ConfigError(missing field \`agents\`)))
6464
/// ```
65-
fn _main(agent_control_config: AgentControlCliConfig) -> Result<(), Box<dyn Error>> {
66-
// Acquire the file logger guard (if any) for the whole duration of the program
67-
// Needed for remaining usages of `tracing` macros in `main`.
68-
let _guard: FileLoggerGuard = agent_control_config.file_logger_guard;
69-
65+
fn _main(
66+
agent_control_config: AgentControlCliConfig,
67+
_tracer: Vec<TracingGuardBox>, // Needs to take ownership of the tracer as it can be shutdown on drop
68+
) -> Result<(), Box<dyn Error>> {
7069
#[cfg(all(unix, feature = "onhost"))]
7170
if !nix::unistd::Uid::effective().is_root() {
7271
return Err("Program must run as root".into());
@@ -89,6 +88,7 @@ fn _main(agent_control_config: AgentControlCliConfig) -> Result<(), Box<dyn Erro
8988
AgentControlRunner::new(agent_control_config.run_config, application_event_consumer)?.run()?;
9089

9190
info!("exiting gracefully");
91+
9292
Ok(())
9393
}
9494

agent-control/src/cli.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
mod one_shot_operation;
77
#[cfg(debug_assertions)]
88
use crate::agent_control::run::set_debug_dirs;
9+
use crate::instrumentation::tracing::{
10+
try_init_tracing, TracingConfig, TracingError, TracingGuardBox,
11+
};
912
use crate::opamp::client_builder::DEFAULT_POLL_INTERVAL;
1013
use crate::values::file::YAMLConfigRepositoryFile;
1114
use crate::{
1215
agent_control::{
1316
config_storer::{loader_storer::AgentControlConfigLoader, store::AgentControlConfigStore},
1417
run::{AgentControlRunConfig, BasePaths},
1518
},
16-
logging::config::{FileLoggerGuard, LoggingError},
1719
utils::binary_metadata::binary_metadata,
1820
};
1921
use clap::Parser;
@@ -26,17 +28,15 @@ use tracing::info;
2628
pub struct AgentControlCliConfig {
2729
/// The configuration to run the agent control
2830
pub run_config: AgentControlRunConfig,
29-
/// Structure that keeps the file logging active
30-
pub file_logger_guard: FileLoggerGuard,
3131
}
3232

3333
/// All possible errors that can happen while running the CLI.
3434
#[derive(Debug, Error)]
3535
pub enum CliError {
36-
/// The logging could not be initialized
37-
#[error("Could not initialize logging: `{0}`")]
38-
LoggingInit(#[from] LoggingError),
39-
/// The K8s config is missing
36+
/// Could not initialize tracer
37+
#[error("Could not initialize tracer: `{0}`")]
38+
TracerError(#[from] TracingError),
39+
/// K8s config is missing
4040
#[error("k8s config missing while running on k8s ")]
4141
K8sConfig(),
4242
/// The config could not be read
@@ -50,7 +50,7 @@ pub enum CliError {
5050
/// What action was requested from the CLI?
5151
pub enum CliCommand {
5252
/// Normal operation requested. Get the required config and continue.
53-
InitAgentControl(AgentControlCliConfig),
53+
InitAgentControl(AgentControlCliConfig, Vec<TracingGuardBox>),
5454
/// Do an "one-shot" operation and exit successfully.
5555
/// In the future, many different operations could be added here.
5656
OneShot(OneShotCommand),
@@ -119,9 +119,20 @@ impl Cli {
119119
)
120120
})?;
121121

122-
let file_logger_guard = agent_control_config
123-
.log
124-
.try_init(base_paths.log_dir.clone())?;
122+
let proxy = agent_control_config
123+
.proxy
124+
.try_with_url_from_env()
125+
.map_err(|err| CliError::InvalidConfig(err.to_string()))?;
126+
127+
let tracing_config = TracingConfig::from_logging_path(base_paths.log_dir.clone())
128+
.with_logging_config(agent_control_config.log)
129+
.with_instrumentation_config(
130+
agent_control_config
131+
.self_instrumentation
132+
.with_proxy_config(proxy.clone()),
133+
);
134+
let tracer = try_init_tracing(tracing_config)?;
135+
125136
info!("{}", binary_metadata());
126137
info!(
127138
"Starting NewRelic Agent Control with config folder '{}'",
@@ -130,10 +141,6 @@ impl Cli {
130141

131142
let opamp = agent_control_config.fleet_control;
132143
let http_server = agent_control_config.server;
133-
let proxy = agent_control_config
134-
.proxy
135-
.try_with_url_from_env()
136-
.map_err(|err| CliError::InvalidConfig(err.to_string()))?;
137144

138145
let run_config = AgentControlRunConfig {
139146
opamp,
@@ -151,12 +158,9 @@ impl Cli {
151158
garbage_collector_interval: DEFAULT_POLL_INTERVAL - std::time::Duration::from_secs(5),
152159
};
153160

154-
let cli_config = AgentControlCliConfig {
155-
run_config,
156-
file_logger_guard,
157-
};
161+
let cli_config = AgentControlCliConfig { run_config };
158162

159-
Ok(CliCommand::InitAgentControl(cli_config))
163+
Ok(CliCommand::InitAgentControl(cli_config, tracer))
160164
}
161165

162166
fn print_version(&self) -> bool {

agent-control/src/http/client.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
//! # Helpers to build a reqwest blocking client and handle responses and handle responses
2+
//!
23
use crate::http::config::HttpConfig;
4+
use async_trait::async_trait;
5+
use bytes::Bytes;
36
use http::Response as HttpResponse;
47
use http::{Request, Response};
58
use nix::NixPath;
69
use nr_auth::http_client::HttpClient as OauthHttpClient;
710
use nr_auth::http_client::HttpClientError as OauthHttpClientError;
811
use opamp_client::http::HttpClientError as OpampHttpClientError;
12+
use opentelemetry_http::HttpError;
913
use reqwest::tls::TlsInfo;
1014
use reqwest::{
1115
blocking::{Client, Response as BlockingResponse},
@@ -131,6 +135,24 @@ impl From<HttpResponseError> for OauthHttpClientError {
131135
}
132136
}
133137

138+
// Implements opentelemetry_http HttpClient so it can be injected to an opentelemetry_otlp exporter
139+
#[async_trait]
140+
impl opentelemetry_http::HttpClient for HttpClient {
141+
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
142+
let (parts, body) = request.into_parts();
143+
let vec_body = Vec::from(body);
144+
let req_vec = Request::from_parts(parts, vec_body);
145+
146+
let response = self.send(req_vec)?;
147+
148+
let (parts, body) = response.into_parts();
149+
let bytes_body = Bytes::from(body);
150+
let resp_vec = Response::from_parts(parts, bytes_body);
151+
152+
Ok(resp_vec)
153+
}
154+
}
155+
134156
/// Helper to build a [HttpResponse<Vec<u8>>] from a reqwest's blocking response.
135157
/// It includes status, version and body. Headers are not included but they could be added if needed.
136158
fn try_build_response(res: BlockingResponse) -> Result<HttpResponse<Vec<u8>>, HttpResponseError> {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod config;
2+
pub mod tracing;
3+
pub mod tracing_layers;

0 commit comments

Comments
 (0)