From 0325b89d26d257732aed9561b4651061a6db0d35 Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 13:55:32 -0400 Subject: [PATCH 1/6] [SVLS-9883] feat: add datadog-serverless-logging crate for DD_LOG_LEVEL_BY_TARGET Adds a crate with the DD_LOG_LEVEL_BY_TARGET name and build_env_filter, which adds each valid `target=level` entry to an agent's base filter and returns the invalid entries. The Lambda extension will use it, and datadog-serverless-compat can use it later. Each agent keeps its own subscriber and log format. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 7 ++ crates/datadog-serverless-logging/Cargo.toml | 10 ++ crates/datadog-serverless-logging/README.md | 8 ++ crates/datadog-serverless-logging/src/lib.rs | 120 +++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 crates/datadog-serverless-logging/Cargo.toml create mode 100644 crates/datadog-serverless-logging/README.md create mode 100644 crates/datadog-serverless-logging/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index acede494..17e928f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,13 @@ dependencies = [ "zstd", ] +[[package]] +name = "datadog-serverless-logging" +version = "0.1.0" +dependencies = [ + "tracing-subscriber", +] + [[package]] name = "datadog-trace-agent" version = "0.1.0" diff --git a/crates/datadog-serverless-logging/Cargo.toml b/crates/datadog-serverless-logging/Cargo.toml new file mode 100644 index 00000000..337d2b79 --- /dev/null +++ b/crates/datadog-serverless-logging/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "datadog-serverless-logging" +version = "0.1.0" +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +tracing-subscriber = { version = "0.3", default-features = false, features = ["std", "env-filter"] } diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md new file mode 100644 index 00000000..b5562d48 --- /dev/null +++ b/crates/datadog-serverless-logging/README.md @@ -0,0 +1,8 @@ +# Datadog Serverless Logging + +Log filter setup shared by the serverless agents. + +`build_env_filter` builds a `tracing-subscriber` filter from the agent's base directives and the +value of `DD_LOG_LEVEL_BY_TARGET`, a comma-separated list of `target=level` entries. For +example, `DD_LOG_LEVEL_BY_TARGET=dogstatsd=debug` turns on debug logs from the `dogstatsd` +crate only. Each agent keeps its own subscriber and log format. diff --git a/crates/datadog-serverless-logging/src/lib.rs b/crates/datadog-serverless-logging/src/lib.rs new file mode 100644 index 00000000..23d4ea33 --- /dev/null +++ b/crates/datadog-serverless-logging/src/lib.rs @@ -0,0 +1,120 @@ +// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Log filter setup shared by the serverless agents. + +use tracing_subscriber::{ + EnvFilter, + filter::{Directive, ParseError}, +}; + +/// Comma-separated `target=level` entries, each of which sets the log level of one tracing +/// target. The other targets keep the agent's default level. +pub const LEVEL_BY_TARGET_ENV_VAR: &str = "DD_LOG_LEVEL_BY_TARGET"; + +/// Builds a log filter from the agent's base directives, which include its default level, such +/// as `h2=off,info`, and a [`LEVEL_BY_TARGET_ENV_VAR`] value. +/// +/// Targets include the module paths of dependencies, which use underscores, such as +/// `dogstatsd`. An entry matches every target that starts with its text. +/// +/// Returns the entries that are not valid, so that the caller can log them after it sets up the +/// subscriber. A typo then gives a warning instead of a failed startup. +/// +/// # Errors +/// +/// Returns an error if `base` is not a valid filter. +pub fn build_env_filter( + base: &str, + levels_by_target: &str, +) -> Result<(EnvFilter, Vec), ParseError> { + let mut env_filter = EnvFilter::try_new(base)?; + let mut invalid_entries = Vec::new(); + for entry in levels_by_target + .split(',') + .map(str::trim) + .filter(|e| !e.is_empty()) + { + match parse_target_level(entry) { + Some(directive) => env_filter = env_filter.add_directive(directive), + None => invalid_entries.push(entry.to_string()), + } + } + Ok((env_filter, invalid_entries)) +} + +/// Parses one `target=level` entry. Rejects the other forms that `EnvFilter` accepts, such as a +/// span filter like `[span]`, or a level with no target, which would replace the default level. +fn parse_target_level(entry: &str) -> Option { + let (target, level) = entry.split_once('=')?; + let (target, level) = (target.trim(), level.trim()); + let valid_target = + !target.is_empty() && !target.contains(|c: char| c.is_whitespace() || "[]{}".contains(c)); + let valid_level = matches!( + level.to_ascii_lowercase().as_str(), + "off" | "error" | "warn" | "info" | "debug" | "trace" + ); + if !(valid_target && valid_level) { + return None; + } + format!("{target}={level}").parse().ok() +} + +#[cfg(test)] +mod tests { + use super::build_env_filter; + + fn directives(base: &str, levels_by_target: &str) -> (Vec, Vec) { + let (filter, invalid) = + build_env_filter(base, levels_by_target).expect("base filter is valid"); + let directives = filter.to_string().split(',').map(String::from).collect(); + (directives, invalid) + } + + #[test] + fn adds_directive_per_entry() { + let (directives, invalid) = directives( + "h2=off,info", + " cold_start_duration=debug, trace_flush_duration = TRACE ,", + ); + assert!(directives.contains(&"cold_start_duration=debug".to_string())); + assert!(directives.contains(&"trace_flush_duration=trace".to_string())); + assert!(directives.contains(&"info".to_string())); + assert!(invalid.is_empty()); + } + + #[test] + fn skips_invalid_entries() { + let (directives, invalid) = directives( + "h2=off,info", + "debug,cold_start_duration,cold_start_duration=verbose,=debug,[span]=debug,a b=debug,trace_flush_duration=debug", + ); + assert!(directives.contains(&"trace_flush_duration=debug".to_string())); + assert!(directives.contains(&"info".to_string())); + assert!(!directives.contains(&"debug".to_string())); + assert_eq!( + invalid, + vec![ + "debug", + "cold_start_duration", + "cold_start_duration=verbose", + "=debug", + "[span]=debug", + "a b=debug", + ] + ); + } + + #[test] + fn overrides_base_directives() { + let (directives, _) = directives("h2=off,debug", "bottlecap::logs=warn,h2=debug"); + assert!(directives.contains(&"bottlecap::logs=warn".to_string())); + assert!(directives.contains(&"h2=debug".to_string())); + assert!(!directives.contains(&"h2=off".to_string())); + } + + #[test] + fn rejects_invalid_base() { + assert!(build_env_filter("info,cold_start_duration=verbose", "").is_err()); + } +} From b35c085583e4c85b3ad0c885bc7440e9640f9faf Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:10:24 -0400 Subject: [PATCH 2/6] [SVLS-9883] docs: list the kinds of targets for DD_LOG_LEVEL_BY_TARGET Co-Authored-By: Claude Opus 5.5 --- crates/datadog-serverless-logging/README.md | 34 ++++++++++++++++++-- crates/datadog-serverless-logging/src/lib.rs | 5 +-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md index b5562d48..95a9d0df 100644 --- a/crates/datadog-serverless-logging/README.md +++ b/crates/datadog-serverless-logging/README.md @@ -3,6 +3,34 @@ Log filter setup shared by the serverless agents. `build_env_filter` builds a `tracing-subscriber` filter from the agent's base directives and the -value of `DD_LOG_LEVEL_BY_TARGET`, a comma-separated list of `target=level` entries. For -example, `DD_LOG_LEVEL_BY_TARGET=dogstatsd=debug` turns on debug logs from the `dogstatsd` -crate only. Each agent keeps its own subscriber and log format. +value of `DD_LOG_LEVEL_BY_TARGET`. The value is a comma-separated list of `target=level` +entries. Each entry sets the log level of one target, and the other targets keep the agent's +default level. Each agent keeps its own subscriber and log format. + +For example, with the shell command + +```sh +export DD_LOG_LEVEL_BY_TARGET="cold_start_duration=debug,dogstatsd=debug" +``` + +the agent logs debug messages with the target `cold_start_duration`, and debug messages from the +`dogstatsd` crate. + +## Targets + +Every `tracing` statement has a target. An entry matches each target that starts with the +entry's text. + +| Kind of target | Where the target comes from | Example entry | +| --- | --- | --- | +| Custom target | The statement sets it: `debug!(target: "cold_start_duration", ...)` | `cold_start_duration=debug` | +| Module of the agent | A statement with no `target:` gets its module path, such as `bottlecap::traces::trace_flusher`. The entry also matches the modules under it. | `bottlecap::traces=debug` | +| All of the agent's own code | The crate name matches all its module paths. It does not match custom targets. | `bottlecap=debug` | +| Crate from this repository | Its module paths start with the crate name, with underscores in place of hyphens | `dogstatsd=debug`, `datadog_agent_config=debug` | +| Third-party crate | The same as for a crate from this repository | `hyper=debug` | + +Because of the prefix match, do not give a custom target a name that starts with the name of +another target. For example, an entry for `trace_flush` also matches `trace_flush_duration`. + +Only `tracing` events are filtered. A crate that logs with the `log` crate needs a `log` bridge +in the agent. diff --git a/crates/datadog-serverless-logging/src/lib.rs b/crates/datadog-serverless-logging/src/lib.rs index 23d4ea33..74bbb2be 100644 --- a/crates/datadog-serverless-logging/src/lib.rs +++ b/crates/datadog-serverless-logging/src/lib.rs @@ -15,8 +15,9 @@ pub const LEVEL_BY_TARGET_ENV_VAR: &str = "DD_LOG_LEVEL_BY_TARGET"; /// Builds a log filter from the agent's base directives, which include its default level, such /// as `h2=off,info`, and a [`LEVEL_BY_TARGET_ENV_VAR`] value. /// -/// Targets include the module paths of dependencies, which use underscores, such as -/// `dogstatsd`. An entry matches every target that starts with its text. +/// A target is a custom target that a statement sets, such as `cold_start_duration`, or a +/// module path, such as `bottlecap::traces` or the crate name `dogstatsd`. An entry matches +/// every target that starts with its text. The crate README lists the kinds of targets. /// /// Returns the entries that are not valid, so that the caller can log them after it sets up the /// subscriber. A typo then gives a warning instead of a failed startup. From c7af0ce026a7cf6fb07551c08a8360a929001fdc Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:14:13 -0400 Subject: [PATCH 3/6] [SVLS-9883] docs: show DD_LOG_LEVEL_BY_TARGET as a function environment variable Co-Authored-By: Claude Opus 5.5 --- crates/datadog-serverless-logging/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md index 95a9d0df..3afc6671 100644 --- a/crates/datadog-serverless-logging/README.md +++ b/crates/datadog-serverless-logging/README.md @@ -7,14 +7,14 @@ value of `DD_LOG_LEVEL_BY_TARGET`. The value is a comma-separated list of `targe entries. Each entry sets the log level of one target, and the other targets keep the agent's default level. Each agent keeps its own subscriber and log format. -For example, with the shell command +Set it as an environment variable of the function, such as a Lambda function. For example: -```sh -export DD_LOG_LEVEL_BY_TARGET="cold_start_duration=debug,dogstatsd=debug" -``` +| Environment variable | Value | +| --- | --- | +| `DD_LOG_LEVEL_BY_TARGET` | `cold_start_duration=debug,dogstatsd=debug` | -the agent logs debug messages with the target `cold_start_duration`, and debug messages from the -`dogstatsd` crate. +With this value, the agent logs debug messages with the target `cold_start_duration`, and debug +messages from the `dogstatsd` crate. ## Targets From 4f1163ac7a40da8b371e624913bc08379898b3d3 Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:16:42 -0400 Subject: [PATCH 4/6] [SVLS-9883] docs: say base filter instead of directives Co-Authored-By: Claude Opus 5.5 --- crates/datadog-serverless-logging/README.md | 5 ++- crates/datadog-serverless-logging/src/lib.rs | 39 ++++++++++---------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md index 3afc6671..1e13ac3e 100644 --- a/crates/datadog-serverless-logging/README.md +++ b/crates/datadog-serverless-logging/README.md @@ -2,8 +2,9 @@ Log filter setup shared by the serverless agents. -`build_env_filter` builds a `tracing-subscriber` filter from the agent's base directives and the -value of `DD_LOG_LEVEL_BY_TARGET`. The value is a comma-separated list of `target=level` +`build_env_filter` builds a `tracing-subscriber` filter from the agent's base filter, such as +`h2=off,info`, and the value of `DD_LOG_LEVEL_BY_TARGET`. The base filter turns off some targets +and sets the default level. The value is a comma-separated list of `target=level` entries. Each entry sets the log level of one target, and the other targets keep the agent's default level. Each agent keeps its own subscriber and log format. diff --git a/crates/datadog-serverless-logging/src/lib.rs b/crates/datadog-serverless-logging/src/lib.rs index 74bbb2be..7f0b6175 100644 --- a/crates/datadog-serverless-logging/src/lib.rs +++ b/crates/datadog-serverless-logging/src/lib.rs @@ -12,8 +12,9 @@ use tracing_subscriber::{ /// target. The other targets keep the agent's default level. pub const LEVEL_BY_TARGET_ENV_VAR: &str = "DD_LOG_LEVEL_BY_TARGET"; -/// Builds a log filter from the agent's base directives, which include its default level, such -/// as `h2=off,info`, and a [`LEVEL_BY_TARGET_ENV_VAR`] value. +/// Builds a log filter from the agent's base filter and a [`LEVEL_BY_TARGET_ENV_VAR`] value. +/// The base filter uses the same comma-separated syntax, and it also sets the default level. For +/// example, `h2=off,info` turns off the `h2` logs and sets the default level to info. /// /// A target is a custom target that a statement sets, such as `cold_start_duration`, or a /// module path, such as `bottlecap::traces` or the crate name `dogstatsd`. An entry matches @@ -65,34 +66,34 @@ fn parse_target_level(entry: &str) -> Option { mod tests { use super::build_env_filter; - fn directives(base: &str, levels_by_target: &str) -> (Vec, Vec) { + fn filter_entries(base: &str, levels_by_target: &str) -> (Vec, Vec) { let (filter, invalid) = build_env_filter(base, levels_by_target).expect("base filter is valid"); - let directives = filter.to_string().split(',').map(String::from).collect(); - (directives, invalid) + let entries = filter.to_string().split(',').map(String::from).collect(); + (entries, invalid) } #[test] - fn adds_directive_per_entry() { - let (directives, invalid) = directives( + fn adds_level_per_entry() { + let (entries, invalid) = filter_entries( "h2=off,info", " cold_start_duration=debug, trace_flush_duration = TRACE ,", ); - assert!(directives.contains(&"cold_start_duration=debug".to_string())); - assert!(directives.contains(&"trace_flush_duration=trace".to_string())); - assert!(directives.contains(&"info".to_string())); + assert!(entries.contains(&"cold_start_duration=debug".to_string())); + assert!(entries.contains(&"trace_flush_duration=trace".to_string())); + assert!(entries.contains(&"info".to_string())); assert!(invalid.is_empty()); } #[test] fn skips_invalid_entries() { - let (directives, invalid) = directives( + let (entries, invalid) = filter_entries( "h2=off,info", "debug,cold_start_duration,cold_start_duration=verbose,=debug,[span]=debug,a b=debug,trace_flush_duration=debug", ); - assert!(directives.contains(&"trace_flush_duration=debug".to_string())); - assert!(directives.contains(&"info".to_string())); - assert!(!directives.contains(&"debug".to_string())); + assert!(entries.contains(&"trace_flush_duration=debug".to_string())); + assert!(entries.contains(&"info".to_string())); + assert!(!entries.contains(&"debug".to_string())); assert_eq!( invalid, vec![ @@ -107,11 +108,11 @@ mod tests { } #[test] - fn overrides_base_directives() { - let (directives, _) = directives("h2=off,debug", "bottlecap::logs=warn,h2=debug"); - assert!(directives.contains(&"bottlecap::logs=warn".to_string())); - assert!(directives.contains(&"h2=debug".to_string())); - assert!(!directives.contains(&"h2=off".to_string())); + fn overrides_base_filter() { + let (entries, _) = filter_entries("h2=off,debug", "bottlecap::logs=warn,h2=debug"); + assert!(entries.contains(&"bottlecap::logs=warn".to_string())); + assert!(entries.contains(&"h2=debug".to_string())); + assert!(!entries.contains(&"h2=off".to_string())); } #[test] From 6ca81fdbb6af22c5486dadc687375071c88ca438 Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:19:58 -0400 Subject: [PATCH 5/6] [SVLS-9883] docs: write the entry format as = Co-Authored-By: Claude Opus 5.5 --- crates/datadog-serverless-logging/README.md | 2 +- crates/datadog-serverless-logging/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md index 1e13ac3e..3c187e28 100644 --- a/crates/datadog-serverless-logging/README.md +++ b/crates/datadog-serverless-logging/README.md @@ -4,7 +4,7 @@ Log filter setup shared by the serverless agents. `build_env_filter` builds a `tracing-subscriber` filter from the agent's base filter, such as `h2=off,info`, and the value of `DD_LOG_LEVEL_BY_TARGET`. The base filter turns off some targets -and sets the default level. The value is a comma-separated list of `target=level` +and sets the default level. The value is a comma-separated list of `=` entries. Each entry sets the log level of one target, and the other targets keep the agent's default level. Each agent keeps its own subscriber and log format. diff --git a/crates/datadog-serverless-logging/src/lib.rs b/crates/datadog-serverless-logging/src/lib.rs index 7f0b6175..991904f8 100644 --- a/crates/datadog-serverless-logging/src/lib.rs +++ b/crates/datadog-serverless-logging/src/lib.rs @@ -8,7 +8,7 @@ use tracing_subscriber::{ filter::{Directive, ParseError}, }; -/// Comma-separated `target=level` entries, each of which sets the log level of one tracing +/// Comma-separated `=` entries, each of which sets the log level of one tracing /// target. The other targets keep the agent's default level. pub const LEVEL_BY_TARGET_ENV_VAR: &str = "DD_LOG_LEVEL_BY_TARGET"; @@ -45,7 +45,7 @@ pub fn build_env_filter( Ok((env_filter, invalid_entries)) } -/// Parses one `target=level` entry. Rejects the other forms that `EnvFilter` accepts, such as a +/// Parses one `=` entry. Rejects the other forms that `EnvFilter` accepts, such as a /// span filter like `[span]`, or a level with no target, which would replace the default level. fn parse_target_level(entry: &str) -> Option { let (target, level) = entry.split_once('=')?; From e5524046ed1184b32de656e3a86eed91976180f8 Mon Sep 17 00:00:00 2001 From: lym953 <10097700+lym953@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:24:19 -0400 Subject: [PATCH 6/6] [SVLS-9883] docs: split the README into Usage, Targets and Use in an agent Co-Authored-By: Claude Opus 5.5 --- crates/datadog-serverless-logging/README.md | 45 +++++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/datadog-serverless-logging/README.md b/crates/datadog-serverless-logging/README.md index 3c187e28..b514793b 100644 --- a/crates/datadog-serverless-logging/README.md +++ b/crates/datadog-serverless-logging/README.md @@ -1,14 +1,14 @@ # Datadog Serverless Logging -Log filter setup shared by the serverless agents. +This crate lets a user set the log level of one part of a serverless agent, such as the Lambda +extension. Then the user can see the debug logs of that part without the debug logs of the +whole agent. -`build_env_filter` builds a `tracing-subscriber` filter from the agent's base filter, such as -`h2=off,info`, and the value of `DD_LOG_LEVEL_BY_TARGET`. The base filter turns off some targets -and sets the default level. The value is a comma-separated list of `=` -entries. Each entry sets the log level of one target, and the other targets keep the agent's -default level. Each agent keeps its own subscriber and log format. +## Usage -Set it as an environment variable of the function, such as a Lambda function. For example: +Set the environment variable `DD_LOG_LEVEL_BY_TARGET` on the function, such as a Lambda +function. Its value is a comma-separated list of `=` entries. Each entry sets the +log level of one target, and the other targets keep the agent's default level. For example: | Environment variable | Value | | --- | --- | @@ -17,21 +17,40 @@ Set it as an environment variable of the function, such as a Lambda function. Fo With this value, the agent logs debug messages with the target `cold_start_duration`, and debug messages from the `dogstatsd` crate. +The level is one of `off`, `error`, `warn`, `info`, `debug` and `trace`, in any case. The agent +logs a warning for an entry that is not valid, and ignores that entry. For example, `debug` with +no target is not valid. + ## Targets -Every `tracing` statement has a target. An entry matches each target that starts with the -entry's text. +The agents log with [`tracing`](https://docs.rs/tracing), a Rust logging library. Every log +statement has a target. An entry matches each target that starts with the entry's text. | Kind of target | Where the target comes from | Example entry | | --- | --- | --- | | Custom target | The statement sets it: `debug!(target: "cold_start_duration", ...)` | `cold_start_duration=debug` | -| Module of the agent | A statement with no `target:` gets its module path, such as `bottlecap::traces::trace_flusher`. The entry also matches the modules under it. | `bottlecap::traces=debug` | -| All of the agent's own code | The crate name matches all its module paths. It does not match custom targets. | `bottlecap=debug` | +| Module of the agent | A statement with no `target:` gets its module path, such as `bottlecap::traces::trace_flusher` in the Lambda extension. The entry also matches the modules under it. | `bottlecap::traces=debug` | +| All of the agent's own code | The crate name matches all its module paths. The Lambda extension's crate is `bottlecap`. It does not match custom targets. | `bottlecap=debug` | | Crate from this repository | Its module paths start with the crate name, with underscores in place of hyphens | `dogstatsd=debug`, `datadog_agent_config=debug` | | Third-party crate | The same as for a crate from this repository | `hyper=debug` | Because of the prefix match, do not give a custom target a name that starts with the name of another target. For example, an entry for `trace_flush` also matches `trace_flush_duration`. -Only `tracing` events are filtered. A crate that logs with the `log` crate needs a `log` bridge -in the agent. +## Use in an agent + +The agent calls the function `build_env_filter` once, when it sets up logging. The function +takes two inputs: + +- The agent's base filter, in the same format. It turns off some targets and sets the default + level. For example, `h2=off,info` turns off the `h2` logs and sets the default level to info. +- The value of `DD_LOG_LEVEL_BY_TARGET`. + +It returns the filter, and the entries that are not valid. The agent gives the filter to +[`tracing-subscriber`](https://docs.rs/tracing-subscriber), the library that writes the agent's +logs. Then it logs a warning for the entries that are not valid. Each agent keeps its own log +format. + +Only `tracing` events go through the filter. If a crate logs with the +[`log`](https://docs.rs/log) crate, the agent must install a bridge from `log` to `tracing`, for +example `tracing_log::LogTracer`.