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