feat(zlog)!: JSON logging support
Some checks failed
Build Zenyx ⚡ / 🔧 Setup Environment (push) Failing after 51s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (push) Has been skipped
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (push) Has been skipped
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (push) Has been skipped
Build Zenyx ⚡ / 🔧 Setup Environment (pull_request) Failing after 56s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (pull_request) Has been skipped
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (pull_request) Has been skipped
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (pull_request) Has been skipped

This commit is contained in:
lily 2025-04-19 13:44:43 -04:00
parent eb67f61a0c
commit 9f8633ff62
Signed by: lily
GPG key ID: 601F3263FBCBC4B9
5 changed files with 162 additions and 12 deletions

53
Cargo.lock generated
View file

@ -85,6 +85,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -497,6 +503,20 @@ dependencies = [
"num-traits",
]
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "codespan-reporting"
version = "0.12.0"
@ -990,6 +1010,30 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "image"
version = "0.25.6"
@ -3268,6 +3312,12 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.2.0"
@ -3694,7 +3744,10 @@ dependencies = [
name = "zlog"
version = "0.1.0"
dependencies = [
"chrono",
"pretty_assertions",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]

View file

@ -485,8 +485,13 @@ impl ApplicationHandler for App<'_> {
}
pub fn main() -> Result<(), terminator::Terminator> {
let config = LoggerConfig::default()
.colored_stdout(true)
.log_to_stdout(true)
.colored_stdout(false)
.log_to_stdout(false)
.log_use_json(true)
.log_json_show_timestamp(true)
.log_json_show_level(true)
.log_json_show_message(true)
.log_json_show_additional_fields(true)
.file_include_time(true)
.log_to_file(true)
.level(LogLevel::Info)

View file

@ -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"]

View file

@ -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
}
}
}

View file

@ -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()
}