use chrono::Utc;
use serde_json::Value;
use crate::LogRecord;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct InklogOpsEvent {
pub kind: String,
pub timestamp: chrono::DateTime<Utc>,
pub sink: Option<String>,
pub detail: Value,
}
impl InklogOpsEvent {
pub fn now(kind: impl Into<String>, sink: Option<&str>, detail: Value) -> Self {
Self {
kind: kind.into(),
timestamp: Utc::now(),
sink: sink.map(str::to_string),
detail,
}
}
pub fn to_log_record(&self) -> LogRecord {
let warn = matches!(
self.kind.as_str(),
"sink_degraded" | "records_dropped" | "sink_unavailable"
);
let mut record = LogRecord::new(
if warn {
tracing::Level::WARN
} else {
tracing::Level::INFO
},
"inklog::ops".to_string(),
serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string()),
);
record
.fields
.insert("ops_kind".to_string(), Value::String(self.kind.clone()));
if let Some(sink) = &self.sink {
record
.fields
.insert("ops_sink".to_string(), Value::String(sink.clone()));
}
record
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ops_event_to_log_record_mapping() {
let event = InklogOpsEvent::now(
"sink_recovered",
Some("database"),
serde_json::json!({ "attempts": 2 }),
);
let record = event.to_log_record();
assert_eq!(record.target, "inklog::ops");
assert_eq!(record.level, "INFO");
assert_eq!(record.fields.get("ops_kind").unwrap(), "sink_recovered");
assert_eq!(record.fields.get("ops_sink").unwrap(), "database");
let json: Value = serde_json::from_str(&record.message).unwrap();
assert_eq!(json["detail"]["attempts"], 2);
}
#[test]
fn test_ops_event_warn_level_for_degraded_kinds() {
let event = InklogOpsEvent::now("sink_degraded", Some("file"), Value::Null);
assert_eq!(event.to_log_record().level, "WARN");
let event = InklogOpsEvent::now("config_loaded", None, Value::Null);
assert_eq!(event.to_log_record().level, "INFO");
}
}