449 lines
9.5 KiB
Rust
449 lines
9.5 KiB
Rust
use anyhow::Result;
|
|
use clap::{Parser, ValueEnum};
|
|
use serde::Deserialize;
|
|
use std::{fmt::Display, str::FromStr};
|
|
use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt};
|
|
|
|
//
|
|
// MARK: loglevel
|
|
//
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, ValueEnum)]
|
|
pub enum LogLevel {
|
|
Trace,
|
|
Debug,
|
|
Info,
|
|
Warn,
|
|
Error,
|
|
}
|
|
|
|
impl Default for LogLevel {
|
|
fn default() -> Self {
|
|
Self::Info
|
|
}
|
|
}
|
|
|
|
impl Display for LogLevel {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Trace => write!(f, "trace"),
|
|
Self::Debug => write!(f, "debug"),
|
|
Self::Info => write!(f, "info"),
|
|
Self::Warn => write!(f, "warn"),
|
|
Self::Error => write!(f, "error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// MARK: logconfig
|
|
//
|
|
|
|
/// Configures log levels for known sources
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
|
|
pub struct LoggingConfig {
|
|
pub other: LogLevel,
|
|
pub silence: LogLevel,
|
|
|
|
// Libs
|
|
pub libservice: LogLevel,
|
|
pub toolbox: LogLevel,
|
|
|
|
// Bins
|
|
pub service: LogLevel,
|
|
pub webpage: LogLevel,
|
|
}
|
|
|
|
impl From<LoggingConfig> for EnvFilter {
|
|
fn from(conf: LoggingConfig) -> Self {
|
|
// Should never fail
|
|
#[expect(clippy::unwrap_used)]
|
|
EnvFilter::from_str(
|
|
&[
|
|
//
|
|
// Silence
|
|
//
|
|
// http
|
|
format!("hyper_util={}", conf.silence),
|
|
format!("h2={}", conf.silence),
|
|
format!("rustls={}", conf.silence),
|
|
format!("tower={}", conf.silence),
|
|
//
|
|
// Libs
|
|
//
|
|
format!("toolbox={}", conf.toolbox),
|
|
format!("libservice={}", conf.libservice),
|
|
//
|
|
// Bins
|
|
//
|
|
format!("service_webpage={}", conf.service),
|
|
format!("webpage={}", conf.webpage),
|
|
conf.other.to_string(),
|
|
]
|
|
.join(","),
|
|
)
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
//
|
|
// MARK: LogCliVQ
|
|
//
|
|
|
|
/// Provides global -v and -q cli arguments.
|
|
/// Use with `#[arg(flatten)]`.
|
|
#[derive(Parser, Debug, Clone)]
|
|
pub struct LogCliVQ {
|
|
/// Increase verbosity (can be repeated)
|
|
#[arg(default_value = "0")]
|
|
#[arg(short, action = clap::ArgAction::Count,global = true)]
|
|
v: u8,
|
|
|
|
/// Decrease verbosity (can be repeated)
|
|
#[arg(default_value = "0")]
|
|
#[arg(short, action = clap::ArgAction::Count, global = true)]
|
|
q: u8,
|
|
}
|
|
|
|
impl LogCliVQ {
|
|
pub fn into_preset(&self) -> LogFilterPreset {
|
|
let level_i: i16 = self.v as i16 - self.q as i16;
|
|
|
|
let preset;
|
|
if level_i <= -2 {
|
|
preset = LogFilterPreset::Error
|
|
} else if level_i == -1 {
|
|
preset = LogFilterPreset::Warn
|
|
} else if level_i == 0 {
|
|
preset = LogFilterPreset::Info
|
|
} else if level_i == 1 {
|
|
preset = LogFilterPreset::Debug
|
|
} else if level_i >= 2 {
|
|
preset = LogFilterPreset::Trace
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
|
|
return preset;
|
|
}
|
|
}
|
|
|
|
//
|
|
// MARK: logpreset
|
|
//
|
|
|
|
/// Provides preset configurations of [LoggingConfig]
|
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
|
pub enum LogFilterPreset {
|
|
/// Standard "error" log level
|
|
Error,
|
|
|
|
/// Standard "warn" log level
|
|
Warn,
|
|
|
|
/// Standard "info" log level.
|
|
/// This is the default.
|
|
Info,
|
|
|
|
/// Standard "debug" log level
|
|
Debug,
|
|
|
|
/// Standard "trace" log level
|
|
Trace,
|
|
|
|
/// Filter for loki subscriber.
|
|
///
|
|
/// This is similar to `Trace`,
|
|
/// but excludes particularly spammy sources.
|
|
Loki,
|
|
}
|
|
|
|
impl Default for LogFilterPreset {
|
|
fn default() -> Self {
|
|
return Self::Info;
|
|
}
|
|
}
|
|
|
|
impl From<LogFilterPreset> for LoggingConfig {
|
|
fn from(val: LogFilterPreset) -> Self {
|
|
val.get_config()
|
|
}
|
|
}
|
|
|
|
impl LogFilterPreset {
|
|
pub fn get_config(&self) -> LoggingConfig {
|
|
match self {
|
|
Self::Error => LoggingConfig {
|
|
other: LogLevel::Error,
|
|
silence: LogLevel::Error,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Error,
|
|
toolbox: LogLevel::Error,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Error,
|
|
service: LogLevel::Error,
|
|
},
|
|
|
|
Self::Warn => LoggingConfig {
|
|
other: LogLevel::Warn,
|
|
silence: LogLevel::Warn,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Warn,
|
|
toolbox: LogLevel::Warn,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Warn,
|
|
service: LogLevel::Warn,
|
|
},
|
|
|
|
Self::Info => LoggingConfig {
|
|
other: LogLevel::Warn,
|
|
silence: LogLevel::Warn,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Info,
|
|
toolbox: LogLevel::Info,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Info,
|
|
service: LogLevel::Info,
|
|
},
|
|
|
|
Self::Debug => LoggingConfig {
|
|
other: LogLevel::Warn,
|
|
silence: LogLevel::Warn,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Debug,
|
|
toolbox: LogLevel::Debug,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Debug,
|
|
service: LogLevel::Debug,
|
|
},
|
|
|
|
Self::Trace => LoggingConfig {
|
|
other: LogLevel::Trace,
|
|
silence: LogLevel::Warn,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Trace,
|
|
toolbox: LogLevel::Trace,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Trace,
|
|
service: LogLevel::Trace,
|
|
},
|
|
|
|
Self::Loki => LoggingConfig {
|
|
other: LogLevel::Trace,
|
|
silence: LogLevel::Warn,
|
|
|
|
// Libs
|
|
libservice: LogLevel::Trace,
|
|
toolbox: LogLevel::Trace,
|
|
|
|
// Bins
|
|
webpage: LogLevel::Trace,
|
|
service: LogLevel::Trace,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// MARK: initializer
|
|
//
|
|
|
|
#[cfg(feature = "loki")]
|
|
#[derive(Deserialize, Clone)]
|
|
pub struct LokiConfig {
|
|
pub loki_host: url::Url,
|
|
pub loki_user: String,
|
|
pub loki_pass: String,
|
|
pub loki_node_name: String,
|
|
}
|
|
|
|
/// Where to print logs
|
|
pub enum LoggingTarget {
|
|
/// Send logs to stdout
|
|
Stdout { format: LoggingFormat },
|
|
|
|
/// Send logs to stderr
|
|
Stderr { format: LoggingFormat },
|
|
/*
|
|
/// Send logs to an IndicatifWriter.
|
|
///
|
|
/// This is the same as Stderr { format: Ansi {color:true} },
|
|
/// but uses an indicatifwriter with the given multiprogress.
|
|
Indicatif(MultiProgress),
|
|
*/
|
|
}
|
|
|
|
/// How to print logs
|
|
#[derive(Debug, Clone, Copy, Deserialize)]
|
|
pub enum LoggingFormat {
|
|
Ansi,
|
|
AnsiNoColor,
|
|
Json,
|
|
}
|
|
|
|
impl Default for LoggingFormat {
|
|
fn default() -> Self {
|
|
Self::Ansi
|
|
}
|
|
}
|
|
|
|
pub struct LoggingInitializer {
|
|
pub app_name: &'static str,
|
|
|
|
/// If `Some`, send logs to the given loki server
|
|
#[cfg(feature = "loki")]
|
|
pub loki: Option<LokiConfig>,
|
|
|
|
/// Log filter for printed logs
|
|
pub preset: LogFilterPreset,
|
|
|
|
/// Where to print logs
|
|
pub target: LoggingTarget,
|
|
}
|
|
|
|
impl LoggingInitializer {
|
|
pub fn initialize(self) -> Result<()> {
|
|
let mut stderr_ansi_layer = None;
|
|
let mut stderr_json_layer = None;
|
|
let mut stdout_ansi_layer = None;
|
|
let mut stdout_json_layer = None;
|
|
match self.target {
|
|
LoggingTarget::Stderr {
|
|
format: LoggingFormat::Ansi,
|
|
} => {
|
|
stderr_ansi_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(true)
|
|
.with_writer(std::io::stderr)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
|
|
LoggingTarget::Stderr {
|
|
format: LoggingFormat::AnsiNoColor,
|
|
} => {
|
|
stderr_ansi_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(false)
|
|
.with_writer(std::io::stderr)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
|
|
LoggingTarget::Stderr {
|
|
format: LoggingFormat::Json,
|
|
} => {
|
|
stderr_json_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(false)
|
|
.json()
|
|
.with_writer(std::io::stderr)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
|
|
LoggingTarget::Stdout {
|
|
format: LoggingFormat::Ansi,
|
|
} => {
|
|
stdout_ansi_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(true)
|
|
.with_writer(std::io::stdout)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
|
|
LoggingTarget::Stdout {
|
|
format: LoggingFormat::AnsiNoColor,
|
|
} => {
|
|
stdout_ansi_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(false)
|
|
.with_writer(std::io::stdout)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
|
|
LoggingTarget::Stdout {
|
|
format: LoggingFormat::Json,
|
|
} => {
|
|
stdout_json_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(false)
|
|
.json()
|
|
.with_writer(std::io::stdout)
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
} /*
|
|
LoggingTarget::Indicatif(mp) => {
|
|
let writer: IndicatifWriter<tracing_indicatif::writer::Stderr> =
|
|
IndicatifWriter::new(mp);
|
|
|
|
indicatif_layer = Some(
|
|
tracing_subscriber::fmt::Layer::default()
|
|
.without_time()
|
|
.with_ansi(true)
|
|
.with_writer(writer.make_writer())
|
|
.with_filter::<EnvFilter>(self.preset.get_config().into()),
|
|
)
|
|
}
|
|
*/
|
|
}
|
|
|
|
let loki_layer = {
|
|
#[cfg(feature = "loki")]
|
|
if let Some(cfg) = self.loki {
|
|
use anyhow::Context;
|
|
use base64::{Engine, prelude::BASE64_STANDARD};
|
|
|
|
let basic_auth = format!("{}:{}", cfg.loki_user, cfg.loki_pass);
|
|
let encoded_basic_auth = BASE64_STANDARD.encode(basic_auth.as_bytes());
|
|
|
|
let (layer, task) = tracing_loki::builder()
|
|
.label("node_name", cfg.loki_node_name)
|
|
.context("while building loki node_name label")?
|
|
.label("app", self.app_name)
|
|
.context("while building loki app label")?
|
|
.http_header("Authorization", format!("Basic {encoded_basic_auth}"))
|
|
.context("while building loki header")?
|
|
.build_url(cfg.loki_host)
|
|
.context("while building loki layer")?;
|
|
|
|
tokio::spawn(task);
|
|
Some(layer.with_filter::<EnvFilter>(LogFilterPreset::Loki.get_config().into()))
|
|
} else {
|
|
None
|
|
}
|
|
|
|
#[cfg(not(feature = "loki"))]
|
|
None::<Box<dyn Layer<_> + Send + Sync>>
|
|
};
|
|
|
|
tracing_subscriber::registry()
|
|
.with(loki_layer)
|
|
.with(stdout_ansi_layer)
|
|
.with(stdout_json_layer)
|
|
.with(stderr_ansi_layer)
|
|
.with(stderr_json_layer)
|
|
.init();
|
|
|
|
Ok(())
|
|
}
|
|
}
|