diff --git a/quickwit/quickwit-metrics/examples/http_service.rs b/quickwit/quickwit-metrics/examples/http_service.rs index 5c7d76f29bf..d7dc5bf4480 100644 --- a/quickwit/quickwit-metrics/examples/http_service.rs +++ b/quickwit/quickwit-metrics/examples/http_service.rs @@ -67,6 +67,47 @@ static HTTP_ACTIVE_CONNECTIONS_BY_REGION: LazyGauge = lazy_gauge!( "region" => "us-east-1", ); +// ─── Custom system prefix ─── +// +// Override the default system prefix ("quickwit") with a custom value. +// This produces metric names like "myapp_db_queries_total" instead of +// "quickwit_db_queries_total". + +static DB_QUERIES_TOTAL: LazyCounter = lazy_counter!( + name: "queries_total", + description: "Total number of database queries", + system: "myapp", + subsystem: "db", +); + +static DB_QUERY_DURATION: LazyHistogram = lazy_histogram!( + name: "query_duration_seconds", + description: "Time spent executing database queries", + system: "myapp", + subsystem: "db", + buckets: vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0], +); + +static DB_CONNECTIONS: LazyGauge = lazy_gauge!( + name: "connections", + description: "Number of active database connections", + system: "myapp", + subsystem: "db", +); + +// ─── Custom separator ─── +// +// Override the default "_" separator with ".". +// This produces metric names like "myapp.http.requests_total". + +static HTTP_REQUESTS_DOTTED: LazyCounter = lazy_counter!( + name: "requests_total", + description: "Total HTTP requests with dotted metric name", + system: "myapp", + subsystem: "http", + separator: ".", +); + // ─── LabelNames examples ─── const ROUTE_LABEL_NAMES: LabelNames<2> = label_names!("method", "path"); @@ -183,6 +224,23 @@ fn main() { println!(" set quickwit_http_requests_total absolute = 1,000,000"); println!(); + println!("=== Custom system prefix ==="); + DB_QUERIES_TOTAL.inc(); + println!(" myapp_db_queries_total = {}", DB_QUERIES_TOTAL.get()); + DB_QUERY_DURATION.observe(0.042); + println!(" myapp_db_query_duration_seconds observed 0.042"); + DB_CONNECTIONS.set(5.0); + println!(" myapp_db_connections = {}", DB_CONNECTIONS.get()); + println!(); + + println!("=== Custom separator ==="); + HTTP_REQUESTS_DOTTED.inc_by(3); + println!( + " myapp.http.requests_total = {}", + HTTP_REQUESTS_DOTTED.get() + ); + println!(); + println!("Prometheus scrape endpoint: http://127.0.0.1:9000/metrics"); println!("Press Ctrl+C to stop."); std::thread::park(); diff --git a/quickwit/quickwit-metrics/src/counter.rs b/quickwit/quickwit-metrics/src/counter.rs index bb241fa8c78..afe02e337e3 100644 --- a/quickwit/quickwit-metrics/src/counter.rs +++ b/quickwit/quickwit-metrics/src/counter.rs @@ -260,18 +260,22 @@ impl CounterFn for Counter { /// ``` #[macro_export] macro_rules! counter { - // Base declaration: all-static name, labels, and key — zero allocations. + // Base declaration with explicit separator, system, and subsystem prefix - zero allocations. ( name: $name:literal, description: $description:literal, - subsystem: $subsystem:tt + system: $system:expr, + subsystem: $subsystem:expr, + separator: $separator:expr $(, $label:literal => $value:literal)* $(,)? ) => {{ $crate::__key_info_metadata!( kind: $crate::MetricKind::Counter, name: $name, description: $description, - subsystem: $subsystem + system: $system, + subsystem: $subsystem, + separator: $separator $(, $label => $value)* ); $crate::__metric_declaration!( @@ -282,6 +286,58 @@ macro_rules! counter { ) }}; + // Base declaration with explicit system and subsystem prefix - zero allocations. + ( + name: $name:literal, + description: $description:literal, + system: $system:expr, + subsystem: $subsystem:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::counter!( + name: $name, + description: $description, + system: $system, + subsystem: $subsystem, + separator: $crate::SEPARATOR + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr, + separator: $separator:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::counter!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem, + separator: $separator + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::counter!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem + $(, $label => $value)* + ) + }}; + // Parent extension with inline dynamic labels. // Derives a child counter by inheriting the parent's name and labels, // appending new (possibly dynamic) key => value pairs. diff --git a/quickwit/quickwit-metrics/src/gauge.rs b/quickwit/quickwit-metrics/src/gauge.rs index bf98d1ca4df..127bdda9995 100644 --- a/quickwit/quickwit-metrics/src/gauge.rs +++ b/quickwit/quickwit-metrics/src/gauge.rs @@ -331,18 +331,22 @@ impl Drop for GaugeGuard { /// ``` #[macro_export] macro_rules! gauge { - // Base declaration: all-static name, labels, and key — zero allocations. + // Base declaration with explicit separator, system, and subsystem prefix - zero allocations. ( name: $name:literal, description: $description:literal, - subsystem: $subsystem:tt + system: $system:expr, + subsystem: $subsystem:expr, + separator: $separator:expr $(, $label:literal => $value:literal)* $(,)? ) => {{ $crate::__key_info_metadata!( kind: $crate::MetricKind::Gauge, name: $name, description: $description, - subsystem: $subsystem + system: $system, + subsystem: $subsystem, + separator: $separator $(, $label => $value)* ); $crate::__metric_declaration!( @@ -353,6 +357,58 @@ macro_rules! gauge { ) }}; + // Base declaration with explicit system and subsystem prefix - zero allocations. + ( + name: $name:literal, + description: $description:literal, + system: $system:expr, + subsystem: $subsystem:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::gauge!( + name: $name, + description: $description, + system: $system, + subsystem: $subsystem, + separator: $crate::SEPARATOR + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr, + separator: $separator:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::gauge!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem, + separator: $separator + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::gauge!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem + $(, $label => $value)* + ) + }}; + // Parent extension with inline dynamic labels. // Derives a child gauge by inheriting the parent's name and labels, // appending new (possibly dynamic) key => value pairs. diff --git a/quickwit/quickwit-metrics/src/histogram.rs b/quickwit/quickwit-metrics/src/histogram.rs index 95ab8380d1f..a3b2bad6757 100644 --- a/quickwit/quickwit-metrics/src/histogram.rs +++ b/quickwit/quickwit-metrics/src/histogram.rs @@ -261,30 +261,30 @@ impl Drop for HistogramTimer { /// ``` #[macro_export] macro_rules! histogram { - // Base declaration: all-static name, labels, and key — zero allocations. + // Base declaration with explicit separator, system, and subsystem prefix - zero allocations. ( name: $name:literal, description: $description:literal, - subsystem: $subsystem:tt, + system: $system:expr, + subsystem: $subsystem:expr, + separator: $separator:expr, buckets: $buckets:expr $(, $label:literal => $value:literal)* $(,)? ) => {{ - // Expand compile-time statics: KEY_NAME, INFO, KEY, LABELS, METADATA. $crate::__key_info_metadata!( kind: $crate::MetricKind::Histogram, name: $name, description: $description, - subsystem: $subsystem + system: $system, + subsystem: $subsystem, + separator: $separator $(, $label => $value)* ); - // Link histogram bucket configuration to the metric info and register it - // with the inventory so the recorder can configure bucket boundaries. static HISTOGRAM_CONFIG: $crate::HistogramConfig = $crate::HistogramConfig { info: &INFO, buckets_fn: || $buckets, }; $crate::__inventory::submit!(HISTOGRAM_CONFIG); - // Thread-local cache + global DashMap registration. $crate::__metric_declaration!( metric_type: $crate::Histogram, register_fn: $crate::__histogram_get_or_register, @@ -293,6 +293,64 @@ macro_rules! histogram { ) }}; + // Base declaration with explicit system and subsystem prefix - zero allocations. + ( + name: $name:literal, + description: $description:literal, + system: $system:expr, + subsystem: $subsystem:expr, + buckets: $buckets:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::histogram!( + name: $name, + description: $description, + system: $system, + subsystem: $subsystem, + separator: $crate::SEPARATOR, + buckets: $buckets + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr, + separator: $separator:expr, + buckets: $buckets:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::histogram!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem, + separator: $separator, + buckets: $buckets + $(, $label => $value)* + ) + }}; + + // Base declaration with subsystem only — system defaults to SYSTEM. + ( + name: $name:literal, + description: $description:literal, + subsystem: $subsystem:expr, + buckets: $buckets:expr + $(, $label:literal => $value:literal)* $(,)? + ) => {{ + $crate::histogram!( + name: $name, + description: $description, + system: $crate::SYSTEM, + subsystem: $subsystem, + buckets: $buckets + $(, $label => $value)* + ) + }}; + // Parent extension with inline dynamic labels. // Derives a child histogram by inheriting the parent's name and labels, // appending new (possibly dynamic) key => value pairs. diff --git a/quickwit/quickwit-metrics/src/inner.rs b/quickwit/quickwit-metrics/src/inner.rs index 6a218297dc5..69f0858a971 100644 --- a/quickwit/quickwit-metrics/src/inner.rs +++ b/quickwit/quickwit-metrics/src/inner.rs @@ -24,20 +24,7 @@ use std::hash::{Hash, Hasher}; pub use const_format::concatcp as __concatcp; use rustc_hash::FxHasher; -// ─── Helper macros ─── - -/// Builds the fully-qualified metric key name at compile time: -/// `"{SYSTEM}_{name}"` or `"{SYSTEM}_{subsystem}_{name}"`. -#[doc(hidden)] -#[macro_export] -macro_rules! __key_name { - ("", $name:literal) => { - $crate::__concatcp!($crate::SYSTEM, "_", $name) - }; - ($subsystem:literal, $name:literal) => { - $crate::__concatcp!($crate::SYSTEM, "_", $subsystem, "_", $name) - }; -} +// ─── Helper functions ─── /// Counts the number of token-tree arguments at compile time. #[doc(hidden)] @@ -65,6 +52,15 @@ macro_rules! __metadata { }; } +/// Returns `separator` for non-empty strings, `""` for empty ones. +/// +/// Used inside `concatcp!` to conditionally insert separators when +/// composing metric key names at compile time. +#[doc(hidden)] +pub const fn __sep<'a>(s: &'a str, separator: &'a str) -> &'a str { + if s.is_empty() { "" } else { separator } +} + /// Declares the compile-time statics that every metric declaration arm needs: /// `KEY_NAME`, `INFO`, `KEY`, `LABELS`, and `METADATA`. /// Also registers `INFO` with the `inventory` crate for runtime discovery. @@ -75,10 +71,16 @@ macro_rules! __key_info_metadata { kind: $kind:expr, name: $name:literal, description: $description:literal, - subsystem: $subsystem:tt + system: $system:expr, + subsystem: $subsystem:tt, + separator: $separator:expr $(, $label:literal => $value:literal)* $(,)? ) => { - const KEY_NAME: &str = $crate::__key_name!($subsystem, $name); + const KEY_NAME: &str = $crate::__concatcp!( + $system, $crate::__sep($system, $separator), + $subsystem, $crate::__sep($subsystem, $separator), + $name + ); static METADATA: $crate::__metrics::Metadata<'static> = $crate::__metadata!($subsystem); static INFO: $crate::MetricInfo = $crate::MetricInfo { key_name: KEY_NAME, diff --git a/quickwit/quickwit-metrics/src/lib.rs b/quickwit/quickwit-metrics/src/lib.rs index e7bbe4235bd..bf1992ef2fd 100644 --- a/quickwit/quickwit-metrics/src/lib.rs +++ b/quickwit/quickwit-metrics/src/lib.rs @@ -264,10 +264,9 @@ /// System-level prefix prepended to every metric name. /// /// Every metric declared via [`counter!`], [`gauge!`], or [`histogram!`] -/// has its name composed at compile time as `{SYSTEM}_{subsystem}_{name}`. -/// -/// Hardcoded for now — making this configurable is tracked separately. +/// has its name composed at compile time as `{system}_{subsystem}_{name}`. pub const SYSTEM: &str = "quickwit"; +pub const SEPARATOR: &str = "_"; // ─── Metric modules ─── mod counter; @@ -289,7 +288,7 @@ pub use gauge::__gauge_get_or_register; #[doc(hidden)] pub use histogram::__histogram_get_or_register; #[doc(hidden)] -pub use inner::{__concatcp, __key_hash}; +pub use inner::{__concatcp, __key_hash, __sep}; // Re-exports of `metrics` and `inventory` used inside macro expansions. #[doc(hidden)] diff --git a/quickwit/quickwit-metrics/tests/counter.rs b/quickwit/quickwit-metrics/tests/counter.rs index 91dc4f92d88..7f2d82903d0 100644 --- a/quickwit/quickwit-metrics/tests/counter.rs +++ b/quickwit/quickwit-metrics/tests/counter.rs @@ -17,7 +17,7 @@ mod common; use common::with_recorder; use metrics::with_local_recorder; use metrics_util::debugging::{DebugValue, DebuggingRecorder}; -use quickwit_metrics::{Counter, counter, label_names, label_values, labels}; +use quickwit_metrics::{Counter, SYSTEM, counter, label_names, label_values, labels}; #[test] fn base_increments() { @@ -346,3 +346,148 @@ fn local_counter_clone_is_equal() { a.inc(); assert_eq!(b.get(), 1); } + +#[test] +fn custom_system_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + system: "myapp", + subsystem: "http", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_http_requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn default_system_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + subsystem: "http", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, &format!("{SYSTEM}_http_requests_total")); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn empty_system_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + system: "", + subsystem: "http", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "http_requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn empty_subsystem_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + system: "myapp", + subsystem: "", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn empty_system_and_subsystem_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + system: "", + subsystem: "", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn const_system_key_name() { + const MY_SYSTEM: &str = "custom"; + let entries = with_recorder(|| { + let c = counter!( + name: "ops_total", + description: "total ops", + system: MY_SYSTEM, + subsystem: "db", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "custom_db_ops_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn custom_separator_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + system: "myapp", + subsystem: "http", + separator: ".", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp.http.requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} + +#[test] +fn default_system_custom_separator_key_name() { + let entries = with_recorder(|| { + let c = counter!( + name: "requests_total", + description: "total requests", + subsystem: "http", + separator: ".", + ); + c.inc(); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "quickwit.http.requests_total"); + assert_eq!(*value, DebugValue::Counter(1)); +} diff --git a/quickwit/quickwit-metrics/tests/gauge.rs b/quickwit/quickwit-metrics/tests/gauge.rs index 3d49ab50f97..c7ec5e34f4f 100644 --- a/quickwit/quickwit-metrics/tests/gauge.rs +++ b/quickwit/quickwit-metrics/tests/gauge.rs @@ -17,7 +17,7 @@ mod common; use common::with_recorder; use metrics::with_local_recorder; use metrics_util::debugging::{DebugValue, DebuggingRecorder}; -use quickwit_metrics::{Gauge, GaugeGuard, gauge, label_names, label_values, labels}; +use quickwit_metrics::{Gauge, GaugeGuard, SYSTEM, gauge, label_names, label_values, labels}; #[test] fn set() { @@ -348,3 +348,148 @@ fn local_gauge_clone_is_equal() { a.set(7.0); assert_eq!(b.get(), 7.0); } + +#[test] +fn custom_system_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + system: "myapp", + subsystem: "db", + ); + g.set(5.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_db_connections"); + assert_eq!(*value, DebugValue::Gauge(5.0.into())); +} + +#[test] +fn default_system_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + subsystem: "db", + ); + g.set(1.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, &format!("{SYSTEM}_db_connections")); + assert_eq!(*value, DebugValue::Gauge(1.0.into())); +} + +#[test] +fn empty_system_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + system: "", + subsystem: "db", + ); + g.set(1.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "db_connections"); + assert_eq!(*value, DebugValue::Gauge(1.0.into())); +} + +#[test] +fn empty_subsystem_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + system: "myapp", + subsystem: "", + ); + g.set(1.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_connections"); + assert_eq!(*value, DebugValue::Gauge(1.0.into())); +} + +#[test] +fn empty_system_and_subsystem_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + system: "", + subsystem: "", + ); + g.set(1.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "connections"); + assert_eq!(*value, DebugValue::Gauge(1.0.into())); +} + +#[test] +fn const_system_key_name() { + const MY_SYSTEM: &str = "custom"; + let entries = with_recorder(|| { + let g = gauge!( + name: "pool_size", + description: "pool size", + system: MY_SYSTEM, + subsystem: "db", + ); + g.set(8.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "custom_db_pool_size"); + assert_eq!(*value, DebugValue::Gauge(8.0.into())); +} + +#[test] +fn custom_separator_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + system: "myapp", + subsystem: "db", + separator: ".", + ); + g.set(5.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp.db.connections"); + assert_eq!(*value, DebugValue::Gauge(5.0.into())); +} + +#[test] +fn default_system_custom_separator_key_name() { + let entries = with_recorder(|| { + let g = gauge!( + name: "connections", + description: "active connections", + subsystem: "db", + separator: ".", + ); + g.set(5.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "quickwit.db.connections"); + assert_eq!(*value, DebugValue::Gauge(5.0.into())); +} diff --git a/quickwit/quickwit-metrics/tests/histogram.rs b/quickwit/quickwit-metrics/tests/histogram.rs index 42a906d5a67..94c2230d09e 100644 --- a/quickwit/quickwit-metrics/tests/histogram.rs +++ b/quickwit/quickwit-metrics/tests/histogram.rs @@ -17,7 +17,7 @@ mod common; use common::with_recorder; use metrics::with_local_recorder; use metrics_util::debugging::{DebugValue, DebuggingRecorder}; -use quickwit_metrics::{HistogramTimer, histogram, label_names, label_values, labels}; +use quickwit_metrics::{HistogramTimer, SYSTEM, histogram, label_names, label_values, labels}; #[test] fn base_records_value() { @@ -234,3 +234,156 @@ fn timer_observe_duration_records_once() { other => panic!("expected Histogram, got {other:?}"), } } + +#[test] +fn custom_system_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + system: "myapp", + subsystem: "http", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_http_duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn default_system_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + subsystem: "http", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, &format!("{SYSTEM}_http_duration_seconds")); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn empty_system_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + system: "", + subsystem: "http", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "http_duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn empty_subsystem_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + system: "myapp", + subsystem: "", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp_duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn empty_system_and_subsystem_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + system: "", + subsystem: "", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn const_system_key_name() { + const MY_SYSTEM: &str = "custom"; + let entries = with_recorder(|| { + let h = histogram!( + name: "latency_ms", + description: "latency", + system: MY_SYSTEM, + subsystem: "rpc", + buckets: vec![1.0, 10.0, 100.0] + ); + h.observe(5.0); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "custom_rpc_latency_ms"); + assert_eq!(value, &DebugValue::Histogram(vec![5.0.into()])); +} + +#[test] +fn custom_separator_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + system: "myapp", + subsystem: "http", + separator: ".", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "myapp.http.duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +} + +#[test] +fn default_system_custom_separator_key_name() { + let entries = with_recorder(|| { + let h = histogram!( + name: "duration_seconds", + description: "request duration", + subsystem: "http", + separator: ".", + buckets: vec![0.01, 0.1, 1.0] + ); + h.observe(0.05); + }); + + assert_eq!(entries.len(), 1); + let (name, _, value) = &entries[0]; + assert_eq!(name, "quickwit.http.duration_seconds"); + assert_eq!(value, &DebugValue::Histogram(vec![0.05.into()])); +}