Skip to content

Commit d5b0dba

Browse files
committed
Add declarative configuration initial version.
1 parent cff5728 commit d5b0dba

23 files changed

+1256
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"opentelemetry-*",
55
"opentelemetry-*/examples/*",
66
"opentelemetry-otlp/tests/*",
7+
"opentelemetry-declarative-config",
78
"examples/*",
89
"stress",
910
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Changelog
2+
3+
## vNext
4+
5+
## v0.1.0
6+
7+
### Added
8+
9+
- Initial declarative configuration
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "opentelemetry-declarative-config"
3+
version = "0.29.1"
4+
description = "Declarative configuration for OpenTelemetry SDK"
5+
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-declarative-config"
6+
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-declarative-config"
7+
readme = "README.md"
8+
categories = [
9+
"development-tools::debugging",
10+
"development-tools::profiling",
11+
]
12+
keywords = ["opentelemetry", "declarative", "metrics", "configuration"]
13+
license = "Apache-2.0"
14+
edition = "2021"
15+
rust-version = "1.75.0"
16+
17+
[package.metadata.docs.rs]
18+
all-features = true
19+
rustdoc-args = ["--cfg", "docsrs"]
20+
21+
[dependencies]
22+
opentelemetry = { version = "0.31.0" }
23+
opentelemetry_sdk = { version = "0.31.0", features = ["experimental_metrics_custom_reader"] }
24+
opentelemetry-stdout = { version = "0.31.0" }
25+
opentelemetry-otlp = { version = "0.31.0" }
26+
serde = { workspace = true, features = ["derive"] }
27+
serde_yaml = "0.9.34"
28+
tokio = { workspace = true, features = ["sync", "rt"], optional = true }
29+
30+
[dev-dependencies]
31+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
32+
33+
[features]
34+
tonic-client = ["opentelemetry-otlp/grpc-tonic", "opentelemetry-otlp/trace", "opentelemetry-otlp/logs", "opentelemetry-otlp/metrics"]
35+
36+
# Keep tonic as the default client
37+
default = ["tonic-client"]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# OpenTelemetry Declarative Configuration
2+
3+
![OpenTelemetry — An observability framework for cloud-native software.][splash]
4+
5+
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
6+
7+
Declarative configuration for applications instrumented with [`OpenTelemetry`].
8+
9+
## OpenTelemetry Overview
10+
11+
OpenTelemetry is an Observability framework and toolkit designed to create and
12+
manage telemetry data such as traces, metrics, and logs. OpenTelemetry is
13+
vendor- and tool-agnostic, meaning that it can be used with a broad variety of
14+
Observability backends, including open source tools like [Jaeger] and
15+
[Prometheus], as well as commercial offerings.
16+
17+
OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other
18+
commercial vendors. OpenTelemetry is focused on the generation, collection,
19+
management, and export of telemetry. A major goal of OpenTelemetry is that you
20+
can easily instrument your applications or systems, no matter their language,
21+
infrastructure, or runtime environment. Crucially, the storage and visualization
22+
of telemetry is intentionally left to other tools.
23+
24+
[`Prometheus`]: https://prometheus.io
25+
[`OpenTelemetry`]: https://crates.io/crates/opentelemetry
26+
27+
## Release Notes
28+
29+
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-declarative-config/CHANGELOG.md).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use opentelemetry_declarative_config::Configurator;
2+
3+
/// Example of configuring OpenTelemetry telemetry using declarative YAML configuration.
4+
5+
#[tokio::main]
6+
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
7+
let configurator = Configurator::new();
8+
let config_yaml = r#"
9+
metrics:
10+
readers:
11+
- periodic:
12+
exporter:
13+
otlp:
14+
protocol: http/protobuf
15+
endpoint: https://backend:4318
16+
stdout:
17+
temporality: cumulative
18+
logs:
19+
processors:
20+
- batch:
21+
exporter:
22+
stdout:
23+
otlp:
24+
protocol: http/protobuf
25+
endpoint: https://backend:4318
26+
resource:
27+
service.name: sample-service
28+
service.version: "1.0.0"
29+
"#;
30+
let result = configurator.configure_telemetry_from_yaml(config_yaml.into());
31+
if let Err(ref e) = result {
32+
panic!("Failed to configure telemetry from YAML file: {}", e);
33+
}
34+
assert!(result.is_ok());
35+
let telemetry_providers = result.unwrap();
36+
assert!(telemetry_providers.meter_provider().is_some());
37+
assert!(telemetry_providers.logs_provider().is_some());
38+
assert!(telemetry_providers.traces_provider().is_none());
39+
40+
println!("All the expected telemetry providers were configured successfully. Shutting down...");
41+
42+
telemetry_providers.shutdown()?;
43+
Ok(())
44+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//! # OpenTelemetry declarative configuration
2+
//!
3+
//! This crate provides a declarative way to configure OpenTelemetry SDKs using YAML files.
4+
5+
pub mod telemetry_config;
6+
pub mod metrics;
7+
pub mod logs;
8+
9+
use opentelemetry::global;
10+
use opentelemetry_sdk::{error::OTelSdkResult, logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider};
11+
12+
use crate::{
13+
logs::{
14+
BatchExporterFactory,
15+
LogsConfig,
16+
LogsBatchExporterFactory,
17+
},
18+
metrics::{
19+
MetricsConfig,
20+
MetricsPeriodicExporterFactory,
21+
MetricsPullExporterFactory,
22+
PeriodicExporterFactory,
23+
PullExporterFactory,
24+
exporters::prometheus_pull_exporter::PrometheusMetricsPullExporterFactory,
25+
},
26+
telemetry_config::TelemetryConfig,
27+
};
28+
29+
pub struct Configurator {
30+
}
31+
32+
impl Configurator {
33+
pub fn new() -> Self {
34+
Self {
35+
}
36+
}
37+
38+
pub fn configure_telemetry_from_yaml(&self, telemetry_config_str: String) -> Result<TelemetryProviders, Box<dyn std::error::Error>> {
39+
let config = TelemetryConfig::from_yaml(&telemetry_config_str)?;
40+
self.configure_telemetry(config)
41+
}
42+
43+
pub fn configure_telemetry_from_yaml_file(&self, file_path: &str) -> Result<TelemetryProviders, Box<dyn std::error::Error>> {
44+
let config = TelemetryConfig::from_yaml_file(file_path)?;
45+
self.configure_telemetry(config)
46+
}
47+
48+
pub fn configure_telemetry(&self, telemetry_config: TelemetryConfig) -> Result<TelemetryProviders, Box<dyn std::error::Error>> {
49+
50+
let mut configured_telemetry_providers = TelemetryProviders::new();
51+
52+
let resource = Self::as_resource(telemetry_config.resource);
53+
if let Some(metrics_config) = telemetry_config.metrics {
54+
let sdk_meter_provider_option = self.build_metrics_sdk_provider(metrics_config, resource.clone())?;
55+
configured_telemetry_providers = configured_telemetry_providers
56+
.with_meter_provider(sdk_meter_provider_option);
57+
}
58+
59+
if let Some(logs_config) = telemetry_config.logs {
60+
let sdk_logger_provider_option = self.build_logs_sdk_provider(logs_config, resource.clone())?;
61+
configured_telemetry_providers = configured_telemetry_providers
62+
.with_logs_provider(sdk_logger_provider_option);
63+
}
64+
65+
//TODO: Add similar configuration for traces and logs when implemented
66+
Ok(configured_telemetry_providers)
67+
}
68+
69+
fn build_metrics_sdk_provider(&self, metrics_config: MetricsConfig, resource: opentelemetry_sdk::Resource) -> Result<SdkMeterProvider, Box<dyn std::error::Error>> {
70+
71+
let mut provider_builder = SdkMeterProvider::builder()
72+
.with_resource(resource);
73+
74+
for reader_config in metrics_config.readers {
75+
if let Some(periodic_reader) = reader_config.periodic {
76+
let periodic_exporter_config = periodic_reader.exporter;
77+
if let Some(periodic_exporter_mapping) = periodic_exporter_config.as_mapping() {
78+
for (key, value) in periodic_exporter_mapping {
79+
if let Some(periodic_exporter_factory_name) = key.as_str() {
80+
let exporter_factory = PeriodicExporterFactory::from_name(periodic_exporter_factory_name)?;
81+
let config = value;
82+
match exporter_factory {
83+
PeriodicExporterFactory::Stdout(factory) => {
84+
let periodic_exporter = factory.create_metrics_periodic_exporter(config)?;
85+
provider_builder = provider_builder.with_periodic_exporter(periodic_exporter);
86+
},
87+
PeriodicExporterFactory::Otlp(factory) => {
88+
let periodic_exporter = factory.create_metrics_periodic_exporter(config)?;
89+
provider_builder = provider_builder.with_periodic_exporter(periodic_exporter);
90+
},
91+
}
92+
} else {
93+
return Err("Invalid periodic exporter factory configuration".into());
94+
}
95+
}
96+
} else {
97+
return Err("Periodic exporter configuration must be defined".into());
98+
}
99+
}
100+
if let Some(pull_reader) = reader_config.pull {
101+
let pull_exporter_config = pull_reader.exporter;
102+
if let Some(pull_exporter_mapping) = pull_exporter_config.as_mapping() {
103+
for (key, value) in pull_exporter_mapping {
104+
if let Some(pull_exporter_factory_name) = key.as_str() {
105+
let exporter_factory = PullExporterFactory::from_name(pull_exporter_factory_name)?;
106+
let config = value;
107+
match exporter_factory {
108+
PullExporterFactory::Prometheus(factory) => {
109+
let factory_casted : PrometheusMetricsPullExporterFactory = factory;
110+
let pull_exporter = factory_casted.create_metrics_pull_exporter(config)?;
111+
provider_builder = provider_builder.with_reader(pull_exporter);
112+
},
113+
}
114+
} else {
115+
return Err("Invalid pull exporter factory configuration".into());
116+
}
117+
}
118+
} else {
119+
return Err("Pull exporter configuration must be defined".into());
120+
}
121+
}
122+
}
123+
124+
let provider = provider_builder.build();
125+
global::set_meter_provider(provider.clone());
126+
Ok(provider)
127+
}
128+
129+
fn build_logs_sdk_provider(&self, logs_config: LogsConfig, resource: opentelemetry_sdk::Resource) -> Result<SdkLoggerProvider, Box<dyn std::error::Error>> {
130+
131+
let mut provider_builder = SdkLoggerProvider::builder()
132+
.with_resource(resource);
133+
134+
for processor_config in logs_config.processors {
135+
if let Some(batch_processor) = processor_config.batch {
136+
let batch_exporter_config = batch_processor.exporter;
137+
if let Some(batch_exporter_mapping) = batch_exporter_config.as_mapping() {
138+
for (key, value) in batch_exporter_mapping {
139+
if let Some(batch_exporter_factory_name) = key.as_str() {
140+
let exporter_factory = BatchExporterFactory::from_name(batch_exporter_factory_name)?;
141+
let config = value;
142+
match exporter_factory {
143+
BatchExporterFactory::Stdout(factory) => {
144+
let batch_exporter = factory.create_logs_batch_exporter(config)?;
145+
provider_builder = provider_builder.with_batch_exporter(batch_exporter);
146+
},
147+
BatchExporterFactory::Otlp(factory) => {
148+
let batch_exporter = factory.create_logs_batch_exporter(config)?;
149+
provider_builder = provider_builder.with_batch_exporter(batch_exporter);
150+
},
151+
}
152+
} else {
153+
return Err("Invalid batch exporter factory configuration".into());
154+
}
155+
}
156+
} else {
157+
return Err("Batch exporter configuration must be defined".into());
158+
}
159+
}
160+
}
161+
162+
let provider = provider_builder.build();
163+
Ok(provider)
164+
}
165+
166+
fn as_resource(resource_attributes: std::collections::HashMap<String, String>) -> opentelemetry_sdk::Resource {
167+
let mut resource_builder = opentelemetry_sdk::Resource::builder();
168+
for (key, value) in resource_attributes {
169+
resource_builder = resource_builder.with_attribute(opentelemetry::KeyValue::new(key, value));
170+
}
171+
resource_builder.build()
172+
}
173+
}
174+
175+
impl Default for Configurator {
176+
fn default() -> Self {
177+
Self::new()
178+
}
179+
}
180+
181+
/// Holds the configured telemetry providers
182+
pub struct TelemetryProviders {
183+
meter_provider: Option<SdkMeterProvider>,
184+
traces_provider: Option<SdkTracerProvider>,
185+
logs_provider: Option<SdkLoggerProvider>,
186+
}
187+
188+
impl TelemetryProviders {
189+
pub fn new() -> Self {
190+
TelemetryProviders {
191+
meter_provider: None,
192+
traces_provider: None,
193+
logs_provider: None,
194+
}
195+
}
196+
197+
pub fn with_meter_provider(mut self, meter_provider: SdkMeterProvider) -> Self {
198+
self.meter_provider = Some(meter_provider);
199+
self
200+
}
201+
202+
pub fn with_traces_provider(mut self, traces_provider: SdkTracerProvider) -> Self {
203+
self.traces_provider = Some(traces_provider);
204+
self
205+
}
206+
pub fn with_logs_provider(mut self, logs_provider: SdkLoggerProvider) -> Self {
207+
self.logs_provider = Some(logs_provider);
208+
self
209+
}
210+
211+
pub fn meter_provider(&self) -> Option<&SdkMeterProvider> {
212+
self.meter_provider.as_ref()
213+
}
214+
215+
pub fn traces_provider(&self) -> Option<&SdkTracerProvider> {
216+
self.traces_provider.as_ref()
217+
}
218+
219+
pub fn logs_provider(&self) -> Option<&SdkLoggerProvider> {
220+
self.logs_provider.as_ref()
221+
}
222+
223+
pub fn shutdown(self) -> OTelSdkResult {
224+
if let Some(meter_provider) = self.meter_provider {
225+
meter_provider.shutdown()?;
226+
}
227+
if let Some(traces_provider) = self.traces_provider {
228+
traces_provider.shutdown()?;
229+
}
230+
if let Some(logs_provider) = self.logs_provider {
231+
logs_provider.shutdown()?;
232+
}
233+
Ok(())
234+
}
235+
}
236+
237+
impl Default for TelemetryProviders {
238+
fn default() -> Self {
239+
Self::new()
240+
}
241+
}
242+
243+
#[cfg(test)]
244+
mod tests {
245+
use super::*;
246+
247+
#[test]
248+
fn test_config_empty() -> Result<(), Box<dyn std::error::Error>> {
249+
let config = Configurator::new();
250+
let telemetry_config = TelemetryConfig::default();
251+
let telemetry_providers = config.configure_telemetry(telemetry_config)?;
252+
253+
assert!(telemetry_providers.meter_provider().is_none());
254+
assert!(telemetry_providers.traces_provider().is_none());
255+
assert!(telemetry_providers.logs_provider().is_none());
256+
257+
assert!(telemetry_providers.shutdown().is_ok());
258+
Ok(())
259+
}
260+
261+
#[test]
262+
fn test_shutdown_empty_providers() {
263+
let providers = TelemetryProviders::new();
264+
assert!(providers.shutdown().is_ok());
265+
}
266+
}

0 commit comments

Comments
 (0)