use strum_macros::{Display, EnumString};
pub static NOOSPHERE_LOG_LEVEL_CRATES: &[&str] = &[
"noosphere",
"noosphere_core",
"noosphere_storage",
"noosphere_common",
"noosphere_sphere",
"noosphere_into",
"noosphere_gateway",
"noosphere_collections",
"noosphere_cli",
"noosphere_car",
"noosphere_api",
"noosphere_ns",
"orb",
"orb_ns",
"tower_http",
];
#[derive(Clone, Display, EnumString)]
pub enum NoosphereLog {
#[strum(serialize = "silent")]
Silent,
#[strum(serialize = "basic")]
Basic,
#[strum(serialize = "chatty")]
Chatty,
#[strum(serialize = "informed")]
Informed,
#[strum(serialize = "academic")]
Academic,
#[strum(serialize = "tiresome")]
Tiresome,
#[strum(serialize = "deafening")]
Deafening,
}
impl From<NoosphereLog> for NoosphereLogFormat {
fn from(noosphere_log: NoosphereLog) -> Self {
match noosphere_log {
NoosphereLog::Silent | NoosphereLog::Basic | NoosphereLog::Chatty => {
NoosphereLogFormat::Minimal
}
NoosphereLog::Informed | NoosphereLog::Tiresome => NoosphereLogFormat::Verbose,
NoosphereLog::Academic | NoosphereLog::Deafening => NoosphereLogFormat::Pretty,
}
}
}
impl From<NoosphereLog> for NoosphereLogLevel {
fn from(noosphere_log: NoosphereLog) -> Self {
match noosphere_log {
NoosphereLog::Silent => NoosphereLogLevel::Off,
NoosphereLog::Basic => NoosphereLogLevel::Info,
NoosphereLog::Chatty | NoosphereLog::Informed | NoosphereLog::Academic => {
NoosphereLogLevel::Debug
}
NoosphereLog::Tiresome | NoosphereLog::Deafening => NoosphereLogLevel::Trace,
}
}
}
#[derive(Clone, Display, EnumString)]
pub enum NoosphereLogFormat {
#[strum(serialize = "minimal")]
Minimal,
#[strum(serialize = "verbose")]
Verbose,
#[strum(serialize = "pretty")]
Pretty,
#[strum(serialize = "structured")]
Structured,
}
impl Default for NoosphereLogFormat {
fn default() -> Self {
NoosphereLogFormat::Minimal
}
}
#[derive(Clone, Display, EnumString)]
pub enum NoosphereLogLevel {
#[strum(serialize = "trace")]
Trace,
#[strum(serialize = "debug")]
Debug,
#[strum(serialize = "info")]
Info,
#[strum(serialize = "warn")]
Warn,
#[strum(serialize = "error")]
Error,
#[strum(serialize = "off")]
Off,
}
impl Default for NoosphereLogLevel {
fn default() -> Self {
NoosphereLogLevel::Info
}
}
#[cfg(not(target_arch = "wasm32"))]
impl From<NoosphereLogLevel> for Vec<tracing_subscriber::filter::Directive> {
fn from(noosphere_log_level: NoosphereLogLevel) -> Self {
let mut directives = vec![];
let log_level = noosphere_log_level.to_string();
for name in NOOSPHERE_LOG_LEVEL_CRATES {
if let Ok(directive) = format!("{name}={log_level}").parse() {
directives.push(directive);
}
}
directives
}
}
#[cfg(target_arch = "wasm32")]
mod inner {
use super::NoosphereLog;
use std::sync::Once;
static INITIALIZE_TRACING: Once = Once::new();
pub fn initialize_tracing(_noosphere_log: Option<NoosphereLog>) {
INITIALIZE_TRACING.call_once(|| {
console_error_panic_hook::set_once();
tracing_wasm::set_as_global_default();
})
}
}
#[cfg(not(target_arch = "wasm32"))]
mod inner {
use anyhow::Result;
use std::{marker::PhantomData, sync::Once};
use tracing::{Event, Subscriber};
use tracing_subscriber::{
filter::Directive,
fmt::{format, FmtContext, FormatEvent, FormatFields, FormattedFields, Layer as FmtLayer},
prelude::*,
registry::LookupSpan,
EnvFilter, Layer, Registry,
};
#[cfg(target_os = "ios")]
const USE_ANSI_COLORS: bool = false;
#[cfg(not(target_os = "ios"))]
const USE_ANSI_COLORS: bool = true;
use super::{NoosphereLog, NoosphereLogFormat, NoosphereLogLevel};
#[cfg(docs)]
use super::NOOSPHERE_LOG_LEVEL_CRATES;
static INITIALIZE_TRACING: Once = Once::new();
pub fn initialize_tracing(noosphere_log: Option<NoosphereLog>) {
INITIALIZE_TRACING.call_once(|| {
if let Err(error) = initialize_tracing_subscriber::<
Option<Box<dyn Layer<Registry> + Send + Sync>>,
>(noosphere_log, None)
{
println!("Failed to initialize tracing: {}", error);
}
});
}
pub fn initialize_tracing_with_layer<T>(noosphere_log: Option<NoosphereLog>, layer: T)
where
T: Layer<Registry> + Send + Sync + Sized,
{
INITIALIZE_TRACING.call_once(|| {
if let Err(error) = initialize_tracing_subscriber(noosphere_log, layer) {
println!("Failed to initialize tracing: {}", error);
}
});
}
fn initialize_tracing_subscriber<T>(
noosphere_log: Option<NoosphereLog>,
layer: T,
) -> anyhow::Result<()>
where
T: Layer<Registry> + Send + Sync + Sized,
{
let rust_log_env = std::env::var("RUST_LOG").ok();
let noosphere_log_env = std::env::var("NOOSPHERE_LOG").ok();
let noosphere_log_level_env = std::env::var("NOOSPHERE_LOG_LEVEL").ok();
let noosphere_log_format_env = std::env::var("NOOSPHERE_LOG_FORMAT").ok();
let noosphere_log = match noosphere_log_env {
Some(value) => match value.parse() {
Ok(noosphere_log) => Some(noosphere_log),
_ => noosphere_log,
},
None => noosphere_log,
};
let (mut noosphere_log_level_default, mut noosphere_log_format_default) =
if rust_log_env.is_some() {
(None, NoosphereLogFormat::Verbose)
} else {
(Some(NoosphereLogLevel::Info), NoosphereLogFormat::Minimal)
};
if let Some(noosphere_log) = noosphere_log {
noosphere_log_level_default = Some(noosphere_log.clone().into());
noosphere_log_format_default = noosphere_log.into();
}
let noosphere_log_level = match noosphere_log_level_env {
Some(noosphere_log_level_env) => match noosphere_log_level_env.parse() {
Ok(noosphere_log_level) => Some(noosphere_log_level),
_ => noosphere_log_level_default,
},
None => noosphere_log_level_default,
};
let noosphere_log_format = match noosphere_log_format_env {
Some(noosphere_log_format_env) => match noosphere_log_format_env.parse() {
Ok(noosphere_log_format) => noosphere_log_format,
_ => noosphere_log_format_default,
},
None => noosphere_log_format_default,
};
let mut env_filter = EnvFilter::default();
let mut rust_log_directives = rust_log_env
.unwrap_or_default()
.split(',')
.filter(|s| !s.is_empty())
.map(|s| s.parse())
.collect::<Result<Vec<Directive>, _>>()
.unwrap_or_else(|_| Vec::new());
let mut directives: Vec<Directive> = match noosphere_log_level {
Some(noosphere_log_level) => noosphere_log_level.into(),
None => Vec::new(),
};
directives.append(&mut rust_log_directives);
for directive in directives {
env_filter = env_filter.add_directive(directive)
}
let subscriber = layer
.and_then(env_filter)
.with_subscriber(tracing_subscriber::registry());
match noosphere_log_format {
NoosphereLogFormat::Minimal => {
let subscriber = subscriber.with(
FmtLayer::default().event_format(NoosphereMinimalFormatter::new(
tracing_subscriber::fmt::format()
.without_time()
.with_target(false)
.with_ansi(USE_ANSI_COLORS),
)),
);
#[cfg(feature = "sentry")]
let subscriber = subscriber.with(sentry_tracing::layer());
subscriber.init();
}
NoosphereLogFormat::Verbose => {
let subscriber =
subscriber.with(tracing_subscriber::fmt::layer().with_ansi(USE_ANSI_COLORS));
#[cfg(feature = "sentry")]
let subscriber = subscriber.with(sentry_tracing::layer());
subscriber.init();
}
NoosphereLogFormat::Pretty => {
let subscriber =
subscriber.with(FmtLayer::default().pretty().with_ansi(USE_ANSI_COLORS));
#[cfg(feature = "sentry")]
let subscriber = subscriber.with(sentry_tracing::layer());
subscriber.init();
}
NoosphereLogFormat::Structured => {
let subscriber =
subscriber.with(FmtLayer::default().json().with_ansi(USE_ANSI_COLORS));
#[cfg(feature = "sentry")]
let subscriber = subscriber.with(sentry_tracing::layer());
subscriber.init();
}
};
Ok(())
}
struct NoosphereMinimalFormatter<F, S, N>(F, PhantomData<S>, PhantomData<N>)
where
F: FormatEvent<S, N>,
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static;
impl<F, S, N> NoosphereMinimalFormatter<F, S, N>
where
F: FormatEvent<S, N>,
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
pub fn new(formatter: F) -> Self {
Self(formatter, PhantomData, PhantomData)
}
}
impl<F, S, N> FormatEvent<S, N> for NoosphereMinimalFormatter<F, S, N>
where
F: FormatEvent<S, N>,
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: format::Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
let metadata = event.metadata();
match metadata.level().as_str() {
"INFO" => (),
_ => {
return self.0.format_event(ctx, writer, event);
}
};
if let Some(scope) = ctx.event_scope() {
for span in scope.from_root() {
write!(writer, "{}", span.name())?;
let ext = span.extensions();
let fields = &ext
.get::<FormattedFields<N>>()
.expect("will never be `None`");
if !fields.is_empty() {
write!(writer, "{{{}}}", fields)?;
}
write!(writer, ": ")?;
}
}
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}
}
pub use inner::*;