diff --git a/Cargo.lock b/Cargo.lock index f283504ec0f..09964ad7edd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4766,6 +4766,7 @@ dependencies = [ "mockall", "multimap", "nix 0.29.0", + "nu-ansi-term 0.50.1", "ockam", "ockam_abac", "ockam_core", diff --git a/implementations/rust/ockam/ockam_api/Cargo.toml b/implementations/rust/ockam/ockam_api/Cargo.toml index 4c6fbf4116b..c50a7d7f814 100644 --- a/implementations/rust/ockam/ockam_api/Cargo.toml +++ b/implementations/rust/ockam/ockam_api/Cargo.toml @@ -78,6 +78,7 @@ log = "0.4" miette = { version = "7.2.0", features = ["fancy-no-backtrace"] } minicbor = { version = "0.25.1", default-features = false, features = ["alloc", "derive"] } nix = { version = "0.29", features = ["signal"] } +nu-ansi-term = "0.50" once_cell = { version = "1", default-features = false } open = "5.3.0" opentelemetry = { version = "0.26.0", features = ["logs", "metrics", "trace"] } diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs b/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs index 04f187dd93a..36936a77364 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs @@ -108,7 +108,6 @@ impl CliState { } fn notify(&self, notification: Notification) { - debug!("{:?}", notification.contents()); let _ = self.notifications.send(notification); } } diff --git a/implementations/rust/ockam/ockam_api/src/logs/logging_options.rs b/implementations/rust/ockam/ockam_api/src/logs/logging_options.rs index 0af21ff0143..c4c3da65976 100644 --- a/implementations/rust/ockam/ockam_api/src/logs/logging_options.rs +++ b/implementations/rust/ockam/ockam_api/src/logs/logging_options.rs @@ -1,6 +1,11 @@ +use nu_ansi_term::{Color, Style}; use ockam_core::env::FromString; use ockam_core::errcode::{Kind, Origin}; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; +use tracing_core::{Event, Level, Subscriber}; +use tracing_subscriber::fmt::format::Writer; +use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields, FormattedFields}; +use tracing_subscriber::registry::LookupSpan; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum LoggingEnabled { @@ -73,6 +78,7 @@ pub enum LogFormat { Default, Pretty, Json, + Compact, } impl FromString for LogFormat { @@ -80,17 +86,190 @@ impl FromString for LogFormat { match s { "pretty" => Ok(LogFormat::Pretty), "json" => Ok(LogFormat::Json), + "compact" => Ok(LogFormat::Compact), _ => Ok(LogFormat::Default), } } } -impl std::fmt::Display for LogFormat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl Display for LogFormat { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { match self { LogFormat::Default => write!(f, "default"), LogFormat::Pretty => write!(f, "pretty"), LogFormat::Json => write!(f, "json"), + LogFormat::Compact => write!(f, "compact"), + } + } +} + +pub struct OckamLogFormat { + display_level: bool, + display_target: bool, + display_line_number: bool, + display_filename: bool, +} + +impl OckamLogFormat { + pub fn new( + display_level: bool, + display_target: bool, + display_line_number: bool, + display_filename: bool, + ) -> Self { + Self { + display_level, + display_target, + display_line_number, + display_filename, + } + } + + fn format_timestamp(&self, writer: &mut Writer<'_>) -> std::fmt::Result { + let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"); + if writer.has_ansi_escapes() { + let style = Style::new().dimmed(); + write!(writer, "{}", style.prefix())?; + write!(writer, "{}", now)?; + write!(writer, "{} ", style.suffix())?; + } else { + write!(writer, "{}", now)?; + writer.write_char(' ')?; + } + Ok(()) + } +} + +impl Default for OckamLogFormat { + fn default() -> Self { + Self { + display_level: true, + display_target: true, + display_line_number: true, + display_filename: true, + } + } +} + +impl FormatEvent for OckamLogFormat +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> std::fmt::Result { + let meta = event.metadata(); + + self.format_timestamp(&mut writer)?; + + if self.display_level { + let fmt_level = FmtLevel::new(meta.level(), writer.has_ansi_escapes()); + write!(writer, "{} ", fmt_level)?; + } + + ctx.format_fields(writer.by_ref(), event)?; + writer.write_char(' ')?; + + let dimmed = Style::new().dimmed(); + + if let Some(scope) = ctx.event_scope() { + let bold = Style::new().bold(); + + let mut seen = false; + + for span in scope.from_root() { + write!(writer, "{}", bold.paint(span.metadata().name()))?; + seen = true; + + let ext = span.extensions(); + if let Some(fields) = &ext.get::>() { + if !fields.is_empty() { + write!(writer, "{}{}{}", bold.paint("{"), fields, bold.paint("}"))?; + } + } + write!(writer, "{}", dimmed.paint(":"))?; + } + + if seen { + writer.write_char(' ')?; + } + }; + + if self.display_target { + write!(writer, "{} ", dimmed.paint(meta.target()),)?; + } + + let line_number = if self.display_line_number { + meta.line() + } else { + None + }; + + if self.display_filename { + if let Some(filename) = meta.file() { + write!( + writer, + "{}{}{}", + dimmed.paint(filename), + dimmed.paint(":"), + if line_number.is_some() { "" } else { " " } + )?; + } + } + + if let Some(line_number) = line_number { + write!( + writer, + "{}{}{}", + dimmed.prefix(), + line_number, + dimmed.suffix() + )?; + } + + writeln!(writer) + } +} + +struct FmtLevel<'a> { + level: &'a Level, + ansi: bool, +} + +impl<'a> FmtLevel<'a> { + pub(crate) fn new(level: &'a Level, ansi: bool) -> Self { + Self { level, ansi } + } +} + +const TRACE_STR: &str = "TRACE"; +const DEBUG_STR: &str = "DEBUG"; +const INFO_STR: &str = " INFO"; +const WARN_STR: &str = " WARN"; +const ERROR_STR: &str = "ERROR"; + +impl Display for FmtLevel<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.ansi { + match *self.level { + Level::TRACE => write!(f, "{}", Color::Purple.paint(TRACE_STR)), + Level::DEBUG => write!(f, "{}", Color::Blue.paint(DEBUG_STR)), + Level::INFO => write!(f, "{}", Color::Green.paint(INFO_STR)), + Level::WARN => write!(f, "{}", Color::Yellow.paint(WARN_STR)), + Level::ERROR => write!(f, "{}", Color::Red.paint(ERROR_STR)), + } + } else { + match *self.level { + Level::TRACE => f.pad(TRACE_STR), + Level::DEBUG => f.pad(DEBUG_STR), + Level::INFO => f.pad(INFO_STR), + Level::WARN => f.pad(WARN_STR), + Level::ERROR => f.pad(ERROR_STR), + } } } } diff --git a/implementations/rust/ockam/ockam_api/src/logs/setup.rs b/implementations/rust/ockam/ockam_api/src/logs/setup.rs index 5023f48ace4..f48b13a85b5 100644 --- a/implementations/rust/ockam/ockam_api/src/logs/setup.rs +++ b/implementations/rust/ockam/ockam_api/src/logs/setup.rs @@ -8,7 +8,6 @@ use opentelemetry_sdk::export::trace::SpanExporter; use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider}; use opentelemetry_sdk::propagation::TraceContextPropagator; use opentelemetry_sdk::trace::{BatchConfig, BatchConfigBuilder, BatchSpanProcessor}; -use opentelemetry_sdk::{self as sdk}; use opentelemetry_sdk::{logs, Resource}; use opentelemetry_semantic_conventions::attribute; use std::io::{empty, stdout}; @@ -30,6 +29,7 @@ use ockam_node::Executor; use crate::logs::tracing_guard::TracingGuard; use crate::logs::{ ExportingConfiguration, GlobalErrorHandler, LoggingConfiguration, OckamLogExporter, + OckamLogFormat, }; use crate::logs::{LogFormat, OckamSpanExporter}; @@ -109,7 +109,10 @@ impl LoggingTracing { let result = match logging_configuration.format() { LogFormat::Pretty => layers.with(appender.pretty()).try_init(), LogFormat::Json => layers.with(appender.json()).try_init(), - LogFormat::Default => layers.with(appender).try_init(), + LogFormat::Compact => layers.with(appender.compact()).try_init(), + LogFormat::Default => layers + .with(appender.event_format(OckamLogFormat::default())) + .try_init(), }; result.expect("Failed to initialize tracing subscriber"); @@ -130,7 +133,10 @@ impl LoggingTracing { let result = match logging_configuration.format() { LogFormat::Pretty => layers.with(appender.pretty()).try_init(), LogFormat::Json => layers.with(appender.json()).try_init(), - LogFormat::Default => layers.with(appender).try_init(), + LogFormat::Compact => layers.with(appender.compact()).try_init(), + LogFormat::Default => layers + .with(appender.event_format(OckamLogFormat::default())) + .try_init(), }; result.expect("Failed to initialize tracing subscriber"); }; @@ -230,7 +236,7 @@ fn create_opentelemetry_tracing_layer< exporting_configuration: &ExportingConfiguration, span_exporter: S, ) -> ( - OpenTelemetryLayer, + OpenTelemetryLayer, opentelemetry_sdk::trace::TracerProvider, ) { let app = app_name.to_string(); @@ -243,7 +249,8 @@ fn create_opentelemetry_tracing_layer< let is_ockam_developer = exporting_configuration.is_ockam_developer(); let span_export_cutoff = exporting_configuration.span_export_cutoff(); Executor::execute_future(async move { - let trace_config = sdk::trace::Config::default().with_resource(make_resource(app)); + let trace_config = + opentelemetry_sdk::trace::Config::default().with_resource(make_resource(app)); let (tracer, tracer_provider) = create_tracer( trace_config, batch_config, @@ -410,11 +417,14 @@ fn set_global_error_handler(logging_configuration: &LoggingConfiguration) { /// Create a Tracer using the provided span exporter fn create_tracer( - trace_config: sdk::trace::Config, + trace_config: opentelemetry_sdk::trace::Config, batch_config: BatchConfig, exporter: S, -) -> (sdk::trace::Tracer, opentelemetry_sdk::trace::TracerProvider) { - let span_processor = BatchSpanProcessor::builder(exporter, sdk::runtime::Tokio) +) -> ( + opentelemetry_sdk::trace::Tracer, + opentelemetry_sdk::trace::TracerProvider, +) { + let span_processor = BatchSpanProcessor::builder(exporter, opentelemetry_sdk::runtime::Tokio) .with_batch_config(batch_config) .build(); let provider = opentelemetry_sdk::trace::TracerProvider::builder() diff --git a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs index 222a89f4681..346077b6978 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs @@ -68,7 +68,17 @@ impl CreateCommand { // Set node_name so that node can isolate its data in the storage from other nodes self.get_or_create_identity(&opts, &self.identity).await?; - let _notification_handler = NotificationHandler::start(&opts.state, opts.terminal.clone()); + let _notification_handler = if self.foreground_args.child_process { + // If enabled, the user's terminal will receive notifications + // from the node after the command exited. + None + } else { + // Enable the notifications only on explicit foreground nodes. + Some(NotificationHandler::start( + &opts.state, + opts.terminal.clone(), + )) + }; let node_info = opts .state .start_node_with_optional_values(