From bb0c479152495f836e34b2a8574ea13198e03be3 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 19 Mar 2024 11:52:20 +1000 Subject: [PATCH 1/7] treat log's key-values as attributes in log bridge --- Cargo.toml | 2 +- opentelemetry-appender-log/Cargo.toml | 4 +- .../examples/logs-basic-in-memory.rs | 5 +- opentelemetry-appender-log/src/lib.rs | 600 +++++++++++++++++- 4 files changed, 598 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3092889e71..5e29932d6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ futures-util = { version = "0.3", default-features = false } hyper = { version = "0.14", default-features = false } http = { version = "0.2", default-features = false } isahc = { version = "1.4", default-features = false } -log = "0.4" +log = "0.4.21" once_cell = "1.13" ordered-float = "4.0" pin-project-lite = "0.2" diff --git a/opentelemetry-appender-log/Cargo.toml b/opentelemetry-appender-log/Cargo.toml index 0ed81735f3..637ee89f06 100644 --- a/opentelemetry-appender-log/Cargo.toml +++ b/opentelemetry-appender-log/Cargo.toml @@ -12,7 +12,8 @@ edition = "2021" [dependencies] opentelemetry = { version = "0.22", path = "../opentelemetry", features = ["logs"]} -log = { workspace = true, features = ["kv_unstable", "std"]} +log = { workspace = true, features = ["kv", "kv_serde", "std"]} +serde = { workspace = true, features = ["std"] } [features] logs_level_enabled = ["opentelemetry/logs_level_enabled"] @@ -21,3 +22,4 @@ default = ["logs_level_enabled"] [dev-dependencies] opentelemetry_sdk = { path = "../opentelemetry-sdk", features = [ "testing", "logs_level_enabled" ] } tokio = { workspace = true } +serde = { workspace = true, features = ["std", "derive"] } diff --git a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs b/opentelemetry-appender-log/examples/logs-basic-in-memory.rs index 3348c6f3cf..05e804c4aa 100644 --- a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs +++ b/opentelemetry-appender-log/examples/logs-basic-in-memory.rs @@ -25,7 +25,10 @@ async fn main() { log::set_max_level(Level::Info.to_level_filter()); // Emit logs using macros from the log crate. - error!("hello from {}. My price is {}", "apple", 2.99); + let user = "apple"; + let price = 2.99; + + error!(user, price; "hello from {user}. My price is {price}"); warn!("warn!"); info!("test log!"); diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index 66f58feb22..b8891a5426 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -1,5 +1,5 @@ use log::{Level, Metadata, Record}; -use opentelemetry::logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}; +use opentelemetry::{logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}, Key}; use std::borrow::Cow; pub struct OpenTelemetryLogBridge @@ -34,6 +34,7 @@ where // 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(), ); } @@ -70,13 +71,512 @@ const fn severity_of_level(level: Level) -> Severity { } } -#[cfg(all(test, feature = "testing", feature = "logs"))] +fn log_attributes(kvs: impl log::kv::Source) -> Vec<(Key, AnyValue)> { + struct AttributeVisitor(Vec<(Key, AnyValue)>); + + impl<'kvs> log::kv::VisitSource<'kvs> for AttributeVisitor { + fn visit_pair(&mut self, key: log::kv::Key<'kvs>, value: log::kv::Value<'kvs>) -> Result<(), log::kv::Error> { + let key = Key::from(String::from(key.as_str())); + + if let Some(value) = any_value::serialize(value) { + self.0.push((key, value)); + } + + Ok(()) + } + } + + let mut visitor = AttributeVisitor(Vec::new()); + + let _ = kvs.visit(&mut visitor); + + visitor.0 +} + +// This could make a nice addition to the SDK itself for serializing into `AnyValue`s +mod any_value { + use std::{collections::HashMap, fmt}; + + use opentelemetry::{logs::AnyValue, Key, StringValue}; + use serde::ser::{ + Error, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, + SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, StdError, + }; + + /// Serialize an arbitrary `serde::Serialize` into an `AnyValue`. + /// + /// This method performs the following translations when converting between `serde`'s data model and OpenTelemetry's: + /// + /// - Integers that don't fit in a `i64` are converted into strings. + /// - Unit types and nones are discarded (effectively treated as undefined). + /// - Struct and tuple variants are converted into an internally tagged map. + /// - Unit variants are converted into strings. + pub(crate) fn serialize(value: impl serde::Serialize) -> Option { + value.serialize(ValueSerializer).ok()? + } + + struct ValueSerializer; + + struct ValueSerializeSeq { + value: Vec, + } + + struct ValueSerializeTuple { + value: Vec, + } + + struct ValueSerializeTupleStruct { + value: Vec, + } + + struct ValueSerializeMap { + key: Option, + value: HashMap, + } + + struct ValueSerializeStruct { + value: HashMap, + } + + struct ValueSerializeTupleVariant { + variant: &'static str, + value: Vec, + } + + struct ValueSerializeStructVariant { + variant: &'static str, + value: HashMap, + } + + #[derive(Debug)] + struct ValueError(String); + + impl fmt::Display for ValueError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } + } + + impl Error for ValueError { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + ValueError(msg.to_string()) + } + } + + impl StdError for ValueError {} + + impl Serializer for ValueSerializer { + type Ok = Option; + + type Error = ValueError; + + type SerializeSeq = ValueSerializeSeq; + + type SerializeTuple = ValueSerializeTuple; + + type SerializeTupleStruct = ValueSerializeTupleStruct; + + type SerializeTupleVariant = ValueSerializeTupleVariant; + + type SerializeMap = ValueSerializeMap; + + type SerializeStruct = ValueSerializeStruct; + + type SerializeStructVariant = ValueSerializeStructVariant; + + fn serialize_bool(self, v: bool) -> Result { + Ok(Some(AnyValue::Boolean(v))) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(Some(AnyValue::Int(v))) + } + + fn serialize_i128(self, v: i128) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u16(self, v: u16) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u32(self, v: u32) -> Result { + self.serialize_i64(v as i64) + } + + fn serialize_u64(self, v: u64) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_u128(self, v: u128) -> Result { + if let Ok(v) = v.try_into() { + self.serialize_i64(v) + } else { + self.collect_str(&v) + } + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(v as f64) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(Some(AnyValue::Double(v))) + } + + fn serialize_char(self, v: char) -> Result { + self.collect_str(&v) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(Some(AnyValue::String(StringValue::from(v.to_owned())))) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Some(AnyValue::Bytes(v.to_owned()))) + } + + fn serialize_none(self) -> Result { + Ok(None) + } + + fn serialize_some(self, value: &T) -> Result + where + T: serde::Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(None) + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + name.serialize(self) + } + + fn serialize_unit_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + ) -> Result { + variant.serialize(self) + } + + fn serialize_newtype_struct( + self, + _: &'static str, + value: &T, + ) -> Result + where + T: serde::Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: serde::Serialize, + { + let mut map = self.serialize_map(Some(1))?; + map.serialize_entry(variant, value)?; + map.end() + } + + fn serialize_seq(self, _: Option) -> Result { + Ok(ValueSerializeSeq { + value: Vec::new(), + }) + } + + fn serialize_tuple(self, _: usize) -> Result { + Ok(ValueSerializeTuple { + value: Vec::new(), + }) + } + + fn serialize_tuple_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeTupleStruct { + value: Vec::new(), + }) + } + + fn serialize_tuple_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeTupleVariant { + variant, + value: Vec::new(), + }) + } + + fn serialize_map(self, _: Option) -> Result { + Ok(ValueSerializeMap { + key: None, + value: HashMap::new(), + }) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeStruct { + value: HashMap::new(), + }) + } + + fn serialize_struct_variant( + self, + _: &'static str, + _: u32, + variant: &'static str, + _: usize, + ) -> Result { + Ok(ValueSerializeStructVariant { + variant, + value: HashMap::new(), + }) + } + } + + impl SerializeSeq for ValueSerializeSeq { + type Ok = Option; + + type Error = ValueError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(self.value))) + } + } + + impl SerializeTuple for ValueSerializeTuple { + type Ok = Option; + + type Error = ValueError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(self.value))) + } + } + + impl SerializeTupleStruct for ValueSerializeTupleStruct { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::ListAny(self.value))) + } + } + + impl SerializeTupleVariant for ValueSerializeTupleVariant { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.push(value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map({ + let mut variant = HashMap::new(); + variant.insert(Key::from(self.variant), AnyValue::ListAny(self.value)); + variant + }))) + } + } + + impl SerializeMap for ValueSerializeMap { + type Ok = Option; + + type Error = ValueError; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = match key.serialize(ValueSerializer)? { + Some(AnyValue::String(key)) => Key::from(String::from(key)), + key => Key::from(format!("{:?}", key)), + }; + + self.key = Some(key); + + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = self.key.take().ok_or_else(|| Self::Error::custom("missing key"))?; + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map(self.value))) + } + } + + impl SerializeStruct for ValueSerializeStruct { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = Key::from(key); + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map(self.value))) + } + } + + impl SerializeStructVariant for ValueSerializeStructVariant { + type Ok = Option; + + type Error = ValueError; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = Key::from(key); + + if let Some(value) = value.serialize(ValueSerializer)? { + self.value.insert(key, value); + } + + Ok(()) + } + + fn end(self) -> Result { + Ok(Some(AnyValue::Map({ + let mut variant = HashMap::new(); + variant.insert(Key::from(self.variant), AnyValue::Map(self.value)); + variant + }))) + } + } +} + +#[cfg(test)] mod tests { + use std::collections::HashMap; + use super::OpenTelemetryLogBridge; + use opentelemetry::{logs::AnyValue, Key, StringValue}; use opentelemetry_sdk::{logs::LoggerProvider, testing::logs::InMemoryLogsExporter}; - use log::{Level, Log}; + use log::Log; #[test] fn logbridge_with_default_metadata_is_enabled() { @@ -113,14 +613,20 @@ mod tests { let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); - log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); - log::set_max_level(Level::Trace.to_level_filter()); + // log::trace!("TRACE") + otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Trace).args(format_args!("TRACE")).build()); + + // log::trace!("DEBUG") + otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Debug).args(format_args!("DEBUG")).build()); - log::trace!("TRACE"); - log::debug!("DEBUG"); - log::info!("INFO"); - log::warn!("WARN"); - log::error!("ERROR"); + // log::trace!("INFO") + otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Info).args(format_args!("INFO")).build()); + + // log::trace!("WARN") + otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Warn).args(format_args!("WARN")).build()); + + // log::trace!("ERROR") + otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Error).args(format_args!("ERROR")).build()); let logs = exporter.get_emitted_logs().unwrap(); @@ -134,6 +640,80 @@ mod tests { } } + #[test] + fn logbridge_attributes() { + #[derive(serde::Serialize)] + struct Map { + a: i32, + b: i32, + c: i32, + } + + let exporter = InMemoryLogsExporter::default(); + + let logger_provider = LoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + + /*log::info!( + string_value = "a string", + int_value = 42, + double_value = 3.14, + boolean_value = true, + list_value:serde = [1, 2, 3], + map_value:serde = Map { a: 1, b: 2, c: 3 }; + "body" + );*/ + otel_log_appender.log(&log::RecordBuilder::new() + .level(log::Level::Info) + .args(format_args!("body")) + .key_values(&[ + ("string_value", log::kv::Value::from("a string")), + ("int_value", log::kv::Value::from(42)), + ("double_value", log::kv::Value::from(3.14)), + ("boolean_value", log::kv::Value::from(true)), + ("list_value", log::kv::Value::from_serde(&[1, 2, 3])), + ("map_value", log::kv::Value::from_serde(&Map { a: 1, b: 2, c: 3 })), + ]) + .build() + ); + + let logs = exporter.get_emitted_logs().unwrap(); + let attributes = &logs[0].record.attributes.as_ref().unwrap(); + + let get = |needle: &str| attributes.iter().find_map(|(k, v)| if k.as_str() == needle { + Some(v.clone()) + } else { + None + }); + + assert_eq!(AnyValue::String(StringValue::from("a string")), get("string_value").unwrap()); + + assert_eq!(AnyValue::Int(42), get("int_value").unwrap()); + + assert_eq!(AnyValue::Double(3.14), get("double_value").unwrap()); + + assert_eq!(AnyValue::Boolean(true), get("boolean_value").unwrap()); + + assert_eq!(AnyValue::ListAny(vec![ + AnyValue::Int(1), + AnyValue::Int(2), + AnyValue::Int(3), + ]), get("list_value").unwrap()); + + assert_eq!(AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(2)); + map.insert(Key::from("c"), AnyValue::Int(3)); + + map + }), get("map_value").unwrap()); + } + #[test] fn test_flush() { let exporter = InMemoryLogsExporter::default(); From 4f23ac5ba34417d413674825e625bbd02f41887b Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 19 Mar 2024 12:07:01 +1000 Subject: [PATCH 2/7] run fmt --- opentelemetry-appender-log/src/lib.rs | 230 +++++++++++++++----------- 1 file changed, 130 insertions(+), 100 deletions(-) diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index b8891a5426..15ad03af38 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -1,5 +1,8 @@ use log::{Level, Metadata, Record}; -use opentelemetry::{logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}, Key}; +use opentelemetry::{ + logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}, + Key, +}; use std::borrow::Cow; pub struct OpenTelemetryLogBridge @@ -75,7 +78,11 @@ fn log_attributes(kvs: impl log::kv::Source) -> Vec<(Key, AnyValue)> { struct AttributeVisitor(Vec<(Key, AnyValue)>); impl<'kvs> log::kv::VisitSource<'kvs> for AttributeVisitor { - fn visit_pair(&mut self, key: log::kv::Key<'kvs>, value: log::kv::Value<'kvs>) -> Result<(), log::kv::Error> { + fn visit_pair( + &mut self, + key: log::kv::Key<'kvs>, + value: log::kv::Value<'kvs>, + ) -> Result<(), log::kv::Error> { let key = Key::from(String::from(key.as_str())); if let Some(value) = any_value::serialize(value) { @@ -104,9 +111,9 @@ mod any_value { }; /// Serialize an arbitrary `serde::Serialize` into an `AnyValue`. - /// + /// /// This method performs the following translations when converting between `serde`'s data model and OpenTelemetry's: - /// + /// /// - Integers that don't fit in a `i64` are converted into strings. /// - Unit types and nones are discarded (effectively treated as undefined). /// - Struct and tuple variants are converted into an internally tagged map. @@ -267,10 +274,10 @@ mod any_value { Ok(None) } - fn serialize_some(self, value: &T) -> Result - where - T: serde::Serialize, - { + fn serialize_some( + self, + value: &T, + ) -> Result { value.serialize(self) } @@ -291,42 +298,32 @@ mod any_value { variant.serialize(self) } - fn serialize_newtype_struct( + fn serialize_newtype_struct( self, _: &'static str, value: &T, - ) -> Result - where - T: serde::Serialize, - { + ) -> Result { value.serialize(self) } - fn serialize_newtype_variant( + fn serialize_newtype_variant( self, _: &'static str, _: u32, variant: &'static str, value: &T, - ) -> Result - where - T: serde::Serialize, - { + ) -> Result { let mut map = self.serialize_map(Some(1))?; map.serialize_entry(variant, value)?; map.end() } fn serialize_seq(self, _: Option) -> Result { - Ok(ValueSerializeSeq { - value: Vec::new(), - }) + Ok(ValueSerializeSeq { value: Vec::new() }) } fn serialize_tuple(self, _: usize) -> Result { - Ok(ValueSerializeTuple { - value: Vec::new(), - }) + Ok(ValueSerializeTuple { value: Vec::new() }) } fn serialize_tuple_struct( @@ -334,9 +331,7 @@ mod any_value { _: &'static str, _: usize, ) -> Result { - Ok(ValueSerializeTupleStruct { - value: Vec::new(), - }) + Ok(ValueSerializeTupleStruct { value: Vec::new() }) } fn serialize_tuple_variant( @@ -388,10 +383,10 @@ mod any_value { type Error = ValueError; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { + fn serialize_element( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { if let Some(value) = value.serialize(ValueSerializer)? { self.value.push(value); } @@ -409,10 +404,10 @@ mod any_value { type Error = ValueError; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { + fn serialize_element( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { if let Some(value) = value.serialize(ValueSerializer)? { self.value.push(value); } @@ -430,10 +425,10 @@ mod any_value { type Error = ValueError; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { if let Some(value) = value.serialize(ValueSerializer)? { self.value.push(value); } @@ -451,10 +446,10 @@ mod any_value { type Error = ValueError; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { + fn serialize_field( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { if let Some(value) = value.serialize(ValueSerializer)? { self.value.push(value); } @@ -476,10 +471,10 @@ mod any_value { type Error = ValueError; - fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> - where - T: Serialize, - { + fn serialize_key( + &mut self, + key: &T, + ) -> Result<(), Self::Error> { let key = match key.serialize(ValueSerializer)? { Some(AnyValue::String(key)) => Key::from(String::from(key)), key => Key::from(format!("{:?}", key)), @@ -490,11 +485,14 @@ mod any_value { Ok(()) } - fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - let key = self.key.take().ok_or_else(|| Self::Error::custom("missing key"))?; + fn serialize_value( + &mut self, + value: &T, + ) -> Result<(), Self::Error> { + let key = self + .key + .take() + .ok_or_else(|| Self::Error::custom("missing key"))?; if let Some(value) = value.serialize(ValueSerializer)? { self.value.insert(key, value); @@ -513,14 +511,11 @@ mod any_value { type Error = ValueError; - fn serialize_field( + fn serialize_field( &mut self, key: &'static str, value: &T, - ) -> Result<(), Self::Error> - where - T: Serialize, - { + ) -> Result<(), Self::Error> { let key = Key::from(key); if let Some(value) = value.serialize(ValueSerializer)? { @@ -540,14 +535,11 @@ mod any_value { type Error = ValueError; - fn serialize_field( + fn serialize_field( &mut self, key: &'static str, value: &T, - ) -> Result<(), Self::Error> - where - T: Serialize, - { + ) -> Result<(), Self::Error> { let key = Key::from(key); if let Some(value) = value.serialize(ValueSerializer)? { @@ -614,19 +606,44 @@ mod tests { let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); // log::trace!("TRACE") - otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Trace).args(format_args!("TRACE")).build()); + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Trace) + .args(format_args!("TRACE")) + .build(), + ); // log::trace!("DEBUG") - otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Debug).args(format_args!("DEBUG")).build()); + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Debug) + .args(format_args!("DEBUG")) + .build(), + ); // log::trace!("INFO") - otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Info).args(format_args!("INFO")).build()); + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Info) + .args(format_args!("INFO")) + .build(), + ); // log::trace!("WARN") - otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Warn).args(format_args!("WARN")).build()); + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Warn) + .args(format_args!("WARN")) + .build(), + ); // log::trace!("ERROR") - otel_log_appender.log(&log::RecordBuilder::new().level(log::Level::Error).args(format_args!("ERROR")).build()); + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Error) + .args(format_args!("ERROR")) + .build(), + ); let logs = exporter.get_emitted_logs().unwrap(); @@ -666,52 +683,65 @@ mod tests { map_value:serde = Map { a: 1, b: 2, c: 3 }; "body" );*/ - otel_log_appender.log(&log::RecordBuilder::new() - .level(log::Level::Info) - .args(format_args!("body")) - .key_values(&[ - ("string_value", log::kv::Value::from("a string")), - ("int_value", log::kv::Value::from(42)), - ("double_value", log::kv::Value::from(3.14)), - ("boolean_value", log::kv::Value::from(true)), - ("list_value", log::kv::Value::from_serde(&[1, 2, 3])), - ("map_value", log::kv::Value::from_serde(&Map { a: 1, b: 2, c: 3 })), - ]) - .build() + otel_log_appender.log( + &log::RecordBuilder::new() + .level(log::Level::Info) + .args(format_args!("body")) + .key_values(&[ + ("string_value", log::kv::Value::from("a string")), + ("int_value", log::kv::Value::from(42)), + ("double_value", log::kv::Value::from(3.14)), + ("boolean_value", log::kv::Value::from(true)), + ("list_value", log::kv::Value::from_serde(&[1, 2, 3])), + ( + "map_value", + log::kv::Value::from_serde(&Map { a: 1, b: 2, c: 3 }), + ), + ]) + .build(), ); let logs = exporter.get_emitted_logs().unwrap(); let attributes = &logs[0].record.attributes.as_ref().unwrap(); - let get = |needle: &str| attributes.iter().find_map(|(k, v)| if k.as_str() == needle { - Some(v.clone()) - } else { - None - }); + let get = |needle: &str| { + attributes.iter().find_map(|(k, v)| { + if k.as_str() == needle { + Some(v.clone()) + } else { + None + } + }) + }; + + assert_eq!( + AnyValue::String(StringValue::from("a string")), + get("string_value").unwrap() + ); - assert_eq!(AnyValue::String(StringValue::from("a string")), get("string_value").unwrap()); - assert_eq!(AnyValue::Int(42), get("int_value").unwrap()); - + assert_eq!(AnyValue::Double(3.14), get("double_value").unwrap()); - + assert_eq!(AnyValue::Boolean(true), get("boolean_value").unwrap()); - assert_eq!(AnyValue::ListAny(vec![ - AnyValue::Int(1), - AnyValue::Int(2), - AnyValue::Int(3), - ]), get("list_value").unwrap()); + assert_eq!( + AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(2), AnyValue::Int(3),]), + get("list_value").unwrap() + ); - assert_eq!(AnyValue::Map({ - let mut map = HashMap::new(); + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); - map.insert(Key::from("a"), AnyValue::Int(1)); - map.insert(Key::from("b"), AnyValue::Int(2)); - map.insert(Key::from("c"), AnyValue::Int(3)); + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(2)); + map.insert(Key::from("c"), AnyValue::Int(3)); - map - }), get("map_value").unwrap()); + map + }), + get("map_value").unwrap() + ); } #[test] From ed7c3560768fbfe41c0b079373d9c55b6b8d146d Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 19 Mar 2024 13:37:52 +1000 Subject: [PATCH 3/7] fix a few more lints --- opentelemetry-appender-log/src/lib.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index 15ad03af38..ee3bd78f6f 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -584,15 +584,9 @@ mod tests { // the processor used is a `SimpleLogProcessor` which has an implementation of `event_enabled` // that always returns true. #[cfg(feature = "logs_level_enabled")] - assert_eq!( - otel_log_appender.enabled(&log::Metadata::builder().build()), - true - ); + assert!(otel_log_appender.enabled(&log::Metadata::builder().build())); #[cfg(not(feature = "logs_level_enabled"))] - assert_eq!( - otel_log_appender.enabled(&log::Metadata::builder().build()), - true - ); + assert!(otel_log_appender.enabled(&log::Metadata::builder().build())); } #[test] @@ -677,7 +671,7 @@ mod tests { /*log::info!( string_value = "a string", int_value = 42, - double_value = 3.14, + double_value = 4.2, boolean_value = true, list_value:serde = [1, 2, 3], map_value:serde = Map { a: 1, b: 2, c: 3 }; @@ -690,7 +684,7 @@ mod tests { .key_values(&[ ("string_value", log::kv::Value::from("a string")), ("int_value", log::kv::Value::from(42)), - ("double_value", log::kv::Value::from(3.14)), + ("double_value", log::kv::Value::from(4.2)), ("boolean_value", log::kv::Value::from(true)), ("list_value", log::kv::Value::from_serde(&[1, 2, 3])), ( @@ -721,7 +715,7 @@ mod tests { assert_eq!(AnyValue::Int(42), get("int_value").unwrap()); - assert_eq!(AnyValue::Double(3.14), get("double_value").unwrap()); + assert_eq!(AnyValue::Double(4.2), get("double_value").unwrap()); assert_eq!(AnyValue::Boolean(true), get("boolean_value").unwrap()); From 8c91cb3f27cb466a28c767aedee58fbe9e0fa230 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 19 Mar 2024 15:14:19 +1000 Subject: [PATCH 4/7] make serde support optional --- opentelemetry-appender-log/Cargo.toml | 6 +- opentelemetry-appender-log/src/lib.rs | 120 ++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/opentelemetry-appender-log/Cargo.toml b/opentelemetry-appender-log/Cargo.toml index 637ee89f06..dde69fa4c1 100644 --- a/opentelemetry-appender-log/Cargo.toml +++ b/opentelemetry-appender-log/Cargo.toml @@ -12,14 +12,16 @@ edition = "2021" [dependencies] opentelemetry = { version = "0.22", path = "../opentelemetry", features = ["logs"]} -log = { workspace = true, features = ["kv", "kv_serde", "std"]} -serde = { workspace = true, features = ["std"] } +log = { workspace = true, features = ["kv", "std"]} +serde = { workspace = true, optional = true, features = ["std"] } [features] logs_level_enabled = ["opentelemetry/logs_level_enabled"] +with_serde = ["log/kv_serde", "serde"] default = ["logs_level_enabled"] [dev-dependencies] opentelemetry_sdk = { path = "../opentelemetry-sdk", features = [ "testing", "logs_level_enabled" ] } +log = { workspace = true, features = ["kv_serde"] } tokio = { workspace = true } serde = { workspace = true, features = ["std", "derive"] } diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index ee3bd78f6f..c8f0e75a0d 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -100,7 +100,77 @@ fn log_attributes(kvs: impl log::kv::Source) -> Vec<(Key, AnyValue)> { visitor.0 } +#[cfg(not(feature = "with_serde"))] +mod any_value { + use opentelemetry::{logs::AnyValue, StringValue}; + + pub(crate) fn serialize(value: log::kv::Value) -> Option { + struct ValueVisitor(Option); + + impl<'kvs> log::kv::VisitValue<'kvs> for ValueVisitor { + fn visit_any(&mut self, value: log::kv::Value) -> Result<(), log::kv::Error> { + self.0 = Some(AnyValue::String(StringValue::from(value.to_string()))); + + Ok(()) + } + + fn visit_bool(&mut self, value: bool) -> Result<(), log::kv::Error> { + self.0 = Some(AnyValue::Boolean(value)); + + Ok(()) + } + + fn visit_str(&mut self, value: &str) -> Result<(), log::kv::Error> { + self.0 = Some(AnyValue::String(StringValue::from(value.to_owned()))); + + Ok(()) + } + + fn visit_i64(&mut self, value: i64) -> Result<(), log::kv::Error> { + self.0 = Some(AnyValue::Int(value)); + + Ok(()) + } + + fn visit_u64(&mut self, value: u64) -> Result<(), log::kv::Error> { + if let Ok(value) = value.try_into() { + self.visit_i64(value) + } else { + self.visit_any(log::kv::Value::from(value)) + } + } + + fn visit_i128(&mut self, value: i128) -> Result<(), log::kv::Error> { + if let Ok(value) = value.try_into() { + self.visit_i64(value) + } else { + self.visit_any(log::kv::Value::from(value)) + } + } + + fn visit_u128(&mut self, value: u128) -> Result<(), log::kv::Error> { + if let Ok(value) = value.try_into() { + self.visit_i64(value) + } else { + self.visit_any(log::kv::Value::from(value)) + } + } + + fn visit_f64(&mut self, value: f64) -> Result<(), log::kv::Error> { + self.0 = Some(AnyValue::Double(value)); + + Ok(()) + } + } + + let mut visitor = ValueVisitor(None); + value.visit(&mut visitor).unwrap(); + visitor.0 + } +} + // This could make a nice addition to the SDK itself for serializing into `AnyValue`s +#[cfg(feature = "with_serde")] mod any_value { use std::{collections::HashMap, fmt}; @@ -561,11 +631,9 @@ mod any_value { #[cfg(test)] mod tests { - use std::collections::HashMap; - use super::OpenTelemetryLogBridge; - use opentelemetry::{logs::AnyValue, Key, StringValue}; + use opentelemetry::{logs::AnyValue, StringValue}; use opentelemetry_sdk::{logs::LoggerProvider, testing::logs::InMemoryLogsExporter}; use log::Log; @@ -719,23 +787,41 @@ mod tests { assert_eq!(AnyValue::Boolean(true), get("boolean_value").unwrap()); - assert_eq!( - AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(2), AnyValue::Int(3),]), - get("list_value").unwrap() - ); + #[cfg(not(feature = "with_serde"))] + { + assert_eq!( + AnyValue::String(StringValue::from("(1, 2, 3)")), + get("list_value").unwrap() + ); - assert_eq!( - AnyValue::Map({ - let mut map = HashMap::new(); + assert_eq!( + AnyValue::String(StringValue::from("Map { a: 1, b: 2, c: 3 }")), + get("map_value").unwrap() + ); + } + #[cfg(feature = "with_serde")] + { + use opentelemetry::Key; + use std::collections::HashMap; - map.insert(Key::from("a"), AnyValue::Int(1)); - map.insert(Key::from("b"), AnyValue::Int(2)); - map.insert(Key::from("c"), AnyValue::Int(3)); + assert_eq!( + AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(2), AnyValue::Int(3),]), + get("list_value").unwrap() + ); - map - }), - get("map_value").unwrap() - ); + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(2)); + map.insert(Key::from("c"), AnyValue::Int(3)); + + map + }), + get("map_value").unwrap() + ); + } } #[test] From 7de7f09189783b623497dfc6498d78adf04113c6 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 26 Mar 2024 10:58:08 +1000 Subject: [PATCH 5/7] update docs and tests --- opentelemetry-appender-log/Cargo.toml | 3 +- .../examples/logs-basic-in-memory.rs | 4 +- opentelemetry-appender-log/src/lib.rs | 359 ++++++++++++++++-- 3 files changed, 333 insertions(+), 33 deletions(-) diff --git a/opentelemetry-appender-log/Cargo.toml b/opentelemetry-appender-log/Cargo.toml index dde69fa4c1..9a94411637 100644 --- a/opentelemetry-appender-log/Cargo.toml +++ b/opentelemetry-appender-log/Cargo.toml @@ -17,11 +17,12 @@ serde = { workspace = true, optional = true, features = ["std"] } [features] logs_level_enabled = ["opentelemetry/logs_level_enabled"] -with_serde = ["log/kv_serde", "serde"] +with-serde = ["log/kv_serde", "serde"] default = ["logs_level_enabled"] [dev-dependencies] opentelemetry_sdk = { path = "../opentelemetry-sdk", features = [ "testing", "logs_level_enabled" ] } +opentelemetry-stdout = { path = "../opentelemetry-stdout", features = ["logs"]} log = { workspace = true, features = ["kv_serde"] } tokio = { workspace = true } serde = { workspace = true, features = ["std", "derive"] } diff --git a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs b/opentelemetry-appender-log/examples/logs-basic-in-memory.rs index 05e804c4aa..631a4d52b1 100644 --- a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs +++ b/opentelemetry-appender-log/examples/logs-basic-in-memory.rs @@ -25,10 +25,10 @@ async fn main() { log::set_max_level(Level::Info.to_level_filter()); // Emit logs using macros from the log crate. - let user = "apple"; + let fruit = "apple"; let price = 2.99; - error!(user, price; "hello from {user}. My price is {price}"); + error!(fruit, price; "hello from {fruit}. My price is {price}"); warn!("warn!"); info!("test log!"); diff --git a/opentelemetry-appender-log/src/lib.rs b/opentelemetry-appender-log/src/lib.rs index c8f0e75a0d..2f0d8cc337 100644 --- a/opentelemetry-appender-log/src/lib.rs +++ b/opentelemetry-appender-log/src/lib.rs @@ -1,3 +1,99 @@ +//! Bridge `log` into OpenTelemetry. +//! +//! This library implements a log appender for the [`log`] crate using the [Logs Bridge API]. +//! +//! # Getting Started +//! +//! The bridge requires configuration on both the `log` and OpenTelemetry sides. +//! +//! For OpenTelemetry, configure a [`LoggerProvider`] with the desired exporter: +//! +//! ``` +//! # #[tokio::main] async fn main() { +//! # use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider}; +//! # use opentelemetry_sdk::runtime; +//! let exporter = opentelemetry_stdout::LogExporterBuilder::default().build(); +//! +//! let logger_provider = LoggerProvider::builder() +//! .with_log_processor(BatchLogProcessor::builder(exporter, runtime::Tokio).build()) +//! .build(); +//! # } +//! ``` +//! +//! For `log`, set the global logger to an [`OpenTelemetryLogBridge`] instance using the `LoggerProvider`: +//! +//! ``` +//! # #[tokio::main] async fn main() { +//! # use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider}; +//! # use opentelemetry_sdk::runtime; +//! # use opentelemetry_appender_log::OpenTelemetryLogBridge; +//! # let exporter = opentelemetry_stdout::LogExporterBuilder::default().build(); +//! # let logger_provider = LoggerProvider::builder() +//! # .with_log_processor(BatchLogProcessor::builder(exporter, runtime::Tokio).build()) +//! # .build(); +//! let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); +//! +//! log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); +//! # } +//! ``` +//! +//! # Mapping Log Records +//! +//! This section outlines how log records produced by `log` are mapped into OpenTelemetry log records. +//! Each subsection deals with a different property on `opentelemetry::logs::LogRecord`. +//! +//! ## Body +//! +//! The body is the stringified message ([`log::Record::args`]). +//! +//! ## Severity +//! +//! The severity number and text are mapped from the [`log::Level`] ([`log::Record::level`]): +//! +//! | `log::Level` | Severity Text | Severity Number | +//! | ------------ | ------------- | --------------- | +//! | `Error` | Error | 17 | +//! | `Warn` | Warn | 13 | +//! | `Info` | Info | 9 | +//! | `Debug` | Debug | 5 | +//! | `Trace` | Trace | 1 | +//! +//! # Attributes +//! +//! Any key-values ([`log::Record::key_values`]) are converted into attributes: +//! +//! | Type | Result | Notes | +//! | --------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- | +//! | `i8`-`i128` | [`AnyValue::Int`] | If the value is too big then it will be stringified using [`std::fmt::Display`] | +//! | `u8`-`u128` | [`AnyValue::Int`] | If the value is too big then it will be stringified using [`std::fmt::Display`] | +//! | `f32`-`f64` | [`AnyValue::Double`] | | +//! | `bool` | [`AnyValue::Boolean`] | | +//! | `str` | [`AnyValue::String`] | | +//! | Bytes | [`AnyValue::Bytes`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | `()` | - | Unit values are discared | +//! | `Some` | Any | `Some` variants use their inner value | +//! | `None` | - | `None` variants are discared | +//! | Unit struct | [`AnyValue::String`] | Uses the name of the struct | +//! | Unit variant | [`AnyValue::String`] | Uses the name of the variant | +//! | Newtype struct | Any | Uses the inner value of the newtype | +//! | Newtype variant | [`AnyValue::Map`] | An internally-tagged map. Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Sequence | [`AnyValue::ListAny`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Tuple | [`AnyValue::ListAny`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Tuple struct | [`AnyValue::ListAny`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Tuple variant | [`AnyValue::Map`] | An internally-tagged map. Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Map | [`AnyValue::Map`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Struct | [`AnyValue::Map`] | Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! | Struct variant | [`AnyValue::Map`] | An internally-tagged map. Requires the `with-serde` feature, otherwise it will be stringified using [`std::fmt::Debug`] | +//! +//! # Feature Flags +//! +//! This library provides the following Cargo features: +//! +//! - `logs_level_enabled`: Allow users to control the log level. +//! - `with-serde`: Support complex values as attributes without stringifying them. +//! +//! [Logs Bridge API]: https://opentelemetry.io/docs/specs/otel/logs/bridge-api/ + use log::{Level, Metadata, Record}; use opentelemetry::{ logs::{AnyValue, LogRecordBuilder, Logger, LoggerProvider, Severity}, @@ -100,7 +196,7 @@ fn log_attributes(kvs: impl log::kv::Source) -> Vec<(Key, AnyValue)> { visitor.0 } -#[cfg(not(feature = "with_serde"))] +#[cfg(not(feature = "with-serde"))] mod any_value { use opentelemetry::{logs::AnyValue, StringValue}; @@ -170,7 +266,7 @@ mod any_value { } // This could make a nice addition to the SDK itself for serializing into `AnyValue`s -#[cfg(feature = "with_serde")] +#[cfg(feature = "with-serde")] mod any_value { use std::{collections::HashMap, fmt}; @@ -722,12 +818,57 @@ mod tests { #[test] fn logbridge_attributes() { #[derive(serde::Serialize)] + struct Struct { + a: i32, + b: i32, + c: i32, + } + + #[derive(serde::Serialize)] + struct Newtype(i32); + + #[derive(serde::Serialize)] + enum Enum { + Unit, + Newtype(i32), + Struct { a: i32, b: i32, c: i32 }, + Tuple(i32, i32, i32), + } + + struct Bytes(B); + + impl> serde::Serialize for Bytes { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.0.as_ref()) + } + } + struct Map { a: i32, b: i32, c: i32, } + impl serde::Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + + let mut map = serializer.serialize_map(Some(3))?; + + map.serialize_entry(&"a", &self.a)?; + map.serialize_entry(&"b", &self.b)?; + map.serialize_entry(&"c", &self.c)?; + + map.end() + } + } + let exporter = InMemoryLogsExporter::default(); let logger_provider = LoggerProvider::builder() @@ -736,28 +877,65 @@ mod tests { let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); - /*log::info!( - string_value = "a string", - int_value = 42, - double_value = 4.2, - boolean_value = true, - list_value:serde = [1, 2, 3], - map_value:serde = Map { a: 1, b: 2, c: 3 }; - "body" - );*/ otel_log_appender.log( &log::RecordBuilder::new() .level(log::Level::Info) .args(format_args!("body")) .key_values(&[ - ("string_value", log::kv::Value::from("a string")), - ("int_value", log::kv::Value::from(42)), - ("double_value", log::kv::Value::from(4.2)), - ("boolean_value", log::kv::Value::from(true)), - ("list_value", log::kv::Value::from_serde(&[1, 2, 3])), + ("str_value", log::kv::Value::from("a string")), + ("u8_value", log::kv::Value::from(1u8)), + ("u16_value", log::kv::Value::from(2u16)), + ("u32_value", log::kv::Value::from(42u32)), + ("u64_value", log::kv::Value::from(2147483660u64)), + ("u128_small_value", log::kv::Value::from(2147483660u128)), + ( + "u128_big_value", + log::kv::Value::from(9223372036854775820u128), + ), + ("i8_value", log::kv::Value::from(1i8)), + ("i16_value", log::kv::Value::from(2i16)), + ("i32_value", log::kv::Value::from(42i32)), + ("i64_value", log::kv::Value::from(2147483660i64)), + ("i128_small_value", log::kv::Value::from(2147483660i128)), + ( + "i128_big_value", + log::kv::Value::from(9223372036854775820i128), + ), + ("f64_value", log::kv::Value::from(4.2f64)), + ("bool_value", log::kv::Value::from(true)), + ("bytes_value", log::kv::Value::from_serde(&Bytes([1, 1, 1]))), + ("unit_value", log::kv::Value::from_serde(&())), + ("some_value", log::kv::Value::from_serde(&Some(42))), + ("none_value", log::kv::Value::from_serde(&None::)), + ( + "slice_value", + log::kv::Value::from_serde(&(&[1, 1, 1] as &[i32])), + ), ( "map_value", - log::kv::Value::from_serde(&Map { a: 1, b: 2, c: 3 }), + log::kv::Value::from_serde(&Map { a: 1, b: 1, c: 1 }), + ), + ( + "struct_value", + log::kv::Value::from_serde(&Struct { a: 1, b: 1, c: 1 }), + ), + ("tuple_value", log::kv::Value::from_serde(&(1, 1, 1))), + ("newtype_value", log::kv::Value::from_serde(&Newtype(42))), + ( + "unit_variant_value", + log::kv::Value::from_serde(&Enum::Unit), + ), + ( + "newtype_variant_value", + log::kv::Value::from_serde(&Enum::Newtype(42)), + ), + ( + "struct_variant_value", + log::kv::Value::from_serde(&Enum::Struct { a: 1, b: 1, c: 1 }), + ), + ( + "tuple_variant_value", + log::kv::Value::from_serde(&Enum::Tuple(1, 1, 1)), ), ]) .build(), @@ -778,35 +956,82 @@ mod tests { assert_eq!( AnyValue::String(StringValue::from("a string")), - get("string_value").unwrap() + get("str_value").unwrap() ); - assert_eq!(AnyValue::Int(42), get("int_value").unwrap()); + assert_eq!(AnyValue::Int(1), get("i8_value").unwrap()); + assert_eq!(AnyValue::Int(2), get("i16_value").unwrap()); + assert_eq!(AnyValue::Int(42), get("i32_value").unwrap()); + assert_eq!(AnyValue::Int(2147483660), get("i64_value").unwrap()); + assert_eq!(AnyValue::Int(2147483660), get("i128_small_value").unwrap()); + assert_eq!( + AnyValue::String(StringValue::from("9223372036854775820")), + get("i128_big_value").unwrap() + ); - assert_eq!(AnyValue::Double(4.2), get("double_value").unwrap()); + assert_eq!(AnyValue::Double(4.2), get("f64_value").unwrap()); - assert_eq!(AnyValue::Boolean(true), get("boolean_value").unwrap()); + assert_eq!(AnyValue::Boolean(true), get("bool_value").unwrap()); - #[cfg(not(feature = "with_serde"))] + #[cfg(not(feature = "with-serde"))] { assert_eq!( - AnyValue::String(StringValue::from("(1, 2, 3)")), - get("list_value").unwrap() + AnyValue::String(StringValue::from("[1, 1, 1]")), + get("slice_value").unwrap() ); assert_eq!( - AnyValue::String(StringValue::from("Map { a: 1, b: 2, c: 3 }")), + AnyValue::String(StringValue::from("{\"a\": 1, \"b\": 1, \"c\": 1}")), get("map_value").unwrap() ); + + assert_eq!( + AnyValue::String(StringValue::from("Struct { a: 1, b: 1, c: 1 }")), + get("struct_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("(1, 1, 1)")), + get("tuple_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Newtype(42)")), + get("newtype_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Unit")), + get("unit_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Newtype(42)")), + get("newtype_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Struct { a: 1, b: 1, c: 1 }")), + get("struct_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Tuple(1, 1, 1)")), + get("tuple_variant_value").unwrap() + ); } - #[cfg(feature = "with_serde")] + #[cfg(feature = "with-serde")] { use opentelemetry::Key; use std::collections::HashMap; + assert_eq!(None, get("unit_value")); + assert_eq!(None, get("none_value")); + assert_eq!(AnyValue::Int(42), get("some_value").unwrap()); + assert_eq!( - AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(2), AnyValue::Int(3),]), - get("list_value").unwrap() + AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(1), AnyValue::Int(1)]), + get("slice_value").unwrap() ); assert_eq!( @@ -814,13 +1039,87 @@ mod tests { let mut map = HashMap::new(); map.insert(Key::from("a"), AnyValue::Int(1)); - map.insert(Key::from("b"), AnyValue::Int(2)); - map.insert(Key::from("c"), AnyValue::Int(3)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); map }), get("map_value").unwrap() ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); + + map + }), + get("struct_value").unwrap() + ); + + assert_eq!( + AnyValue::ListAny(vec![AnyValue::Int(1), AnyValue::Int(1), AnyValue::Int(1)]), + get("tuple_value").unwrap() + ); + + assert_eq!( + AnyValue::String(StringValue::from("Unit")), + get("unit_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("Newtype"), AnyValue::Int(42)); + + map + }), + get("newtype_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert( + Key::from("Struct"), + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert(Key::from("a"), AnyValue::Int(1)); + map.insert(Key::from("b"), AnyValue::Int(1)); + map.insert(Key::from("c"), AnyValue::Int(1)); + + map + }), + ); + + map + }), + get("struct_variant_value").unwrap() + ); + + assert_eq!( + AnyValue::Map({ + let mut map = HashMap::new(); + + map.insert( + Key::from("Tuple"), + AnyValue::ListAny(vec![ + AnyValue::Int(1), + AnyValue::Int(1), + AnyValue::Int(1), + ]), + ); + + map + }), + get("tuple_variant_value").unwrap() + ); } } From bc267e0381b3e0ec79cb243e0b81185f86b37bfc Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 23 Apr 2024 12:51:04 +1000 Subject: [PATCH 6/7] use the stdout exporter in the log example --- .../{logs-basic-in-memory.rs => logs-basic.rs} | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) rename opentelemetry-appender-log/examples/{logs-basic-in-memory.rs => logs-basic.rs} (74%) diff --git a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs b/opentelemetry-appender-log/examples/logs-basic.rs similarity index 74% rename from opentelemetry-appender-log/examples/logs-basic-in-memory.rs rename to opentelemetry-appender-log/examples/logs-basic.rs index 631a4d52b1..adc3602c2a 100644 --- a/opentelemetry-appender-log/examples/logs-basic-in-memory.rs +++ b/opentelemetry-appender-log/examples/logs-basic.rs @@ -1,4 +1,4 @@ -//! run with `$ cargo run --example logs-basic-in-memory +//! run with `$ cargo run --example logs-basic` /// This example shows how to use in_memory_exporter for logs. This uses opentelemetry-appender-log crate, which is a /// [logging appender](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#log-appender--bridge) that bridges logs from the [log crate](https://docs.rs/log/latest/log/) to OpenTelemetry. @@ -8,15 +8,15 @@ use log::{error, info, warn, Level}; use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider}; use opentelemetry_sdk::runtime; -use opentelemetry_sdk::testing::logs::InMemoryLogsExporter; +use opentelemetry_stdout::LogExporter; #[tokio::main] async fn main() { - //Create an InMemoryLogsExporter - let exporter: InMemoryLogsExporter = InMemoryLogsExporter::default(); + //Create an exporter that writes to stdout + let exporter = LogExporter::default(); //Create a LoggerProvider and register the exporter let logger_provider = LoggerProvider::builder() - .with_log_processor(BatchLogProcessor::builder(exporter.clone(), runtime::Tokio).build()) + .with_log_processor(BatchLogProcessor::builder(exporter, runtime::Tokio).build()) .build(); // Setup Log Appender for the log crate. @@ -33,9 +33,4 @@ async fn main() { info!("test log!"); logger_provider.force_flush(); - - let emitted_logs = exporter.get_emitted_logs().unwrap(); - for log in emitted_logs { - println!("{:?}", log); - } } From e6478600a949e4b7ea8b7e17add5594e26b9cb3f Mon Sep 17 00:00:00 2001 From: KodrAus Date: Thu, 2 May 2024 08:54:07 +1000 Subject: [PATCH 7/7] add changelog entry for log key-values as attributes --- opentelemetry-appender-log/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opentelemetry-appender-log/CHANGELOG.md b/opentelemetry-appender-log/CHANGELOG.md index 6ecf96c7e2..dd574e51ca 100644 --- a/opentelemetry-appender-log/CHANGELOG.md +++ b/opentelemetry-appender-log/CHANGELOG.md @@ -2,6 +2,8 @@ ## vNext +- Add log key-values as attributes [#1628](https://github.com/open-telemetry/opentelemetry-rust/pull/1628) + ## v0.3.0 ## v0.2.0