forked from nonsensical-dev/zenyx-engine
feat(zlog)!: JSON logging support
This commit is contained in:
parent
01c3699d86
commit
e347fe6d54
5 changed files with 162 additions and 12 deletions
|
@ -6,6 +6,14 @@ edition = "2024"
|
|||
[dependencies]
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
serde = { version = "1.0.219", optional = true }
|
||||
serde_json = { version = "1.0.140", optional = true }
|
||||
chrono = { version = "0.4.40", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
|
||||
[features]
|
||||
default = ["json"]
|
||||
json = ["dep:serde_json", "dep:chrono", "serde"]
|
||||
serde = ["dep:serde"]
|
|
@ -12,6 +12,11 @@ pub struct LoggerConfig {
|
|||
pub(crate) stdout_include_time: bool,
|
||||
pub(crate) file_include_time: bool,
|
||||
pub(crate) crate_max_level: Option<LogLevel>,
|
||||
pub(crate) log_use_json: bool,
|
||||
pub(crate) log_json_show_timestamp: bool,
|
||||
pub(crate) log_json_show_level: bool,
|
||||
pub(crate) log_json_show_message: bool,
|
||||
pub(crate) log_json_show_additional_fields: bool,
|
||||
}
|
||||
|
||||
impl LoggerConfig {
|
||||
|
@ -49,6 +54,31 @@ impl LoggerConfig {
|
|||
self.file_include_time = i;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_use_json(mut self, i: bool) -> Self {
|
||||
self.log_use_json = i;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_json_show_timestamp(mut self, i: bool) -> Self {
|
||||
self.log_json_show_timestamp = i;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_json_show_level(mut self, i: bool) -> Self {
|
||||
self.log_json_show_level = i;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_json_show_message(mut self, i: bool) -> Self {
|
||||
self.log_json_show_message = i;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn log_json_show_additional_fields(mut self, i: bool) -> Self {
|
||||
self.log_json_show_additional_fields = i;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoggerConfig {
|
||||
|
@ -62,6 +92,11 @@ impl Default for LoggerConfig {
|
|||
stdout_color: true,
|
||||
stdout_include_time: false,
|
||||
file_include_time: false,
|
||||
log_use_json: false,
|
||||
log_json_show_timestamp: false,
|
||||
log_json_show_level: false,
|
||||
log_json_show_message: false,
|
||||
log_json_show_additional_fields: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,15 @@ use tracing_subscriber::{
|
|||
util::SubscriberInitExt,
|
||||
};
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum LogEvent {
|
||||
Log(LogEntry),
|
||||
|
@ -36,6 +45,8 @@ pub struct LogEntry {
|
|||
timestamp: SystemTime,
|
||||
level: Level,
|
||||
message: String,
|
||||
#[cfg(feature = "json")]
|
||||
additional_fields: serde_json::Map<String, Value>,
|
||||
}
|
||||
|
||||
impl PartialOrd for LogEntry {
|
||||
|
@ -91,6 +102,8 @@ where
|
|||
let metadata = event.metadata();
|
||||
let level = *metadata.level();
|
||||
let timestamp = SystemTime::now();
|
||||
#[cfg(feature = "json")]
|
||||
let additional_fields = serde_json::Map::new();
|
||||
let mut message = String::new();
|
||||
let mut visitor = LogVisitor::new(&mut message);
|
||||
event.record(&mut visitor);
|
||||
|
@ -99,6 +112,8 @@ where
|
|||
timestamp,
|
||||
level,
|
||||
message,
|
||||
#[cfg(feature = "json")]
|
||||
additional_fields
|
||||
});
|
||||
|
||||
if let LogEvent::Log(ref entry) = log_entry {
|
||||
|
@ -189,20 +204,19 @@ impl Logger {
|
|||
let mut senders = Vec::new();
|
||||
let mut handles = Vec::new();
|
||||
|
||||
if config.log_to_stdout {
|
||||
if config.log_to_stdout || config.log_use_json {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
senders.push(tx);
|
||||
let config_clone = config.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
for msg in rx {
|
||||
match msg {
|
||||
LogEvent::Log(entry) => {
|
||||
LogEvent::Log(mut entry) => {
|
||||
println!(
|
||||
"{}",
|
||||
format_entry(
|
||||
&entry,
|
||||
config_clone.stdout_color,
|
||||
config_clone.stdout_include_time
|
||||
&mut entry,
|
||||
&config_clone
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -216,8 +230,8 @@ impl Logger {
|
|||
if config.log_to_file {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
senders.push(tx);
|
||||
let config_clone = config.clone();
|
||||
let path = config.log_file_path.clone();
|
||||
let include_time = config.file_include_time;
|
||||
let handle = thread::spawn(move || {
|
||||
let file = OpenOptions::new()
|
||||
.append(true)
|
||||
|
@ -227,8 +241,8 @@ impl Logger {
|
|||
let mut writer = BufWriter::new(file);
|
||||
for msg in rx {
|
||||
match msg {
|
||||
LogEvent::Log(entry) => {
|
||||
let line = format_entry(&entry, false, include_time);
|
||||
LogEvent::Log(mut entry) => {
|
||||
let line = format_entry(&mut entry, &config_clone);
|
||||
writeln!(writer, "{}", line).expect("Failed to write to log file");
|
||||
writer.flush().expect("Failed to flush log file");
|
||||
}
|
||||
|
@ -280,8 +294,20 @@ impl Drop for Logger {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_entry(entry: &LogEntry, use_color: bool, _: bool) -> String {
|
||||
let lvl = if use_color {
|
||||
fn format_entry(entry: &mut LogEntry, log_config: &LoggerConfig) -> String {
|
||||
if log_config.log_use_json {
|
||||
return format_entry_json(entry, log_config);
|
||||
}
|
||||
|
||||
if log_config.log_to_stdout || log_config.log_to_file {
|
||||
return format_entry_string(entry, log_config);
|
||||
} else {
|
||||
return String::new();
|
||||
}
|
||||
}
|
||||
|
||||
fn format_entry_string(entry: &LogEntry, log_config: &LoggerConfig) -> String {
|
||||
let lvl = if log_config.stdout_color {
|
||||
match entry.level {
|
||||
Level::ERROR => "\x1b[31mERROR\x1b[0m",
|
||||
Level::WARN => "\x1b[33mWARN\x1b[0m",
|
||||
|
@ -295,3 +321,26 @@ fn format_entry(entry: &LogEntry, use_color: bool, _: bool) -> String {
|
|||
|
||||
format!("{} {}", lvl, entry.message)
|
||||
}
|
||||
|
||||
/// Formats the log entry as a json object ([`serde_json`]) and returns it as a [`String`]
|
||||
fn format_entry_json(entry: &mut LogEntry, log_config: &LoggerConfig) -> String {
|
||||
let mut json_object = serde_json::Map::new();
|
||||
|
||||
if log_config.log_json_show_timestamp {
|
||||
json_object.insert("timestamp".to_string(), Value::String(DateTime::<Utc>::from(entry.timestamp).to_rfc3339()));
|
||||
}
|
||||
|
||||
if log_config.log_json_show_level {
|
||||
json_object.insert("level".to_string(), Value::String(entry.level.to_string()));
|
||||
}
|
||||
|
||||
if log_config.log_json_show_message {
|
||||
json_object.insert("message".to_string(), Value::String(entry.message.to_string()));
|
||||
}
|
||||
|
||||
if log_config.log_json_show_additional_fields {
|
||||
json_object.append(&mut entry.additional_fields);
|
||||
}
|
||||
|
||||
serde_json::to_string(&json_object).unwrap()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue