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
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:
parent
eb67f61a0c
commit
9f8633ff62
5 changed files with 162 additions and 12 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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