From d21b13ae1ee5f2c2fef28f4b0743c2f0f2004b93 Mon Sep 17 00:00:00 2001 From: Lalit Kumar Bhasin Date: Thu, 23 May 2024 09:08:35 -0700 Subject: [PATCH 1/2] Integration tests for Logs (#1759) Co-authored-by: Zhongyang Wu --- .../tests/integration_test/Cargo.toml | 3 +- .../expected/failed_logs.json | 36 +++++++ .../tests/integration_test/expected/logs.json | 36 +++++++ .../otel-collector-config.yaml | 5 +- .../tests/integration_test/src/lib.rs | 3 +- .../integration_test/src/logs_asserter.rs | 102 ++++++++++++++++++ .../src/{asserter.rs => trace_asserter.rs} | 0 .../tests/integration_tests.rs | 69 +++++++++--- .../tests/integration_test/tests/logs.rs | 58 ++++++++++ .../tests/integration_test/tests/traces.rs | 2 +- .../tonic/opentelemetry.proto.logs.v1.rs | 7 ++ opentelemetry-proto/tests/grpc_build.rs | 6 +- 12 files changed, 307 insertions(+), 20 deletions(-) create mode 100644 opentelemetry-otlp/tests/integration_test/expected/failed_logs.json create mode 100644 opentelemetry-otlp/tests/integration_test/expected/logs.json create mode 100644 opentelemetry-otlp/tests/integration_test/src/logs_asserter.rs rename opentelemetry-otlp/tests/integration_test/src/{asserter.rs => trace_asserter.rs} (100%) create mode 100644 opentelemetry-otlp/tests/integration_test/tests/logs.rs diff --git a/opentelemetry-otlp/tests/integration_test/Cargo.toml b/opentelemetry-otlp/tests/integration_test/Cargo.toml index b21861d19d..662d7ea9fd 100644 --- a/opentelemetry-otlp/tests/integration_test/Cargo.toml +++ b/opentelemetry-otlp/tests/integration_test/Cargo.toml @@ -12,7 +12,8 @@ opentelemetry_sdk = { path = "../../../opentelemetry-sdk", features = ["rt-tokio opentelemetry-otlp = { path = "../../../opentelemetry-otlp", features = ["tonic", "metrics", "logs"] } opentelemetry-semantic-conventions = { path = "../../../opentelemetry-semantic-conventions" } opentelemetry-proto = { path = "../../../opentelemetry-proto", features = ["gen-tonic-messages", "trace", "with-serde"] } +opentelemetry-appender-log = { path = "../../../opentelemetry-appender-log", default-features = false} +log = { workspace = true } tokio = { version = "1.0", features = ["full"] } serde_json = "1" testcontainers = "0.15.0" -# env_logger = "0.11.3" // uncomment if needed for local debugging. diff --git a/opentelemetry-otlp/tests/integration_test/expected/failed_logs.json b/opentelemetry-otlp/tests/integration_test/expected/failed_logs.json new file mode 100644 index 0000000000..923316dfed --- /dev/null +++ b/opentelemetry-otlp/tests/integration_test/expected/failed_logs.json @@ -0,0 +1,36 @@ +{ + "resourceLogs": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "logs-integration-test" + } + } + ] + }, + "scopeLogs": [ + { + "scope": { + "name": "opentelemetry-log-appender", + "version": "0.3.0" + }, + "logRecords": [ + { + "observedTimeUnixNano": "1715753202587469939", + "severityNumber": 9, + "severityText": "INFO", + "body": { + "stringValue": "hello from apple. My price is 2.99." + }, + "traceId": "", + "spanId": "" + } + ] + } + ] + } + ] +} diff --git a/opentelemetry-otlp/tests/integration_test/expected/logs.json b/opentelemetry-otlp/tests/integration_test/expected/logs.json new file mode 100644 index 0000000000..4653189c82 --- /dev/null +++ b/opentelemetry-otlp/tests/integration_test/expected/logs.json @@ -0,0 +1,36 @@ +{ + "resourceLogs": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "logs-integration-test" + } + } + ] + }, + "scopeLogs": [ + { + "scope": { + "name": "opentelemetry-log-appender", + "version": "0.3.0" + }, + "logRecords": [ + { + "observedTimeUnixNano": "1715753202587469939", + "severityNumber": 9, + "severityText": "INFO", + "body": { + "stringValue": "hello from banana. My price is 2.99." + }, + "traceId": "", + "spanId": "" + } + ] + } + ] + } + ] +} diff --git a/opentelemetry-otlp/tests/integration_test/otel-collector-config.yaml b/opentelemetry-otlp/tests/integration_test/otel-collector-config.yaml index bcd9d9a429..39d755cc9a 100644 --- a/opentelemetry-otlp/tests/integration_test/otel-collector-config.yaml +++ b/opentelemetry-otlp/tests/integration_test/otel-collector-config.yaml @@ -8,10 +8,13 @@ exporters: logging: loglevel: debug file: - path: /testresults/traces.json + path: /testresults/result.json service: pipelines: traces: receivers: [otlp] exporters: [file] + logs: + receivers: [otlp] + exporters: [file] diff --git a/opentelemetry-otlp/tests/integration_test/src/lib.rs b/opentelemetry-otlp/tests/integration_test/src/lib.rs index 54080e6844..d6b7196d84 100644 --- a/opentelemetry-otlp/tests/integration_test/src/lib.rs +++ b/opentelemetry-otlp/tests/integration_test/src/lib.rs @@ -1,2 +1,3 @@ -pub mod asserter; pub mod images; +pub mod logs_asserter; +pub mod trace_asserter; diff --git a/opentelemetry-otlp/tests/integration_test/src/logs_asserter.rs b/opentelemetry-otlp/tests/integration_test/src/logs_asserter.rs new file mode 100644 index 0000000000..da045691f5 --- /dev/null +++ b/opentelemetry-otlp/tests/integration_test/src/logs_asserter.rs @@ -0,0 +1,102 @@ +use opentelemetry_proto::tonic::logs::v1::{LogRecord, LogsData, ResourceLogs}; +use std::fs::File; + +// Given two ResourceLogs, assert that they are equal except for the timestamps +pub struct LogsAsserter { + results: Vec, + expected: Vec, +} + +impl LogsAsserter { + // Create a new LogsAsserter + pub fn new(results: Vec, expected: Vec) -> Self { + LogsAsserter { results, expected } + } + + pub fn assert(self) { + self.assert_resource_logs_eq(&self.results, &self.expected); + } + + fn assert_resource_logs_eq(&self, results: &[ResourceLogs], expected: &[ResourceLogs]) { + let mut results_logs = Vec::new(); + let mut expected_logs = Vec::new(); + + assert_eq!(results.len(), expected.len()); + for i in 0..results.len() { + let result_resource_logs = &results[i]; + let expected_resource_logs = &expected[i]; + assert_eq!( + result_resource_logs.resource, + expected_resource_logs.resource + ); + assert_eq!( + result_resource_logs.schema_url, + expected_resource_logs.schema_url + ); + + assert_eq!( + result_resource_logs.scope_logs.len(), + expected_resource_logs.scope_logs.len() + ); + + for i in 0..result_resource_logs.scope_logs.len() { + let result_scope_logs = &result_resource_logs.scope_logs[i]; + let expected_scope_logs = &expected_resource_logs.scope_logs[i]; + + results_logs.extend(result_scope_logs.log_records.clone()); + expected_logs.extend(expected_scope_logs.log_records.clone()); + } + } + + for (result_log, expected_log) in results_logs.iter().zip(expected_logs.iter()) { + assert_eq!( + LogRecordWrapper(result_log.clone()), + LogRecordWrapper(expected_log.clone()) + ); + } + } +} + +pub struct LogRecordWrapper(pub LogRecord); + +impl PartialEq for LogRecordWrapper { + fn eq(&self, other: &Self) -> bool { + let LogRecordWrapper(ref a) = *self; + let LogRecordWrapper(ref b) = *other; + + assert_eq!( + a.severity_number, b.severity_number, + "severity_number does not match" + ); + assert_eq!( + a.severity_text, b.severity_text, + "severity_text does not match" + ); + assert_eq!(a.body, b.body, "body does not match"); + assert_eq!(a.attributes, b.attributes, "attributes do not match"); + assert_eq!( + a.dropped_attributes_count, b.dropped_attributes_count, + "dropped_attributes_count does not match" + ); + assert_eq!(a.flags, b.flags, "flags do not match"); + assert_eq!(a.trace_id, b.trace_id, "trace_id does not match"); + assert_eq!(a.span_id, b.span_id, "span_id does not match"); + + true + } +} + +impl std::fmt::Debug for LogRecordWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let LogRecordWrapper(ref inner) = *self; + inner.fmt(f) + } +} + +// read a file contains ResourceSpans in json format +pub fn read_logs_from_json(file: File) -> Vec { + let reader = std::io::BufReader::new(file); + + let log_data: LogsData = serde_json::from_reader(reader).unwrap(); + log_data.resource_logs +} diff --git a/opentelemetry-otlp/tests/integration_test/src/asserter.rs b/opentelemetry-otlp/tests/integration_test/src/trace_asserter.rs similarity index 100% rename from opentelemetry-otlp/tests/integration_test/src/asserter.rs rename to opentelemetry-otlp/tests/integration_test/src/trace_asserter.rs diff --git a/opentelemetry-otlp/tests/integration_test/tests/integration_tests.rs b/opentelemetry-otlp/tests/integration_test/tests/integration_tests.rs index a2d4e5b7ab..01df65239a 100644 --- a/opentelemetry-otlp/tests/integration_test/tests/integration_tests.rs +++ b/opentelemetry-otlp/tests/integration_test/tests/integration_tests.rs @@ -8,34 +8,33 @@ use testcontainers::clients::Cli; use testcontainers::core::Port; use testcontainers::RunnableImage; +mod logs; mod traces; const COLLECTOR_CONTAINER_NAME: &str = "otel-collector"; const TEST_RESULT_DIR_IN_CONTAINER: &str = "testresults"; const EXPECTED_DIR: &str = "./expected"; +const RESULT_FILE_PATH: &str = "./result.json"; struct TestSuite { - result_file_path: &'static str, + expected_file_path: &'static str, } impl TestSuite { - fn new(result_file_path: &'static str) -> Self { - Self { result_file_path } + fn new(expected_file_path: &'static str) -> Self { + Self { expected_file_path } } pub fn expected_file_path(&self) -> String { - format!("{}/{}", EXPECTED_DIR, self.result_file_path) + format!("{}/{}", EXPECTED_DIR, self.expected_file_path) } pub fn result_file_path_in_container(&self) -> String { - format!( - "/{}/{}", - TEST_RESULT_DIR_IN_CONTAINER, self.result_file_path - ) + format!("/{}/{}", TEST_RESULT_DIR_IN_CONTAINER, RESULT_FILE_PATH) } pub fn result_file_path(&self) -> String { - format!("./{}", self.result_file_path) + format!("./{}", RESULT_FILE_PATH) } /// Create a empty file on localhost and copy it to container with proper permissions @@ -52,13 +51,12 @@ impl TestSuite { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] #[ignore] // skip when running unit test async fn integration_tests() { - // Uncomment to see logs from testcontainers - // env_logger::init(); - // Run locally using the below command - // cargo test -- --ignored --nocapture + trace_integration_tests().await; + logs_integration_tests().await; +} +async fn trace_integration_tests() { let test_suites = [TestSuite::new("traces.json")]; - let mut collector_image = Collector::default(); for test in test_suites.as_ref() { let _ = test.create_temporary_result_file(); @@ -98,3 +96,46 @@ async fn integration_tests() { collector_container.stop(); } + +async fn logs_integration_tests() { + let test_suites = [TestSuite::new("logs.json")]; + + let mut collector_image = Collector::default(); + for test in test_suites.as_ref() { + let _ = test.create_temporary_result_file(); + collector_image = collector_image.with_volume( + test.result_file_path().as_str(), + test.result_file_path_in_container().as_str(), + ); + } + + let docker = Cli::default(); + let mut image = + RunnableImage::from(collector_image).with_container_name(COLLECTOR_CONTAINER_NAME); + + for port in [ + 4317, // gRPC port + 4318, // HTTP port + ] { + image = image.with_mapped_port(Port { + local: port, + internal: port, + }) + } + + let collector_container = docker.run(image); + + tokio::time::sleep(Duration::from_secs(5)).await; + logs::logs().await.unwrap(); + + // wait for file to flush to disks + // ideally we should use volume mount but otel collector file exporter doesn't handle permission too well + // bind mount mitigate the issue by set up the permission correctly on host system + tokio::time::sleep(Duration::from_secs(5)).await; + logs::assert_logs_results( + test_suites[0].result_file_path().as_str(), + test_suites[0].expected_file_path().as_str(), + ); + + collector_container.stop(); +} diff --git a/opentelemetry-otlp/tests/integration_test/tests/logs.rs b/opentelemetry-otlp/tests/integration_test/tests/logs.rs new file mode 100644 index 0000000000..22fabd0ae9 --- /dev/null +++ b/opentelemetry-otlp/tests/integration_test/tests/logs.rs @@ -0,0 +1,58 @@ +#![cfg(unix)] + +use integration_test_runner::logs_asserter::{read_logs_from_json, LogsAsserter}; +use log::{info, Level}; +use opentelemetry::logs::LogError; +use opentelemetry::KeyValue; +use opentelemetry_appender_log::OpenTelemetryLogBridge; +use opentelemetry_sdk::{logs as sdklogs, runtime, Resource}; +use std::error::Error; +use std::fs::File; +use std::os::unix::fs::MetadataExt; + +fn init_logs() -> Result { + opentelemetry_otlp::new_pipeline() + .logging() + .with_exporter(opentelemetry_otlp::new_exporter().tonic()) + .with_log_config( + sdklogs::config().with_resource(Resource::new(vec![KeyValue::new( + opentelemetry_semantic_conventions::resource::SERVICE_NAME, + "logs-integration-test", + )])), + ) + .install_batch(runtime::Tokio) +} + +pub async fn logs() -> Result<(), Box> { + let logger_provider = init_logs().unwrap(); + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender))?; + log::set_max_level(Level::Info.to_level_filter()); + + info!(target: "my-target", "hello from {}. My price is {}.", "banana", 2.99); + let _ = logger_provider.shutdown(); + Ok(()) +} + +pub fn assert_logs_results(result: &str, expected: &str) { + let left = read_logs_from_json(File::open(expected).unwrap()); + let right = read_logs_from_json(File::open(result).unwrap()); + + LogsAsserter::new(left, right).assert(); + + assert!(File::open(result).unwrap().metadata().unwrap().size() > 0) +} + +#[test] +#[should_panic(expected = "assertion `left == right` failed: body does not match")] +pub fn test_assert_logs_eq_failure() { + let left = read_logs_from_json(File::open("./expected/logs.json").unwrap()); + let right = read_logs_from_json(File::open("./expected/failed_logs.json").unwrap()); + LogsAsserter::new(right, left).assert(); +} + +#[test] +pub fn test_assert_logs_eq() { + let logs = read_logs_from_json(File::open("./expected/logs.json").unwrap()); + LogsAsserter::new(logs.clone(), logs).assert(); +} diff --git a/opentelemetry-otlp/tests/integration_test/tests/traces.rs b/opentelemetry-otlp/tests/integration_test/tests/traces.rs index 64d59e911e..e68e0d36a7 100644 --- a/opentelemetry-otlp/tests/integration_test/tests/traces.rs +++ b/opentelemetry-otlp/tests/integration_test/tests/traces.rs @@ -1,6 +1,6 @@ #![cfg(unix)] -use integration_test_runner::asserter::{read_spans_from_json, TraceAsserter}; +use integration_test_runner::trace_asserter::{read_spans_from_json, TraceAsserter}; use opentelemetry::global; use opentelemetry::global::shutdown_tracer_provider; use opentelemetry::trace::TraceError; diff --git a/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.logs.v1.rs b/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.logs.v1.rs index 487261a893..9b737c5ead 100644 --- a/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.logs.v1.rs +++ b/opentelemetry-proto/src/proto/tonic/opentelemetry.proto.logs.v1.rs @@ -126,6 +126,13 @@ pub struct LogRecord { /// string message (including multi-line) describing the event in a free form or it can /// be a structured data composed of arrays and maps of other values. \[Optional\]. #[prost(message, optional, tag = "5")] + #[cfg_attr( + feature = "with-serde", + serde( + serialize_with = "crate::proto::serializers::serialize_to_value", + deserialize_with = "crate::proto::serializers::deserialize_from_value" + ) + )] pub body: ::core::option::Option, /// Additional attributes that describe the specific event occurrence. \[Optional\]. /// Attribute keys MUST be unique (it is not allowed to have more than one diff --git a/opentelemetry-proto/tests/grpc_build.rs b/opentelemetry-proto/tests/grpc_build.rs index d9265ac4f7..7201a0f8f8 100644 --- a/opentelemetry-proto/tests/grpc_build.rs +++ b/opentelemetry-proto/tests/grpc_build.rs @@ -94,8 +94,10 @@ fn build_tonic() { } // add custom serializer and deserializer for AnyValue - builder = builder - .field_attribute("common.v1.KeyValue.value", "#[cfg_attr(feature =\"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_to_value\", deserialize_with = \"crate::proto::serializers::deserialize_from_value\"))]"); + for path in ["common.v1.KeyValue.value", "logs.v1.LogRecord.body"] { + builder = builder + .field_attribute(path, "#[cfg_attr(feature =\"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_to_value\", deserialize_with = \"crate::proto::serializers::deserialize_from_value\"))]"); + } builder .out_dir(out_dir.path()) From 33abef2a78884daaf3d23388fa1b19752394c198 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Thu, 23 May 2024 09:49:39 -0700 Subject: [PATCH 2/2] Log SDK, OTLP builders to accept Resource directly instead of wrapping in Config (#1788) Co-authored-by: Zhongyang Wu --- examples/logs-basic/src/main.rs | 12 ++- .../benches/logs.rs | 12 ++- .../examples/basic.rs | 15 ++-- opentelemetry-otlp/CHANGELOG.md | 5 ++ .../examples/basic-otlp-http/src/main.rs | 4 +- .../examples/basic-otlp/src/main.rs | 3 +- opentelemetry-otlp/src/logs.rs | 37 +++++---- .../tests/integration_test/tests/logs.rs | 10 +-- opentelemetry-sdk/CHANGELOG.md | 5 ++ opentelemetry-sdk/src/logs/config.rs | 23 ------ opentelemetry-sdk/src/logs/log_emitter.rs | 79 ++++++++----------- opentelemetry-sdk/src/logs/log_processor.rs | 11 ++- opentelemetry-sdk/src/logs/mod.rs | 4 +- opentelemetry-stdout/Cargo.toml | 2 + opentelemetry-stdout/examples/basic.rs | 17 +++- 15 files changed, 110 insertions(+), 129 deletions(-) delete mode 100644 opentelemetry-sdk/src/logs/config.rs diff --git a/examples/logs-basic/src/main.rs b/examples/logs-basic/src/main.rs index b19928b1e2..1bcf75a99d 100644 --- a/examples/logs-basic/src/main.rs +++ b/examples/logs-basic/src/main.rs @@ -1,7 +1,7 @@ use log::{error, Level}; use opentelemetry::KeyValue; use opentelemetry_appender_log::OpenTelemetryLogBridge; -use opentelemetry_sdk::logs::{Config, LoggerProvider}; +use opentelemetry_sdk::logs::LoggerProvider; use opentelemetry_sdk::Resource; use opentelemetry_semantic_conventions::resource::SERVICE_NAME; @@ -13,12 +13,10 @@ fn main() { // Ok(serde_json::to_writer_pretty(writer, &data).unwrap())) .build(); let logger_provider = LoggerProvider::builder() - .with_config( - Config::default().with_resource(Resource::new(vec![KeyValue::new( - SERVICE_NAME, - "logs-basic-example", - )])), - ) + .with_resource(Resource::new(vec![KeyValue::new( + SERVICE_NAME, + "logs-basic-example", + )])) .with_simple_exporter(exporter) .build(); diff --git a/opentelemetry-appender-tracing/benches/logs.rs b/opentelemetry-appender-tracing/benches/logs.rs index f41f99e180..9f73e9c890 100644 --- a/opentelemetry-appender-tracing/benches/logs.rs +++ b/opentelemetry-appender-tracing/benches/logs.rs @@ -19,7 +19,7 @@ use opentelemetry::logs::LogResult; use opentelemetry::KeyValue; use opentelemetry_appender_tracing::layer as tracing_layer; use opentelemetry_sdk::export::logs::{LogData, LogExporter}; -use opentelemetry_sdk::logs::{Config, LogProcessor, LoggerProvider}; +use opentelemetry_sdk::logs::{LogProcessor, LoggerProvider}; use opentelemetry_sdk::Resource; use tracing::error; use tracing_subscriber::prelude::*; @@ -125,12 +125,10 @@ fn benchmark_with_ot_layer(c: &mut Criterion, enabled: bool, bench_name: &str) { let exporter = NoopExporter { enabled }; let processor = NoopProcessor::new(Box::new(exporter)); let provider = LoggerProvider::builder() - .with_config( - Config::default().with_resource(Resource::new(vec![KeyValue::new( - "service.name", - "benchmark", - )])), - ) + .with_resource(Resource::new(vec![KeyValue::new( + "service.name", + "benchmark", + )])) .with_log_processor(processor) .build(); let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider); diff --git a/opentelemetry-appender-tracing/examples/basic.rs b/opentelemetry-appender-tracing/examples/basic.rs index 9e8e8366b3..689c3f7550 100644 --- a/opentelemetry-appender-tracing/examples/basic.rs +++ b/opentelemetry-appender-tracing/examples/basic.rs @@ -2,22 +2,17 @@ use opentelemetry::KeyValue; use opentelemetry_appender_tracing::layer; -use opentelemetry_sdk::{ - logs::{Config, LoggerProvider}, - Resource, -}; +use opentelemetry_sdk::{logs::LoggerProvider, Resource}; use tracing::error; use tracing_subscriber::prelude::*; fn main() { let exporter = opentelemetry_stdout::LogExporter::default(); let provider: LoggerProvider = LoggerProvider::builder() - .with_config( - Config::default().with_resource(Resource::new(vec![KeyValue::new( - "service.name", - "log-appender-tracing-example", - )])), - ) + .with_resource(Resource::new(vec![KeyValue::new( + "service.name", + "log-appender-tracing-example", + )])) .with_simple_exporter(exporter) .build(); let layer = layer::OpenTelemetryTracingBridge::new(&provider); diff --git a/opentelemetry-otlp/CHANGELOG.md b/opentelemetry-otlp/CHANGELOG.md index f39e0f8cec..07213b96aa 100644 --- a/opentelemetry-otlp/CHANGELOG.md +++ b/opentelemetry-otlp/CHANGELOG.md @@ -7,6 +7,11 @@ - `OtlpMetricPipeline.build()` no longer invoke the `global::set_meter_provider`. User who setup the pipeline must do it themselves using `global::set_meter_provider(meter_provider.clone());`. +- Add `with_resource` on `OtlpLogPipeline`, replacing the `with_config` method. +Instead of using +`.with_config(Config::default().with_resource(RESOURCE::default()))` users must +now use `.with_resource(RESOURCE::default())` to configure Resource when using +`OtlpLogPipeline`. ## v0.16.0 diff --git a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs index 34a294f6af..7cc6490164 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs @@ -9,7 +9,7 @@ use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use opentelemetry_otlp::WithExportConfig; use opentelemetry_sdk::trace as sdktrace; use opentelemetry_sdk::{ - logs::{self as sdklogs, Config}, + logs::{self as sdklogs}, Resource, }; use tracing::info; @@ -28,7 +28,7 @@ static RESOURCE: Lazy = Lazy::new(|| { fn init_logs() -> Result { opentelemetry_otlp::new_pipeline() .logging() - .with_log_config(Config::default().with_resource(RESOURCE.clone())) + .with_resource(RESOURCE.clone()) .with_exporter( opentelemetry_otlp::new_exporter() .http() diff --git a/opentelemetry-otlp/examples/basic-otlp/src/main.rs b/opentelemetry-otlp/examples/basic-otlp/src/main.rs index 507d6c6fce..605916801d 100644 --- a/opentelemetry-otlp/examples/basic-otlp/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp/src/main.rs @@ -9,7 +9,6 @@ use opentelemetry::{ }; use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use opentelemetry_otlp::{ExportConfig, WithExportConfig}; -use opentelemetry_sdk::logs::Config; use opentelemetry_sdk::{runtime, trace as sdktrace, Resource}; use std::error::Error; use tracing::info; @@ -58,7 +57,7 @@ fn init_metrics() -> Result Result { opentelemetry_otlp::new_pipeline() .logging() - .with_log_config(Config::default().with_resource(RESOURCE.clone())) + .with_resource(RESOURCE.clone()) .with_exporter( opentelemetry_otlp::new_exporter() .tonic() diff --git a/opentelemetry-otlp/src/logs.rs b/opentelemetry-otlp/src/logs.rs index 714d4508a3..13e407a678 100644 --- a/opentelemetry-otlp/src/logs.rs +++ b/opentelemetry-otlp/src/logs.rs @@ -14,7 +14,7 @@ use std::fmt::Debug; use opentelemetry::logs::LogError; -use opentelemetry_sdk::{export::logs::LogData, runtime::RuntimeChannel}; +use opentelemetry_sdk::{export::logs::LogData, runtime::RuntimeChannel, Resource}; /// Compression algorithm to use, defaults to none. pub const OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION"; @@ -35,7 +35,7 @@ impl OtlpPipeline { /// Create a OTLP logging pipeline. pub fn logging(self) -> OtlpLogPipeline { OtlpLogPipeline { - log_config: None, + resource: None, exporter_builder: NoExporterConfig(()), batch_config: None, } @@ -111,15 +111,17 @@ impl opentelemetry_sdk::export::logs::LogExporter for LogExporter { #[derive(Debug)] pub struct OtlpLogPipeline { exporter_builder: EB, - log_config: Option, + resource: Option, batch_config: Option, } impl OtlpLogPipeline { - /// Set the log provider configuration. - pub fn with_log_config(mut self, log_config: opentelemetry_sdk::logs::Config) -> Self { - self.log_config = Some(log_config); - self + /// Set the Resource associated with log provider. + pub fn with_resource(self, resource: Resource) -> Self { + OtlpLogPipeline { + resource: Some(resource), + ..self + } } /// Set the batch log processor configuration, and it will override the env vars. @@ -137,7 +139,7 @@ impl OtlpLogPipeline { ) -> OtlpLogPipeline { OtlpLogPipeline { exporter_builder: pipeline.into(), - log_config: self.log_config, + resource: self.resource, batch_config: self.batch_config, } } @@ -152,7 +154,7 @@ impl OtlpLogPipeline { pub fn install_simple(self) -> Result { Ok(build_simple_with_exporter( self.exporter_builder.build_log_exporter()?, - self.log_config, + self.resource, )) } @@ -168,7 +170,7 @@ impl OtlpLogPipeline { ) -> Result { Ok(build_batch_with_exporter( self.exporter_builder.build_log_exporter()?, - self.log_config, + self.resource, runtime, self.batch_config, )) @@ -177,20 +179,21 @@ impl OtlpLogPipeline { fn build_simple_with_exporter( exporter: LogExporter, - log_config: Option, + resource: Option, ) -> opentelemetry_sdk::logs::LoggerProvider { let mut provider_builder = opentelemetry_sdk::logs::LoggerProvider::builder().with_simple_exporter(exporter); - if let Some(config) = log_config { - provider_builder = provider_builder.with_config(config); + if let Some(resource) = resource { + provider_builder = provider_builder.with_resource(resource); } - // logger would be created in the tracing appender + // logger would be created in the appenders like + // opentelemetry-appender-tracing, opentelemetry-appender-log etc. provider_builder.build() } fn build_batch_with_exporter( exporter: LogExporter, - log_config: Option, + resource: Option, runtime: R, batch_config: Option, ) -> opentelemetry_sdk::logs::LoggerProvider { @@ -200,8 +203,8 @@ fn build_batch_with_exporter( .build(); provider_builder = provider_builder.with_log_processor(batch_processor); - if let Some(config) = log_config { - provider_builder = provider_builder.with_config(config); + if let Some(resource) = resource { + provider_builder = provider_builder.with_resource(resource); } // logger would be created in the tracing appender provider_builder.build() diff --git a/opentelemetry-otlp/tests/integration_test/tests/logs.rs b/opentelemetry-otlp/tests/integration_test/tests/logs.rs index 22fabd0ae9..0c4fb773e9 100644 --- a/opentelemetry-otlp/tests/integration_test/tests/logs.rs +++ b/opentelemetry-otlp/tests/integration_test/tests/logs.rs @@ -14,12 +14,10 @@ fn init_logs() -> Result { opentelemetry_otlp::new_pipeline() .logging() .with_exporter(opentelemetry_otlp::new_exporter().tonic()) - .with_log_config( - sdklogs::config().with_resource(Resource::new(vec![KeyValue::new( - opentelemetry_semantic_conventions::resource::SERVICE_NAME, - "logs-integration-test", - )])), - ) + .with_resource(Resource::new(vec![KeyValue::new( + opentelemetry_semantic_conventions::resource::SERVICE_NAME, + "logs-integration-test", + )])) .install_batch(runtime::Tokio) } diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 3d141e1ad5..47c4a7f179 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -4,6 +4,11 @@ - Add "metrics", "logs" to default features. With this, default feature list is "trace", "metrics" and "logs". +- Add `with_resource` on Builder for LoggerProvider, replacing the `with_config` + method. Instead of using + `.with_config(Config::default().with_resource(RESOURCE::default()))` users + must now use `.with_resource(RESOURCE::default())` to configure Resource on + logger provider. - Removed dependency on `ordered-float`. ## v0.23.0 diff --git a/opentelemetry-sdk/src/logs/config.rs b/opentelemetry-sdk/src/logs/config.rs deleted file mode 100644 index d58efa9b13..0000000000 --- a/opentelemetry-sdk/src/logs/config.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::borrow::Cow; - -use crate::Resource; - -/// Default log configuration -pub fn config() -> Config { - Config::default() -} - -/// Log emitter configuration. -#[derive(Debug, Default)] -pub struct Config { - /// Contains attributes representing an entity that produces telemetry. - pub resource: Cow<'static, crate::Resource>, -} - -impl Config { - /// Specify the attributes representing the entity that produces telemetry - pub fn with_resource(mut self, resource: Resource) -> Self { - self.resource = Cow::Owned(resource); - self - } -} diff --git a/opentelemetry-sdk/src/logs/log_emitter.rs b/opentelemetry-sdk/src/logs/log_emitter.rs index ffa08d8ab1..3a552e4e57 100644 --- a/opentelemetry-sdk/src/logs/log_emitter.rs +++ b/opentelemetry-sdk/src/logs/log_emitter.rs @@ -1,7 +1,8 @@ -use super::{BatchLogProcessor, Config, LogProcessor, LogRecord, SimpleLogProcessor, TraceContext}; +use super::{BatchLogProcessor, LogProcessor, LogRecord, SimpleLogProcessor, TraceContext}; use crate::{ export::logs::{LogData, LogExporter}, runtime::RuntimeChannel, + Resource, }; use opentelemetry::{ global, @@ -25,7 +26,7 @@ use once_cell::sync::Lazy; static NOOP_LOGGER_PROVIDER: Lazy = Lazy::new(|| LoggerProvider { inner: Arc::new(LoggerProviderInner { processors: Vec::new(), - config: Config::default(), + resource: Resource::empty(), }), is_shutdown: Arc::new(AtomicBool::new(true)), }); @@ -89,9 +90,9 @@ impl LoggerProvider { Builder::default() } - /// Config associated with this provider. - pub fn config(&self) -> &Config { - &self.inner.config + /// Resource associated with this provider. + pub fn resource(&self) -> &Resource { + &self.inner.resource } /// Log processors associated with this provider. @@ -137,7 +138,7 @@ impl LoggerProvider { #[derive(Debug)] struct LoggerProviderInner { processors: Vec>, - config: Config, + resource: Resource, } impl Drop for LoggerProviderInner { @@ -154,7 +155,7 @@ impl Drop for LoggerProviderInner { /// Builder for provider attributes. pub struct Builder { processors: Vec>, - config: Config, + resource: Option, } impl Builder { @@ -184,21 +185,25 @@ impl Builder { Builder { processors, ..self } } - /// The `Config` that this provider should use. - pub fn with_config(self, config: Config) -> Self { - Builder { config, ..self } + /// The `Resource` to be associated with this Provider. + pub fn with_resource(self, resource: Resource) -> Self { + Builder { + resource: Some(resource), + ..self + } } /// Create a new provider from this configuration. pub fn build(self) -> LoggerProvider { + let resource = self.resource.unwrap_or_default(); // invoke set_resource on all the processors for processor in &self.processors { - processor.set_resource(&self.config.resource); + processor.set_resource(&resource); } LoggerProvider { inner: Arc::new(LoggerProviderInner { processors: self.processors, - config: self.config, + resource, }), is_shutdown: Arc::new(AtomicBool::new(false)), } @@ -357,8 +362,7 @@ mod tests { expect: Option<&'static str>| { assert_eq!( provider - .config() - .resource + .resource() .get(Key::from_static_str(resource_key)) .map(|v| v.to_string()), expect.map(|s| s.to_string()) @@ -366,18 +370,15 @@ mod tests { }; let assert_telemetry_resource = |provider: &super::LoggerProvider| { assert_eq!( - provider - .config() - .resource - .get(TELEMETRY_SDK_LANGUAGE.into()), + provider.resource().get(TELEMETRY_SDK_LANGUAGE.into()), Some(Value::from("rust")) ); assert_eq!( - provider.config().resource.get(TELEMETRY_SDK_NAME.into()), + provider.resource().get(TELEMETRY_SDK_NAME.into()), Some(Value::from("opentelemetry")) ); assert_eq!( - provider.config().resource.get(TELEMETRY_SDK_VERSION.into()), + provider.resource().get(TELEMETRY_SDK_VERSION.into()), Some(Value::from(env!("CARGO_PKG_VERSION"))) ); }; @@ -395,15 +396,13 @@ mod tests { // If user provided a resource, use that. let custom_config_provider = super::LoggerProvider::builder() - .with_config(Config { - resource: Cow::Owned(Resource::new(vec![KeyValue::new( - SERVICE_NAME, - "test_service", - )])), - }) + .with_resource(Resource::new(vec![KeyValue::new( + SERVICE_NAME, + "test_service", + )])) .build(); assert_resource(&custom_config_provider, SERVICE_NAME, Some("test_service")); - assert_eq!(custom_config_provider.config().resource.len(), 1); + assert_eq!(custom_config_provider.resource().len(), 1); // If `OTEL_RESOURCE_ATTRIBUTES` is set, read them automatically temp_env::with_var( @@ -419,7 +418,7 @@ mod tests { assert_resource(&env_resource_provider, "key1", Some("value1")); assert_resource(&env_resource_provider, "k3", Some("value2")); assert_telemetry_resource(&env_resource_provider); - assert_eq!(env_resource_provider.config().resource.len(), 6); + assert_eq!(env_resource_provider.resource().len(), 6); }, ); @@ -429,12 +428,10 @@ mod tests { Some("my-custom-key=env-val,k2=value2"), || { let user_provided_resource_config_provider = super::LoggerProvider::builder() - .with_config(Config { - resource: Cow::Owned(Resource::default().merge(&mut Resource::new(vec![ - KeyValue::new("my-custom-key", "my-custom-value"), - KeyValue::new("my-custom-key2", "my-custom-value2"), - ]))), - }) + .with_resource(Resource::default().merge(&mut Resource::new(vec![ + KeyValue::new("my-custom-key", "my-custom-value"), + KeyValue::new("my-custom-key2", "my-custom-value2"), + ]))) .build(); assert_resource( &user_provided_resource_config_provider, @@ -457,23 +454,15 @@ mod tests { Some("value2"), ); assert_telemetry_resource(&user_provided_resource_config_provider); - assert_eq!( - user_provided_resource_config_provider - .config() - .resource - .len(), - 7 - ); + assert_eq!(user_provided_resource_config_provider.resource().len(), 7); }, ); // If user provided a resource, it takes priority during collision. let no_service_name = super::LoggerProvider::builder() - .with_config(Config { - resource: Cow::Owned(Resource::empty()), - }) + .with_resource(Resource::empty()) .build(); - assert_eq!(no_service_name.config().resource.len(), 0); + assert_eq!(no_service_name.resource().len(), 0); } #[test] diff --git a/opentelemetry-sdk/src/logs/log_processor.rs b/opentelemetry-sdk/src/logs/log_processor.rs index fd0be00769..1aad790c64 100644 --- a/opentelemetry-sdk/src/logs/log_processor.rs +++ b/opentelemetry-sdk/src/logs/log_processor.rs @@ -503,8 +503,7 @@ mod tests { OTEL_BLRP_EXPORT_TIMEOUT_DEFAULT, OTEL_BLRP_MAX_EXPORT_BATCH_SIZE_DEFAULT, OTEL_BLRP_MAX_QUEUE_SIZE_DEFAULT, OTEL_BLRP_SCHEDULE_DELAY_DEFAULT, }, - BatchConfig, BatchConfigBuilder, Config, LogProcessor, LoggerProvider, - SimpleLogProcessor, + BatchConfig, BatchConfigBuilder, LogProcessor, LoggerProvider, SimpleLogProcessor, }, runtime, testing::logs::InMemoryLogsExporter, @@ -699,12 +698,12 @@ mod tests { let processor = SimpleLogProcessor::new(Box::new(exporter.clone())); let _ = LoggerProvider::builder() .with_log_processor(processor) - .with_config(Config::default().with_resource(Resource::new(vec![ + .with_resource(Resource::new(vec![ KeyValue::new("k1", "v1"), KeyValue::new("k2", "v3"), KeyValue::new("k3", "v3"), KeyValue::new("k4", "v4"), - ]))) + ])) .build(); assert_eq!(exporter.get_resource().unwrap().into_iter().count(), 4); } @@ -721,12 +720,12 @@ mod tests { ); let provider = LoggerProvider::builder() .with_log_processor(processor) - .with_config(Config::default().with_resource(Resource::new(vec![ + .with_resource(Resource::new(vec![ KeyValue::new("k1", "v1"), KeyValue::new("k2", "v3"), KeyValue::new("k3", "v3"), KeyValue::new("k4", "v4"), - ]))) + ])) .build(); assert_eq!(exporter.get_resource().unwrap().into_iter().count(), 4); let _ = provider.shutdown(); diff --git a/opentelemetry-sdk/src/logs/mod.rs b/opentelemetry-sdk/src/logs/mod.rs index 036b7ffe7e..207da4255c 100644 --- a/opentelemetry-sdk/src/logs/mod.rs +++ b/opentelemetry-sdk/src/logs/mod.rs @@ -1,11 +1,9 @@ //! # OpenTelemetry Log SDK -mod config; mod log_emitter; mod log_processor; mod record; -pub use config::{config, Config}; pub use log_emitter::{Builder, Logger, LoggerProvider}; pub use log_processor::{ BatchConfig, BatchConfigBuilder, BatchLogProcessor, BatchLogProcessorBuilder, LogProcessor, @@ -35,7 +33,7 @@ mod tests { ]); let exporter: InMemoryLogsExporter = InMemoryLogsExporter::default(); let logger_provider = LoggerProvider::builder() - .with_config(Config::default().with_resource(resource.clone())) + .with_resource(resource.clone()) .with_log_processor(SimpleLogProcessor::new(Box::new(exporter.clone()))) .build(); diff --git a/opentelemetry-stdout/Cargo.toml b/opentelemetry-stdout/Cargo.toml index 78a395c0a6..e139df2b9d 100644 --- a/opentelemetry-stdout/Cargo.toml +++ b/opentelemetry-stdout/Cargo.toml @@ -36,6 +36,8 @@ ordered-float = { workspace = true } opentelemetry = { path = "../opentelemetry", features = ["metrics"] } opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["rt-tokio", "metrics"] } opentelemetry-appender-tracing = { path = "../opentelemetry-appender-tracing"} +opentelemetry-semantic-conventions = { path = "../opentelemetry-semantic-conventions" } tracing = { workspace = true, features = ["std"]} tracing-subscriber = { workspace = true, features = ["registry", "std"] } tokio = { workspace = true, features = ["full"] } +once_cell = { workspace = true } diff --git a/opentelemetry-stdout/examples/basic.rs b/opentelemetry-stdout/examples/basic.rs index ae2b7f6ec9..baa2b41d18 100644 --- a/opentelemetry-stdout/examples/basic.rs +++ b/opentelemetry-stdout/examples/basic.rs @@ -1,5 +1,6 @@ //! run with `$ cargo run --example basic +use once_cell::sync::Lazy; use opentelemetry::{global, KeyValue}; #[cfg(feature = "trace")] @@ -11,14 +12,24 @@ use opentelemetry_sdk::runtime; #[cfg(feature = "metrics")] use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; +use opentelemetry_sdk::trace::Config; #[cfg(feature = "trace")] use opentelemetry_sdk::trace::TracerProvider; +use opentelemetry_sdk::Resource; + +static RESOURCE: Lazy = Lazy::new(|| { + Resource::default().merge(&Resource::new(vec![KeyValue::new( + opentelemetry_semantic_conventions::resource::SERVICE_NAME, + "basic-stdout-example", + )])) +}); #[cfg(feature = "trace")] fn init_trace() { let exporter = opentelemetry_stdout::SpanExporter::default(); let provider = TracerProvider::builder() .with_simple_exporter(exporter) + .with_config(Config::default().with_resource(RESOURCE.clone())) .build(); global::set_tracer_provider(provider); } @@ -27,7 +38,10 @@ fn init_trace() { fn init_metrics() -> opentelemetry_sdk::metrics::SdkMeterProvider { let exporter = opentelemetry_stdout::MetricsExporter::default(); let reader = PeriodicReader::builder(exporter, runtime::Tokio).build(); - let provider = SdkMeterProvider::builder().with_reader(reader).build(); + let provider = SdkMeterProvider::builder() + .with_reader(reader) + .with_resource(RESOURCE.clone()) + .build(); global::set_meter_provider(provider.clone()); provider } @@ -41,6 +55,7 @@ fn init_logs() -> opentelemetry_sdk::logs::LoggerProvider { let exporter = opentelemetry_stdout::LogExporter::default(); let provider: LoggerProvider = LoggerProvider::builder() .with_simple_exporter(exporter) + .with_resource(RESOURCE.clone()) .build(); let layer = layer::OpenTelemetryTracingBridge::new(&provider); tracing_subscriber::registry().with(layer).init();