use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Level {
Trace,
Debug,
Info,
Warn,
Error,
}
impl Level {
pub fn to_tracing_level(&self) -> tracing::Level {
match self {
Level::Trace => tracing::Level::TRACE,
Level::Debug => tracing::Level::DEBUG,
Level::Info => tracing::Level::INFO,
Level::Warn => tracing::Level::WARN,
Level::Error => tracing::Level::ERROR,
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Level::Trace => write!(f, "trace"),
Level::Debug => write!(f, "debug"),
Level::Info => write!(f, "info"),
Level::Warn => write!(f, "warn"),
Level::Error => write!(f, "error"),
}
}
}
pub struct StandardFields;
impl StandardFields {
pub const SERVICE: &'static str = "service";
pub const MODULE: &'static str = "module";
pub const LEVEL: &'static str = "level";
pub const MESSAGE: &'static str = "message";
pub const TIMESTAMP: &'static str = "timestamp";
pub const ERROR: &'static str = "error";
pub const ERROR_TYPE: &'static str = "error_type";
pub const ERROR_MESSAGE: &'static str = "error_message";
pub const ERROR_STACK: &'static str = "error_stack";
pub const TRACE_ID: &'static str = "trace_id";
pub const SPAN_ID: &'static str = "span_id";
pub const REQUEST_ID: &'static str = "request_id";
pub const USER_ID: &'static str = "user_id";
pub const TENANT_ID: &'static str = "tenant_id";
pub const HTTP_METHOD: &'static str = "http.method";
pub const HTTP_PATH: &'static str = "http.path";
pub const HTTP_STATUS: &'static str = "http.status";
pub const HTTP_DURATION_MS: &'static str = "http.duration_ms";
pub const CLIENT_IP: &'static str = "client.ip";
pub const USER_AGENT: &'static str = "user_agent";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
#[serde(rename = "service")]
pub service: String,
#[serde(rename = "module")]
pub module: Option<String>,
#[serde(rename = "level")]
pub level: Level,
#[serde(rename = "message")]
pub message: String,
#[serde(rename = "timestamp")]
pub timestamp: String,
#[serde(flatten)]
pub fields: serde_json::Value,
}
impl Event {
pub fn new(
service: impl Into<String>,
module: Option<String>,
level: Level,
message: impl Into<String>,
) -> Self {
Self {
service: service.into(),
module,
level,
message: message.into(),
timestamp: chrono::Utc::now().to_rfc3339(),
fields: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn format_error(error: &dyn std::error::Error) -> serde_json::Value {
let mut error_obj = serde_json::Map::new();
error_obj.insert(
StandardFields::ERROR_TYPE.to_string(),
serde_json::Value::String(std::any::type_name_of_val(error).to_string()),
);
error_obj.insert(
StandardFields::ERROR_MESSAGE.to_string(),
serde_json::Value::String(error.to_string()),
);
let mut source_chain = Vec::new();
let mut current: Option<&dyn std::error::Error> = Some(error);
while let Some(err) = current {
source_chain.push(err.to_string());
current = err.source();
}
if source_chain.len() > 1 {
error_obj.insert(
StandardFields::ERROR_STACK.to_string(),
serde_json::Value::Array(
source_chain
.into_iter()
.map(serde_json::Value::String)
.collect(),
),
);
}
serde_json::Value::Object(error_obj)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_display() {
assert_eq!(Level::Info.to_string(), "info");
assert_eq!(Level::Error.to_string(), "error");
}
#[test]
fn test_event_creation() {
let event = Event::new("my-service", Some("my-module".to_string()), Level::Info, "test");
assert_eq!(event.service, "my-service");
assert_eq!(event.module, Some("my-module".to_string()));
assert_eq!(event.level, Level::Info);
assert_eq!(event.message, "test");
}
}