diff --git a/Cargo.lock b/Cargo.lock index 048b10b3..31fe7d7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4630,6 +4630,16 @@ dependencies = [ "zbus_systemd", ] +[[package]] +name = "orb-telemetry" +version = "0.0.0" +dependencies = [ + "console-subscriber", + "tracing", + "tracing-journald", + "tracing-subscriber", +] + [[package]] name = "orb-thermal-cam-ctrl" version = "0.0.5" @@ -4660,7 +4670,7 @@ dependencies = [ "async-trait", "chrono", "clap", - "console-subscriber", + "color-eyre", "dashmap", "derive_more", "eyre", @@ -4669,6 +4679,7 @@ dependencies = [ "orb-messages 0.0.0 (git+https://github.com/worldcoin/orb-messages?rev=787ab78581b705af0946bcfe3a0453b64af2193f)", "orb-rgb", "orb-sound", + "orb-telemetry", "orb-uart", "pid", "prost 0.12.6", @@ -4678,7 +4689,6 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-subscriber", "zbus", ] diff --git a/Cargo.toml b/Cargo.toml index 8a88e3f4..8d37b1ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,18 +15,19 @@ members = [ "hil", "mcu-interface", "mcu-util", - "ui", - "ui/cone", - "ui/pid", - "ui/sound", - "ui/uart", "qr-link", "security-utils", "seek-camera/sys", "seek-camera/wrapper", "slot-ctrl", "supervisor", + "telemetry", "thermal-cam-ctrl", + "ui", + "ui/cone", + "ui/pid", + "ui/sound", + "ui/uart", "update-agent", "update-agent/core", "update-verifier", @@ -81,6 +82,7 @@ orb-build-info.path = "build-info" orb-const-concat.path = "const-concat" orb-security-utils.path = "security-utils" orb-slot-ctrl.path = "slot-ctrl" +orb-telemetry.path = "telemetry" orb-update-agent-core.path = "update-agent/core" orb-zbus-proxies.path = "zbus-proxies" diff --git a/telemetry/Cargo.toml b/telemetry/Cargo.toml new file mode 100644 index 00000000..08104544 --- /dev/null +++ b/telemetry/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "orb-telemetry" +version = "0.0.0" +description = "Standardized telemetry setup for the orb" +authors = ["Ryan Butler "] +publish = false + +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +tracing-journald.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true + +[target.'cfg(tokio_unstable)'.dependencies] +console-subscriber.workspace = true + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(tokio_unstable)'] diff --git a/telemetry/src/lib.rs b/telemetry/src/lib.rs new file mode 100644 index 00000000..b5cd6b10 --- /dev/null +++ b/telemetry/src/lib.rs @@ -0,0 +1,84 @@ +use std::io::IsTerminal as _; + +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{ + layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, +}; + +#[derive(Debug)] +pub struct TelemetryConfig { + syslog_identifier: Option, + global_filter: EnvFilter, +} + +impl TelemetryConfig { + /// Provides all required arguments for telemetry configuration. + /// - `log_identifier` will be used for journald, if appropriate. + #[expect(clippy::new_without_default, reason = "may add required args later")] + pub fn new() -> Self { + Self { + syslog_identifier: None, + global_filter: EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + } + } + + /// Enables journald, and uses the provided syslog identifier. + /// + /// If you run the application in a tty, stderr will be used instead. + pub fn with_journald(self, syslog_identifier: &str) -> Self { + Self { + syslog_identifier: Some(syslog_identifier.to_owned()), + ..self + } + } + + /// Override the global filter to a custom filter. + /// Only do this if actually necessary to deviate from the orb's defaults. + pub fn with_global_filter(self, filter: EnvFilter) -> Self { + Self { + global_filter: filter, + ..self + } + } + + /// Initializes the telemetry config. Call this only once, at the beginning of the + /// program. + /// + /// Calling this more than once or when another tracing subscriber is registered + /// will cause a panic. + pub fn init(self) { + let registry = tracing_subscriber::registry(); + // The type is only there to get it to compile. + let tokio_console_layer: Option = None; + #[cfg(tokio_unstable)] + let tokio_console_layer = console_subscriber::spawn(); + // Checking for a terminal helps detect if we are running under systemd. + let journald_layer = if !std::io::stderr().is_terminal() { + self.syslog_identifier.and_then(|syslog_identifier| { + tracing_journald::layer() + .inspect_err(|err| { + eprintln!( + "failed connecting to journald socket. \ + will write to stderr: {err}" + ); + }) + .map(|layer| layer.with_syslog_identifier(syslog_identifier)) + .ok() + }) + } else { + None + }; + let stderr_layer = journald_layer + .is_none() + .then(|| tracing_subscriber::fmt::layer().with_writer(std::io::stderr)); + assert!(stderr_layer.is_some() || journald_layer.is_some()); + registry + .with(tokio_console_layer) + .with(stderr_layer) + .with(journald_layer) + .with(self.global_filter) + .init(); + } +} diff --git a/ui/Cargo.toml b/ui/Cargo.toml index f3f1954c..6c25b5c8 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -13,6 +13,7 @@ rust-version.workspace = true [dependencies] async-trait = "0.1.74" clap.workspace = true +color-eyre.workspace = true dashmap = "5.5.3" derive_more.workspace = true eyre.workspace = true @@ -21,6 +22,7 @@ orb-build-info.path = "../build-info" orb-messages.workspace = true orb-rgb.path = "rgb" orb-sound.path = "sound" +orb-telemetry.workspace = true orb-uart.path = "uart" pid.path = "pid" prost = "0.12.3" @@ -29,13 +31,9 @@ serde.workspace = true serde_json = "1.0.108" tokio-stream = "0.1.14" tokio.workspace = true -tracing-subscriber.workspace = true tracing.workspace = true zbus.workspace = true -[target.'cfg(tokio_unstable)'.dependencies] -console-subscriber.workspace = true - [build-dependencies] orb-build-info = { path = "../build-info", features = ["build-script"] } diff --git a/ui/examples/ui-replay.rs b/ui/examples/ui-replay.rs index c0107af5..455fb86f 100644 --- a/ui/examples/ui-replay.rs +++ b/ui/examples/ui-replay.rs @@ -1,16 +1,13 @@ use chrono::{DateTime, Utc}; use clap::Parser; -use eyre::{Context, ContextCompat, Result}; +use color_eyre::{eyre::WrapErr, Result}; +use eyre::OptionExt; use std::fs::File; use std::io; use std::io::BufRead; use std::str::FromStr; use tokio::time::sleep; -use tracing::level_filters::LevelFilter; use tracing::{debug, info, warn}; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{fmt, EnvFilter}; use zbus::Connection; const RECORDS_FILE: &str = "worldcoin-ui-logs.txt"; @@ -56,7 +53,7 @@ impl FromStr for EventRecord { // split line to take everything after "UI event:" let (_, event) = line .split_once("UI event: ") - .wrap_err(format!("Unable to split line: {}", line))?; + .ok_or_eyre(format!("Unable to split line: {}", line))?; let event = event.to_string(); match timestamp_str.parse::>() { Ok(timestamp) => { @@ -70,14 +67,8 @@ impl FromStr for EventRecord { #[tokio::main] async fn main() -> Result<()> { - tracing_subscriber::registry() - .with(fmt::layer()) - .with( - EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy(), - ) - .init(); + color_eyre::install()?; + orb_telemetry::TelemetryConfig::new().init(); let args = Args::parse(); let connection = Connection::session().await?; diff --git a/ui/src/main.rs b/ui/src/main.rs index 0f03d281..ebe176f7 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -11,9 +11,6 @@ use futures::channel::mpsc; use orb_build_info::{make_build_info, BuildInfo}; use tokio::time; use tracing::debug; -use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::util::SubscriberInitExt; -use tracing_subscriber::{filter::LevelFilter, fmt, EnvFilter}; use crate::engine::{Engine, EventChannel}; use crate::observer::listen; @@ -29,6 +26,7 @@ pub mod sound; const INPUT_CAPACITY: usize = 100; const BUILD_INFO: BuildInfo = make_build_info!(); +const SYSLOG_IDENTIFIER: &str = "worldcoin-ui"; /// Utility args #[derive(Parser, Debug)] @@ -112,16 +110,9 @@ async fn get_hw_version() -> Result { #[tokio::main] async fn main() -> Result<()> { - let registry = tracing_subscriber::registry(); - #[cfg(tokio_unstable)] - let registry = registry.with(console_subscriber::spawn()); - registry - .with(fmt::layer()) - .with( - EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy(), - ) + color_eyre::install()?; + orb_telemetry::TelemetryConfig::new() + .with_journald(SYSLOG_IDENTIFIER) .init(); let args = Args::parse();