2023-02-16 02:14:54 -05:00
|
|
|
// This file is part of Moonfire NVR, a security camera network video recorder.
|
|
|
|
// Copyright (C) 2023 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
|
|
|
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
|
|
|
|
|
|
|
|
//! Logic for setting up a `tracing` subscriber according to our preferences
|
|
|
|
//! and [OpenTelemetry conventions](https://opentelemetry.io/docs/reference/specification/logs/).
|
|
|
|
|
|
|
|
use tracing::error;
|
|
|
|
use tracing_core::{Event, Level, Subscriber};
|
|
|
|
use tracing_log::NormalizeEvent;
|
|
|
|
use tracing_subscriber::{
|
|
|
|
fmt::{format::Writer, time::FormatTime, FmtContext, FormatFields, FormattedFields},
|
|
|
|
layer::SubscriberExt,
|
|
|
|
registry::LookupSpan,
|
|
|
|
Layer,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct FormatSystemd;
|
|
|
|
|
|
|
|
struct ChronoTimer;
|
|
|
|
|
|
|
|
impl FormatTime for ChronoTimer {
|
|
|
|
fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
|
|
|
|
const TIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.6f";
|
|
|
|
write!(w, "{}", chrono::Local::now().format(TIME_FORMAT))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn systemd_prefix(level: Level) -> &'static str {
|
|
|
|
if level >= Level::TRACE {
|
|
|
|
"<7>" // SD_DEBUG
|
|
|
|
} else if level >= Level::DEBUG {
|
|
|
|
"<6>" // SD_INFO
|
|
|
|
} else if level >= Level::INFO {
|
|
|
|
"<5>" // SD_NOTICE
|
|
|
|
} else if level >= Level::WARN {
|
|
|
|
"<4>" // SD_WARN
|
|
|
|
} else {
|
|
|
|
"<3>" // SD_ERROR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for FormatSystemd
|
|
|
|
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 normalized_meta = event.normalized_metadata();
|
|
|
|
let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
|
|
|
|
let prefix = systemd_prefix(*meta.level());
|
|
|
|
|
|
|
|
let thread = std::thread::current();
|
|
|
|
let thread_name = thread.name().unwrap_or("unnamed-thread");
|
|
|
|
write!(writer, "{prefix}{thread_name} ")?;
|
|
|
|
if let Some(scope) = ctx.event_scope() {
|
|
|
|
let mut seen = false;
|
|
|
|
|
|
|
|
for span in scope.from_root() {
|
|
|
|
write!(writer, "{}", span.metadata().name())?;
|
|
|
|
seen = true;
|
|
|
|
|
|
|
|
let ext = span.extensions();
|
|
|
|
if let Some(fields) = &ext.get::<FormattedFields<N>>() {
|
|
|
|
if !fields.is_empty() {
|
|
|
|
write!(writer, "{{{fields}}}")?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writer.write_char(':')?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if seen {
|
|
|
|
writer.write_char(' ')?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(writer, "{}: ", meta.target())?;
|
|
|
|
ctx.format_fields(writer.by_ref(), event)?;
|
|
|
|
writeln!(writer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Custom panic hook that logs instead of directly writing to stderr.
|
|
|
|
///
|
|
|
|
/// This means it includes a timestamp, follows [OpenTelemetry Semantic
|
|
|
|
/// Conventions for Exceptions](https://opentelemetry.io/docs/reference/specification/logs/semantic_conventions/exceptions/),
|
|
|
|
/// etc.
|
|
|
|
fn panic_hook(p: &std::panic::PanicInfo) {
|
|
|
|
let payload: Option<&str> = if let Some(s) = p.payload().downcast_ref::<&str>() {
|
|
|
|
Some(*s)
|
|
|
|
} else if let Some(s) = p.payload().downcast_ref::<String>() {
|
|
|
|
Some(s)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
error!(
|
|
|
|
target: std::env!("CARGO_CRATE_NAME"),
|
|
|
|
location = p.location().map(tracing::field::display),
|
|
|
|
payload = payload.map(tracing::field::display),
|
|
|
|
backtrace = %std::backtrace::Backtrace::force_capture(),
|
|
|
|
"panic",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn install() {
|
|
|
|
let filter = tracing_subscriber::EnvFilter::builder()
|
|
|
|
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
|
|
|
|
.with_env_var("MOONFIRE_LOG")
|
|
|
|
.from_env_lossy();
|
|
|
|
tracing_log::LogTracer::init().unwrap();
|
|
|
|
|
|
|
|
match std::env::var("MOONFIRE_FORMAT") {
|
|
|
|
Ok(s) if s == "systemd" => {
|
|
|
|
let sub = tracing_subscriber::registry().with(
|
|
|
|
tracing_subscriber::fmt::Layer::new()
|
2023-08-03 16:48:19 -04:00
|
|
|
.with_writer(std::io::stderr)
|
2023-02-16 02:14:54 -05:00
|
|
|
.with_ansi(false)
|
|
|
|
.event_format(FormatSystemd)
|
|
|
|
.with_filter(filter),
|
|
|
|
);
|
|
|
|
tracing::subscriber::set_global_default(sub).unwrap();
|
|
|
|
}
|
|
|
|
Ok(s) if s == "json" => {
|
|
|
|
let sub = tracing_subscriber::registry().with(
|
|
|
|
tracing_subscriber::fmt::Layer::new()
|
2023-08-03 16:48:19 -04:00
|
|
|
.with_writer(std::io::stderr)
|
2023-02-16 02:14:54 -05:00
|
|
|
.with_thread_names(true)
|
|
|
|
.json()
|
|
|
|
.with_filter(filter),
|
|
|
|
);
|
|
|
|
tracing::subscriber::set_global_default(sub).unwrap();
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let sub = tracing_subscriber::registry().with(
|
|
|
|
tracing_subscriber::fmt::Layer::new()
|
2023-08-03 16:48:19 -04:00
|
|
|
.with_writer(std::io::stderr)
|
2023-02-16 02:14:54 -05:00
|
|
|
.with_timer(ChronoTimer)
|
|
|
|
.with_thread_names(true)
|
|
|
|
.with_filter(filter),
|
|
|
|
);
|
|
|
|
tracing::subscriber::set_global_default(sub).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let use_panic_hook = ::std::env::var("MOONFIRE_PANIC_HOOK")
|
|
|
|
.map(|s| s != "false" && s != "0")
|
|
|
|
.unwrap_or(true);
|
|
|
|
if use_panic_hook {
|
|
|
|
std::panic::set_hook(Box::new(&panic_hook));
|
|
|
|
}
|
|
|
|
}
|
2023-08-08 21:24:50 -04:00
|
|
|
|
|
|
|
pub fn install_for_tests() {
|
|
|
|
let filter = tracing_subscriber::EnvFilter::builder()
|
|
|
|
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
|
|
|
|
.with_env_var("MOONFIRE_LOG")
|
|
|
|
.from_env_lossy();
|
|
|
|
tracing_log::LogTracer::init().unwrap();
|
|
|
|
let sub = tracing_subscriber::registry().with(
|
|
|
|
tracing_subscriber::fmt::Layer::new()
|
|
|
|
.with_test_writer()
|
|
|
|
.with_timer(ChronoTimer)
|
|
|
|
.with_thread_names(true)
|
|
|
|
.with_filter(filter),
|
|
|
|
);
|
|
|
|
tracing::subscriber::set_global_default(sub).unwrap();
|
|
|
|
}
|