Skip to content

[SVLS-9883] feat: add DD_LOG_LEVEL_BY_TARGET to set log levels per target - #1413

Open
lym953 wants to merge 14 commits into
yiming.luo/cold-start-duration-logsfrom
yiming.luo/log-debug-targets
Open

lym953 wants to merge 14 commits into
yiming.luo/cold-start-duration-logsfrom
yiming.luo/log-debug-targets

Conversation

@lym953

@lym953 lym953 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1412. Review that PR first. The new variable is parsed by the crate datadog-serverless-logging, from DataDog/serverless-components#180.

Problem

DD_LOG_LEVEL=debug turns on debug logs from all parts of the extension. In a test, three cold starts at debug level logged 117 debug lines, when only 33 of them were cold start durations. Too many logs can be noisy for users (either customers or Datadog engineers) who just want to debug cold starts.

This PR

This PR adds DD_LOG_LEVEL_BY_TARGET, a comma-separated list of <target>=<level> entries. Each entry sets the log level of one tracing target, and DD_LOG_LEVEL stays the level of all other targets. Both are environment variables of the Lambda function. For example:

Environment variable Value
DD_LOG_LEVEL info
DD_LOG_LEVEL_BY_TARGET cold_start_duration=debug,dogstatsd=debug

Suppose there are logging statements with target cold_start_duration, like:

debug!(
    target: "cold_start_duration",
    "Datadog Next-Gen Extension ready in {:}ms",
    start_time.elapsed().as_millis().to_string()
);

Then here are some examples (each row is one scenario):

DD_LOG_LEVEL DD_LOG_LEVEL_BY_TARGET Logs that you get
info cold_start_duration=debug Info logs, plus the cold start step durations from #1412
debug cold_start_duration=off All debug logs except the cold start step durations
info dogstatsd=debug Info logs, plus debug logs from the dogstatsd crate of serverless-components
not set cold_start_duration=debug Info logs, plus the cold start step durations. Info is the default level.

A target can be a custom target that the logging statement sets, a module path such as bottlecap::traces, or a crate name such as dogstatsd. An entry matches every target that starts with its text. The README of datadog-serverless-logging lists all the kinds of targets.

The extension logs a warning for an entry that is not valid, and skips it. Examples are a level with no target such as debug, which would otherwise replace DD_LOG_LEVEL, and an unknown level such as cold_start_duration=verbose.

Why not DD_LOG_LEVEL

The Datadog libraries and tracers in the function also read DD_LOG_LEVEL, and they accept only a plain level. For example, datadog-lambda-python logs "Invalid log level" for any other value, and dd-trace-rs logs a parse error. Support for <target>=<level> entries in DD_LOG_LEVEL would need changes in several repositories. These include tracers such as dd-trace-java and dd-trace-dotnet, which Serverless teams do not own and which run in many environments other than Lambda.

Testing

I built an arm64 layer of this branch at 4a71d023 and deployed it on a Python 3.13 arm64 function. Since then, the branch was rebased onto main, changed one code comment, and moved to the merge commit of #180, which has the same code. For each row, I forced 2 cold starts and counted the extension's log lines in CloudWatch. Each cold start logs 11 cold start duration lines.

DD_LOG_LEVEL DD_LOG_LEVEL_BY_TARGET Debug lines Cold start duration lines Warnings ERROR lines
info cold_start_duration=debug 22 22 0 0
debug cold_start_duration=off 56 0 0 0
info debug,cold_start_duration=verbose,cold_start_duration=debug 22 22 2 0
info dogstatsd=debug 10 0 0 0
not set cold_start_duration=debug 22 22 0 0

In the third row, the extension ignored debug and cold_start_duration=verbose with a warning, and debug did not turn on the other debug logs. In the fourth row, all the debug messages come from the dogstatsd crate. I did not test on amd64 or FIPS builds.

🤖 Generated with Claude Code

@datadog-datadog-prod-us1

This comment has been minimized.

@lym953
lym953 force-pushed the yiming.luo/log-debug-targets branch from 9862713 to 24a9990 Compare October 7, 2026 17:25
@lym953 lym953 changed the title [SVLS-9883] feat: add DD_LOG_DEBUG_TARGETS to turn on debug logs per target [SVLS-9883] feat: add DD_LOG_LEVEL_BY_TARGET to set log levels per target Oct 7, 2026
@lym953
lym953 marked this pull request as ready for review October 7, 2026 18:56
@lym953
lym953 requested a review from a team as a code owner October 7, 2026 18:56
@lym953
lym953 requested a review from duncanista October 7, 2026 18:56
@lym953
lym953 marked this pull request as draft October 7, 2026 19:27
@lym953

lym953 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Changing back to draft. Will make the change in serverless-components and make DD_LOG_LEVEL support values like: warn,cold_start_duration=debug,trace_flush_duration=debug.

@lym953 lym953 changed the title [SVLS-9883] feat: add DD_LOG_LEVEL_BY_TARGET to set log levels per target [SVLS-9883] feat: accept per-target log levels in DD_LOG_LEVEL Oct 7, 2026
@lym953

lym953 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Publishing this again since the serverless-components PR is published: DataDog/serverless-components#178

@lym953
lym953 marked this pull request as ready for review October 8, 2026 19:00
@lym953
lym953 requested review from a team and clifordshelton and a balanced review from Copilot and removed request for a team and duncanista October 8, 2026 19:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The bundled wrapper does not support directive lists, and the dependencies remain pinned to a temporary upstream revision.

2 open findings
What changed in this PR

Adds target-specific DD_LOG_LEVEL filtering while preserving default logging behavior.

Changes:

  • Parses comma-separated tracing filter directives and reports invalid entries.
  • Adds unit coverage for filtering behavior.
  • Updates serverless-components dependencies.
File Description
bottlecap/​src/​logger.rs Documents the cold-start logging target.
bottlecap/​src/​bin/​bottlecap/​main.rs Implements and tests directive-based filtering.
bottlecap/​Cargo.toml Updates shared component revisions.
bottlecap/​Cargo.lock Locks the updated revisions.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread bottlecap/Cargo.toml Outdated
Comment thread bottlecap/src/bin/bottlecap/main.rs Outdated
@lym953
lym953 marked this pull request as draft October 9, 2026 15:39
@lym953 lym953 changed the title [SVLS-9883] feat: accept per-target log levels in DD_LOG_LEVEL [SVLS-9883] feat: add DD_LOG_LEVEL_BY_TARGET to set log levels per target Oct 9, 2026
@lym953
lym953 requested a balanced review from Copilot October 9, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation matches the documented behavior and includes focused coverage for valid, invalid, and overriding directives.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

@lym953
lym953 marked this pull request as ready for review October 9, 2026 17:08
@lym953
lym953 marked this pull request as draft October 9, 2026 17:19
@lym953

lym953 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Converting to draft again. Will move the helper function to a crate in serverless-components, as discussed with @duncanpharvey .

DD_LOG_DEBUG_TARGETS is a comma-separated list of tracing targets that log
at debug level while DD_LOG_LEVEL stays at its normal level. For example,
DD_LOG_DEBUG_TARGETS=cold_start_duration shows only the cold start step
durations.

Each entry is parsed on its own. An entry that does not parse, such as
"cold_start_duration=trace", is logged as a warning and skipped, because
the filter's expect() would otherwise fail startup. At DD_LOG_LEVEL=debug
or trace the variable is ignored, since a target directive could only
lower trace to debug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lym953 and others added 10 commits October 9, 2026 14:56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DD_LOG_LEVEL_BY_TARGET takes comma-separated `target=level` entries in
the RUST_LOG syntax, so that a target can also get a level other than
debug, or be made quieter than DD_LOG_LEVEL. An entry with no level
turns on all levels for that target. Entries also override the built-in
h2, hyper, reqwest and rustls defaults.

Each entry is used as written, so the rule that ignored the variable at
DD_LOG_LEVEL=debug or trace no longer applies. Invalid entries are still
logged as a warning and skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ther

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop DD_LOG_LEVEL_BY_TARGET and read the same `target=level` directives
from DD_LOG_LEVEL, for example `info,cold_start_duration=debug`. This
matches the Agent Data Plane and datadog-serverless-compat, which both
accept directives in DD_LOG_LEVEL. A plain level still works as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without it, the config crate logs "Invalid log level" at ERROR on each
cold start when DD_LOG_LEVEL holds filter directives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DD_LOG_LEVEL is also read by the Datadog libraries and tracers in the
function, and they accept only a plain level, so directives there would
change or break their logging. A separate variable that only the
extension reads avoids that, and no longer needs the serverless-components
change.

Restores the code of 2afd657 and documents that targets include
dependency module paths, such as `dogstatsd`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each entry must be a target, `=`, and a level name. A level with no
target, such as `debug`, used to replace DD_LOG_LEVEL, and a target with
no level or a span filter such as `[span]` was also accepted. These are
now invalid entries, which the extension logs as a warning and skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parsing and the variable name move to the new crate in
serverless-components (DataDog/serverless-components#180), so that other
serverless agents can share them. Points all five serverless-components
dependencies at the head of #180. Its other changes since the current
revision are docs and a lock file bump that the extension does not use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lym953
lym953 force-pushed the yiming.luo/cold-start-duration-logs branch from 776fb27 to acf98dd Compare October 9, 2026 18:56
@lym953
lym953 force-pushed the yiming.luo/log-debug-targets branch from 4a71d02 to e47df3c Compare October 9, 2026 18:56
…stant

The crate README of datadog-serverless-logging documents how to set the
variable and the prefix match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lym953
lym953 marked this pull request as ready for review October 9, 2026 19:14
@lym953
lym953 requested a balanced review from Copilot October 9, 2026 19:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The temporary upstream revision must be replaced with the final merge commit before approval.

1 open finding

🧠 Review effort: Balanced

Comment thread bottlecap/Cargo.toml Outdated
Comment on lines +98 to +102
dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "e5524046ed1184b32de656e3a86eed91976180f8", default-features = false }
datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "e5524046ed1184b32de656e3a86eed91976180f8", default-features = false }
datadog-agent-config = { git = "https://github.com/DataDog/serverless-components", rev = "e5524046ed1184b32de656e3a86eed91976180f8", default-features = false }
datadog-agent-trace-sampler = { git = "https://github.com/DataDog/serverless-components", rev = "e5524046ed1184b32de656e3a86eed91976180f8", default-features = false }
datadog-serverless-logging = { git = "https://github.com/DataDog/serverless-components", rev = "e5524046ed1184b32de656e3a86eed91976180f8", default-features = false }

@clifordshelton clifordshelton left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for moving the filter logic into a shared crate. Keeping it out of DD_LOG_LEVEL makes sense too, since the tracers and libraries also read that variable. I have two inline comments and two broader ones here.

1. Decide whether DD_LOG_LEVEL_BY_TARGET is for customers or for us

This adds a new environment variable that customers can set, but it isn't documented anywhere they'd see it. The README in serverless-components#180 is written for developers. Before we document it, it's worth noticing what the setting depends on. Besides custom targets like cold_start_duration, it accepts our internal Rust module paths, like bottlecap::traces, and crate names with underscores. Those can change any time we move or rename code. If customers start using them, a refactor on our side would quietly change what their setting does, and nobody would notice.

My suggestion is to treat it as a support and debugging tool for now. Only document the custom targets we add on purpose (cold_start_duration, and later ones like trace_flush_duration), since those are names we can keep stable. Module paths still work, but we don't promote them. That way we can add or rename modules without breaking anyone. It would be good to confirm this with the team before merging, since the name and behavior are hard to change once customers rely on them.

2. Bring back the note about target names

An entry matches every target that starts with its text, so trace_flush=debug would also turn on trace_flush_duration. There was a code comment warning about this, but the "Drop the DD_LOG_LEVEL_BY_TARGET notes" commit removed it, and now it's only in the serverless-components README. Whoever adds the next target will be working in logger.rs, not reading that README. It's better to put the note back next to COLD_START_DURATION in logger.rs, so new target names don't accidentally start with an existing one.

Comment thread bottlecap/Cargo.toml Outdated
let env_filter = format!(
"h2=off,hyper=off,reqwest=off,rustls=off,datadog-trace-mini-agent=off,{log_level:?}",
);
let (env_filter, invalid_entries) = build_env_filter(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base filter turns off h2, hyper, reqwest and rustls for a reason, and with this change a DD_LOG_LEVEL_BY_TARGET entry can turn them back on. The extension reads its own stdout and sends it to Datadog as logs (see the "Be careful adding logs inside the logs pipeline" note in AGENTS.md). These HTTP crates log on every request, and that includes the requests the extension makes to send logs. So with hyper=debug, every flush creates more log lines, which get sent in the next flush, which creates more lines, and so on. That adds a lot of noise to the customer's CloudWatch and Datadog logs, and it costs them money.

Since this is an environment variable customers can set, I'd keep those four crates locked to off. You can skip any entry for them and add it to invalid_entries, so the user gets the same warning as for a typo. The cleanest place for this is probably the crate in serverless-components#180, for example by letting the caller pass a list of targets that can't be changed. That would mean making the change before #180 merges. The overrides_base_filter test that sets h2=debug would then change to check that the entry is rejected.

lym953 and others added 2 commits October 13, 2026 12:43
The merge commit has the same tree as the branch head that this PR used
before, so the code does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… constant

A DD_LOG_LEVEL_BY_TARGET entry matches every target that starts with its
text. The next target is added in logger.rs, so the warning belongs there,
not only in the serverless-components README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants