fix(logging): scope bare levels to the application

Production NGIT_LOG_LEVEL=info currently enables INFO records from every dependency as well as ngit-grasp, obscuring application health with SDK and protocol chatter.

Expand bare levels into an explicit warn dependency baseline plus the requested ngit_grasp level. Preserve full EnvFilter expressions verbatim so operators retain precise temporary diagnostics, and document the severity contract consistently across source, NixOS, the environment example, and reference material.

This commit deliberately does not reclassify individual application call sites; those changes remain separately reviewable. Validated with cargo fmt --check and the focused logging unit tests (4 passed).
This commit is contained in:
DanConwayDev
2026-08-15 14:13:35 +00:00
parent 22abda22a8
commit 7b7b098b8e
8 changed files with 89 additions and 14 deletions
+2 -1
View File
@@ -116,10 +116,11 @@
# LOGGING
# ============================================================================
# Log level for application logging
# Application log level or explicit tracing filter expression
# CLI: --log-level <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
+2
View File
@@ -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
+18
View File
@@ -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:
+12 -6
View File
@@ -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=<level>` 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
+2 -1
View File
@@ -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"
'';
+2 -1
View File
@@ -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,
}
+40 -1
View File
@@ -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() {
+11 -4
View File
@@ -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?;