spdlog_opentelemetry/
lib.rs1#![warn(missing_docs)]
16
17mod error;
18
19use std::{convert::Infallible, result::Result as StdResult, sync::Arc};
20
21pub use error::{Error, Result};
22use opentelemetry::{
23 Key as OtelKey,
24 logs::{
25 AnyValue as OtelAnyValue, LogRecord as _, Logger as OtelLogger,
26 LoggerProvider as OtelLoggerProvider, Severity as OtelSeverity,
27 },
28};
29use spdlog::{
30 ErrorHandler, Record, StringBuf,
31 formatter::{Formatter, FormatterContext, FullFormatter},
32 prelude::*,
33 sink::{GetSinkProp, Sink, SinkProp},
34};
35use value_bag::{ValueBag, visit::Visit as ValueBagVisit};
36
37#[doc(hidden)]
38pub mod __private {
39 pub struct NotSet;
40}
41use __private::NotSet;
42
43pub struct OpenTelemetryBuilder<ArgL> {
52 prop: SinkProp,
53 logger: ArgL,
54}
55
56impl<ArgL> OpenTelemetryBuilder<ArgL> {
57 pub fn provider<P>(self, provider: &P) -> OpenTelemetryBuilder<P::Logger>
64 where
65 P: OtelLoggerProvider,
66 {
67 let logger = provider.logger("");
70 OpenTelemetryBuilder {
71 prop: self.prop,
72 logger,
73 }
74 }
75
76 #[must_use]
83 pub fn level_filter(self, level_filter: LevelFilter) -> Self {
84 self.prop.set_level_filter(level_filter);
85 self
86 }
87
88 #[must_use]
93 pub fn formatter<F>(self, formatter: F) -> Self
94 where
95 F: Formatter + 'static,
96 {
97 self.prop.set_formatter(formatter);
98 self
99 }
100
101 #[must_use]
106 pub fn error_handler<F>(self, handler: F) -> Self
107 where
108 F: Into<ErrorHandler>,
109 {
110 self.prop.set_error_handler(handler);
111 self
112 }
113}
114
115impl OpenTelemetryBuilder<NotSet> {
116 #[doc(hidden)]
117 #[deprecated(note = "\n\n\
118 builder compile-time error:\n\
119 - missing required parameter `provider`\n\n\
120 ")]
121 pub fn build(self, _: Infallible) {}
122}
123
124impl<ArgL> OpenTelemetryBuilder<ArgL>
125where
126 ArgL: OtelLogger,
127{
128 pub fn build(self) -> Result<OpenTelemetrySink<ArgL>> {
130 Ok(OpenTelemetrySink {
131 prop: self.prop,
132 logger: self.logger,
133 })
134 }
135
136 pub fn build_arc(self) -> Result<Arc<OpenTelemetrySink<ArgL>>> {
138 Self::build(self).map(Arc::new)
139 }
140}
141
142pub struct OpenTelemetrySink<L> {
159 prop: SinkProp,
160 logger: L,
161}
162
163impl OpenTelemetrySink<()> {
164 #[must_use]
179 pub fn builder() -> OpenTelemetryBuilder<NotSet> {
180 let prop = SinkProp::default();
181 prop.set_formatter(
182 FullFormatter::builder()
183 .time(false)
184 .level(false)
185 .source_location(false)
186 .eol(false)
187 .build(),
188 );
189 OpenTelemetryBuilder {
190 prop,
191 logger: NotSet,
192 }
193 }
194}
195
196impl<L> OpenTelemetrySink<L> {
197 const LEVELS_MAPPING: LevelsMapping = LevelsMapping::new();
198}
199
200impl<L> GetSinkProp for OpenTelemetrySink<L> {
201 fn prop(&self) -> &SinkProp {
202 &self.prop
203 }
204}
205
206impl<L> Sink for OpenTelemetrySink<L>
207where
208 L: OtelLogger + Send + Sync,
209{
210 fn log(&self, record: &Record) -> spdlog::Result<()> {
216 let mut string_buf = StringBuf::new();
217 let mut ctx = FormatterContext::new();
218 self.prop
219 .formatter()
220 .format(record, &mut string_buf, &mut ctx)?;
221
222 let mut otel_record = self.logger.create_log_record();
223 if let Some(logger_name) = record.logger_name() {
224 otel_record.set_target(logger_name.to_owned());
225 }
226 otel_record.set_timestamp(record.time());
227 otel_record.set_severity_text(record.level().as_str());
228 otel_record.set_severity_number(Self::LEVELS_MAPPING.level(record.level()));
229 otel_record.set_body(OtelAnyValue::from(string_buf));
230 if let Some(srcloc) = record.source_location() {
231 otel_record.add_attribute("code.line.number", srcloc.line());
233 otel_record.add_attribute("code.column.number", srcloc.column());
234 otel_record.add_attribute("code.file.path", srcloc.file());
235 otel_record.add_attribute("code.function.name", srcloc.module_path());
236 }
237 otel_record.add_attributes(convert_attrs(record)?);
238
239 self.logger.emit(otel_record);
240 Ok(())
241 }
242
243 fn flush(&self) -> spdlog::Result<()> {
244 Ok(())
245 }
246}
247
248struct LevelsMapping([OtelSeverity; Level::count()]);
251
252impl LevelsMapping {
253 #[must_use]
254 const fn new() -> Self {
255 Self([
256 OtelSeverity::Fatal, OtelSeverity::Error, OtelSeverity::Warn, OtelSeverity::Info, OtelSeverity::Debug, OtelSeverity::Trace, ])
263 }
264
265 #[must_use]
266 fn level(&self, level: Level) -> OtelSeverity {
267 self.0[level as usize]
268 }
269}
270
271fn convert_attrs(record: &Record) -> spdlog::Result<Vec<(OtelKey, OtelAnyValue)>> {
272 record
273 .key_values()
274 .iter()
275 .map(|(k, v)| convert_value(v).map(|v| (OtelKey::from(k.as_str().to_owned()), v)))
276 .collect::<spdlog::Result<_>>()
277}
278
279fn convert_value(value: ValueBag) -> spdlog::Result<OtelAnyValue> {
280 struct Converter(Option<OtelAnyValue>);
281
282 impl<'a> ValueBagVisit<'a> for Converter {
283 fn visit_any(&mut self, value: ValueBag) -> StdResult<(), value_bag::Error> {
284 self.0 = Some(OtelAnyValue::from(value.to_string()));
285 Ok(())
286 }
287
288 fn visit_i64(&mut self, value: i64) -> StdResult<(), value_bag::Error> {
289 self.0 = Some(OtelAnyValue::Int(value));
290 Ok(())
291 }
292
293 fn visit_u64(&mut self, value: u64) -> StdResult<(), value_bag::Error> {
294 if let Ok(value) = value.try_into() {
295 self.visit_i64(value)
296 } else {
297 self.visit_any(value.into())
298 }
299 }
300
301 fn visit_i128(&mut self, value: i128) -> StdResult<(), value_bag::Error> {
302 if let Ok(value) = value.try_into() {
303 self.visit_i64(value)
304 } else {
305 self.visit_any(value.into())
306 }
307 }
308
309 fn visit_u128(&mut self, value: u128) -> StdResult<(), value_bag::Error> {
310 if let Ok(value) = value.try_into() {
311 self.visit_i64(value)
312 } else {
313 self.visit_any(value.into())
314 }
315 }
316
317 fn visit_f64(&mut self, value: f64) -> StdResult<(), value_bag::Error> {
318 self.0 = Some(OtelAnyValue::Double(value));
319 Ok(())
320 }
321
322 fn visit_bool(&mut self, value: bool) -> StdResult<(), value_bag::Error> {
323 self.0 = Some(OtelAnyValue::Boolean(value));
324 Ok(())
325 }
326 }
327
328 let mut cvt = Converter(None);
329 value
330 .visit(&mut cvt)
331 .map_err(|err| spdlog::Error::Downstream(Box::new(Error::ValueBag(err))))?;
332 Ok(cvt.0.unwrap())
333}