diff --git a/Cargo.lock b/Cargo.lock index d78e9cc28..930265d84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9134,6 +9134,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.17" @@ -9144,12 +9154,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 474919cc6..d8d9875a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,11 @@ tower-http = "0.3" tower-layer = "0.3" tracing = "0.1" tracing-opentelemetry = "0.18" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-subscriber = { version = "0.3", features = [ + "ansi", + "env-filter", + "json", +] } tracing-test = { version = "0.2" } trust-dns-resolver = "0.22.0" unsigned-varint = "0.7" diff --git a/beetle/iroh-metrics/src/config.rs b/beetle/iroh-metrics/src/config.rs index 9d45a1bf3..f4360bafa 100644 --- a/beetle/iroh-metrics/src/config.rs +++ b/beetle/iroh-metrics/src/config.rs @@ -26,6 +26,20 @@ pub struct Config { #[cfg(feature = "tokio-console")] /// Enables tokio console debugging. pub tokio_console: bool, + /// How to print log lines to STDOUT + pub log_format: LogFormat, +} + +/// Format of log events +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum LogFormat { + // Print log lines on multiple lines + #[default] + MultiLine, + // Print log lines on a single line + SingleLine, + // Print logs a newline delimited JSON + Json, } impl Config { @@ -58,6 +72,7 @@ impl Default for Config { prom_gateway_endpoint: "http://localhost:9091".to_string(), #[cfg(feature = "tokio-console")] tokio_console: false, + log_format: LogFormat::default(), } } } diff --git a/beetle/iroh-metrics/src/lib.rs b/beetle/iroh-metrics/src/lib.rs index 7aa3078fe..2e06356e4 100644 --- a/beetle/iroh-metrics/src/lib.rs +++ b/beetle/iroh-metrics/src/lib.rs @@ -35,9 +35,19 @@ use prometheus_client::registry::Registry; use std::env::consts::{ARCH, OS}; use std::time::Duration; use tokio::task::JoinHandle; -use tracing::{debug, warn}; +use tracing::{debug, metadata::LevelFilter, warn}; use tracing_opentelemetry::OpenTelemetrySpanExt; -use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; +use tracing_subscriber::{ + fmt::{ + self, + format::{Compact, Json, Pretty}, + time::SystemTime, + FormatEvent, + }, + layer::SubscriberExt, + util::SubscriberInitExt, + EnvFilter, Layer, +}; #[derive(Debug)] pub struct MetricsHandle { @@ -110,14 +120,50 @@ async fn init_metrics(cfg: Config) -> Option> { None } +struct Format { + kind: config::LogFormat, + single: tracing_subscriber::fmt::format::Format, + multi: tracing_subscriber::fmt::format::Format, + json: tracing_subscriber::fmt::format::Format, +} +impl FormatEvent for Format +where + S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, + N: for<'a> fmt::FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &fmt::FmtContext<'_, S, N>, + writer: fmt::format::Writer<'_>, + event: &tracing::Event<'_>, + ) -> std::fmt::Result { + match self.kind { + config::LogFormat::SingleLine => self.single.format_event(ctx, writer, event), + config::LogFormat::MultiLine => self.multi.format_event(ctx, writer, event), + config::LogFormat::Json => self.json.format_event(ctx, writer, event), + } + } +} + /// Initialize the tracing subsystem. fn init_tracer(cfg: Config) -> Result<(), Box> { #[cfg(feature = "tokio-console")] let console_subscriber = cfg.tokio_console.then(console_subscriber::spawn); - let log_subscriber = fmt::layer() - .pretty() - .with_filter(EnvFilter::from_default_env()); + // Default to INFO if no env is specified + let filter_builder = EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()); + + let log_filter = filter_builder.from_env()?; + let otlp_filter = filter_builder.from_env()?; + + let format = Format { + kind: cfg.log_format, + single: fmt::format().with_ansi(true).compact(), + multi: fmt::format().with_ansi(true).pretty(), + json: fmt::format().with_ansi(false).json(), + }; + + let log_subscriber = fmt::layer().event_format(format).with_filter(log_filter); let opentelemetry_subscriber = if cfg.tracing { global::set_text_map_propagator(TraceContextPropagator::new()); @@ -143,7 +189,7 @@ fn init_tracer(cfg: Config) -> Result<(), Box> { Some( tracing_opentelemetry::layer() .with_tracer(tracer) - .with_filter(EnvFilter::from_default_env()), + .with_filter(otlp_filter), ) } else { None diff --git a/one/src/main.rs b/one/src/main.rs index a81887523..ed1cf1995 100644 --- a/one/src/main.rs +++ b/one/src/main.rs @@ -108,6 +108,22 @@ struct DaemonOpts { /// When true Recon will be used to synchronized events with peers. #[arg(long, default_value_t = false, env = "CERAMIC_ONE_RECON")] recon: bool, + + /// Specify the format of log events + #[arg(long, default_value = "multi-line", env = "CERAMIC_ONE_LOG_FORMAT")] + log_format: LogFormat, +} + +#[derive(ValueEnum, Debug, Clone, Default)] +enum LogFormat { + /// Format log events on multiple lines using ANSI colors. + #[default] + MultiLine, + /// Format log events on a single line using ANSI colors. + SingleLine, + /// Format log events newline delimited JSON objects. + /// No ANSI colors are used. + Json, } #[derive(ValueEnum, Debug, Clone)] @@ -146,14 +162,6 @@ struct EyeOpts { #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { - let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stdout()); - - let subscriber = tracing_subscriber::fmt().with_writer(non_blocking); - - tracing::subscriber::with_default(subscriber.finish(), || { - tracing::event!(tracing::Level::INFO, "Ceramic One Server Running"); - }); - let args = Cli::parse(); match args.command { Command::Daemon(opts) => { @@ -194,6 +202,11 @@ impl Daemon { // Do not push metrics to any endpoint. metrics_config.export = false; metrics_config.tracing = opts.tracing; + metrics_config.log_format = match opts.log_format { + LogFormat::SingleLine => iroh_metrics::config::LogFormat::SingleLine, + LogFormat::MultiLine => iroh_metrics::config::LogFormat::MultiLine, + LogFormat::Json => iroh_metrics::config::LogFormat::Json, + }; let service_name = metrics_config.service_name.clone(); let instance_id = metrics_config.instance_id.clone();