mirror of
https://github.com/Xevion/banner.git
synced 2025-12-06 01:14:22 -06:00
feat: setup span recording for CustomJsonFormatter, use 'yansi' for better ANSI terminal colors in CustomPrettyFormatter
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -258,6 +258,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ rust-embed = { version = "8.0", features = ["debug-embed", "include-exclude"] }
|
|||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
rapidhash = "4.1.0"
|
rapidhash = "4.1.0"
|
||||||
|
yansi = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use tracing::{Event, Level, Subscriber};
|
|||||||
use tracing_subscriber::fmt::format::Writer;
|
use tracing_subscriber::fmt::format::Writer;
|
||||||
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields};
|
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields};
|
||||||
use tracing_subscriber::registry::LookupSpan;
|
use tracing_subscriber::registry::LookupSpan;
|
||||||
|
use yansi::Paint;
|
||||||
|
|
||||||
/// Cached format description for timestamps
|
/// Cached format description for timestamps
|
||||||
/// Uses 3 subsecond digits on Emscripten, 5 otherwise for better performance
|
/// Uses 3 subsecond digits on Emscripten, 5 otherwise for better performance
|
||||||
@@ -26,11 +27,6 @@ const TIMESTAMP_FORMAT: &[FormatItem<'static>] =
|
|||||||
/// Re-implementation of the Full formatter with improved timestamp display.
|
/// Re-implementation of the Full formatter with improved timestamp display.
|
||||||
pub struct CustomPrettyFormatter;
|
pub struct CustomPrettyFormatter;
|
||||||
|
|
||||||
/// A custom JSON formatter that flattens fields to root level
|
|
||||||
///
|
|
||||||
/// Outputs logs in the format: { "message": "...", "level": "...", "customAttribute": "..." }
|
|
||||||
pub struct CustomJsonFormatter;
|
|
||||||
|
|
||||||
impl<S, N> FormatEvent<S, N> for CustomPrettyFormatter
|
impl<S, N> FormatEvent<S, N> for CustomPrettyFormatter
|
||||||
where
|
where
|
||||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||||
@@ -63,20 +59,20 @@ where
|
|||||||
for span in scope.from_root() {
|
for span in scope.from_root() {
|
||||||
write_bold(&mut writer, span.metadata().name())?;
|
write_bold(&mut writer, span.metadata().name())?;
|
||||||
saw_any = true;
|
saw_any = true;
|
||||||
|
|
||||||
|
write_dimmed(&mut writer, ":")?;
|
||||||
|
|
||||||
let ext = span.extensions();
|
let ext = span.extensions();
|
||||||
if let Some(fields) = &ext.get::<FormattedFields<N>>() {
|
if let Some(fields) = &ext.get::<FormattedFields<N>>()
|
||||||
if !fields.is_empty() {
|
&& !fields.fields.is_empty()
|
||||||
write_bold(&mut writer, "{")?;
|
{
|
||||||
write!(writer, "{}", fields)?;
|
write_bold(&mut writer, "{")?;
|
||||||
write_bold(&mut writer, "}")?;
|
writer.write_str(fields.fields.as_str())?;
|
||||||
}
|
write_bold(&mut writer, "}")?;
|
||||||
}
|
|
||||||
if writer.has_ansi_escapes() {
|
|
||||||
write!(writer, "\x1b[2m:\x1b[0m")?;
|
|
||||||
} else {
|
|
||||||
writer.write_char(':')?;
|
|
||||||
}
|
}
|
||||||
|
write_dimmed(&mut writer, ":")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if saw_any {
|
if saw_any {
|
||||||
writer.write_char(' ')?;
|
writer.write_char(' ')?;
|
||||||
}
|
}
|
||||||
@@ -84,7 +80,7 @@ where
|
|||||||
|
|
||||||
// 4) Target (dimmed), then a space
|
// 4) Target (dimmed), then a space
|
||||||
if writer.has_ansi_escapes() {
|
if writer.has_ansi_escapes() {
|
||||||
write!(writer, "\x1b[2m{}\x1b[0m\x1b[2m:\x1b[0m ", meta.target())?;
|
write!(writer, "{}: ", Paint::new(meta.target()).dim())?;
|
||||||
} else {
|
} else {
|
||||||
write!(writer, "{}: ", meta.target())?;
|
write!(writer, "{}: ", meta.target())?;
|
||||||
}
|
}
|
||||||
@@ -97,6 +93,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A custom JSON formatter that flattens fields to root level
|
||||||
|
///
|
||||||
|
/// Outputs logs in the format: { "message": "...", "level": "...", "customAttribute": "..." }
|
||||||
|
pub struct CustomJsonFormatter;
|
||||||
|
|
||||||
impl<S, N> FormatEvent<S, N> for CustomJsonFormatter
|
impl<S, N> FormatEvent<S, N> for CustomJsonFormatter
|
||||||
where
|
where
|
||||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||||
@@ -104,7 +105,7 @@ where
|
|||||||
{
|
{
|
||||||
fn format_event(
|
fn format_event(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &FmtContext<'_, S, N>,
|
ctx: &FmtContext<'_, S, N>,
|
||||||
mut writer: Writer<'_>,
|
mut writer: Writer<'_>,
|
||||||
event: &Event<'_>,
|
event: &Event<'_>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
@@ -116,12 +117,15 @@ where
|
|||||||
level: String,
|
level: String,
|
||||||
target: String,
|
target: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
spans: Map<String, Value>,
|
||||||
|
#[serde(flatten)]
|
||||||
fields: Map<String, Value>,
|
fields: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let (message, fields) = {
|
let (message, fields, spans) = {
|
||||||
let mut message: Option<String> = None;
|
let mut message: Option<String> = None;
|
||||||
let mut fields: Map<String, Value> = Map::new();
|
let mut fields: Map<String, Value> = Map::new();
|
||||||
|
let mut spans: Map<String, Value> = Map::new();
|
||||||
|
|
||||||
struct FieldVisitor<'a> {
|
struct FieldVisitor<'a> {
|
||||||
message: &'a mut Option<String>,
|
message: &'a mut Option<String>,
|
||||||
@@ -184,13 +188,42 @@ where
|
|||||||
};
|
};
|
||||||
event.record(&mut visitor);
|
event.record(&mut visitor);
|
||||||
|
|
||||||
(message, fields)
|
// Collect span information from the span hierarchy
|
||||||
|
if let Some(scope) = ctx.event_scope() {
|
||||||
|
for span in scope.from_root() {
|
||||||
|
let span_name = span.metadata().name().to_string();
|
||||||
|
let mut span_fields: Map<String, Value> = Map::new();
|
||||||
|
|
||||||
|
// Try to extract fields from FormattedFields
|
||||||
|
let ext = span.extensions();
|
||||||
|
if let Some(formatted_fields) = ext.get::<FormattedFields<N>>() {
|
||||||
|
// Try to parse as JSON first
|
||||||
|
if let Ok(json_fields) = serde_json::from_str::<Map<String, Value>>(
|
||||||
|
formatted_fields.fields.as_str(),
|
||||||
|
) {
|
||||||
|
span_fields.extend(json_fields);
|
||||||
|
} else {
|
||||||
|
// If not valid JSON, treat the entire field string as a single field
|
||||||
|
span_fields.insert(
|
||||||
|
"raw".to_string(),
|
||||||
|
Value::String(formatted_fields.fields.as_str().to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert span as a nested object directly into the spans map
|
||||||
|
spans.insert(span_name, Value::Object(span_fields));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(message, fields, spans)
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = EventFields {
|
let json = EventFields {
|
||||||
message: message.unwrap_or_default(),
|
message: message.unwrap_or_default(),
|
||||||
level: meta.level().to_string(),
|
level: meta.level().to_string(),
|
||||||
target: meta.target().to_string(),
|
target: meta.target().to_string(),
|
||||||
|
spans,
|
||||||
fields,
|
fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -205,15 +238,14 @@ where
|
|||||||
/// Write the verbosity level with the same coloring/alignment as the Full formatter.
|
/// Write the verbosity level with the same coloring/alignment as the Full formatter.
|
||||||
fn write_colored_level(writer: &mut Writer<'_>, level: &Level) -> fmt::Result {
|
fn write_colored_level(writer: &mut Writer<'_>, level: &Level) -> fmt::Result {
|
||||||
if writer.has_ansi_escapes() {
|
if writer.has_ansi_escapes() {
|
||||||
// Basic ANSI color sequences; reset with \x1b[0m
|
let paint = match *level {
|
||||||
let (color, text) = match *level {
|
Level::TRACE => Paint::new("TRACE").magenta(),
|
||||||
Level::TRACE => ("\x1b[35m", "TRACE"), // purple
|
Level::DEBUG => Paint::new("DEBUG").blue(),
|
||||||
Level::DEBUG => ("\x1b[34m", "DEBUG"), // blue
|
Level::INFO => Paint::new(" INFO").green(),
|
||||||
Level::INFO => ("\x1b[32m", " INFO"), // green, note leading space
|
Level::WARN => Paint::new(" WARN").yellow(),
|
||||||
Level::WARN => ("\x1b[33m", " WARN"), // yellow, note leading space
|
Level::ERROR => Paint::new("ERROR").red(),
|
||||||
Level::ERROR => ("\x1b[31m", "ERROR"), // red
|
|
||||||
};
|
};
|
||||||
write!(writer, "{}{}\x1b[0m", color, text)
|
write!(writer, "{}", paint)
|
||||||
} else {
|
} else {
|
||||||
// Right-pad to width 5 like Full's non-ANSI mode
|
// Right-pad to width 5 like Full's non-ANSI mode
|
||||||
match *level {
|
match *level {
|
||||||
@@ -228,7 +260,7 @@ fn write_colored_level(writer: &mut Writer<'_>, level: &Level) -> fmt::Result {
|
|||||||
|
|
||||||
fn write_dimmed(writer: &mut Writer<'_>, s: impl fmt::Display) -> fmt::Result {
|
fn write_dimmed(writer: &mut Writer<'_>, s: impl fmt::Display) -> fmt::Result {
|
||||||
if writer.has_ansi_escapes() {
|
if writer.has_ansi_escapes() {
|
||||||
write!(writer, "\x1b[2m{}\x1b[0m", s)
|
write!(writer, "{}", Paint::new(s).dim())
|
||||||
} else {
|
} else {
|
||||||
write!(writer, "{}", s)
|
write!(writer, "{}", s)
|
||||||
}
|
}
|
||||||
@@ -236,7 +268,7 @@ fn write_dimmed(writer: &mut Writer<'_>, s: impl fmt::Display) -> fmt::Result {
|
|||||||
|
|
||||||
fn write_bold(writer: &mut Writer<'_>, s: impl fmt::Display) -> fmt::Result {
|
fn write_bold(writer: &mut Writer<'_>, s: impl fmt::Display) -> fmt::Result {
|
||||||
if writer.has_ansi_escapes() {
|
if writer.has_ansi_escapes() {
|
||||||
write!(writer, "\x1b[1m{}\x1b[0m", s)
|
write!(writer, "{}", Paint::new(s).bold())
|
||||||
} else {
|
} else {
|
||||||
write!(writer, "{}", s)
|
write!(writer, "{}", s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use num_format::{Locale, ToFormattedString};
|
|||||||
use serenity::all::{ActivityData, ClientBuilder, GatewayIntents};
|
use serenity::all::{ActivityData, ClientBuilder, GatewayIntents};
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
use tracing_subscriber::fmt::format::JsonFields;
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
use crate::banner::BannerApi;
|
use crate::banner::BannerApi;
|
||||||
@@ -185,6 +186,7 @@ async fn main() {
|
|||||||
FmtSubscriber::builder()
|
FmtSubscriber::builder()
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.event_format(formatter::CustomJsonFormatter)
|
.event_format(formatter::CustomJsonFormatter)
|
||||||
|
.fmt_fields(JsonFields::new())
|
||||||
.with_env_filter(filter)
|
.with_env_filter(filter)
|
||||||
.finish(),
|
.finish(),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user