use crate::metric_primitive::MetricPrimitive; use crate::timestamp::Timestamp; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// A named, standalone parameter: how to pull a value out of a matching log line, and what kind it /// parses as. Ticket 037 unified this with the metric-declaration-embedded matcher that used to /// exist alongside it (`RegexToMetricPrimitiveMatcher`) — a metric now references params by name /// (`MetricDeclaration::label_params`/`value_param`) instead of redeclaring its own extractor per /// param, so a param declared once (e.g. via the Params panel) can be reused as a label by one /// metric and as the value by another without re-entering its regex/capture-group/kind. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ParamDeclaration { pub name: String, pub message_match_regex: String, pub capture_group: String, /// Alternative extraction source to `capture_group`: a dot-separated JSON field path (e.g. /// `foo.bar`), read from lines that parse as a JSON object instead of via the regex capture /// group. When present and non-empty, this takes precedence over `capture_group`. /// `#[serde(default)]` so profile files/requests saved before this field existed still load /// as plain regex-capture-group params. #[serde(default)] pub json_field_path: Option, pub kind: MetricPrimitiveKind, } /// Reads a value out of a JSON object by dot-separated field path (e.g. `"foo.bar"`), for /// [`ParamDeclaration::json_field_path`]. Returns `None` when `text` doesn't parse as a JSON /// object, the path doesn't resolve to a value, or the resolved value isn't a scalar /// (string/number/bool) — there's no sensible flat text form of a nested object/array to feed into /// `parse_metric_primitive`. pub fn extract_json_field(text: &str, field_path: &str) -> Option { let root: serde_json::Value = serde_json::from_str(text).ok()?; let mut current = root.as_object()?; let mut segments = field_path.split('.').peekable(); loop { let segment = segments.next()?; let next_value = current.get(segment)?; if segments.peek().is_none() { return match next_value { serde_json::Value::String(s) => Some(s.clone()), serde_json::Value::Number(n) => Some(n.to_string()), serde_json::Value::Bool(b) => Some(b.to_string()), _ => None, }; } current = next_value.as_object()?; } } /// `rename_all = "lowercase"` matches other `kind`-style enums, so JSON bodies use "string"/"number"/"timestamp". #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum MetricPrimitiveKind { String, Number, Timestamp, /// On a metric's Value param this is derived as elapsed time since the previous event (not parsed from text); otherwise it's a literal i64 millisecond count. Duration, /// A standalone param declared with this kind doesn't require (or use) a capture group: its value is /// simply whether `message_match_regex` matches the line, case-insensitively or per the regex's own /// flags — see `crate::engine::derive_log_params`. Unlike other kinds it's present as `true`/`false` /// on every line rather than omitted when unmatched. Boolean, } /// Value type paired with [`crate::index_key::IndexKey`] in the engine's primary index. #[allow(dead_code)] #[derive(Debug, Clone, Serialize)] pub enum DataValue { /// Paired with `IndexKey::IngestedLog`. LogLiteral { text: String }, /// Paired with `IndexKey::ParamDeclaration`. ParamDeclaration(ParamDeclaration), /// Paired with `IndexKey::MetricDeclaration`. `label_params`/`value_param` reference standalone /// `ParamDeclaration`s by name (resolved against the engine's param store in /// `crate::engine::apply_metric_to_log`) rather than embedding their own extractor. MetricDeclaration { message_match_regex: String, label_params: Vec, /// At most one value param per metric; its primitive becomes the event's bucket value instead of a label. value_param: Option, /// See `crate::engine::DeclareMetricRequest::matched_labels`. matched_labels: Option>, /// See `crate::engine::DeclareMetricRequest::unmatched_labels`. unmatched_labels: Option>, }, /// Paired with `IndexKey::Bucket`. BucketValue { values: Vec<(u64, Timestamp, MetricPrimitive)>, }, /// Paired with `IndexKey::LastMetricEvent`. LastEventTimestamp { timestamp: Timestamp, }, }