• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::{collections::HashMap, fmt, sync::RwLock};
2 use tracing::{field::Visit, Subscriber};
3 use tracing_core::Field;
4 
5 use opentelemetry::{
6     metrics::{Counter, Histogram, Meter, MeterProvider, UpDownCounter},
7     sdk::metrics::controllers::BasicController,
8     Context as OtelContext,
9 };
10 use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
11 
12 const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
13 const INSTRUMENTATION_LIBRARY_NAME: &str = "tracing/tracing-opentelemetry";
14 
15 const METRIC_PREFIX_MONOTONIC_COUNTER: &str = "monotonic_counter.";
16 const METRIC_PREFIX_COUNTER: &str = "counter.";
17 const METRIC_PREFIX_HISTOGRAM: &str = "histogram.";
18 const I64_MAX: u64 = i64::MAX as u64;
19 
20 #[derive(Default)]
21 pub(crate) struct Instruments {
22     u64_counter: MetricsMap<Counter<u64>>,
23     f64_counter: MetricsMap<Counter<f64>>,
24     i64_up_down_counter: MetricsMap<UpDownCounter<i64>>,
25     f64_up_down_counter: MetricsMap<UpDownCounter<f64>>,
26     u64_histogram: MetricsMap<Histogram<u64>>,
27     i64_histogram: MetricsMap<Histogram<i64>>,
28     f64_histogram: MetricsMap<Histogram<f64>>,
29 }
30 
31 type MetricsMap<T> = RwLock<HashMap<&'static str, T>>;
32 
33 #[derive(Copy, Clone, Debug)]
34 pub(crate) enum InstrumentType {
35     CounterU64(u64),
36     CounterF64(f64),
37     UpDownCounterI64(i64),
38     UpDownCounterF64(f64),
39     HistogramU64(u64),
40     HistogramI64(i64),
41     HistogramF64(f64),
42 }
43 
44 impl Instruments {
update_metric( &self, cx: &OtelContext, meter: &Meter, instrument_type: InstrumentType, metric_name: &'static str, )45     pub(crate) fn update_metric(
46         &self,
47         cx: &OtelContext,
48         meter: &Meter,
49         instrument_type: InstrumentType,
50         metric_name: &'static str,
51     ) {
52         fn update_or_insert<T>(
53             map: &MetricsMap<T>,
54             name: &'static str,
55             insert: impl FnOnce() -> T,
56             update: impl FnOnce(&T),
57         ) {
58             {
59                 let lock = map.read().unwrap();
60                 if let Some(metric) = lock.get(name) {
61                     update(metric);
62                     return;
63                 }
64             }
65 
66             // that metric did not already exist, so we have to acquire a write lock to
67             // create it.
68             let mut lock = map.write().unwrap();
69             // handle the case where the entry was created while we were waiting to
70             // acquire the write lock
71             let metric = lock.entry(name).or_insert_with(insert);
72             update(metric)
73         }
74 
75         match instrument_type {
76             InstrumentType::CounterU64(value) => {
77                 update_or_insert(
78                     &self.u64_counter,
79                     metric_name,
80                     || meter.u64_counter(metric_name).init(),
81                     |ctr| ctr.add(cx, value, &[]),
82                 );
83             }
84             InstrumentType::CounterF64(value) => {
85                 update_or_insert(
86                     &self.f64_counter,
87                     metric_name,
88                     || meter.f64_counter(metric_name).init(),
89                     |ctr| ctr.add(cx, value, &[]),
90                 );
91             }
92             InstrumentType::UpDownCounterI64(value) => {
93                 update_or_insert(
94                     &self.i64_up_down_counter,
95                     metric_name,
96                     || meter.i64_up_down_counter(metric_name).init(),
97                     |ctr| ctr.add(cx, value, &[]),
98                 );
99             }
100             InstrumentType::UpDownCounterF64(value) => {
101                 update_or_insert(
102                     &self.f64_up_down_counter,
103                     metric_name,
104                     || meter.f64_up_down_counter(metric_name).init(),
105                     |ctr| ctr.add(cx, value, &[]),
106                 );
107             }
108             InstrumentType::HistogramU64(value) => {
109                 update_or_insert(
110                     &self.u64_histogram,
111                     metric_name,
112                     || meter.u64_histogram(metric_name).init(),
113                     |rec| rec.record(cx, value, &[]),
114                 );
115             }
116             InstrumentType::HistogramI64(value) => {
117                 update_or_insert(
118                     &self.i64_histogram,
119                     metric_name,
120                     || meter.i64_histogram(metric_name).init(),
121                     |rec| rec.record(cx, value, &[]),
122                 );
123             }
124             InstrumentType::HistogramF64(value) => {
125                 update_or_insert(
126                     &self.f64_histogram,
127                     metric_name,
128                     || meter.f64_histogram(metric_name).init(),
129                     |rec| rec.record(cx, value, &[]),
130                 );
131             }
132         };
133     }
134 }
135 
136 pub(crate) struct MetricVisitor<'a> {
137     pub(crate) instruments: &'a Instruments,
138     pub(crate) meter: &'a Meter,
139 }
140 
141 impl<'a> Visit for MetricVisitor<'a> {
record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug)142     fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {
143         // Do nothing
144     }
145 
record_u64(&mut self, field: &Field, value: u64)146     fn record_u64(&mut self, field: &Field, value: u64) {
147         let cx = OtelContext::current();
148         if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
149             self.instruments.update_metric(
150                 &cx,
151                 self.meter,
152                 InstrumentType::CounterU64(value),
153                 metric_name,
154             );
155         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
156             if value <= I64_MAX {
157                 self.instruments.update_metric(
158                     &cx,
159                     self.meter,
160                     InstrumentType::UpDownCounterI64(value as i64),
161                     metric_name,
162                 );
163             } else {
164                 eprintln!(
165                     "[tracing-opentelemetry]: Received Counter metric, but \
166                     provided u64: {} is greater than i64::MAX. Ignoring \
167                     this metric.",
168                     value
169                 );
170             }
171         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) {
172             self.instruments.update_metric(
173                 &cx,
174                 self.meter,
175                 InstrumentType::HistogramU64(value),
176                 metric_name,
177             );
178         }
179     }
180 
record_f64(&mut self, field: &Field, value: f64)181     fn record_f64(&mut self, field: &Field, value: f64) {
182         let cx = OtelContext::current();
183         if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
184             self.instruments.update_metric(
185                 &cx,
186                 self.meter,
187                 InstrumentType::CounterF64(value),
188                 metric_name,
189             );
190         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
191             self.instruments.update_metric(
192                 &cx,
193                 self.meter,
194                 InstrumentType::UpDownCounterF64(value),
195                 metric_name,
196             );
197         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) {
198             self.instruments.update_metric(
199                 &cx,
200                 self.meter,
201                 InstrumentType::HistogramF64(value),
202                 metric_name,
203             );
204         }
205     }
206 
record_i64(&mut self, field: &Field, value: i64)207     fn record_i64(&mut self, field: &Field, value: i64) {
208         let cx = OtelContext::current();
209         if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_MONOTONIC_COUNTER) {
210             self.instruments.update_metric(
211                 &cx,
212                 self.meter,
213                 InstrumentType::CounterU64(value as u64),
214                 metric_name,
215             );
216         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_COUNTER) {
217             self.instruments.update_metric(
218                 &cx,
219                 self.meter,
220                 InstrumentType::UpDownCounterI64(value),
221                 metric_name,
222             );
223         } else if let Some(metric_name) = field.name().strip_prefix(METRIC_PREFIX_HISTOGRAM) {
224             self.instruments.update_metric(
225                 &cx,
226                 self.meter,
227                 InstrumentType::HistogramI64(value),
228                 metric_name,
229             );
230         }
231     }
232 }
233 
234 /// A layer that publishes metrics via the OpenTelemetry SDK.
235 ///
236 /// # Usage
237 ///
238 /// No configuration is needed for this Layer, as it's only responsible for
239 /// pushing data out to the `opentelemetry` family of crates. For example, when
240 /// using `opentelemetry-otlp`, that crate will provide its own set of
241 /// configuration options for setting up the duration metrics will be collected
242 /// before exporting to the OpenTelemetry Collector, aggregation of data points,
243 /// etc.
244 ///
245 /// ```no_run
246 /// use tracing_opentelemetry::MetricsLayer;
247 /// use tracing_subscriber::layer::SubscriberExt;
248 /// use tracing_subscriber::Registry;
249 /// # use opentelemetry::sdk::metrics::controllers::BasicController;
250 ///
251 /// // Constructing a BasicController is out-of-scope for the docs here, but there
252 /// // are examples in the opentelemetry repository. See:
253 /// // https://github.com/open-telemetry/opentelemetry-rust/blob/d4b9befea04bcc7fc19319a6ebf5b5070131c486/examples/basic-otlp/src/main.rs#L35-L52
254 /// # let controller: BasicController = unimplemented!();
255 ///
256 /// let opentelemetry_metrics =  MetricsLayer::new(controller);
257 /// let subscriber = Registry::default().with(opentelemetry_metrics);
258 /// tracing::subscriber::set_global_default(subscriber).unwrap();
259 /// ```
260 ///
261 /// To publish a new metric, add a key-value pair to your `tracing::Event` that
262 /// contains following prefixes:
263 /// - `monotonic_counter.` (non-negative numbers): Used when the counter should
264 ///   only ever increase
265 /// - `counter.`: Used when the counter can go up or down
266 /// - `histogram.`: Used for discrete data points (i.e., summing them does not make
267 ///   semantic sense)
268 ///
269 /// Examples:
270 /// ```
271 /// # use tracing::info;
272 /// info!(monotonic_counter.foo = 1);
273 /// info!(monotonic_counter.bar = 1.1);
274 ///
275 /// info!(counter.baz = 1);
276 /// info!(counter.baz = -1);
277 /// info!(counter.xyz = 1.1);
278 ///
279 /// info!(histogram.qux = 1);
280 /// info!(histogram.abc = -1);
281 /// info!(histogram.def = 1.1);
282 /// ```
283 ///
284 /// # Mixing data types
285 ///
286 /// ## Floating-point numbers
287 ///
288 /// Do not mix floating point and non-floating point numbers for the same
289 /// metric. If a floating point number will be used for a given metric, be sure
290 /// to cast any other usages of that metric to a floating point number.
291 ///
292 /// Do this:
293 /// ```
294 /// # use tracing::info;
295 /// info!(monotonic_counter.foo = 1_f64);
296 /// info!(monotonic_counter.foo = 1.1);
297 /// ```
298 ///
299 /// This is because all data published for a given metric name must be the same
300 /// numeric type.
301 ///
302 /// ## Integers
303 ///
304 /// Positive and negative integers can be mixed freely. The instrumentation
305 /// provided by `tracing` assumes that all integers are `i64` unless explicitly
306 /// cast to something else. In the case that an integer *is* cast to `u64`, this
307 /// subscriber will handle the conversion internally.
308 ///
309 /// For example:
310 /// ```
311 /// # use tracing::info;
312 /// // The subscriber receives an i64
313 /// info!(counter.baz = 1);
314 ///
315 /// // The subscriber receives an i64
316 /// info!(counter.baz = -1);
317 ///
318 /// // The subscriber receives a u64, but casts it to i64 internally
319 /// info!(counter.baz = 1_u64);
320 ///
321 /// // The subscriber receives a u64, but cannot cast it to i64 because of
322 /// // overflow. An error is printed to stderr, and the metric is dropped.
323 /// info!(counter.baz = (i64::MAX as u64) + 1)
324 /// ```
325 ///
326 /// # Implementation Details
327 ///
328 /// `MetricsLayer` holds a set of maps, with each map corresponding to a
329 /// type of metric supported by OpenTelemetry. These maps are populated lazily.
330 /// The first time that a metric is emitted by the instrumentation, a `Metric`
331 /// instance will be created and added to the corresponding map. This means that
332 /// any time a metric is emitted by the instrumentation, one map lookup has to
333 /// be performed.
334 ///
335 /// In the future, this can be improved by associating each `Metric` instance to
336 /// its callsite, eliminating the need for any maps.
337 ///
338 #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
339 pub struct MetricsLayer {
340     meter: Meter,
341     instruments: Instruments,
342 }
343 
344 impl MetricsLayer {
345     /// Create a new instance of MetricsLayer.
new(controller: BasicController) -> Self346     pub fn new(controller: BasicController) -> Self {
347         let meter =
348             controller.versioned_meter(INSTRUMENTATION_LIBRARY_NAME, Some(CARGO_PKG_VERSION), None);
349         MetricsLayer {
350             meter,
351             instruments: Default::default(),
352         }
353     }
354 }
355 
356 impl<S> Layer<S> for MetricsLayer
357 where
358     S: Subscriber + for<'span> LookupSpan<'span>,
359 {
on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>)360     fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
361         let mut metric_visitor = MetricVisitor {
362             instruments: &self.instruments,
363             meter: &self.meter,
364         };
365         event.record(&mut metric_visitor);
366     }
367 }
368