feat(logging): Add configurable rate limit for logging#1464
feat(logging): Add configurable rate limit for logging#1464sergeymatov wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an optional CLI-configured tracing/logging rate limiter so lower-severity logs (info/debug/trace) can be throttled while keeping warn/error unthrottled, and ensures tracing is initialized early enough for startup logs to respect CLI settings.
Changes:
- Add
--tracing-rate-limit BURST:REPLENISH_PER_SECONDparsing + config plumbing inargs. - Add
TracingControl::init_with_rate_limit(...)and integratetracing-throttleintracectl. - Initialize tracing from
dataplane::main()before emitting startup logs / applying tracing commands.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tracectl/src/lib.rs | Re-export TracingRateLimitConfig from tracectl. |
| tracectl/src/control.rs | Introduce rate-limit config + initialization path using OnceLock and tracing-throttle. |
| tracectl/Cargo.toml | Add tracing-throttle dependency. |
| dataplane/src/main.rs | Initialize tracing with optional rate limit before startup logs and tracing command processing. |
| args/src/lib.rs | Add TracingRateLimit type, FromStr parser, CLI flag, and config section field. |
| Cargo.toml | Add workspace dependency for tracing-throttle (and minor formatting change). |
| Cargo.lock | Lockfile updates to include tracing-throttle and transitive dependency changes. |
10856c0 to
5404136
Compare
d7b33f3 to
082662d
Compare
There was a problem hiding this comment.
Pull request overview
Adds an optional CLI-configurable tracing/logging rate limit and ensures tracing is initialized early in dataplane so startup logs honor the CLI settings.
Changes:
- Add
--tracing-rate-limit BURST:REPLENISH_PER_SECONDparsing and plumbing into runtime configuration. - Extend
tracectltracing initialization to optionally apply a throttle layer for INFO/DEBUG/TRACE (leaving WARN/ERROR unthrottled). - Initialize tracing from
dataplane::main()before emitting startup logs.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tracectl/src/lib.rs |
Re-export TracingRateLimitConfig for downstream initialization. |
tracectl/src/control.rs |
Introduce rate-limit config + OnceLock init, and wire in tracing-throttle for lower-severity logs. |
tracectl/Cargo.toml |
Add tracing-throttle dependency. |
dataplane/src/main.rs |
Initialize tracing earlier and pass CLI-provided rate-limit config. |
args/src/lib.rs |
Add TracingRateLimit type, CLI flag, config propagation, and parsing tests. |
Cargo.toml |
Add workspace dependency on tracing-throttle. |
Cargo.lock |
Lockfile updates for new dependency (and transitive bumps). |
Comments suppressed due to low confidence (1)
args/src/lib.rs:1616
confidence: 10
tags: [logic]
`args/src/lib.rs` defines `#[cfg(test)] mod tests` twice at the same module scope, which will not compile (duplicate module name). Merge the new `TracingRateLimit` tests into the existing `mod tests` block (or rename one of the modules).
#[test]
fn tracing_rate_limit_parses_valid_values() {
let rate_limit = TracingRateLimit::from_str("10:20").unwrap();
assert_eq!(rate_limit.burst, 10);
assert_eq!(rate_limit.replenish_per_second, 20);
}
#[test]
fn tracing_rate_limit_rejects_missing_separator() {
let err = TracingRateLimit::from_str("10").unwrap_err();
assert_eq!(err, "Bad syntax: missing :");
}
#[test]
fn tracing_rate_limit_rejects_non_numeric_values() {
let err = TracingRateLimit::from_str("abc:20").unwrap_err();
assert!(err.starts_with("Bad burst value:"));
let err = TracingRateLimit::from_str("10:def").unwrap_err();
assert!(err.starts_with("Bad replenish-per-second value:"));
}
#[test]
fn tracing_rate_limit_rejects_zero_values() {
let err = TracingRateLimit::from_str("0:20").unwrap_err();
assert_eq!(err, "Burst must be greater than 0");
let err = TracingRateLimit::from_str("10:0").unwrap_err();
assert_eq!(err, "Replenish-per-second must be greater than 0");
}
}
</details>
| if args.tracing().is_none() { | ||
| get_trace_ctl() | ||
| .set_default_level(LevelFilter::DEBUG) | ||
| .expect("Setting default loglevel failed"); | ||
| } |
| Ok(policy) => match TracingRateLimitLayer::builder() | ||
| .with_policy(policy) | ||
| .with_summary_interval(Duration::from_secs(30)) | ||
| .build() | ||
| { | ||
| Ok(rate_limit) => { | ||
| let throttled_fmt_layer = tracing_subscriber::fmt::layer() | ||
| .with_line_number(true) | ||
| .with_target(true) | ||
| .with_thread_ids(false) | ||
| .with_thread_names(true) | ||
| .with_level(true) | ||
| .with_filter(throttled_level_filter.and(rate_limit)); | ||
| if let Err(e) = tracing_subscriber::registry() | ||
| .with(filter) | ||
| .with(unthrottled_fmt_layer) | ||
| .with(throttled_fmt_layer) | ||
| .with(tracing_error::ErrorLayer::default()) | ||
| .try_init() | ||
| { | ||
| eprintln!("Failed to set global tracing subscriber: {e} !!"); | ||
| } | ||
| } | ||
| Err(e) => { | ||
| eprintln!( | ||
| "Failed to create tracing throttle layer: {e}; falling back to unthrottled logging" | ||
| ); | ||
| let throttled_fmt_layer = tracing_subscriber::fmt::layer() | ||
| .with_line_number(true) | ||
| .with_target(true) | ||
| .with_thread_ids(false) | ||
| .with_thread_names(true) | ||
| .with_level(true) | ||
| .with_filter(throttled_level_filter); | ||
| if let Err(e) = tracing_subscriber::registry() | ||
| .with(filter) | ||
| .with(unthrottled_fmt_layer) | ||
| .with(throttled_fmt_layer) | ||
| .with(tracing_error::ErrorLayer::default()) | ||
| .try_init() | ||
| { | ||
| eprintln!("Failed to set global tracing subscriber: {e} !!"); | ||
| } | ||
| } | ||
| }, | ||
| Err(e) => { | ||
| eprintln!( | ||
| "Failed to create tracing throttle policy: {e}; falling back to unthrottled logging" | ||
| ); | ||
| let throttled_fmt_layer = tracing_subscriber::fmt::layer() | ||
| .with_line_number(true) | ||
| .with_target(true) | ||
| .with_thread_ids(false) | ||
| .with_thread_names(true) | ||
| .with_level(true) | ||
| .with_filter(throttled_level_filter); | ||
| if let Err(e) = tracing_subscriber::registry() | ||
| .with(filter) | ||
| .with(unthrottled_fmt_layer) | ||
| .with(throttled_fmt_layer) | ||
| .with(tracing_error::ErrorLayer::default()) | ||
| .try_init() | ||
| { | ||
| eprintln!("Failed to set global tracing subscriber: {e} !!"); | ||
| } |
| } | ||
| pub fn init_with_rate_limit(rate_limit_config: Option<TracingRateLimitConfig>) { | ||
| let mut initialized = false; | ||
| let has_rate_limit_config = rate_limit_config.is_some(); | ||
| let _ = TRACING_CTL.get_or_init(|| { | ||
| initialized = true; | ||
| TracingControl::new(rate_limit_config) | ||
| }); | ||
| if has_rate_limit_config && !initialized { | ||
| warn!("TracingControl already initialized; ignoring provided rate-limit config"); | ||
| } |
There was a problem hiding this comment.
This comment can be ignored
There was a problem hiding this comment.
How hard is it to write a test to cover this instead of telling copilot to go away?
There was a problem hiding this comment.
(And can AI write the test with some guidance?)
6e32425 to
b83c1bc
Compare
Introduce --tracing-rate-limit BURST:REPLENISH_PER_SECOND. if omitted, logging is unchanged and no throttling is applied. Initialize tracing explicitly from main() so CLI-provided throttle settings are applied before startup logs. Signed-off-by: Sergey Matov <sergey.matov@githedgehog.com>
b83c1bc to
d7eee76
Compare
Introduce --tracing-rate-limit BURST:REPLENISH_PER_SECOND. if omitted, logging is unchanged and no throttling is applied. Initialize tracing explicitly from main() so CLI-provided throttle settings are applied before startup logs.