1use std::{collections::HashMap, fmt, sync::RwLock};
2use tracing::{field::Visit, Subscriber};
3use tracing_core::{Field, Interest, Metadata};
4
5use opentelemetry::{
6 metrics::{Counter, Gauge, Histogram, Meter, MeterProvider, UpDownCounter},
7 InstrumentationScope, KeyValue, Value,
8};
9use tracing_subscriber::{
10 filter::Filtered,
11 layer::{Context, Filter},
12 registry::LookupSpan,
13 Layer,
14};
15
16use smallvec::SmallVec;
17
18const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
19const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry";
20
21const METRIC_PREFIX_MONOTONIC_COUNTER: &str = "monotonic_counter.";
22const METRIC_PREFIX_COUNTER: &str = "counter.";
23const METRIC_PREFIX_HISTOGRAM: &str = "histogram.";
24const METRIC_PREFIX_GAUGE: &str = "gauge.";
25
26const I64_MAX: u64 = i64::MAX as u64;
27
28#[derive(Default)]
29pub(crate) struct Instruments {
30 u64_counter: MetricsMap<Counter<u64>>,
31 f64_counter: MetricsMap<Counter<f64>>,
32 i64_up_down_counter: MetricsMap<UpDownCounter<i64>>,
33 f64_up_down_counter: MetricsMap<UpDownCounter<f64>>,
34 u64_histogram: MetricsMap<Histogram<u64>>,
35 f64_histogram: MetricsMap<Histogram<f64>>,
36 u64_gauge: MetricsMap<Gauge<u64>>,
37 i64_gauge: MetricsMap<Gauge<i64>>,
38 f64_gauge: MetricsMap<Gauge<f64>>,
39}
40
41type MetricsMap<T> = RwLock<HashMap<&'static str, T>>;
42
43#[derive(Copy, Clone, Debug)]
44pub(crate) enum InstrumentType {
45 CounterU64(u64),
46 CounterF64(f64),
47 UpDownCounterI64(i64),
48 UpDownCounterF64(f64),
49 HistogramU64(u64),
50 HistogramF64(f64),
51 GaugeU64(u64),
52 GaugeI64(i64),
53 GaugeF64(f64),
54}
55
56impl Instruments {
57 pub(crate) fn update_metric(
58 &self,
59 meter: &Meter,
60 instrument_type: InstrumentType,
61 metric_name: &'static str,
62 attributes: &[KeyValue],
63 ) {
64 fn update_or_insert<T>(
65 map: &MetricsMap<T>,
66 name: &'static str,
67 insert: impl FnOnce() -> T,
68 update: impl FnOnce(&T),
69 ) {
70 {
71 let lock = map.read().unwrap();
72 if let Some(metric) = lock.get(name) {
73 update(metric);
74 return;
75 }
76 }
77
78 let mut lock = map.write().unwrap();
81 let metric = lock.entry(name).or_insert_with(insert);
84 update(metric)
85 }
86
87 match instrument_type {
88 InstrumentType::CounterU64(value) => {
89 update_or_insert(
90 &self.u64_counter,
91 metric_name,
92 || meter.u64_counter(metric_name).build(),
93 |ctr| ctr.add(value, attributes),
94 );
95 }
96 InstrumentType::CounterF64(value) => {
97 update_or_insert(
98 &self.f64_counter,
99 metric_name,
100 || meter.f64_counter(metric_name).build(),
101 |ctr| ctr.add(value, attributes),
102 );
103 }
104 InstrumentType::UpDownCounterI64(value) => {
105 update_or_insert(
106 &self.i64_up_down_counter,
107 metric_name,
108 || meter.i64_up_down_counter(metric_name).build(),
109 |ctr| ctr.add(value, attributes),
110 );
111 }
112 InstrumentType::UpDownCounterF64(value) => {
113 update_or_insert(
114 &self.f64_up_down_counter,
115 metric_name,
116 || meter.f64_up_down_counter(metric_name).build(),
117 |ctr| ctr.add(value, attributes),
118 );
119 }
120 InstrumentType::HistogramU64(value) => {
121 update_or_insert(
122 &self.u64_histogram,
123 metric_name,
124 || meter.u64_histogram(metric_name).build(),
125 |rec| rec.record(value, attributes),
126 );
127 }
128 InstrumentType::HistogramF64(value) => {
129 update_or_insert(
130 &self.f64_histogram,
131 metric_name,
132 || meter.f64_histogram(metric_name).build(),
133 |rec| rec.record(value, attributes),
134 );
135 }
136 InstrumentType::GaugeU64(value) => {
137 update_or_insert(
138 &self.u64_gauge,
139 metric_name,
140 || meter.u64_gauge(metric_name).build(),
141 |rec| rec.record(value, attributes),
142 );
143 }
144 InstrumentType::GaugeI64(value) => {
145 update_or_insert(
146 &self.i64_gauge,
147 metric_name,
148 || meter.i64_gauge(metric_name).build(),
149 |rec| rec.record(value, attributes),
150 );
151 }
152 InstrumentType::GaugeF64(value) => {
153 update_or_insert(
154 &self.f64_gauge,
155 metric_name,
156 || meter.f64_gauge(metric_name).build(),
157 |rec| rec.record(value, attributes),
158 );
159 }
160 };
161 }
162}
163
164pub(crate) struct MetricVisitor<'a> {
165 attributes: &'a mut SmallVec<[KeyValue; 8]>,
166 visited_metrics: &'a mut SmallVec<[(&'static str, InstrumentType); 2]>,
167}
168
169impl Visit for MetricVisitor<'_> {
170 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
171 self.attributes
172 .push(KeyValue::new(field.name(), format!("{value:?}")));
173 }
174
175 fn record_u64(&mut self, field: &Field, value: u64) {
176 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) {
177 self.visited_metrics
178 .push((metric_name, InstrumentType::GaugeU64(value)));
179 return;
180 }
181 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
182 self.visited_metrics
183 .push((metric_name, InstrumentType::CounterU64(value)));
184 } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
185 if value <= I64_MAX {
186 self.visited_metrics
187 .push((metric_name, InstrumentType::UpDownCounterI64(value as i64)));
188 } else {
189 eprintln!(
190 "[tracing-opentelemetry]: Received Counter metric, but \
191 provided u64: {value} is greater than i64::MAX. Ignoring \
192 this metric."
193 );
194 }
195 } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) {
196 self.visited_metrics
197 .push((metric_name, InstrumentType::HistogramU64(value)));
198 } else if value <= I64_MAX {
199 self.attributes
200 .push(KeyValue::new(field.name(), Value::I64(value as i64)));
201 }
202 }
203
204 fn record_f64(&mut self, field: &Field, value: f64) {
205 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) {
206 self.visited_metrics
207 .push((metric_name, InstrumentType::GaugeF64(value)));
208 return;
209 }
210 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
211 self.visited_metrics
212 .push((metric_name, InstrumentType::CounterF64(value)));
213 } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
214 self.visited_metrics
215 .push((metric_name, InstrumentType::UpDownCounterF64(value)));
216 } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) {
217 self.visited_metrics
218 .push((metric_name, InstrumentType::HistogramF64(value)));
219 } else {
220 self.attributes
221 .push(KeyValue::new(field.name(), Value::F64(value)));
222 }
223 }
224
225 fn record_i64(&mut self, field: &Field, value: i64) {
226 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_GAUGE) {
227 self.visited_metrics
228 .push((metric_name, InstrumentType::GaugeI64(value)));
229 return;
230 }
231 if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
232 self.visited_metrics
233 .push((metric_name, InstrumentType::CounterU64(value as u64)));
234 } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
235 self.visited_metrics
236 .push((metric_name, InstrumentType::UpDownCounterI64(value)));
237 } else {
238 self.attributes.push(KeyValue::new(field.name(), value));
239 }
240 }
241
242 fn record_str(&mut self, field: &Field, value: &str) {
243 self.attributes
244 .push(KeyValue::new(field.name(), value.to_owned()));
245 }
246
247 fn record_bool(&mut self, field: &Field, value: bool) {
248 self.attributes.push(KeyValue::new(field.name(), value));
249 }
250}
251
252#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
372pub struct MetricsLayer<S, M> {
373 inner: Filtered<InstrumentLayer, MetricsFilter, S>,
374 _meter_provider: M,
376}
377
378impl<S, M> MetricsLayer<S, M>
379where
380 S: Subscriber + for<'span> LookupSpan<'span>,
381{
382 pub fn new(meter_provider: M) -> MetricsLayer<S, M>
384 where
385 M: MeterProvider,
386 {
387 let meter = meter_provider.meter_with_scope(
388 InstrumentationScope::builder(INSTRUMENTATION_LIBRARY_NAME)
389 .with_version(CARGO_PKG_VERSION)
390 .build(),
391 );
392
393 let layer = InstrumentLayer {
394 meter,
395 instruments: Default::default(),
396 };
397
398 MetricsLayer {
399 inner: layer.with_filter(MetricsFilter),
400 _meter_provider: meter_provider,
401 }
402 }
403}
404
405struct MetricsFilter;
406
407impl MetricsFilter {
408 fn is_metrics_event(&self, meta: &Metadata<'_>) -> bool {
409 meta.is_event()
410 && meta.fields().iter().any(|field| {
411 let name = field.name();
412
413 if name.starts_with(METRIC_PREFIX_COUNTER)
414 || name.starts_with(METRIC_PREFIX_MONOTONIC_COUNTER)
415 || name.starts_with(METRIC_PREFIX_HISTOGRAM)
416 || name.starts_with(METRIC_PREFIX_GAUGE)
417 {
418 return true;
419 }
420
421 false
422 })
423 }
424}
425
426impl<S> Filter<S> for MetricsFilter {
427 fn enabled(&self, meta: &Metadata<'_>, _cx: &Context<'_, S>) -> bool {
428 self.is_metrics_event(meta)
429 }
430
431 fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
432 if self.is_metrics_event(meta) {
433 Interest::always()
434 } else {
435 Interest::never()
436 }
437 }
438}
439
440struct InstrumentLayer {
441 meter: Meter,
442 instruments: Instruments,
443}
444
445impl<S> Layer<S> for InstrumentLayer
446where
447 S: Subscriber + for<'span> LookupSpan<'span>,
448{
449 fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
450 let mut attributes = SmallVec::new();
451 let mut visited_metrics = SmallVec::new();
452 let mut metric_visitor = MetricVisitor {
453 attributes: &mut attributes,
454 visited_metrics: &mut visited_metrics,
455 };
456 event.record(&mut metric_visitor);
457
458 visited_metrics
460 .into_iter()
461 .for_each(|(metric_name, value)| {
462 self.instruments.update_metric(
463 &self.meter,
464 value,
465 metric_name,
466 attributes.as_slice(),
467 );
468 })
469 }
470}
471
472impl<S, M: 'static> Layer<S> for MetricsLayer<S, M>
473where
474 S: Subscriber + for<'span> LookupSpan<'span>,
475{
476 fn on_layer(&mut self, subscriber: &mut S) {
477 self.inner.on_layer(subscriber)
478 }
479
480 fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest {
481 self.inner.register_callsite(metadata)
482 }
483
484 fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool {
485 self.inner.enabled(metadata, ctx)
486 }
487
488 fn on_new_span(
489 &self,
490 attrs: &tracing_core::span::Attributes<'_>,
491 id: &tracing_core::span::Id,
492 ctx: Context<'_, S>,
493 ) {
494 self.inner.on_new_span(attrs, id, ctx)
495 }
496
497 fn max_level_hint(&self) -> Option<tracing_core::LevelFilter> {
498 self.inner.max_level_hint()
499 }
500
501 fn on_record(
502 &self,
503 span: &tracing_core::span::Id,
504 values: &tracing_core::span::Record<'_>,
505 ctx: Context<'_, S>,
506 ) {
507 self.inner.on_record(span, values, ctx)
508 }
509
510 fn on_follows_from(
511 &self,
512 span: &tracing_core::span::Id,
513 follows: &tracing_core::span::Id,
514 ctx: Context<'_, S>,
515 ) {
516 self.inner.on_follows_from(span, follows, ctx)
517 }
518
519 fn on_event(&self, event: &tracing_core::Event<'_>, ctx: Context<'_, S>) {
520 self.inner.on_event(event, ctx)
521 }
522
523 fn on_enter(&self, id: &tracing_core::span::Id, ctx: Context<'_, S>) {
524 self.inner.on_enter(id, ctx)
525 }
526
527 fn on_exit(&self, id: &tracing_core::span::Id, ctx: Context<'_, S>) {
528 self.inner.on_exit(id, ctx)
529 }
530
531 fn on_close(&self, id: tracing_core::span::Id, ctx: Context<'_, S>) {
532 self.inner.on_close(id, ctx)
533 }
534
535 fn on_id_change(
536 &self,
537 old: &tracing_core::span::Id,
538 new: &tracing_core::span::Id,
539 ctx: Context<'_, S>,
540 ) {
541 self.inner.on_id_change(old, new, ctx)
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use tracing_subscriber::layer::SubscriberExt;
549
550 struct PanicLayer;
551 impl<S> Layer<S> for PanicLayer
552 where
553 S: Subscriber + for<'span> LookupSpan<'span>,
554 {
555 fn on_event(&self, _event: &tracing_core::Event<'_>, _ctx: Context<'_, S>) {
556 panic!("panic");
557 }
558 }
559
560 #[test]
561 fn filter_layer_should_filter_non_metrics_event() {
562 let layer = PanicLayer.with_filter(MetricsFilter);
563 let subscriber = tracing_subscriber::registry().with(layer);
564
565 tracing::subscriber::with_default(subscriber, || {
566 tracing::info!(key = "val", "foo");
567 });
568 }
569}