1#[cfg(feature = "prometheus")]
8mod prometheus;
9#[cfg(feature = "prometheus")]
10use prometheus_client::encoding::prometheus_protobuf::prometheus_data_model::MetricFamily;
11
12use std::fmt;
13use std::sync::atomic::{AtomicU8, Ordering};
14use std::sync::{Arc, OnceLock};
15
16use dashmap::{DashMap, DashSet};
17use metrics::{Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SharedString, Unit};
18use tracing::callsite::{self, Callsite};
19use tracing::field::{self, FieldSet, Value};
20use tracing::metadata;
21use tracing::subscriber::Interest;
22use tracing::{Event, Level as TracingLevel, Metadata as TracingMetadata};
23
24const DEFAULT_TARGET: &str = "metrics";
25const EVENT_NAME: &str = "metric";
26
27#[derive(Clone)]
29pub struct TracingRecorder {
30 inner: Arc<Inner>,
31}
32
33impl TracingRecorder {
34 pub fn new() -> Self {
36 Self::builder().build()
37 }
38
39 #[cfg(feature = "prometheus")]
43 pub fn emit_snapshot(&self, metrics: &[MetricFamily]) {
44 prometheus::emit(self, metrics);
45 }
46
47 #[cfg(feature = "prometheus")]
48 fn emit_statistic(&self, key: &Key, kind: &str, statistic: &str, value: &dyn Value) {
49 let event = "snapshot";
50 let name = key.name();
51 let labels = field::debug(LabelsDebug::from_key(key));
52 let values: [Option<&dyn Value>; 6] = [
53 Some(&event),
54 Some(&name),
55 Some(&kind),
56 Some(&labels),
57 Some(&statistic),
58 Some(value),
59 ];
60 self.inner.dispatch_event(
61 Schema::Snapshot,
62 &self.inner.default_target,
63 self.inner.default_level,
64 &values,
65 );
66 }
67
68 pub fn builder() -> Builder {
70 Builder::default()
71 }
72}
73
74impl Default for TracingRecorder {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80#[derive(Clone, Debug)]
82pub struct Builder {
83 default_level: TracingLevel,
84 default_target: Arc<str>,
85}
86
87impl Default for Builder {
88 fn default() -> Self {
89 Self {
90 default_level: TracingLevel::TRACE,
91 default_target: Arc::from(DEFAULT_TARGET),
92 }
93 }
94}
95
96impl Builder {
97 pub fn default_level(mut self, level: TracingLevel) -> Self {
99 self.default_level = level;
100 self
101 }
102
103 pub fn default_target<T: Into<String>>(mut self, target: T) -> Self {
105 self.default_target = Arc::from(target.into());
106 self
107 }
108
109 pub fn build(self) -> TracingRecorder {
111 TracingRecorder {
112 inner: Arc::new(Inner {
113 default_level: self.default_level,
114 default_target: self.default_target,
115 described: DashSet::new(),
116 callsites: DashMap::new(),
117 }),
118 }
119 }
120}
121
122impl Recorder for TracingRecorder {
123 fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
124 self.inner
125 .describe(MetricKind::Counter, key, unit, description);
126 }
127
128 fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
129 self.inner
130 .describe(MetricKind::Gauge, key, unit, description);
131 }
132
133 fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
134 self.inner
135 .describe(MetricKind::Histogram, key, unit, description);
136 }
137
138 fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
139 let target = self.inner.resolve_target(metadata.target());
140 let level = map_level(*metadata.level());
141 let handle = CounterHandle {
142 inner: Arc::clone(&self.inner),
143 key: key.clone(),
144 target,
145 level,
146 };
147 Counter::from_arc(Arc::new(handle))
148 }
149
150 fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
151 let target = self.inner.resolve_target(metadata.target());
152 let level = map_level(*metadata.level());
153 let handle = GaugeHandle {
154 inner: Arc::clone(&self.inner),
155 key: key.clone(),
156 target,
157 level,
158 };
159 Gauge::from_arc(Arc::new(handle))
160 }
161
162 fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
163 let target = self.inner.resolve_target(metadata.target());
164 let level = map_level(*metadata.level());
165 let handle = HistogramHandle {
166 inner: Arc::clone(&self.inner),
167 key: key.clone(),
168 target,
169 level,
170 };
171 Histogram::from_arc(Arc::new(handle))
172 }
173}
174
175struct Inner {
177 default_level: TracingLevel,
178 default_target: Arc<str>,
179 described: DashSet<SeenKey>,
180 callsites: DashMap<CallsiteKey, &'static DynamicCallsite>,
181}
182
183impl Inner {
184 fn resolve_target(&self, target: &str) -> Arc<str> {
185 if target.is_empty() || target == self.default_target.as_ref() {
186 Arc::clone(&self.default_target)
187 } else {
188 Arc::from(target)
189 }
190 }
191
192 fn describe(
193 &self,
194 kind: MetricKind,
195 key: KeyName,
196 unit: Option<Unit>,
197 description: SharedString,
198 ) {
199 if !self.mark_described(&key, kind) {
200 return;
201 }
202
203 let target = Arc::clone(&self.default_target);
204 let level = self.default_level;
205
206 let event_value = EVENT_DESCRIBE;
207 let name_value = key.as_str();
208 let kind_value = kind.as_ref();
209 let description_value = description.as_ref();
210 let unit_value = unit.map(|unit| unit.as_str());
211
212 let values: [Option<&dyn Value>; 5] = [
213 Some(&event_value as &dyn Value),
214 Some(&name_value as &dyn Value),
215 Some(&kind_value as &dyn Value),
216 Some(&description_value as &dyn Value),
217 unit_value.as_ref().map(|value| value as &dyn Value),
218 ];
219
220 self.dispatch_event(Schema::Describe, &target, level, &values);
221 }
222
223 fn emit_counter(
224 &self,
225 key: &Key,
226 target: &Arc<str>,
227 level: TracingLevel,
228 op: CounterOp,
229 value: u64,
230 ) {
231 let event_value = EVENT_EMIT;
232 let name_value = key.name();
233 let kind_value = MetricKind::Counter.as_ref();
234 let op_value = op.as_ref();
235 let labels_debug = LabelsDebug::from_key(key);
236 let labels_value = field::debug(labels_debug);
237 let values: [Option<&dyn Value>; 6] = [
238 Some(&event_value as &dyn Value),
239 Some(&name_value as &dyn Value),
240 Some(&kind_value as &dyn Value),
241 Some(&labels_value as &dyn Value),
242 Some(&value as &dyn Value),
243 Some(&op_value as &dyn Value),
244 ];
245
246 self.dispatch_event(Schema::Emit, target, level, &values);
247 }
248
249 fn emit_gauge(
250 &self,
251 key: &Key,
252 target: &Arc<str>,
253 level: TracingLevel,
254 op: GaugeOp,
255 value: f64,
256 ) {
257 let event_value = EVENT_EMIT;
258 let name_value = key.name();
259 let kind_value = MetricKind::Gauge.as_ref();
260 let op_value = op.as_ref();
261 let labels_debug = LabelsDebug::from_key(key);
262 let labels_value = field::debug(labels_debug);
263 let values: [Option<&dyn Value>; 6] = [
264 Some(&event_value as &dyn Value),
265 Some(&name_value as &dyn Value),
266 Some(&kind_value as &dyn Value),
267 Some(&labels_value as &dyn Value),
268 Some(&value as &dyn Value),
269 Some(&op_value as &dyn Value),
270 ];
271
272 self.dispatch_event(Schema::Emit, target, level, &values);
273 }
274
275 fn emit_histogram(&self, key: &Key, target: &Arc<str>, level: TracingLevel, value: f64) {
276 let event_value = EVENT_EMIT;
277 let name_value = key.name();
278 let kind_value = MetricKind::Histogram.as_ref();
279 let op_value = HistogramOp::Sample.as_ref();
280 let labels_debug = LabelsDebug::from_key(key);
281 let labels_value = field::debug(labels_debug);
282 let values: [Option<&dyn Value>; 6] = [
283 Some(&event_value as &dyn Value),
284 Some(&name_value as &dyn Value),
285 Some(&kind_value as &dyn Value),
286 Some(&labels_value as &dyn Value),
287 Some(&value as &dyn Value),
288 Some(&op_value as &dyn Value),
289 ];
290
291 self.dispatch_event(Schema::Emit, target, level, &values);
292 }
293
294 fn dispatch_event(
295 &self,
296 schema: Schema,
297 target: &Arc<str>,
298 level: TracingLevel,
299 values: &[Option<&dyn Value>],
300 ) {
301 if !tracing::level_enabled!(level) {
302 return;
303 }
304
305 let callsite = self.callsite_for(schema, level, target);
306 let meta = callsite.metadata_static();
307
308 let interest = callsite.interest();
309 if interest.is_never() {
310 return;
311 }
312 if !interest.is_always() && !tracing::dispatcher::get_default(|d| d.enabled(meta)) {
313 return;
314 }
315
316 let value_set = meta.fields().value_set_all(values);
317 Event::dispatch(meta, &value_set);
318 }
319
320 fn callsite_for(
321 &self,
322 schema: Schema,
323 level: TracingLevel,
324 target: &Arc<str>,
325 ) -> &'static DynamicCallsite {
326 let key = CallsiteKey {
327 schema,
328 level: level_key(level),
329 target: Arc::clone(target),
330 };
331
332 match self.callsites.entry(key) {
333 dashmap::mapref::entry::Entry::Occupied(entry) => entry.get(),
334 dashmap::mapref::entry::Entry::Vacant(entry) => {
335 let callsite = build_callsite(schema, level, target.as_ref());
336 entry.insert(callsite);
337 callsite
338 }
339 }
340 }
341
342 fn mark_described(&self, name: &KeyName, kind: MetricKind) -> bool {
343 let key = SeenKey {
344 name: name.clone(),
345 kind,
346 };
347
348 self.described.insert(key)
349 }
350}
351
352#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, strum::AsRefStr)]
353#[strum(serialize_all = "snake_case")]
354enum MetricKind {
355 Counter,
356 Gauge,
357 Histogram,
358}
359
360#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
361enum Schema {
362 #[cfg(feature = "prometheus")]
363 Snapshot,
364 #[cfg(feature = "prometheus")]
365 HistogramSnapshot,
366 Emit,
367 Describe,
368}
369
370impl Schema {
371 fn field_names(self) -> &'static [&'static str] {
372 match self {
373 #[cfg(feature = "prometheus")]
374 Schema::Snapshot => &["event", "name", "kind", "labels", "statistic", "value"],
375 #[cfg(feature = "prometheus")]
376 Schema::HistogramSnapshot => &[
377 "event",
378 "name",
379 "kind",
380 "labels",
381 "count",
382 "sum",
383 "schema",
384 "zero_threshold",
385 "zero_count",
386 "buckets",
387 "positive_spans",
388 "positive_deltas",
389 "positive_counts",
390 "negative_spans",
391 "negative_deltas",
392 "negative_counts",
393 ],
394 Schema::Emit => &EMIT_FIELDS,
395 Schema::Describe => &DESCRIBE_FIELDS,
396 }
397 }
398}
399
400#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::AsRefStr)]
401#[strum(serialize_all = "snake_case")]
402enum CounterOp {
403 Increment,
404 Absolute,
405}
406
407#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::AsRefStr)]
408#[strum(serialize_all = "snake_case")]
409enum GaugeOp {
410 Set,
411 Increment,
412 Decrement,
413}
414
415#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::AsRefStr)]
416#[strum(serialize_all = "snake_case")]
417enum HistogramOp {
418 Sample,
419}
420
421#[derive(Clone, Debug, Eq, PartialEq, Hash)]
422struct SeenKey {
423 name: KeyName,
424 kind: MetricKind,
425}
426
427#[derive(Clone, Debug, Eq, PartialEq, Hash)]
428struct CallsiteKey {
429 schema: Schema,
430 level: u8,
431 target: Arc<str>,
432}
433
434const EVENT_EMIT: &str = "emit";
435const EVENT_DESCRIBE: &str = "describe";
436
437const EMIT_FIELDS: [&str; 6] = ["event", "name", "kind", "labels", "value", "op"];
438const DESCRIBE_FIELDS: [&str; 5] = ["event", "name", "kind", "description", "unit"];
439
440struct DynamicCallsite {
442 metadata: OnceLock<TracingMetadata<'static>>,
443 interest: AtomicU8,
444}
445
446impl DynamicCallsite {
447 const INTEREST_NEVER: u8 = 0;
448 const INTEREST_SOMETIMES: u8 = 1;
449 const INTEREST_ALWAYS: u8 = 2;
450
451 fn new() -> Self {
452 Self {
453 metadata: OnceLock::new(),
454 interest: AtomicU8::new(Self::INTEREST_SOMETIMES),
455 }
456 }
457
458 fn set_metadata(&self, metadata: TracingMetadata<'static>) {
459 self.metadata
460 .set(metadata)
461 .expect("callsite metadata set once");
462 }
463
464 fn metadata_static(&'static self) -> &'static TracingMetadata<'static> {
465 self.metadata
466 .get()
467 .expect("callsite metadata set before registration")
468 }
469
470 fn interest(&self) -> Interest {
471 match self.interest.load(Ordering::Relaxed) {
472 Self::INTEREST_NEVER => Interest::never(),
473 Self::INTEREST_ALWAYS => Interest::always(),
474 _ => Interest::sometimes(),
475 }
476 }
477}
478
479impl Callsite for DynamicCallsite {
480 fn set_interest(&self, interest: Interest) {
481 let value = if interest.is_never() {
482 Self::INTEREST_NEVER
483 } else if interest.is_always() {
484 Self::INTEREST_ALWAYS
485 } else {
486 Self::INTEREST_SOMETIMES
487 };
488 self.interest.store(value, Ordering::Relaxed);
489 }
490
491 fn metadata(&self) -> &TracingMetadata<'_> {
492 self.metadata
493 .get()
494 .expect("callsite metadata set before registration")
495 }
496}
497
498fn build_callsite(schema: Schema, level: TracingLevel, target: &str) -> &'static DynamicCallsite {
499 let callsite = Box::leak(Box::new(DynamicCallsite::new()));
502 let target_static: &'static str = Box::leak(target.to_string().into_boxed_str());
503 let fields = FieldSet::new(schema.field_names(), callsite::Identifier(callsite));
504 let meta = TracingMetadata::new(
505 EVENT_NAME,
506 target_static,
507 level,
508 None,
509 None,
510 None,
511 fields,
512 metadata::Kind::EVENT,
513 );
514 callsite.set_metadata(meta);
515 callsite::register(callsite);
516 callsite
517}
518
519fn map_level(level: metrics::Level) -> TracingLevel {
520 match level {
521 metrics::Level::TRACE => TracingLevel::TRACE,
522 metrics::Level::DEBUG => TracingLevel::DEBUG,
523 metrics::Level::INFO => TracingLevel::INFO,
524 metrics::Level::WARN => TracingLevel::WARN,
525 metrics::Level::ERROR => TracingLevel::ERROR,
526 }
527}
528
529fn level_key(level: TracingLevel) -> u8 {
530 match level {
531 TracingLevel::TRACE => 0,
532 TracingLevel::DEBUG => 1,
533 TracingLevel::INFO => 2,
534 TracingLevel::WARN => 3,
535 TracingLevel::ERROR => 4,
536 }
537}
538
539struct LabelsDebug<'a> {
540 key: &'a Key,
541}
542
543impl<'a> LabelsDebug<'a> {
544 fn from_key(key: &'a Key) -> Self {
545 Self { key }
546 }
547}
548
549impl fmt::Debug for LabelsDebug<'_> {
550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551 let mut map = f.debug_map();
552 for label in self.key.labels() {
553 map.entry(&label.key(), &label.value());
554 }
555 map.finish()
556 }
557}
558
559struct CounterHandle {
560 inner: Arc<Inner>,
561 key: Key,
562 target: Arc<str>,
563 level: TracingLevel,
564}
565
566impl metrics::CounterFn for CounterHandle {
567 fn increment(&self, value: u64) {
568 self.inner.emit_counter(
569 &self.key,
570 &self.target,
571 self.level,
572 CounterOp::Increment,
573 value,
574 );
575 }
576
577 fn absolute(&self, value: u64) {
578 self.inner.emit_counter(
579 &self.key,
580 &self.target,
581 self.level,
582 CounterOp::Absolute,
583 value,
584 );
585 }
586}
587
588struct GaugeHandle {
589 inner: Arc<Inner>,
590 key: Key,
591 target: Arc<str>,
592 level: TracingLevel,
593}
594
595impl metrics::GaugeFn for GaugeHandle {
596 fn increment(&self, value: f64) {
597 self.inner.emit_gauge(
598 &self.key,
599 &self.target,
600 self.level,
601 GaugeOp::Increment,
602 value,
603 );
604 }
605
606 fn decrement(&self, value: f64) {
607 self.inner.emit_gauge(
608 &self.key,
609 &self.target,
610 self.level,
611 GaugeOp::Decrement,
612 value,
613 );
614 }
615
616 fn set(&self, value: f64) {
617 self.inner
618 .emit_gauge(&self.key, &self.target, self.level, GaugeOp::Set, value);
619 }
620}
621
622struct HistogramHandle {
623 inner: Arc<Inner>,
624 key: Key,
625 target: Arc<str>,
626 level: TracingLevel,
627}
628
629impl metrics::HistogramFn for HistogramHandle {
630 fn record(&self, value: f64) {
631 self.inner
632 .emit_histogram(&self.key, &self.target, self.level, value);
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639 use serde_json::Value;
640 use std::io;
641 use std::sync::{Arc, Mutex};
642
643 use tracing_subscriber::fmt::MakeWriter;
644 use tracing_subscriber::prelude::*;
645
646 #[derive(Clone, Default)]
647 struct JsonBuffer {
648 buf: Arc<Mutex<Vec<u8>>>,
649 }
650
651 impl JsonBuffer {
652 fn new(buf: Arc<Mutex<Vec<u8>>>) -> Self {
653 Self { buf }
654 }
655 }
656
657 struct JsonWriter {
658 buf: Arc<Mutex<Vec<u8>>>,
659 }
660
661 impl<'a> MakeWriter<'a> for JsonBuffer {
662 type Writer = JsonWriter;
663
664 fn make_writer(&'a self) -> Self::Writer {
665 JsonWriter {
666 buf: Arc::clone(&self.buf),
667 }
668 }
669 }
670
671 impl io::Write for JsonWriter {
672 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
673 let mut guard = self
674 .buf
675 .lock()
676 .map_err(|_| io::Error::other("log buffer poisoned"))?;
677 guard.extend_from_slice(bytes);
678 Ok(bytes.len())
679 }
680
681 fn flush(&mut self) -> io::Result<()> {
682 Ok(())
683 }
684 }
685
686 fn capture_events(f: impl FnOnce()) -> Vec<Value> {
687 let buffer = Arc::new(Mutex::new(Vec::new()));
688 let writer = JsonBuffer::new(Arc::clone(&buffer));
689 let layer = tracing_subscriber::fmt::layer()
690 .json()
691 .with_current_span(false)
692 .with_span_list(false)
693 .with_writer(writer);
694 let subscriber = tracing_subscriber::registry().with(layer);
695 tracing::subscriber::with_default(subscriber, f);
696
697 let bytes = buffer.lock().expect("log buffer").clone();
698 let text = String::from_utf8(bytes).expect("log output utf8");
699 text.lines()
700 .filter(|line| !line.is_empty())
701 .map(|line| serde_json::from_str::<Value>(line).expect("valid json"))
702 .collect()
703 }
704
705 fn event_fields(event: &Value) -> &serde_json::Map<String, Value> {
706 event
707 .get("fields")
708 .and_then(Value::as_object)
709 .expect("fields object")
710 }
711
712 #[cfg(feature = "prometheus")]
713 mod snapshots {
714 use super::*;
715 use prometheus_client::encoding::prometheus_protobuf::prometheus_data_model::{
716 Bucket, BucketSpan, Histogram, LabelPair, Metric, MetricFamily, MetricType,
717 };
718
719 fn histogram() -> MetricFamily {
720 MetricFamily {
721 name: "latency".into(),
722 r#type: MetricType::Histogram.into(),
723 metric: vec![Metric {
724 label: vec![LabelPair {
725 name: "session_id".into(),
726 value: "7".into(),
727 }],
728 histogram: Some(Histogram {
729 sample_count: u64::MAX,
730 sample_sum: 1.5,
731 ..Default::default()
732 }),
733 ..Default::default()
734 }],
735 ..Default::default()
736 }
737 }
738
739 #[test]
740 fn histogram_snapshot_preserves_event_fields_and_bucket_encoding() {
741 let recorder = TracingRecorder::builder()
742 .default_target("received.metrics")
743 .default_level(TracingLevel::INFO)
744 .build();
745 let mut metric = histogram();
746 let histogram = metric.metric[0].histogram.as_mut().unwrap();
747 histogram.bucket = vec![Bucket {
748 cumulative_count: 2,
749 upper_bound: 1.0,
750 ..Default::default()
751 }];
752 histogram.schema = 2;
753 histogram.zero_threshold = 0.01;
754 histogram.zero_count = 1;
755 histogram.positive_span = vec![BucketSpan {
756 offset: 3,
757 length: 2,
758 }];
759 histogram.positive_delta = vec![2, -1];
760 histogram.negative_span = vec![BucketSpan {
761 offset: -2,
762 length: 1,
763 }];
764 histogram.negative_delta = vec![3];
765 let expected = [
766 ("buckets", format!("{:?}", histogram.bucket)),
767 ("positive_spans", format!("{:?}", histogram.positive_span)),
768 ("positive_deltas", format!("{:?}", histogram.positive_delta)),
769 ("negative_spans", format!("{:?}", histogram.negative_span)),
770 ("negative_deltas", format!("{:?}", histogram.negative_delta)),
771 ];
772 let events = capture_events(|| recorder.emit_snapshot(&[metric]));
773 assert_eq!(events.len(), 1);
774 assert_eq!(events[0]["target"], "received.metrics");
775 assert_eq!(events[0]["level"], "INFO");
776 let fields = event_fields(&events[0]);
777 assert_eq!(fields["event"], "snapshot");
778 assert_eq!(fields["name"], "latency");
779 assert_eq!(fields["kind"], "histogram");
780 assert_eq!(fields["labels"], r#"{"session_id": "7"}"#);
781 assert_eq!(fields["count"], u64::MAX);
782 assert_eq!(fields["sum"], 1.5);
783 assert_eq!(fields["schema"], 2);
784 assert_eq!(fields["zero_threshold"], 0.01);
785 assert_eq!(fields["zero_count"], 1);
786 for (name, value) in expected {
787 assert_eq!(fields[name], value);
788 }
789 }
790
791 #[test]
792 fn histogram_snapshot_prefers_float_counts_over_integer_counts() {
793 let recorder = TracingRecorder::new();
794 let mut metric = histogram();
795 let histogram = metric.metric[0].histogram.as_mut().unwrap();
796 histogram.sample_count_float = 2.5;
797 histogram.zero_count = u64::MAX;
798 histogram.zero_count_float = 0.5;
799 histogram.positive_count = vec![1.25];
800 histogram.negative_count = vec![0.75];
801 let events = capture_events(|| recorder.emit_snapshot(&[metric]));
802 let fields = event_fields(&events[0]);
803 assert_eq!(fields["count"], 2.5);
804 assert_eq!(fields["zero_count"], 0.5);
805 assert_eq!(fields["positive_counts"], "[1.25]");
806 assert_eq!(fields["negative_counts"], "[0.75]");
807 }
808 }
809
810 #[test]
811 fn emits_counter_event_fields() {
812 let recorder = TracingRecorder::new();
813 let key = Key::from_parts("my_counter", &[("label", "value")]);
814 let metadata = Metadata::new("my.target", metrics::Level::INFO, None);
815 let counter = recorder.register_counter(&key, &metadata);
816
817 let events = capture_events(|| {
818 counter.increment(3);
819 });
820
821 assert_eq!(events.len(), 1);
822 let event = &events[0];
823 let fields = event_fields(event);
824
825 assert_eq!(event.get("level").and_then(Value::as_str), Some("INFO"));
826 assert_eq!(
827 event.get("target").and_then(Value::as_str),
828 Some("my.target")
829 );
830 assert_eq!(fields.get("event").and_then(Value::as_str), Some("emit"));
831 assert_eq!(
832 fields.get("name").and_then(Value::as_str),
833 Some("my_counter")
834 );
835 assert_eq!(fields.get("kind").and_then(Value::as_str), Some("counter"));
836 assert_eq!(fields.get("op").and_then(Value::as_str), Some("increment"));
837 assert_eq!(fields.get("value"), Some(&Value::from(3)));
838
839 let expected_labels = format!("{:?}", LabelsDebug::from_key(&key));
840 assert_eq!(
841 fields.get("labels").and_then(Value::as_str),
842 Some(expected_labels.as_str())
843 );
844 }
845
846 #[test]
847 fn emits_gauge_set_with_level() {
848 let recorder = TracingRecorder::new();
849 let key = Key::from_name("my_gauge");
850 let metadata = Metadata::new("gauge.target", metrics::Level::WARN, None);
851 let gauge = recorder.register_gauge(&key, &metadata);
852
853 let events = capture_events(|| {
854 gauge.set(2.5);
855 });
856
857 assert_eq!(events.len(), 1);
858 let event = &events[0];
859 let fields = event_fields(event);
860
861 assert_eq!(event.get("level").and_then(Value::as_str), Some("WARN"));
862 assert_eq!(
863 event.get("target").and_then(Value::as_str),
864 Some("gauge.target")
865 );
866 assert_eq!(fields.get("event").and_then(Value::as_str), Some("emit"));
867 assert_eq!(fields.get("kind").and_then(Value::as_str), Some("gauge"));
868 assert_eq!(fields.get("op").and_then(Value::as_str), Some("set"));
869 assert_eq!(fields.get("value"), Some(&Value::from(2.5)));
870 }
871
872 #[test]
873 fn emits_histogram_sample_op() {
874 let recorder = TracingRecorder::new();
875 let key = Key::from_name("my_histogram");
876 let metadata = Metadata::new("hist.target", metrics::Level::DEBUG, None);
877 let histogram = recorder.register_histogram(&key, &metadata);
878
879 let events = capture_events(|| {
880 histogram.record(7.25);
881 });
882
883 assert_eq!(events.len(), 1);
884 let event = &events[0];
885 let fields = event_fields(event);
886
887 assert_eq!(event.get("level").and_then(Value::as_str), Some("DEBUG"));
888 assert_eq!(
889 event.get("target").and_then(Value::as_str),
890 Some("hist.target")
891 );
892 assert_eq!(fields.get("event").and_then(Value::as_str), Some("emit"));
893 assert_eq!(
894 fields.get("kind").and_then(Value::as_str),
895 Some("histogram")
896 );
897 assert_eq!(fields.get("op").and_then(Value::as_str), Some("sample"));
898 assert_eq!(fields.get("value"), Some(&Value::from(7.25)));
899 }
900
901 #[test]
902 fn describe_is_emitted_once() {
903 let recorder = TracingRecorder::new();
904
905 let events = capture_events(|| {
906 recorder.describe_counter(
907 KeyName::from("described_counter"),
908 Some(Unit::Seconds),
909 "desc".into(),
910 );
911 recorder.describe_counter(
912 KeyName::from("described_counter"),
913 Some(Unit::Seconds),
914 "desc".into(),
915 );
916 });
917
918 assert_eq!(events.len(), 1);
919 let event = &events[0];
920 let fields = event_fields(event);
921 assert_eq!(
922 fields.get("event").and_then(Value::as_str),
923 Some("describe")
924 );
925 assert_eq!(
926 fields.get("description").and_then(Value::as_str),
927 Some("desc")
928 );
929 assert_eq!(fields.get("unit").and_then(Value::as_str), Some("seconds"));
930 }
931
932 #[test]
933 fn describe_is_emitted_per_kind() {
934 let recorder = TracingRecorder::new();
935
936 let events = capture_events(|| {
937 recorder.describe_counter(
938 KeyName::from("shared_name"),
939 Some(Unit::Seconds),
940 "counter".into(),
941 );
942 recorder.describe_gauge(KeyName::from("shared_name"), None, "gauge".into());
943 });
944
945 assert_eq!(events.len(), 2);
946 let mut kinds: Vec<_> = events
947 .iter()
948 .map(|event| {
949 event_fields(event)
950 .get("kind")
951 .and_then(Value::as_str)
952 .unwrap_or_default()
953 .to_string()
954 })
955 .collect();
956 kinds.sort();
957 assert_eq!(kinds, vec!["counter".to_string(), "gauge".to_string()]);
958 }
959
960 #[test]
961 fn describe_uses_default_level_and_target() {
962 let recorder = TracingRecorder::new();
963
964 let events = capture_events(|| {
965 recorder.describe_gauge(KeyName::from("gauge"), None, "desc".into());
966 });
967
968 assert_eq!(events.len(), 1);
969 let event = &events[0];
970 assert_eq!(event.get("level").and_then(Value::as_str), Some("TRACE"));
971 assert_eq!(
972 event.get("target").and_then(Value::as_str),
973 Some(DEFAULT_TARGET)
974 );
975 }
976
977 #[test]
978 fn emit_uses_default_target_when_metadata_empty() {
979 let recorder = TracingRecorder::new();
980 let key = Key::from_name("empty_target");
981 let metadata = Metadata::new("", metrics::Level::INFO, None);
982 let counter = recorder.register_counter(&key, &metadata);
983
984 let events = capture_events(|| {
985 counter.increment(1);
986 });
987
988 assert_eq!(events.len(), 1);
989 let event = &events[0];
990 assert_eq!(
991 event.get("target").and_then(Value::as_str),
992 Some(DEFAULT_TARGET)
993 );
994 }
995
996 #[test]
997 fn emit_does_not_include_unit() {
998 let recorder = TracingRecorder::new();
999 let key = Key::from_name("my_histogram");
1000 let metadata = Metadata::new("my.target", metrics::Level::INFO, None);
1001 let histogram = recorder.register_histogram(&key, &metadata);
1002
1003 let events = capture_events(|| {
1004 histogram.record(1.5);
1005 });
1006
1007 assert_eq!(events.len(), 1);
1008 let event = &events[0];
1009 let fields = event_fields(event);
1010 assert_eq!(fields.get("event").and_then(Value::as_str), Some("emit"));
1011 assert!(fields.get("unit").is_none());
1012 }
1013}