use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};
use uuid::Uuid;
use crate::error::{Result, Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
Critical,
Fatal,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LogLevel::Trace => write!(f, "trace"),
LogLevel::Debug => write!(f, "debug"),
LogLevel::Info => write!(f, "info"),
LogLevel::Warn => write!(f, "warn"),
LogLevel::Error => write!(f, "error"),
LogLevel::Critical => write!(f, "critical"),
LogLevel::Fatal => write!(f, "fatal"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaData {
pub id: Uuid,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread: Option<String>,
#[serde(flatten)]
pub custom: HashMap<String, serde_json::Value>,
}
impl Default for MetaData {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
timestamp: Utc::now(),
source: None,
line: None,
thread: None,
custom: HashMap::new(),
}
}
}
impl MetaData {
pub fn new() -> Self {
Default::default()
}
pub fn add_field<T>(&mut self, key: &str, value: T) -> Result<()>
where
T: Serialize,
{
let value = serde_json::to_value(value)
.map_err(Error::SerializationError)?;
self.custom.insert(key.to_string(), value);
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub message: String,
pub level: LogLevel,
pub metadata: MetaData,
#[serde(default)]
pub context: HashMap<String, serde_json::Value>,
}
impl LogEntry {
pub fn new(message: impl Into<String>, level: LogLevel) -> Self {
Self {
message: message.into(),
level,
metadata: MetaData::default(),
context: HashMap::new(),
}
}
pub fn add_context<T>(&mut self, key: impl Into<String>, value: T) -> Result<&mut Self>
where
T: Serialize,
{
let value = serde_json::to_value(value)
.map_err(Error::SerializationError)?;
self.context.insert(key.into(), value);
Ok(self)
}
pub fn with_source(mut self, file: &str, line: u32) -> Self {
self.metadata.source = Some(file.to_string());
self.metadata.line = Some(line);
self
}
pub fn with_thread(mut self, thread_id: impl Into<String>) -> Self {
self.metadata.thread = Some(thread_id.into());
self
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(Error::SerializationError)
}
pub fn to_pretty_json(&self) -> Result<String> {
serde_json::to_string_pretty(self).map_err(Error::SerializationError)
}
}
pub trait Serializable {
fn to_json(&self) -> Result<String>;
fn to_pretty_json(&self) -> Result<String>;
fn to_value(&self) -> Result<serde_json::Value>;
}
impl<T> Serializable for T
where
T: Serialize,
{
fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(Error::SerializationError)
}
fn to_pretty_json(&self) -> Result<String> {
serde_json::to_string_pretty(self).map_err(Error::SerializationError)
}
fn to_value(&self) -> Result<serde_json::Value> {
serde_json::to_value(self).map_err(Error::SerializationError)
}
}