diff --git a/opentelemetry-appender-tracing/Cargo.toml b/opentelemetry-appender-tracing/Cargo.toml index d104014662..37b887a2a6 100644 --- a/opentelemetry-appender-tracing/Cargo.toml +++ b/opentelemetry-appender-tracing/Cargo.toml @@ -26,6 +26,7 @@ opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["logs", "testin tracing-log = "0.2" async-trait = { workspace = true } criterion = { workspace = true } +smallvec = "1.13" [target.'cfg(not(target_os = "windows"))'.dev-dependencies] pprof = { version = "0.13", features = ["flamegraph", "criterion"] } diff --git a/opentelemetry-appender-tracing/benches/logs.rs b/opentelemetry-appender-tracing/benches/logs.rs index 7f832c08c6..a4cfa64824 100644 --- a/opentelemetry-appender-tracing/benches/logs.rs +++ b/opentelemetry-appender-tracing/benches/logs.rs @@ -139,9 +139,9 @@ fn benchmark_with_ot_layer(c: &mut Criterion, enabled: bool, bench_name: &str) { c.bench_function(bench_name, |b| { b.iter(|| { error!( - name = "CheckoutFailed", - book_id = "12345", - book_title = "Rust Programming Adventures", + // name = "CheckoutFailed", + //book_id =12345, + // book_title = "Rust Programming Adventures", "Unable to process checkout." ); }); diff --git a/opentelemetry-appender-tracing/src/layer.rs b/opentelemetry-appender-tracing/src/layer.rs index 45eb85dc92..e31382311e 100644 --- a/opentelemetry-appender-tracing/src/layer.rs +++ b/opentelemetry-appender-tracing/src/layer.rs @@ -39,12 +39,9 @@ impl<'a, LR: LogRecord> EventVisitor<'a, LR> { fn new(log_record: &'a mut LR) -> Self { EventVisitor { log_record } } - fn visit_metadata(&mut self, meta: &Metadata) { - self.log_record - .add_attribute(Key::new("name"), AnyValue::from(meta.name())); - + fn visit_metadata(&mut self, _meta: &Metadata) { #[cfg(feature = "experimental_metadata_attributes")] - self.visit_experimental_metadata(meta); + self.visit_experimental_metadata(_meta); } #[cfg(feature = "experimental_metadata_attributes")] @@ -168,11 +165,11 @@ where #[cfg(not(feature = "experimental_metadata_attributes"))] let meta = event.metadata(); - //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()); log_record.set_target(meta.target().to_string()); + log_record.set_event_name(meta.name()); let mut visitor = EventVisitor::new(&mut log_record); visitor.visit_metadata(meta); @@ -215,9 +212,12 @@ mod tests { use opentelemetry_sdk::testing::logs::InMemoryLogsExporter; use opentelemetry_sdk::trace; use opentelemetry_sdk::trace::{Sampler, TracerProvider}; + use smallvec::SmallVec; use tracing::error; use tracing_subscriber::layer::SubscriberExt; + const PREALLOCATED_ATTRIBUTE_CAPACITY: usize = 8; + // cargo test --features=testing #[test] fn tracing_appender_standalone() { @@ -255,7 +255,7 @@ mod tests { assert!(log.record.trace_context.is_none()); // Validate attributes - let attributes: Vec<(Key, AnyValue)> = log + let attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = log .record .attributes .clone() @@ -352,7 +352,7 @@ mod tests { ); // validate attributes. - let attributes: Vec<(Key, AnyValue)> = log + let attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = log .record .attributes .clone() @@ -419,7 +419,7 @@ mod tests { assert!(log.record.trace_context.is_none()); // Validate attributes - let attributes: Vec<(Key, AnyValue)> = log + let attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = log .record .attributes .clone() @@ -516,7 +516,7 @@ mod tests { ); // validate attributes. - let attributes: Vec<(Key, AnyValue)> = log + let attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = log .record .attributes .clone() diff --git a/opentelemetry-proto/src/transform/logs.rs b/opentelemetry-proto/src/transform/logs.rs index 0a672d3d51..0a33dee41c 100644 --- a/opentelemetry-proto/src/transform/logs.rs +++ b/opentelemetry-proto/src/transform/logs.rs @@ -50,7 +50,8 @@ pub mod tonic { }) .collect(), }), - LogsAnyValue::Bytes(v) => Value::BytesValue(v), + + LogsAnyValue::Bytes(v) => Value::BytesValue(*v), } } } diff --git a/opentelemetry-sdk/Cargo.toml b/opentelemetry-sdk/Cargo.toml index 59d2abc1c1..b83728e3c7 100644 --- a/opentelemetry-sdk/Cargo.toml +++ b/opentelemetry-sdk/Cargo.toml @@ -28,6 +28,7 @@ url = { workspace = true, optional = true } tokio = { workspace = true, features = ["rt", "time"], optional = true } tokio-stream = { workspace = true, optional = true } http = { workspace = true, optional = true } +smallvec = "1.12" [package.metadata.docs.rs] all-features = true diff --git a/opentelemetry-sdk/benches/log.rs b/opentelemetry-sdk/benches/log.rs index 8983a5ac99..4ea3256bab 100644 --- a/opentelemetry-sdk/benches/log.rs +++ b/opentelemetry-sdk/benches/log.rs @@ -100,7 +100,7 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let bytes = AnyValue::Bytes(vec![25u8, 30u8, 40u8]); + let bytes = AnyValue::Bytes(Box::new(vec![25u8, 30u8, 40u8])); log_benchmark_group(c, "simple-log-with-bytes", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); @@ -108,14 +108,14 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let bytes = AnyValue::Bytes(vec![ + let bytes = AnyValue::Bytes(Box::new(vec![ 25u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, 30u8, 40u8, - ]); + ])); log_benchmark_group(c, "simple-log-with-a-lot-of-bytes", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); @@ -123,7 +123,11 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let vec_any_values = AnyValue::ListAny(vec![AnyValue::Int(25), "test".into(), true.into()]); + let vec_any_values = AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(25), + "test".into(), + true.into(), + ])); log_benchmark_group(c, "simple-log-with-vec-any-value", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); @@ -131,13 +135,17 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let vec_any_values = AnyValue::ListAny(vec![AnyValue::Int(25), "test".into(), true.into()]); - let vec_any_values = AnyValue::ListAny(vec![ + let vec_any_values = AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(25), + "test".into(), + true.into(), + ])); + let vec_any_values = AnyValue::ListAny(Box::new(vec![ AnyValue::Int(25), "test".into(), true.into(), vec_any_values, - ]); + ])); log_benchmark_group(c, "simple-log-with-inner-vec-any-value", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); @@ -145,11 +153,11 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let map_any_values = AnyValue::Map(HashMap::from([ + let map_any_values = AnyValue::Map(Box::new(HashMap::from([ ("testint".into(), 2.into()), ("testdouble".into(), 2.2.into()), ("teststring".into(), "test".into()), - ])); + ]))); log_benchmark_group(c, "simple-log-with-map-any-value", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); @@ -157,17 +165,17 @@ fn criterion_benchmark(c: &mut Criterion) { logger.emit(log_record); }); - let map_any_values = AnyValue::Map(HashMap::from([ + let map_any_values = AnyValue::Map(Box::new(HashMap::from([ ("testint".into(), 2.into()), ("testdouble".into(), 2.2.into()), ("teststring".into(), "test".into()), - ])); - let map_any_values = AnyValue::Map(HashMap::from([ + ]))); + let map_any_values = AnyValue::Map(Box::new(HashMap::from([ ("testint".into(), 2.into()), ("testdouble".into(), 2.2.into()), ("teststring".into(), "test".into()), ("testmap".into(), map_any_values), - ])); + ]))); log_benchmark_group(c, "simple-log-with-inner-map-any-value", |logger| { let mut log_record = logger.create_log_record(); log_record.set_body("simple log".into()); diff --git a/opentelemetry-sdk/src/logs/log_emitter.rs b/opentelemetry-sdk/src/logs/log_emitter.rs index 9914c7b408..7cea719eb6 100644 --- a/opentelemetry-sdk/src/logs/log_emitter.rs +++ b/opentelemetry-sdk/src/logs/log_emitter.rs @@ -40,10 +40,6 @@ pub struct LoggerProvider { /// Default logger name if empty string is provided. const DEFAULT_COMPONENT_NAME: &str = "rust.opentelemetry.io/sdk/logger"; -// According to a Go-specific study mentioned on https://go.dev/blog/slog, -// up to 5 attributes is the most common case. We have chosen 8 as the default -// capacity for attributes to avoid reallocation in common scenarios. -const PREALLOCATED_ATTRIBUTE_CAPACITY: usize = 8; impl opentelemetry::logs::LoggerProvider for LoggerProvider { type Logger = Logger; @@ -251,10 +247,7 @@ impl opentelemetry::logs::Logger for Logger { fn create_log_record(&self) -> Self::LogRecord { // Reserve attributes memory for perf optimization. This may change in future. - LogRecord { - attributes: Some(Vec::with_capacity(PREALLOCATED_ATTRIBUTE_CAPACITY)), - ..Default::default() - } + LogRecord::default() } /// Emit a `LogRecord`. diff --git a/opentelemetry-sdk/src/logs/log_processor.rs b/opentelemetry-sdk/src/logs/log_processor.rs index 95a0378a2d..b837aadd9d 100644 --- a/opentelemetry-sdk/src/logs/log_processor.rs +++ b/opentelemetry-sdk/src/logs/log_processor.rs @@ -530,6 +530,7 @@ mod tests { use opentelemetry::logs::{Logger, LoggerProvider as _}; use opentelemetry::Key; use opentelemetry::{logs::LogResult, KeyValue}; + use smallvec::smallvec; use std::borrow::Cow; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -816,7 +817,7 @@ mod tests { impl LogProcessor for FirstProcessor { fn emit(&self, data: &mut LogData) { // add attribute - data.record.attributes.get_or_insert(vec![]).push(( + data.record.attributes.get_or_insert(smallvec![]).push(( Key::from_static_str("processed_by"), AnyValue::String("FirstProcessor".into()), )); diff --git a/opentelemetry-sdk/src/logs/mod.rs b/opentelemetry-sdk/src/logs/mod.rs index 207da4255c..8c5bc17882 100644 --- a/opentelemetry-sdk/src/logs/mod.rs +++ b/opentelemetry-sdk/src/logs/mod.rs @@ -14,11 +14,13 @@ pub use record::{LogRecord, TraceContext}; #[cfg(all(test, feature = "testing"))] mod tests { use super::*; + use crate::logs::record::PREALLOCATED_ATTRIBUTE_CAPACITY; use crate::testing::logs::InMemoryLogsExporter; use crate::Resource; use opentelemetry::logs::LogRecord; use opentelemetry::logs::{Logger, LoggerProvider as _, Severity}; use opentelemetry::{logs::AnyValue, Key, KeyValue}; + use smallvec::SmallVec; use std::borrow::Borrow; use std::collections::HashMap; @@ -80,7 +82,7 @@ mod tests { .expect("Atleast one log is expected to be present."); assert_eq!(log.instrumentation.name, "test-logger"); assert_eq!(log.record.severity_number, Some(Severity::Error)); - let attributes: Vec<(Key, AnyValue)> = log + let attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = log .record .attributes .clone() diff --git a/opentelemetry-sdk/src/logs/record.rs b/opentelemetry-sdk/src/logs/record.rs index 3b79f10f7a..4a008e2abc 100644 --- a/opentelemetry-sdk/src/logs/record.rs +++ b/opentelemetry-sdk/src/logs/record.rs @@ -3,8 +3,14 @@ use opentelemetry::{ trace::{SpanContext, SpanId, TraceFlags, TraceId}, Key, }; +use smallvec::{smallvec, SmallVec}; use std::{borrow::Cow, time::SystemTime}; +// According to a Go-specific study mentioned on https://go.dev/blog/slog, +// up to 5 attributes is the most common case. We have chosen 8 as the default +// capacity for attributes to avoid reallocation in common scenarios. +pub(crate) const PREALLOCATED_ATTRIBUTE_CAPACITY: usize = 8; + #[derive(Debug, Default, Clone)] #[non_exhaustive] /// LogRecord represents all data carried by a log record, and @@ -34,7 +40,7 @@ pub struct LogRecord { pub body: Option, /// Additional attributes associated with this record - pub attributes: Option>, + pub attributes: Option>, } impl opentelemetry::logs::LogRecord for LogRecord { @@ -91,8 +97,6 @@ impl opentelemetry::logs::LogRecord for LogRecord { { if let Some(ref mut attrs) = self.attributes { attrs.push((key.into(), value.into())); - } else { - self.attributes = Some(vec![(key.into(), value.into())]); } } } @@ -186,16 +190,17 @@ mod tests { 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)); + let expected_attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = + smallvec![(Key::new("key"), AnyValue::String("value".into()))]; + assert_eq!(log_record.attributes, Some(expected_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()))]) - ); + let expected_attributes: SmallVec<[(Key, AnyValue); PREALLOCATED_ATTRIBUTE_CAPACITY]> = + smallvec![(Key::new("key"), AnyValue::String("value".into()))]; + assert_eq!(log_record.attributes, Some(expected_attributes)); } } diff --git a/opentelemetry-stdout/src/common.rs b/opentelemetry-stdout/src/common.rs index bd6f178969..0b87455713 100644 --- a/opentelemetry-stdout/src/common.rs +++ b/opentelemetry-stdout/src/common.rs @@ -156,7 +156,7 @@ impl From for Value { }) .collect(), ), - opentelemetry::logs::AnyValue::Bytes(b) => Value::BytesValue(b), + opentelemetry::logs::AnyValue::Bytes(b) => Value::BytesValue(*b), } } } diff --git a/opentelemetry/benches/anyvalue.rs b/opentelemetry/benches/anyvalue.rs index bc93023c76..80a6f551ba 100644 --- a/opentelemetry/benches/anyvalue.rs +++ b/opentelemetry/benches/anyvalue.rs @@ -16,11 +16,23 @@ fn criterion_benchmark(c: &mut Criterion) { fn attributes_creation(c: &mut Criterion) { c.bench_function("CreateOTelValueString", |b| { b.iter(|| { - let _v = black_box(Value::String("value1".into())); + let _v = black_box(Value::String(String::from("value1").into())); }); }); c.bench_function("CreateOTelAnyValueString", |b| { + b.iter(|| { + let _v = black_box(AnyValue::String(String::from("value1").into())); + }); + }); + + c.bench_function("CreateOTelValueStaticStr", |b| { + b.iter(|| { + let _v = black_box(Value::String("value1".into())); + }); + }); + + c.bench_function("CreateOTelAnyValueStaticStr", |b| { b.iter(|| { let _v = black_box(AnyValue::String("value1".into())); }); diff --git a/opentelemetry/src/logs/record.rs b/opentelemetry/src/logs/record.rs index be00a7ab5c..a77f25c072 100644 --- a/opentelemetry/src/logs/record.rs +++ b/opentelemetry/src/logs/record.rs @@ -59,11 +59,11 @@ pub enum AnyValue { /// A boolean value Boolean(bool), /// A byte array - Bytes(Vec), + Bytes(Box>), /// An array of `Any` values - ListAny(Vec), + ListAny(Box>), /// A map of string keys to `Any` values, arbitrarily nested. - Map(HashMap), + Map(Box>), } macro_rules! impl_trivial_from { @@ -98,7 +98,7 @@ impl_trivial_from!(bool, AnyValue::Boolean); impl> FromIterator for AnyValue { /// Creates an [`AnyValue::ListAny`] value from a sequence of `Into` values. fn from_iter>(iter: I) -> Self { - AnyValue::ListAny(iter.into_iter().map(Into::into).collect()) + AnyValue::ListAny(Box::new(iter.into_iter().map(Into::into).collect())) } } @@ -106,9 +106,9 @@ impl, V: Into> FromIterator<(K, V)> for AnyValue { /// Creates an [`AnyValue::Map`] value from a sequence of key-value pairs /// that can be converted into a `Key` and `AnyValue` respectively. fn from_iter>(iter: I) -> Self { - AnyValue::Map(HashMap::from_iter( + AnyValue::Map(Box::new(HashMap::from_iter( iter.into_iter().map(|(k, v)| (k.into(), v.into())), - )) + ))) } }