diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index 137631a0ea..2ac07b45aa 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -96,7 +96,7 @@ use log::{Level, Metadata, Record}; use opentelemetry::{ - logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}, + logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, Key, }; use std::borrow::Cow; @@ -126,16 +126,13 @@ where fn log(&self, record: &Record) { if self.enabled(record.metadata()) { - self.logger.emit( - LogRecordBuilder::new() - .with_severity_number(severity_of_level(record.level())) - .with_severity_text(record.level().as_str()) - // Not populating ObservedTimestamp, instead relying on OpenTelemetry - // API to populate it with current time. - .with_body(AnyValue::from(record.args().to_string())) - .with_attributes(log_attributes(record.key_values())) - .build(), - ); + let mut log_record = self.logger.create_log_record(); + log_record.set_severity_number(severity_of_level(record.level())); + log_record.set_severity_text(record.level().as_str().into()); + log_record.set_body(AnyValue::from(record.args().to_string())); + log_record.add_attributes(log_attributes(record.key_values())); + + self.logger.emit(log_record); } } diff --git a/opentelemetry-appender-tracing/src/layer.rs b/opentelemetry-appender-tracing/src/layer.rs index ef2a691dcc..fe28a786dc 100644 --- a/opentelemetry-appender-tracing/src/layer.rs +++ b/opentelemetry-appender-tracing/src/layer.rs @@ -71,9 +71,11 @@ impl EventVisitor { } } - fn push_to_otel_log_record(self, log_record: &mut LogRecord) { - log_record.body = self.log_record_body; - log_record.attributes = Some(self.log_record_attributes); + fn push_to_otel_log_record(self, log_record: &mut LR) { + if let Some(body) = self.log_record_body { + log_record.set_body(body); + } + log_record.add_attributes(self.log_record_attributes); } } @@ -166,12 +168,10 @@ where #[cfg(not(feature = "experimental_metadata_attributes"))] let meta = event.metadata(); - let mut log_record: LogRecord = LogRecord::default(); - log_record.severity_number = Some(severity_of_level(meta.level())); - log_record.severity_text = Some(meta.level().to_string().into()); - - // Not populating ObservedTimestamp, instead relying on OpenTelemetry - // API to populate it with current time. + //let mut log_record: LogRecord = LogRecord::default(); + let mut log_record = self.logger.create_log_record(); + log_record.set_severity_number(severity_of_level(meta.level())); + log_record.set_severity_text(meta.level().to_string().into()); let mut visitor = EventVisitor::default(); visitor.visit_metadata(meta); diff --git a/opentelemetry-proto/src/transform/logs.rs b/opentelemetry-proto/src/transform/logs.rs index 335526685b..bd851e98e6 100644 --- a/opentelemetry-proto/src/transform/logs.rs +++ b/opentelemetry-proto/src/transform/logs.rs @@ -50,8 +50,8 @@ pub mod tonic { } } - impl From for LogRecord { - fn from(log_record: opentelemetry::logs::LogRecord) -> Self { + impl From for LogRecord { + fn from(log_record: opentelemetry_sdk::logs::LogRecord) -> Self { let trace_context = log_record.trace_context.as_ref(); let severity_number = match log_record.severity_number { Some(Severity::Trace) => SeverityNumber::Trace, @@ -83,7 +83,7 @@ pub mod tonic { LogRecord { time_unix_nano: log_record.timestamp.map(to_nanos).unwrap_or_default(), - observed_time_unix_nano: to_nanos(log_record.observed_timestamp), + observed_time_unix_nano: to_nanos(log_record.observed_timestamp.unwrap()), severity_number: severity_number.into(), severity_text: log_record.severity_text.map(Into::into).unwrap_or_default(), body: log_record.body.map(Into::into), diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 19dac050a4..6209c7331d 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -26,6 +26,8 @@ - `shutdown` methods in `LoggerProvider` and `LogProcessor` now takes a immutable reference - After `shutdown`, `LoggerProvider` will return noop `Logger` - After `shutdown`, `LogProcessor` will not process any new logs +- Moving LogRecord implementation to the SDK. [1702](https://github.com/open-telemetry/opentelemetry-rust/pull/1702). + - Relocated `LogRecord` struct to SDK, as an implementation for the trait in the API. ## v0.22.1 diff --git a/opentelemetry-sdk/benches/log.rs b/opentelemetry-sdk/benches/log.rs index 9b66ad0447..f70bb3e650 100644 --- a/opentelemetry-sdk/benches/log.rs +++ b/opentelemetry-sdk/benches/log.rs @@ -4,12 +4,14 @@ use std::time::SystemTime; use async_trait::async_trait; use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry::logs::{AnyValue, LogRecord, LogResult, Logger, LoggerProvider as _, Severity}; +use opentelemetry::logs::{ + AnyValue, LogRecord as _, LogResult, Logger as _, LoggerProvider as _, Severity, +}; use opentelemetry::trace::Tracer; use opentelemetry::trace::TracerProvider as _; use opentelemetry::Key; use opentelemetry_sdk::export::logs::{LogData, LogExporter}; -use opentelemetry_sdk::logs::LoggerProvider; +use opentelemetry_sdk::logs::{Logger, LoggerProvider}; use opentelemetry_sdk::trace::{config, Sampler, TracerProvider}; #[derive(Debug)] @@ -22,7 +24,7 @@ impl LogExporter for VoidExporter { } } -fn log_benchmark_group(c: &mut Criterion, name: &str, f: F) { +fn log_benchmark_group(c: &mut Criterion, name: &str, f: F) { let mut group = c.benchmark_group(name); group.bench_function("no-context", |b| { @@ -59,53 +61,45 @@ fn log_benchmark_group(c: &mut Criterion, name: &str, f: F) fn criterion_benchmark(c: &mut Criterion) { log_benchmark_group(c, "simple-log", |logger| { - logger.emit(LogRecord::builder().with_body("simple log").build()) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + logger.emit(log_record); }); log_benchmark_group(c, "simple-log-with-int", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testint", 2) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testint", 2); + logger.emit(log_record); }); log_benchmark_group(c, "simple-log-with-double", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testdouble", 2.2) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testdouble", 2.2); + logger.emit(log_record); }); log_benchmark_group(c, "simple-log-with-string", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("teststring", "test") - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("teststring", "test"); + logger.emit(log_record); }); log_benchmark_group(c, "simple-log-with-bool", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testbool", AnyValue::Boolean(true)) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testbool", AnyValue::Boolean(true)); + logger.emit(log_record); }); let bytes = AnyValue::Bytes(vec![25u8, 30u8, 40u8]); log_benchmark_group(c, "simple-log-with-bytes", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testbytes", bytes.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testbytes", bytes.clone()); + logger.emit(log_record); }); let bytes = AnyValue::Bytes(vec![ @@ -117,22 +111,18 @@ fn criterion_benchmark(c: &mut Criterion) { 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, ]); log_benchmark_group(c, "simple-log-with-a-lot-of-bytes", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testbytes", bytes.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testbytes", bytes.clone()); + logger.emit(log_record); }); let vec_any_values = AnyValue::ListAny(vec![AnyValue::Int(25), "test".into(), true.into()]); log_benchmark_group(c, "simple-log-with-vec-any-value", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testvec", vec_any_values.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testvec", vec_any_values.clone()); + logger.emit(log_record); }); let vec_any_values = AnyValue::ListAny(vec![AnyValue::Int(25), "test".into(), true.into()]); @@ -143,12 +133,10 @@ fn criterion_benchmark(c: &mut Criterion) { vec_any_values, ]); log_benchmark_group(c, "simple-log-with-inner-vec-any-value", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testvec", vec_any_values.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testvec", vec_any_values.clone()); + logger.emit(log_record); }); let map_any_values = AnyValue::Map(HashMap::from([ @@ -157,12 +145,10 @@ fn criterion_benchmark(c: &mut Criterion) { ("teststring".into(), "test".into()), ])); log_benchmark_group(c, "simple-log-with-map-any-value", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testmap", map_any_values.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testmap", map_any_values.clone()); + logger.emit(log_record); }); let map_any_values = AnyValue::Map(HashMap::from([ @@ -177,66 +163,60 @@ fn criterion_benchmark(c: &mut Criterion) { ("testmap".into(), map_any_values), ])); log_benchmark_group(c, "simple-log-with-inner-map-any-value", |logger| { - logger.emit( - LogRecord::builder() - .with_body("simple log") - .with_attribute("testmap", map_any_values.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("simple log".into()); + log_record.add_attribute("testmap", map_any_values.clone()); + logger.emit(log_record); }); log_benchmark_group(c, "long-log", |logger| { - logger.emit(LogRecord::builder().with_body("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Gravida in fermentum et sollicitudin ac orci phasellus. Ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at augue. Magna etiam tempor orci eu. Sed tempus urna et pharetra pharetra massa.").build()) + let mut log_record = logger.create_log_record(); + log_record.set_body("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Gravida in fermentum et sollicitudin ac orci phasellus. Ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at augue. Magna etiam tempor orci eu. Sed tempus urna et pharetra pharetra massa.".into()); + logger.emit(log_record); }); let now = SystemTime::now(); log_benchmark_group(c, "full-log", |logger| { - logger.emit( - LogRecord::builder() - .with_body("full log") - .with_timestamp(now) - .with_observed_timestamp(now) - .with_severity_number(Severity::Warn) - .with_severity_text(Severity::Warn.name()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("full log".into()); + log_record.set_timestamp(now); + log_record.set_observed_timestamp(now); + log_record.set_severity_number(Severity::Warn); + log_record.set_severity_text(Severity::Warn.name().into()); + logger.emit(log_record); }); log_benchmark_group(c, "full-log-with-4-attributes", |logger| { - logger.emit( - LogRecord::builder() - .with_body("full log") - .with_timestamp(now) - .with_observed_timestamp(now) - .with_severity_number(Severity::Warn) - .with_severity_text(Severity::Warn.name()) - .with_attribute("name", "my-event-name") - .with_attribute("event.id", 20) - .with_attribute("user.name", "otel") - .with_attribute("user.email", "otel@opentelemetry.io") - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("full log".into()); + log_record.set_timestamp(now); + log_record.set_observed_timestamp(now); + log_record.set_severity_number(Severity::Warn); + log_record.set_severity_text(Severity::Warn.name().into()); + log_record.add_attribute("name", "my-event-name"); + log_record.add_attribute("event.id", 20); + log_record.add_attribute("user.name", "otel"); + log_record.add_attribute("user.email", "otel@opentelemetry.io"); + logger.emit(log_record); }); log_benchmark_group(c, "full-log-with-9-attributes", |logger| { - logger.emit( - LogRecord::builder() - .with_body("full log") - .with_timestamp(now) - .with_observed_timestamp(now) - .with_severity_number(Severity::Warn) - .with_severity_text(Severity::Warn.name()) - .with_attribute("name", "my-event-name") - .with_attribute("event.id", 20) - .with_attribute("user.name", "otel") - .with_attribute("user.email", "otel@opentelemetry.io") - .with_attribute("code.filename", "log.rs") - .with_attribute("code.filepath", "opentelemetry_sdk/benches/log.rs") - .with_attribute("code.lineno", 96) - .with_attribute("code.namespace", "opentelemetry_sdk::benches::log") - .with_attribute("log.target", "opentelemetry_sdk::benches::log") - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("full log".into()); + log_record.set_timestamp(now); + log_record.set_observed_timestamp(now); + log_record.set_severity_number(Severity::Warn); + log_record.set_severity_text(Severity::Warn.name().into()); + log_record.add_attribute("name", "my-event-name"); + log_record.add_attribute("event.id", 20); + log_record.add_attribute("user.name", "otel"); + log_record.add_attribute("user.email", "otel@opentelemetry.io"); + log_record.add_attribute("code.filename", "log.rs"); + log_record.add_attribute("code.filepath", "opentelemetry_sdk/benches/log.rs"); + log_record.add_attribute("code.lineno", 96); + log_record.add_attribute("code.namespace", "opentelemetry_sdk::benches::log"); + log_record.add_attribute("log.target", "opentelemetry_sdk::benches::log"); + logger.emit(log_record); }); let attributes: Vec<(Key, AnyValue)> = vec![ @@ -260,16 +240,14 @@ fn criterion_benchmark(c: &mut Criterion) { ), ]; log_benchmark_group(c, "full-log-with-attributes", |logger| { - logger.emit( - LogRecord::builder() - .with_body("full log") - .with_timestamp(now) - .with_observed_timestamp(now) - .with_severity_number(Severity::Warn) - .with_severity_text(Severity::Warn.name()) - .with_attributes(attributes.clone()) - .build(), - ) + let mut log_record = logger.create_log_record(); + log_record.set_body("full log".into()); + log_record.set_timestamp(now); + log_record.set_observed_timestamp(now); + log_record.set_severity_number(Severity::Warn); + log_record.set_severity_text(Severity::Warn.name().into()); + log_record.add_attributes(attributes.clone()); + logger.emit(log_record); }); } diff --git a/opentelemetry-sdk/src/export/logs/mod.rs b/opentelemetry-sdk/src/export/logs/mod.rs index 8676db9d16..5c56e355b4 100644 --- a/opentelemetry-sdk/src/export/logs/mod.rs +++ b/opentelemetry-sdk/src/export/logs/mod.rs @@ -1,10 +1,11 @@ //! Log exporters +use crate::logs::LogRecord; use crate::Resource; use async_trait::async_trait; #[cfg(feature = "logs_level_enabled")] use opentelemetry::logs::Severity; use opentelemetry::{ - logs::{LogError, LogRecord, LogResult}, + logs::{LogError, LogResult}, InstrumentationLibrary, }; use std::fmt::Debug; diff --git a/opentelemetry-sdk/src/logs/log_emitter.rs b/opentelemetry-sdk/src/logs/log_emitter.rs index e87214228b..6a4969a994 100644 --- a/opentelemetry-sdk/src/logs/log_emitter.rs +++ b/opentelemetry-sdk/src/logs/log_emitter.rs @@ -1,11 +1,11 @@ -use super::{BatchLogProcessor, Config, LogProcessor, SimpleLogProcessor}; +use super::{BatchLogProcessor, Config, LogProcessor, LogRecord, SimpleLogProcessor, TraceContext}; use crate::{ export::logs::{LogData, LogExporter}, runtime::RuntimeChannel, }; use opentelemetry::{ global::{self}, - logs::{LogRecord, LogResult, TraceContext}, + logs::LogResult, trace::TraceContextExt, Context, InstrumentationLibrary, }; @@ -222,8 +222,14 @@ impl Logger { } impl opentelemetry::logs::Logger for Logger { + type LogRecord = LogRecord; + + fn create_log_record(&self) -> Self::LogRecord { + LogRecord::default() + } + /// Emit a `LogRecord`. - fn emit(&self, record: LogRecord) { + fn emit(&self, record: Self::LogRecord) { let provider = self.provider(); let processors = provider.log_processors(); let trace_context = Context::map_current(|cx| { @@ -269,8 +275,7 @@ mod tests { use crate::Resource; use super::*; - use opentelemetry::logs::Logger; - use opentelemetry::logs::LoggerProvider as _; + use opentelemetry::logs::{Logger, LoggerProvider as _}; use opentelemetry::{Key, KeyValue, Value}; use std::fmt::{Debug, Formatter}; use std::sync::atomic::AtomicU64; @@ -462,17 +467,17 @@ mod tests { let logger1 = logger_provider.logger("test-logger1"); let logger2 = logger_provider.logger("test-logger2"); - logger1.emit(LogRecord::default()); - logger2.emit(LogRecord::default()); + logger1.emit(logger1.create_log_record()); + logger2.emit(logger1.create_log_record()); let logger3 = logger_provider.logger("test-logger3"); let handle = thread::spawn(move || { - logger3.emit(LogRecord::default()); + logger3.emit(logger3.create_log_record()); }); handle.join().expect("thread panicked"); let _ = logger_provider.shutdown(); - logger1.emit(LogRecord::default()); + logger1.emit(logger1.create_log_record()); assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 3); } @@ -495,8 +500,8 @@ mod tests { let logger2 = logger_provider.logger("test-logger2"); // Acts - logger1.emit(LogRecord::default()); - logger2.emit(LogRecord::default()); + logger1.emit(logger1.create_log_record()); + logger2.emit(logger1.create_log_record()); // explicitly calling shutdown on logger_provider. This will // indeed do the shutdown, even if there are loggers still alive. diff --git a/opentelemetry-sdk/src/logs/mod.rs b/opentelemetry-sdk/src/logs/mod.rs index 69e5064048..22157b7a59 100644 --- a/opentelemetry-sdk/src/logs/mod.rs +++ b/opentelemetry-sdk/src/logs/mod.rs @@ -3,6 +3,7 @@ mod config; mod log_emitter; mod log_processor; +mod record; pub use config::{config, Config}; pub use log_emitter::{Builder, Logger, LoggerProvider}; @@ -10,12 +11,14 @@ pub use log_processor::{ BatchConfig, BatchConfigBuilder, BatchLogProcessor, BatchLogProcessorBuilder, LogProcessor, SimpleLogProcessor, }; +pub use record::{LogRecord, TraceContext}; #[cfg(all(test, feature = "testing"))] mod tests { use super::*; use crate::testing::logs::InMemoryLogsExporter; - use opentelemetry::logs::{LogRecord, Logger, LoggerProvider as _, Severity}; + use opentelemetry::logs::LogRecord; + use opentelemetry::logs::{Logger, LoggerProvider as _, Severity}; use opentelemetry::{logs::AnyValue, Key, KeyValue}; #[test] @@ -28,14 +31,13 @@ mod tests { // Act let logger = logger_provider.logger("test-logger"); - let mut log_record: LogRecord = LogRecord::default(); - log_record.severity_number = Some(Severity::Error); - log_record.severity_text = Some("Error".into()); - let attributes = vec![ + let mut log_record = logger.create_log_record(); + log_record.set_severity_number(Severity::Error); + log_record.set_severity_text("Error".into()); + log_record.add_attributes(vec![ (Key::new("key1"), "value1".into()), (Key::new("key2"), "value2".into()), - ]; - log_record.attributes = Some(attributes); + ]); logger.emit(log_record); // Assert diff --git a/opentelemetry-sdk/src/logs/record.rs b/opentelemetry-sdk/src/logs/record.rs new file mode 100644 index 0000000000..d9a49100da --- /dev/null +++ b/opentelemetry-sdk/src/logs/record.rs @@ -0,0 +1,159 @@ +use opentelemetry::{ + logs::{AnyValue, Severity}, + trace::{SpanContext, SpanId, TraceFlags, TraceId}, + Key, +}; +use std::{borrow::Cow, time::SystemTime}; + +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +/// LogRecord represents all data carried by a log record, and +/// is provided to `LogExporter`s as input. +pub struct LogRecord { + /// Record timestamp + pub timestamp: Option, + + /// Timestamp for when the record was observed by OpenTelemetry + pub observed_timestamp: Option, + + /// Trace context for logs associated with spans + pub trace_context: Option, + + /// The original severity string from the source + pub severity_text: Option>, + /// The corresponding severity value, normalized + pub severity_number: Option, + + /// Record body + pub body: Option, + + /// Additional attributes associated with this record + pub attributes: Option>, +} + +impl opentelemetry::logs::LogRecord for LogRecord { + fn set_timestamp(&mut self, timestamp: SystemTime) { + self.timestamp = Some(timestamp); + } + + fn set_observed_timestamp(&mut self, timestamp: SystemTime) { + self.observed_timestamp = Some(timestamp); + } + + fn set_severity_text(&mut self, severity_text: Cow<'static, str>) { + self.severity_text = Some(severity_text); + } + + fn set_severity_number(&mut self, severity_number: Severity) { + self.severity_number = Some(severity_number); + } + + fn set_body(&mut self, body: AnyValue) { + self.body = Some(body); + } + + fn add_attributes(&mut self, attributes: Vec<(Key, AnyValue)>) { + self.attributes = Some(attributes); + } + + fn add_attribute(&mut self, key: K, value: V) + where + K: Into, + V: Into, + { + if let Some(ref mut attrs) = self.attributes { + attrs.push((key.into(), value.into())); + } else { + self.attributes = Some(vec![(key.into(), value.into())]); + } + } +} + +/// TraceContext stores the trace context for logs that have an associated +/// span. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct TraceContext { + /// Trace id + pub trace_id: TraceId, + /// Span Id + pub span_id: SpanId, + /// Trace flags + pub trace_flags: Option, +} + +impl From<&SpanContext> for TraceContext { + fn from(span_context: &SpanContext) -> Self { + TraceContext { + trace_id: span_context.trace_id(), + span_id: span_context.span_id(), + trace_flags: Some(span_context.trace_flags()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use opentelemetry::logs::{AnyValue, LogRecord as _, Severity}; + use std::borrow::Cow; + use std::time::SystemTime; + + #[test] + fn test_set_timestamp() { + let mut log_record = LogRecord::default(); + let now = SystemTime::now(); + log_record.set_timestamp(now); + assert_eq!(log_record.timestamp, Some(now)); + } + + #[test] + fn test_set_observed_timestamp() { + let mut log_record = LogRecord::default(); + let now = SystemTime::now(); + log_record.set_observed_timestamp(now); + assert_eq!(log_record.observed_timestamp, Some(now)); + } + + #[test] + fn test_set_severity_text() { + let mut log_record = LogRecord::default(); + let severity_text: Cow<'static, str> = "ERROR".into(); // Explicitly typed + log_record.set_severity_text(severity_text); + assert_eq!(log_record.severity_text, Some(Cow::Borrowed("ERROR"))); + } + + #[test] + fn test_set_severity_number() { + let mut log_record = LogRecord::default(); + let severity_number = Severity::Error; + log_record.set_severity_number(severity_number); + assert_eq!(log_record.severity_number, Some(Severity::Error)); + } + + #[test] + fn test_set_body() { + let mut log_record = LogRecord::default(); + let body = AnyValue::String("Test body".into()); + log_record.set_body(body.clone()); + assert_eq!(log_record.body, Some(body)); + } + + #[test] + fn test_set_attributes() { + let mut log_record = LogRecord::default(); + let attributes = vec![(Key::new("key"), AnyValue::String("value".into()))]; + log_record.add_attributes(attributes.clone()); + assert_eq!(log_record.attributes, Some(attributes)); + } + + #[test] + fn test_set_attribute() { + let mut log_record = LogRecord::default(); + log_record.add_attribute("key", "value"); + assert_eq!( + log_record.attributes, + Some(vec![(Key::new("key"), AnyValue::String("value".into()))]) + ); + } +} diff --git a/opentelemetry-sdk/src/testing/logs/in_memory_exporter.rs b/opentelemetry-sdk/src/testing/logs/in_memory_exporter.rs index 63143f0b12..233da0ccbb 100644 --- a/opentelemetry-sdk/src/testing/logs/in_memory_exporter.rs +++ b/opentelemetry-sdk/src/testing/logs/in_memory_exporter.rs @@ -1,7 +1,8 @@ use crate::export::logs::{LogData, LogExporter}; +use crate::logs::LogRecord; use crate::Resource; use async_trait::async_trait; -use opentelemetry::logs::{LogError, LogRecord, LogResult}; +use opentelemetry::logs::{LogError, LogResult}; use opentelemetry::InstrumentationLibrary; use std::borrow::Cow; use std::sync::{Arc, Mutex}; diff --git a/opentelemetry-stdout/src/logs/transform.rs b/opentelemetry-stdout/src/logs/transform.rs index 10332953d2..7beaa7cc76 100644 --- a/opentelemetry-stdout/src/logs/transform.rs +++ b/opentelemetry-stdout/src/logs/transform.rs @@ -125,8 +125,8 @@ impl From for LogRecord { .unwrap_or_default(), time_unix_nano: value.record.timestamp, time: value.record.timestamp, - observed_time_unix_nano: value.record.observed_timestamp, - observed_time: value.record.observed_timestamp, + observed_time_unix_nano: value.record.observed_timestamp.unwrap(), + observed_time: value.record.observed_timestamp.unwrap(), severity_number: value .record .severity_number diff --git a/opentelemetry/CHANGELOG.md b/opentelemetry/CHANGELOG.md index 828c271c81..13b4ad02be 100644 --- a/opentelemetry/CHANGELOG.md +++ b/opentelemetry/CHANGELOG.md @@ -25,6 +25,10 @@ ### Changed - Deprecate `versioned_logger()` in favor of `logger_builder()` [1567](https://github.com/open-telemetry/opentelemetry-rust/pull/1567). +- **BREAKING** Moving LogRecord implementation to the SDK. [1702](https://github.com/open-telemetry/opentelemetry-rust/pull/1702). + - Relocated `LogRecord` struct to SDK. + - Introduced the `LogRecord` trait in the API for populating log records. This trait is implemented by the SDK. + This is the breaking change for the authors of Log Appenders. Refer to the [opentelemetry-appender-tracing](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-appender-tracing) for more details. Before: diff --git a/opentelemetry/src/logs/logger.rs b/opentelemetry/src/logs/logger.rs index 11b7fb9d60..fd4e18e043 100644 --- a/opentelemetry/src/logs/logger.rs +++ b/opentelemetry/src/logs/logger.rs @@ -8,12 +8,17 @@ use super::Severity; /// The interface for emitting [`LogRecord`]s. pub trait Logger { + /// Specifies the `LogRecord` type associated with this logger. + type LogRecord: LogRecord; + + /// Creates a new log record builder. + fn create_log_record(&self) -> Self::LogRecord; + /// Emit a [`LogRecord`]. If there is active current thread's [`Context`], - /// the logger will set the record's [`TraceContext`] to the active trace context, + /// the logger will set the record's `TraceContext` to the active trace context, /// /// [`Context`]: crate::Context - /// [`TraceContext`]: crate::logs::TraceContext - fn emit(&self, record: LogRecord); + fn emit(&self, record: Self::LogRecord); #[cfg(feature = "logs_level_enabled")] /// Check if the given log level is enabled. diff --git a/opentelemetry/src/logs/mod.rs b/opentelemetry/src/logs/mod.rs index 2207ff45ed..e061bf2400 100644 --- a/opentelemetry/src/logs/mod.rs +++ b/opentelemetry/src/logs/mod.rs @@ -12,7 +12,7 @@ mod record; pub use logger::{Logger, LoggerProvider}; pub use noop::NoopLoggerProvider; -pub use record::{AnyValue, LogRecord, LogRecordBuilder, Severity, TraceContext}; +pub use record::{AnyValue, LogRecord, Severity}; /// Describe the result of operations in log SDK. pub type LogResult = Result; diff --git a/opentelemetry/src/logs/noop.rs b/opentelemetry/src/logs/noop.rs index c99b891145..3456a8bd03 100644 --- a/opentelemetry/src/logs/noop.rs +++ b/opentelemetry/src/logs/noop.rs @@ -1,8 +1,8 @@ -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, sync::Arc, time::SystemTime}; use crate::{ - logs::{LogRecord, Logger, LoggerProvider}, - InstrumentationLibrary, KeyValue, + logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, + InstrumentationLibrary, Key, KeyValue, }; /// A no-op implementation of a [`LoggerProvider`]. @@ -34,12 +34,44 @@ impl LoggerProvider for NoopLoggerProvider { } } +#[derive(Debug, Clone, Default)] +/// A no-operation log record that implements the LogRecord trait. +pub struct NoopLogRecord; + +impl LogRecord for NoopLogRecord { + // Implement the LogRecord trait methods with empty bodies. + #[inline] + fn set_timestamp(&mut self, _timestamp: SystemTime) {} + #[inline] + fn set_observed_timestamp(&mut self, _timestamp: SystemTime) {} + #[inline] + fn set_severity_text(&mut self, _text: Cow<'static, str>) {} + #[inline] + fn set_severity_number(&mut self, _number: Severity) {} + #[inline] + fn set_body(&mut self, _body: AnyValue) {} + #[inline] + fn add_attributes(&mut self, _attributes: Vec<(Key, AnyValue)>) {} + #[inline] + fn add_attribute(&mut self, _key: K, _value: V) + where + K: Into, + V: Into, + { + } +} + /// A no-op implementation of a [`Logger`] #[derive(Clone, Debug)] pub struct NoopLogger(()); impl Logger for NoopLogger { - fn emit(&self, _record: LogRecord) {} + type LogRecord = NoopLogRecord; + + fn create_log_record(&self) -> Self::LogRecord { + NoopLogRecord {} + } + fn emit(&self, _record: Self::LogRecord) {} #[cfg(feature = "logs_level_enabled")] fn event_enabled(&self, _level: super::Severity, _target: &str) -> bool { false diff --git a/opentelemetry/src/logs/record.rs b/opentelemetry/src/logs/record.rs index 81fb27754f..fbbbf5ebc7 100644 --- a/opentelemetry/src/logs/record.rs +++ b/opentelemetry/src/logs/record.rs @@ -1,81 +1,31 @@ -use crate::{ - trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId}, - Array, Key, StringValue, Value, -}; +use crate::{Array, Key, StringValue, Value}; use std::{borrow::Cow, collections::HashMap, time::SystemTime}; -#[derive(Debug, Clone)] -#[non_exhaustive] -/// LogRecord represents all data carried by a log record, and -/// is provided to `LogExporter`s as input. -pub struct LogRecord { - /// Event name. Optional as not all the logging API support it. - pub event_name: Option>, +/// SDK implemented trait for managing log records +pub trait LogRecord { + /// Sets the time when the event occurred measured by the origin clock, i.e. the time at the source. + fn set_timestamp(&mut self, timestamp: SystemTime); - /// Record timestamp - pub timestamp: Option, + /// Sets the observed event timestamp. + fn set_observed_timestamp(&mut self, timestamp: SystemTime); - /// Timestamp for when the record was observed by OpenTelemetry - pub observed_timestamp: SystemTime, + /// Sets severity as text. + fn set_severity_text(&mut self, text: Cow<'static, str>); - /// Trace context for logs associated with spans - pub trace_context: Option, + /// Sets severity as a numeric value. + fn set_severity_number(&mut self, number: Severity); - /// The original severity string from the source - pub severity_text: Option>, - /// The corresponding severity value, normalized - pub severity_number: Option, + /// Sets the message body of the log. + fn set_body(&mut self, body: AnyValue); - /// Record body - pub body: Option, + /// Adds multiple attributes. + fn add_attributes(&mut self, attributes: Vec<(Key, AnyValue)>); - /// Additional attributes associated with this record - pub attributes: Option>, -} - -impl Default for LogRecord { - fn default() -> Self { - LogRecord { - event_name: None, - timestamp: None, - observed_timestamp: SystemTime::now(), - trace_context: None, - severity_text: None, - severity_number: None, - body: None, - attributes: None, - } - } -} - -impl LogRecord { - /// Create a [`LogRecordBuilder`] to create a new Log Record - pub fn builder() -> LogRecordBuilder { - LogRecordBuilder::new() - } -} - -/// TraceContext stores the trace data for logs that have an associated -/// span. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct TraceContext { - /// Trace id - pub trace_id: TraceId, - /// Span Id - pub span_id: SpanId, - /// Trace flags - pub trace_flags: Option, -} - -impl From<&SpanContext> for TraceContext { - fn from(span_context: &SpanContext) -> Self { - TraceContext { - trace_id: span_context.trace_id(), - span_id: span_context.span_id(), - trace_flags: Some(span_context.trace_flags()), - } - } + /// Adds a single attribute. + fn add_attribute(&mut self, key: K, value: V) + where + K: Into, + V: Into; } /// Value types for representing arbitrary values in a log record. @@ -250,149 +200,3 @@ impl Severity { } } } - -/// A builder for [`LogRecord`] values. -#[derive(Debug, Clone)] -pub struct LogRecordBuilder { - record: LogRecord, -} - -impl LogRecordBuilder { - /// Create a new LogRecordBuilder - pub fn new() -> Self { - Self { - record: Default::default(), - } - } - - /// Assign timestamp - pub fn with_timestamp(self, timestamp: SystemTime) -> Self { - Self { - record: LogRecord { - timestamp: Some(timestamp), - ..self.record - }, - } - } - - /// Assign observed timestamp - pub fn with_observed_timestamp(self, timestamp: SystemTime) -> Self { - Self { - record: LogRecord { - observed_timestamp: timestamp, - ..self.record - }, - } - } - - /// Assign the record's [`TraceContext`] - pub fn with_span_context(self, span_context: &SpanContext) -> Self { - Self { - record: LogRecord { - trace_context: Some(TraceContext { - span_id: span_context.span_id(), - trace_id: span_context.trace_id(), - trace_flags: Some(span_context.trace_flags()), - }), - ..self.record - }, - } - } - - /// Assign the record's [`TraceContext`] from a `TraceContextExt` trait - pub fn with_context(self, context: &T) -> Self - where - T: TraceContextExt, - { - if context.has_active_span() { - self.with_span_context(context.span().span_context()) - } else { - self - } - } - - /// Assign severity text - pub fn with_severity_text(self, severity: T) -> Self - where - T: Into>, - { - Self { - record: LogRecord { - severity_text: Some(severity.into()), - ..self.record - }, - } - } - - /// Assign severity number - pub fn with_severity_number(self, severity: Severity) -> Self { - Self { - record: LogRecord { - severity_number: Some(severity), - ..self.record - }, - } - } - - /// Assign body - pub fn with_body(self, body: impl Into) -> Self { - Self { - record: LogRecord { - body: Some(body.into()), - ..self.record - }, - } - } - - /// Assign attributes. - /// The SDK doesn't carry on any deduplication on these attributes. - pub fn with_attributes(self, attributes: Vec<(Key, AnyValue)>) -> Self { - Self { - record: LogRecord { - attributes: Some(attributes), - ..self.record - }, - } - } - - /// Set a single attribute for this record. - /// The SDK doesn't carry on any deduplication on these attributes. - pub fn with_attribute(mut self, key: K, value: V) -> Self - where - K: Into, - V: Into, - { - if let Some(ref mut vec) = self.record.attributes { - vec.push((key.into(), value.into())); - } else { - let vec = vec![(key.into(), value.into())]; - self.record.attributes = Some(vec); - } - - self - } - - /// Sets the `event_name` of a record. - pub fn with_name(self, name: T) -> Self - where - T: Into>, - { - Self { - record: LogRecord { - event_name: Some(name.into()), - ..self.record - }, - } - } - - /// Build the record, consuming the Builder - pub fn build(self) -> LogRecord { - self.record - } -} - -impl Default for LogRecordBuilder { - fn default() -> Self { - Self::new() - } -}