diff --git a/.env.example b/.env.example index 7c4d7fe..445fc55 100644 --- a/.env.example +++ b/.env.example @@ -116,10 +116,11 @@ # LOGGING # ============================================================================ -# Log level for application logging +# Application log level or explicit tracing filter expression # CLI: --log-level # Default: info # Options: error, warn, info, debug, trace +# Bare levels keep dependency logging at warn # Can also use filter expressions: ngit_grasp=debug,actix_web=info # NGIT_LOG_LEVEL=info diff --git a/CHANGELOG.md b/CHANGELOG.md index 8789f22..2dc05c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Scope bare log levels to ngit-grasp while keeping dependencies at warnings; + explicit tracing filter expressions remain unchanged. - Metrics compatibility: removed the `ngit_sync_naughty_relay_info{relay,category,reason}` metric because its relay and raw reason labels were peer-controlled and unbounded. Use the unchanged diff --git a/docs/explanation/monitoring.md b/docs/explanation/monitoring.md index 7d6d939..b0905df 100644 --- a/docs/explanation/monitoring.md +++ b/docs/explanation/monitoring.md @@ -50,6 +50,24 @@ flowchart TB | Abuse threshold | `--abuse-threshold` | `NGIT_ABUSE_THRESHOLD` | `10` | Max connections per IP before flagging | | Top N repos | `--top-n-repos` | `NGIT_TOP_N_REPOS` | `10` | Number of top bandwidth repos to track | +## Logging severity policy + +Operational logging classifies records by who can act on them: + +- `error` identifies an internal, persistence, or process failure that may + require an operator response. +- `warn` identifies a degraded application path, bounded retry, or cooldown + that affects service behavior but remains recoverable. +- `info` records application lifecycle, aggregate batch outcomes, and durable + state transitions. +- `debug` carries individual client, peer, event, filter, and capability + negotiation details. Invalid or unsupported remote input is expected on a + public relay and is not operator-actionable by itself. + +For a bare `NGIT_LOG_LEVEL`, dependencies remain at `warn` while the selected +level applies to ngit-grasp. Use an explicit tracing filter expression when a +dependency needs temporary diagnostics. + ## Privacy Model IP addresses are **never exposed in Prometheus metrics**. The connection tracker maintains per-IP counts internally only for abuse detection: diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 7b298d9..769dc65 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1586,7 +1586,7 @@ equivalent standard NIP-11 field, so it remains documented configuration only. #### `NGIT_LOG_LEVEL` -**Description:** Logging level and filters for application logging +**Description:** Application logging level or explicit tracing filter expression **Type:** String (log level or filter expression) **Default:** `info` **Required:** No @@ -1595,11 +1595,11 @@ equivalent standard NIP-11 field, so it remains documented configuration only. ```bash # Simple levels -NGIT_LOG_LEVEL=error # Errors only -NGIT_LOG_LEVEL=warn # Warnings and errors -NGIT_LOG_LEVEL=info # Info, warnings, errors (default) -NGIT_LOG_LEVEL=debug # Debug and above -NGIT_LOG_LEVEL=trace # Everything (very verbose) +NGIT_LOG_LEVEL=error # ngit-grasp errors; dependency warnings and errors +NGIT_LOG_LEVEL=warn # ngit-grasp warnings and errors; dependency warnings and errors +NGIT_LOG_LEVEL=info # ngit-grasp info and above; dependency warnings and errors (default) +NGIT_LOG_LEVEL=debug # ngit-grasp debug and above; dependency warnings and errors +NGIT_LOG_LEVEL=trace # ngit-grasp trace and above; dependency warnings and errors # Module-specific filtering NGIT_LOG_LEVEL=ngit_grasp=debug,actix_web=info @@ -1608,6 +1608,11 @@ NGIT_LOG_LEVEL=ngit_grasp=debug,actix_web=info NGIT_LOG_LEVEL=debug,hyper=info,tokio=warn ``` +Bare levels are expanded to `warn,ngit_grasp=` so application +diagnostics do not also enable verbose internals from every dependency. +Expressions containing target directives or commas are passed through +unchanged, leaving dependency verbosity under explicit operator control. + **Log levels (most to least verbose):** 1. `trace` - Very detailed, performance impact @@ -1631,6 +1636,7 @@ NGIT_LOG_LEVEL=info **Notes:** - Uses Rust's `tracing` crate filter syntax +- Bare levels apply to ngit-grasp while dependencies remain at `warn` - Supports module-level filtering (e.g., `ngit_grasp=debug,hyper=info`) - `trace` level can significantly impact performance - For production, `info` or `warn` is recommended diff --git a/nix/module.nix b/nix/module.nix index f1eea90..47e6887 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -204,7 +204,8 @@ let default = "info"; example = "debug"; description = '' - Logging level for application logging. + Application logging level or explicit tracing filter expression. + Bare levels keep dependency logging at warn. Can be a simple level (trace, debug, info, warn, error) or a filter expression. Examples: "info", "debug", "ngit_grasp=debug,actix_web=info" ''; diff --git a/src/config.rs b/src/config.rs index ee4b423..89ecb1c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -679,7 +679,8 @@ pub struct Config { #[arg(long, env = "NGIT_RELAY_FILTER_LIMIT", default_value_t = 500)] pub relay_filter_limit: usize, - /// Log level for application logging + /// Application log level or an explicit tracing filter expression. + /// Bare levels keep dependency logging at warn. #[arg(long, env = "NGIT_LOG_LEVEL", default_value = "info")] pub log_level: String, } diff --git a/src/logging.rs b/src/logging.rs index d2bf920..1c939a3 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -6,6 +6,26 @@ use tracing_subscriber::layer::{Context, Filter}; const LOCAL_RELAY_TARGET: &str = "nostr_sdk::local_relay::local::inner"; const UNCLEAN_RESET: &str = "WebSocket protocol error: Connection reset without closing handshake"; +/// Expand a bare application level into an explicit `EnvFilter` directive. +/// +/// `NGIT_LOG_LEVEL` is documented as the application log level. Applying a +/// bare level directly to `EnvFilter` also enables every dependency at that +/// level, which makes ordinary `info` deployments inherit verbose SDK and +/// protocol diagnostics. Keep dependencies at warnings for bare levels while +/// preserving explicit operator-authored filter expressions verbatim. +pub fn effective_log_filter(configured: &str) -> String { + let trimmed = configured.trim(); + let level = trimmed.to_ascii_lowercase(); + if matches!( + level.as_str(), + "trace" | "debug" | "info" | "warn" | "error" | "off" + ) { + format!("warn,ngit_grasp={level}") + } else { + configured.to_string() + } +} + /// Suppresses the routine unclean-disconnect event emitted by the embedded relay. /// /// Internet clients commonly disappear without completing the WebSocket close @@ -56,7 +76,26 @@ fn is_routine_disconnect_message(message: &str) -> bool { #[cfg(test)] mod tests { - use super::is_routine_disconnect_message; + use super::{effective_log_filter, is_routine_disconnect_message}; + + #[test] + fn bare_levels_scope_verbose_output_to_ngit_grasp() { + assert_eq!(effective_log_filter("debug"), "warn,ngit_grasp=debug"); + assert_eq!(effective_log_filter(" INFO "), "warn,ngit_grasp=info"); + assert_eq!(effective_log_filter("off"), "warn,ngit_grasp=off"); + } + + #[test] + fn explicit_filter_expressions_remain_operator_controlled() { + assert_eq!( + effective_log_filter("ngit_grasp=debug,nostr_sdk=info"), + "ngit_grasp=debug,nostr_sdk=info" + ); + assert_eq!( + effective_log_filter("debug,hyper=info,tokio=warn"), + "debug,hyper=info,tokio=warn" + ); + } #[test] fn identifies_reset_without_close_handshake() { diff --git a/src/main.rs b/src/main.rs index 9c44ba3..345ef4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,10 @@ use tracing::info; use tracing_subscriber::{filter::FilterExt, layer::SubscriberExt, EnvFilter, Layer}; use ngit_grasp::{ - cleanup_empty_repos, config::Config, logging::SuppressRoutineDisconnects, nostr, + cleanup_empty_repos, + config::Config, + logging::{effective_log_filter, SuppressRoutineDisconnects}, + nostr, server::RelayServer, }; @@ -70,8 +73,8 @@ async fn main() -> Result<()> { /// this function only owns concerns specific to the standalone binary: /// the global tracing subscriber and signal handling. async fn run_relay(config: Config) -> Result<()> { - // Initialize tracing with configured log level - let filter = EnvFilter::new(&config.log_level).and(SuppressRoutineDisconnects); + let effective_filter = effective_log_filter(&config.log_level); + let filter = EnvFilter::new(&effective_filter).and(SuppressRoutineDisconnects); // Only colorize when stdout is a terminal: ANSI escape codes in redirected // logs corrupt journald/file output and break log-scraping consumers. let subscriber = tracing_subscriber::registry().with( @@ -81,7 +84,11 @@ async fn run_relay(config: Config) -> Result<()> { ); tracing::subscriber::set_global_default(subscriber)?; - info!("Starting ngit-grasp with log level: {}", config.log_level); + info!( + configured_log_level = %config.log_level, + effective_log_filter = %effective_filter, + "Starting ngit-grasp" + ); let server = RelayServer::start(config).await?;