rings-node 0.3.0

Rings is a structured peer-to-peer network implementation using WebRTC, Chord algorithm, and full WebAssembly (WASM) support.
Documentation
//! Logging configuration contains both `node` and `browser`.
use std::fmt;
use std::panic::Location;
use std::panic::PanicInfo;

use backtrace::Backtrace;
use clap::ValueEnum;
use tracing::Level;
use tracing_log::LogTracer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;

#[cfg(feature = "browser")]
pub use self::browser::init_logging;
#[cfg(feature = "node")]
pub use self::node::init_logging;

#[derive(ValueEnum, Debug, Clone)]
pub enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
    Trace,
}

impl From<LogLevel> for Level {
    fn from(val: LogLevel) -> Self {
        match val {
            LogLevel::Trace => Level::TRACE,
            LogLevel::Debug => Level::DEBUG,
            LogLevel::Info => Level::INFO,
            LogLevel::Warn => Level::WARN,
            LogLevel::Error => Level::ERROR,
        }
    }
}

/// Panic location
#[derive(Debug, Clone)]
pub struct PanicLocation {
    file: String,
    line: String,
    column: String,
}

impl<'a, T> From<T> for PanicLocation
where T: Into<Location<'a>>
{
    fn from(lo: T) -> Self {
        let lo: Location = lo.into();
        Self {
            file: lo.file().to_string(),
            line: lo.line().to_string(),
            column: lo.file().to_string(),
        }
    }
}

/// Necessary information for recording panic
#[derive(Debug, Clone)]
pub struct PanicData<'a> {
    message: &'a PanicInfo<'a>,
    backtrace: String,
    location: Option<PanicLocation>,
}

impl<'a, T> From<T> for PanicData<'a>
where T: Into<&'a PanicInfo<'a>>
{
    fn from(panic: T) -> PanicData<'a> {
        let panic = panic.into();
        let backtrace = Backtrace::new();
        let backtrace = format!("{:?}", backtrace);
        let location: Option<PanicLocation> = panic.location().map(|l| PanicLocation::from(*l));
        PanicData {
            message: panic,
            backtrace,
            location,
        }
    }
}

impl fmt::Display for PanicLocation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}:{}", self.file, self.line, self.column)
    }
}

impl<'a> fmt::Display for PanicData<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.location {
            Some(l) => write!(f, "{}, {} \n\n {}", self.message, l, self.backtrace),
            None => write!(f, "{} \n\n {}", self.message, self.backtrace),
        }
    }
}

fn log_panic(panic: &PanicInfo) {
    let data: PanicData = panic.into();
    tracing::error!("{}", data)
}

/// Setup hooks for panic, this function works for both wasm and native.
pub fn set_panic_hook() {
    // Set a panic hook that records the panic as a `tracing` event at the
    // `ERROR` verbosity level.
    //
    // If we are currently in a span when the panic occurred, the logged event
    // will include the current span, allowing the context in which the panic
    // occurred to be recorded.
    std::panic::set_hook(Box::new(|panic| {
        log_panic(panic);
    }));
}

#[cfg(feature = "node")]
/// logging configuration about node.
pub mod node {
    use opentelemetry::global;
    use opentelemetry::sdk::propagation::TraceContextPropagator;
    use tracing_subscriber::filter;
    use tracing_subscriber::fmt;
    use tracing_subscriber::Layer;

    use super::*;

    pub fn init_logging(level: Level) {
        set_panic_hook();

        let subscriber = Registry::default();
        let level_filter = filter::LevelFilter::from_level(level);

        // Filter floating log of mdns
        let mdns_log_filter = filter::FilterFn::new(|metadata| {
            !metadata.target().starts_with("webrtc_mdns::conn")
                || [276, 322]
                    .iter()
                    .all(|&line| !metadata.line().unwrap_or_default() == line)
        });

        // Stderr
        let subscriber = subscriber.with(
            fmt::layer()
                .with_writer(std::io::stderr)
                .with_filter(level_filter)
                .with_filter(mdns_log_filter.clone()),
        );

        // Jaeger
        let subscriber = {
            if let Ok(endpoint) = std::env::var("RINGS_JAEGER_AGENT_ENDPOINT") {
                global::set_text_map_propagator(TraceContextPropagator::new());
                let jaeger = opentelemetry_jaeger::new_agent_pipeline()
                    .with_service_name("rings")
                    .with_endpoint(endpoint)
                    .with_auto_split_batch(true)
                    .install_batch(opentelemetry::runtime::Tokio)
                    .expect("opentelemetry_jaeger install");
                subscriber.with(Some(
                    tracing_opentelemetry::layer()
                        .with_tracer(jaeger)
                        .with_filter(level_filter)
                        .with_filter(mdns_log_filter),
                ))
            } else {
                subscriber.with(None)
            }
        };

        // Enable log compatible layer to convert log record to tracing span.
        // We will ignore any errors that returned by this functions.
        let _ = LogTracer::init();

        // Ignore errors returned by set_global_default.
        let _ = tracing::subscriber::set_global_default(subscriber);
    }
}

#[cfg(feature = "browser")]
pub mod browser {
    use tracing_wasm::ConsoleConfig;
    use tracing_wasm::WASMLayer;
    use tracing_wasm::WASMLayerConfigBuilder;

    use super::*;

    pub fn init_logging(level: Level) {
        set_panic_hook();

        let subscriber = Registry::default();

        // Browser console and profiler
        let subscriber = subscriber.with(WASMLayer::new(
            WASMLayerConfigBuilder::new()
                .set_max_level(level)
                .set_console_config(ConsoleConfig::ReportWithoutConsoleColor)
                .build(),
        ));

        //TODO: Jaeger in browser. How to setup agent endpoint?

        // Enable log compatible layer to convert log record to tracing span.
        // We will ignore any errors that returned by this functions.
        let _ = LogTracer::init();

        // Ignore errors returned by set_global_default.
        let _ = tracing::subscriber::set_global_default(subscriber);
    }
}