diff options
Diffstat
| -rw-r--r-- | Cargo.lock | 187 | +182 −5 |
| -rw-r--r-- | Cargo.toml | 34 | +31 −3 |
| -rw-r--r-- | pkg/zorian.toml | 30 | +30 −0 |
| -rw-r--r-- | src/config.rs | 116 | +112 −4 |
| -rw-r--r-- | src/main.rs | 25 | +8 −17 |
| -rw-r--r-- | src/telemetry.rs | 218 | +218 −0 |
6 files changed, 581 insertions, 29 deletions
diff --git a/Cargo.lock b/Cargo.lock index 9499881..c3f41c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,7 +437,7 @@ dependencies = [ "parking_lot", "rayon", "regex", - "reqwest", + "reqwest 0.13.1", "ring", "rustls", "rustsec", @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "cargo-llvm-cov" -version = "0.6.23" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a19af1cb5512737613b5caa313dd14cd56459732867c53a755da4b967ffefe6" +checksum = "f8132c5e1b6489a345e12bbc3deb7154cb8d8b51bd9d9b168505eff5e5ce88ee" dependencies = [ "anyhow", "camino", @@ -1120,6 +1120,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1145,6 +1156,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1857,7 +1869,7 @@ dependencies = [ "gix-quote", "gix-sec", "gix-url", - "reqwest", + "reqwest 0.13.1", "thiserror 2.0.17", ] @@ -2959,6 +2971,100 @@ dependencies = [ ] [[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest 0.12.28", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest 0.12.28", + "thiserror 2.0.17", + "tokio", + "tonic", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.2", + "thiserror 2.0.17", + "tokio", + "tokio-stream", +] + +[[package]] name = "os_pipe" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3359,6 +3465,40 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reqwest" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" @@ -3461,6 +3601,7 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -4267,7 +4408,7 @@ dependencies = [ "libc", "memchr", "rayon", - "reqwest", + "reqwest 0.13.1", "rustc-stable-hash", "semver", "serde", @@ -4570,9 +4711,11 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", + "rustls-native-certs", "socket2", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-stream", "tower", "tower-layer", @@ -4581,6 +4724,17 @@ dependencies = [ ] [[package]] +name = "tonic-prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] name = "tower" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4705,6 +4859,22 @@ dependencies = [ ] [[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] name = "tracing-serde" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5570,6 +5740,11 @@ dependencies = [ "hyper-tls", "hyper-util", "minijinja", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "rustls", "sd-notify", "semver", @@ -5579,10 +5754,12 @@ dependencies = [ "thiserror 2.0.17", "tokio", "toml", + "tonic", "tower", "tower-http", "tower_governor", "tracing", + "tracing-opentelemetry", "tracing-subscriber", "url", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 762b60d..3f2528a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,14 @@ repository = "https://github.com/mrdimidium/Zorian" [dependencies] axum = { version = "0.8", features = ["http2", "macros", "multipart"] } -axum-server = { version = "0.8", default-features = false, features = ["tls-rustls-no-provider"] } -rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } +axum-server = { version = "0.8", default-features = false, features = [ + "tls-rustls-no-provider", +] } +rustls = { version = "0.23", default-features = false, features = [ + "ring", + "std", + "tls12", +] } axum-extra = { version = "0.12", features = [ "async-read-body", "attachment", @@ -68,8 +74,30 @@ uuid = { version = "1.19", features = ["v4", "v5"] } [target.'cfg(target_os = "linux")'.dependencies] sd-notify = { version = "0.4" } +# OpenTelemetry +opentelemetry = "0.31" +opentelemetry-appender-tracing = "0.31" +opentelemetry-otlp = { version = "0.31", features = [ + "grpc-tonic", + "http-proto", + "logs", + "metrics", + "trace", + "tls", + "tls-roots", +] } +tonic = "0.14" +opentelemetry-semantic-conventions = "0.31" +opentelemetry_sdk = { version = "0.31", features = [ + "rt-tokio", + "logs", + "metrics", + "trace", +] } +tracing-opentelemetry = "0.32" + [dev-dependencies] cargo-deny = "0.19" -cargo-llvm-cov = "0.6" +cargo-llvm-cov = "0.8.2" tempfile = "3.24" url = "2.5" diff --git a/pkg/zorian.toml b/pkg/zorian.toml index 9612063..0fde186 100644 --- a/pkg/zorian.toml +++ b/pkg/zorian.toml @@ -27,3 +27,33 @@ hostnames = ["localhost", "127.0.0.1", "::1"] # For HTTPS, set tls_cert and tls_key to PEM file paths. # tls_crt = "/etc/zorian/cert.pem" # tls_key = "/etc/zorian/key.pem" + +# Telemetry configuration +# Logs to stdout (pretty or json format) +[telemetry.stdout] +enabled = true +log_level = "info" # trace, debug, info, warning, error +log_format = "pretty" # pretty, json + +# OpenTelemetry Collector (OTLP) +# Uncomment to enable sending logs and traces to an OTLP collector +# [telemetry.otelcol] +# enabled = true +# logs = true +# traces = true +# endpoint = "http://localhost:4318" # HTTP protocol (no TLS) +# # endpoint = "grpc://localhost:4317" # gRPC protocol (requires TLS) +# timeout = 10 # seconds +# log_level = "info" +# +# # For gRPC with TLS, specify certificates: +# # tls_ca = "/etc/zorian/otel-ca.crt" +# # tls_crt = "/etc/zorian/otel-client.crt" # for mTLS +# # tls_key = "/etc/zorian/otel-client.key" # for mTLS +# +# # HTTP headers for authentication: +# # [telemetry.otelcol.headers] +# # Authorization = "Bearer <token>" + +# Environment variable ZORIAN_LOG overrides log_level settings. +# Example: ZORIAN_LOG=debug,sqlx=warn diff --git a/src/config.rs b/src/config.rs index 49b5c15..1e684bb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2026 Nikolay Govorov <me@govorov.online> // SPDX-License-Identifier: AGPL-3.0-or-later +use std::collections::HashMap; use std::fs; use std::net::SocketAddr; use std::path::{Path, PathBuf}; @@ -113,7 +114,6 @@ pub struct ListenerConfig { pub addr: SocketAddr, /// Hostnames to accept for this listener. Empty means accept all. - #[serde(default)] pub hostnames: Vec<String>, /// Path to TLS certificate file (PEM format). If set, tls_key must also be set. @@ -138,15 +138,118 @@ impl Default for ListenerConfig { } } +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Trace, + Debug, + Info, + Warning, + Error, +} + +#[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum StdoutFormat { + #[default] + Pretty, + Json, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(default)] +pub struct StdoutConfig { + /// Enables sending logs to the stdout + pub enabled: bool, + + /// Controls which logs will be sent to stdout + pub log_level: LogLevel, + + /// Controls the format of logs in stdout + pub log_format: StdoutFormat, +} +impl Default for StdoutConfig { + fn default() -> Self { + Self { + enabled: true, + log_level: LogLevel::Info, + log_format: StdoutFormat::Pretty, + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(default)] +pub struct OtelcolConfig { + /// Enables sending telemetry to the otlp collector + pub enabled: bool, + + /// Send logs to OTLP at this level (None = disabled) + pub logs: bool, + + /// Send traces to OTLP + pub traces: bool, + + /// Send traces to OTLP + pub metrics: bool, + + /// OTLP endpoint (grpc:// or http://) + pub endpoint: String, + + /// Export timeout in seconds + #[serde(deserialize_with = "deserialize_duration_secs")] + pub timeout: Duration, + + /// Controls which logs will be sent to otlp + pub log_level: LogLevel, + + /// Path to CA certificate for TLS (required for grpcs://) + pub tls_ca: Option<PathBuf>, + + /// Path to client certificate for mTLS + pub tls_crt: Option<PathBuf>, + + /// Path to client key for mTLS + pub tls_key: Option<PathBuf>, + + /// HTTP headers for authentication + pub headers: HashMap<String, String>, +} + +impl Default for OtelcolConfig { + fn default() -> Self { + Self { + enabled: true, + logs: true, + traces: true, + metrics: true, + timeout: Duration::from_secs(10), + endpoint: "http://localhost:4317".into(), + log_level: LogLevel::Info, + tls_ca: None, + tls_crt: None, + tls_key: None, + headers: HashMap::new(), + } + } +} + +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(default)] +pub struct TelemetryConfig { + pub stdout: StdoutConfig, + pub otelcol: Option<OtelcolConfig>, +} + #[derive(Debug, Deserialize)] #[serde(default)] pub struct ConfigService { appname: String, dirname: PathBuf, - server: ServerConfig, listen: Vec<ListenerConfig>, + server: ServerConfig, + telemetry: TelemetryConfig, } - impl Default for ConfigService { fn default() -> Self { Self { @@ -154,10 +257,10 @@ impl Default for ConfigService { dirname: PathBuf::from("./.zorian-state"), server: ServerConfig::default(), listen: vec![ListenerConfig::default()], + telemetry: TelemetryConfig::default(), } } } - impl ConfigService { pub fn from_file(path: &Path) -> Result<Self, ConfigError> { let content = fs::read_to_string(path)?; @@ -227,6 +330,10 @@ impl ConfigService { pub fn listeners(&self) -> &[ListenerConfig] { &self.listen } + + pub fn telemetry(&self) -> &TelemetryConfig { + &self.telemetry + } } #[cfg(test)] @@ -242,6 +349,7 @@ impl ConfigService { tls_crt: None, tls_key: None, }], + telemetry: TelemetryConfig::default(), } } } diff --git a/src/main.rs b/src/main.rs index d6281ff..7dd6c1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod backends; mod config; mod proxy; mod storage; +mod telemetry; mod web; use std::future::Future; @@ -25,7 +26,7 @@ use axum_server::tls_rustls::RustlsConfig; use sd_notify::NotifyState; use tokio::signal; use tracing::{error, info, trace}; -use tracing_subscriber::{layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt}; +use tracing_subscriber::registry::LookupSpan; use crate::backends::zig::ZigController; use crate::web::WebController; @@ -92,21 +93,6 @@ async fn main() { } } - tracing_subscriber::registry() - .with({ - #[cfg(debug_assertions)] - let fmt = tracing_subscriber::fmt::layer().pretty(); - #[cfg(not(debug_assertions))] - let fmt = tracing_subscriber::fmt::layer().json(); - fmt - }) - .with(match std::env::var_os("ZORIAN_LOG") { - None => tracing_subscriber::EnvFilter::new("info,tower_http=info"), - Some(val) => tracing_subscriber::EnvFilter::try_new(val.to_string_lossy()) - .expect("Invalid ZORIAN_LOG"), - }) - .init(); - let config = Arc::new(match config_path { Some(path) => { info!("use config file from {}", path.to_str().unwrap()); @@ -122,10 +108,13 @@ async fn main() { } }); config.validate().unwrap_or_else(|e| { - error!("invalid config: {e}"); + eprintln!("invalid config: {e}"); std::process::exit(1); }); + let mut telemetry = + telemetry::TelemetryService::init(config.telemetry(), config.appname(), VERSION); + let storage = Arc::new(storage::StorageService::new(config.clone()).await.unwrap()); let upstream = Arc::new(proxy::ProxyService::new()); @@ -408,6 +397,8 @@ async fn main() { } else { info!("shutdown complete"); } + + telemetry.shutdown(); } /// Layer that validates the Host header against configured hostnames. diff --git a/src/telemetry.rs b/src/telemetry.rs new file mode 100644 --- /dev/null +++ b/src/telemetry.rs @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: 2026 Nikolay Govorov <me@govorov.online> +// SPDX-License-Identifier: AGPL-3.0-or-later + +use opentelemetry::KeyValue; +use opentelemetry::trace::TracerProvider as _; +use opentelemetry_otlp::{ + LogExporter, Protocol, SpanExporter, WithExportConfig, WithHttpConfig, WithTonicConfig, +}; +use opentelemetry_sdk::Resource; +use opentelemetry_sdk::logs::SdkLoggerProvider; +use opentelemetry_sdk::trace::SdkTracerProvider; +use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue}; +use tonic::transport::{Certificate, ClientTlsConfig, Identity}; +use tracing_subscriber::Layer; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +use crate::config::{LogLevel, OtelcolConfig, StdoutFormat, TelemetryConfig}; + +macro_rules! build_exporter { + ($Exporter:ident, $cfg:expr, $url:expr, $tls:expr, $path:expr) => { + if $cfg.endpoint.starts_with("http://") { + // with_endpoint() requires full path for HTTP protocol + $Exporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) + .with_endpoint(format!("{}{}", $url, $path)) + .with_timeout($cfg.timeout) + .with_headers($cfg.headers.clone()) + .build() + .expect(concat!("failed to build ", stringify!($Exporter))) + } else if $cfg.endpoint.starts_with("grpc://") { + $Exporter::builder() + .with_tonic() + .with_endpoint($url) + .with_timeout($cfg.timeout) + .with_tls_config($tls) + .with_metadata(build_metadata($cfg)) + .build() + .expect(concat!("failed to build ", stringify!($Exporter))) + } else { + panic!( + "invalid OTLP endpoint: {}. Expected http:// or grpc://", + $cfg.endpoint + ) + } + }; +} + +fn build_metadata(cfg: &OtelcolConfig) -> MetadataMap { + let mut metadata = MetadataMap::new(); + for (key, value) in &cfg.headers { + if let (Ok(k), Ok(v)) = ( + key.parse::<MetadataKey<_>>(), + value.parse::<MetadataValue<_>>(), + ) { + metadata.insert(k, v); + } + } + metadata +} + +pub struct TelemetryService { + logger_provider: Option<SdkLoggerProvider>, + tracer_provider: Option<SdkTracerProvider>, +} +impl Drop for TelemetryService { + fn drop(&mut self) { + self.shutdown(); + } +} +impl TelemetryService { + pub fn init(config: &TelemetryConfig, service_name: &str, service_version: &str) -> Self { + let env_filter = match std::env::var_os("ZORIAN_LOG") { + Some(val) => tracing_subscriber::EnvFilter::try_new(val.to_string_lossy()) + .expect("Invalid ZORIAN_LOG"), + None => tracing_subscriber::EnvFilter::new(Self::log_level_to_filter( + config.stdout.log_level, + )), + }; + + let mut tracer_provider = None; + let mut logger_provider = None; + + let stdout = config + .stdout + .enabled + .then(|| match config.stdout.log_format { + StdoutFormat::Json => tracing_subscriber::fmt::layer().json().boxed(), + StdoutFormat::Pretty => tracing_subscriber::fmt::layer().pretty().boxed(), + }); + + let (otel_logs, otel_traces) = match &config.otelcol { + Some(cfg) if cfg.enabled => { + let tls = Self::build_tls(cfg); + let url = Self::parse_endpoint(&cfg.endpoint); + let resource = Resource::builder() + .with_service_name(service_name.to_string()) + .with_attribute(KeyValue::new( + opentelemetry_semantic_conventions::attribute::SERVICE_VERSION, + service_version.to_string(), + )) + .build(); + + let logs = cfg.logs.then(|| { + let provider = SdkLoggerProvider::builder() + .with_resource(resource.clone()) + .with_batch_exporter(build_exporter!( + LogExporter, + cfg, + url.clone(), + tls.clone(), + "/v1/logs" + )) + .build(); + let layer = + opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge::new( + &provider, + ) + .with_filter(Self::to_level_filter(cfg.log_level)); + logger_provider = Some(provider); + layer + }); + + let traces = cfg.traces.then(|| { + let provider = SdkTracerProvider::builder() + .with_resource(resource.clone()) + .with_batch_exporter(build_exporter!( + SpanExporter, + cfg, + url.clone(), + tls.clone(), + "/v1/traces" + )) + .build(); + let layer = tracing_opentelemetry::layer() + .with_tracer(provider.tracer(service_name.to_string())); + tracer_provider = Some(provider); + layer + }); + + (logs, traces) + } + _ => (None, None), + }; + + tracing_subscriber::registry() + .with(stdout) + .with(otel_logs) + .with(otel_traces) + .with(env_filter) + .init(); + + Self { + tracer_provider, + logger_provider, + } + } + + fn parse_endpoint(endpoint: &str) -> String { + if endpoint.starts_with("http://") { + endpoint.to_string() + } else if let Some(rest) = endpoint.strip_prefix("grpc://") { + format!("https://{rest}") + } else { + panic!("invalid OTLP endpoint: {endpoint}. Expected http:// or grpc://") + } + } + + fn build_tls(cfg: &OtelcolConfig) -> ClientTlsConfig { + let mut tls = ClientTlsConfig::new().with_native_roots(); + if let Some(path) = &cfg.tls_ca { + tls = tls.ca_certificate(Certificate::from_pem( + std::fs::read_to_string(path).expect("failed to read CA"), + )); + } + if let (Some(crt), Some(key)) = (&cfg.tls_crt, &cfg.tls_key) { + tls = tls.identity(Identity::from_pem( + std::fs::read_to_string(crt).expect("failed to read cert"), + std::fs::read_to_string(key).expect("failed to read key"), + )); + } + tls + } + + fn to_level_filter(level: LogLevel) -> tracing_subscriber::filter::LevelFilter { + match level { + LogLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE, + LogLevel::Debug => tracing_subscriber::filter::LevelFilter::DEBUG, + LogLevel::Info => tracing_subscriber::filter::LevelFilter::INFO, + LogLevel::Warning => tracing_subscriber::filter::LevelFilter::WARN, + LogLevel::Error => tracing_subscriber::filter::LevelFilter::ERROR, + } + } + + fn log_level_to_filter(level: LogLevel) -> &'static str { + match level { + LogLevel::Trace => "trace", + LogLevel::Debug => "debug", + LogLevel::Info => "info", + LogLevel::Warning => "warn", + LogLevel::Error => "error", + } + } + + pub fn shutdown(&mut self) { + if let Some(p) = self.tracer_provider.take() + && let Err(e) = p.shutdown() + { + tracing::error!("failed to shutdown tracer provider: {e}"); + } + if let Some(p) = self.logger_provider.take() + && let Err(e) = p.shutdown() + { + tracing::error!("failed to shutdown logger provider: {e}"); + } + } +} |
