Skip to content

fix(logger): scope debug sampling log level per invocation under LMI concurrency - #5544

Open
vishwakt wants to merge 1 commit into
aws-powertools:mainfrom
vishwakt:fix/5542-logger-sampling-invoke-store
Open

fix(logger): scope debug sampling log level per invocation under LMI concurrency#5544
vishwakt wants to merge 1 commit into
aws-powertools:mainfrom
vishwakt:fix/5542-logger-sampling-invoke-store

Conversation

@vishwakt

@vishwakt vishwakt commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Changes

refreshSampleRateCalculation() runs once per invocation (from both the injectLambdaContext middy middleware and the decorator) and wrote its random sampling decision into this.logLevel, an instance field shared by all concurrent invocations. Under Lambda Managed Instances concurrency, whichever invocation's refresh ran last won for every in-flight invocation: sampled-in invocations had their debug logs silently dropped after a concurrent sampled-out invocation reset the level, and sampled-out invocations emitted debug logs they were never sampled for.

Following the direction agreed in the issue:

  • The effective log level now lives in the InvokeStore when concurrency is enabled (AWS_LAMBDA_MAX_CONCURRENCY set) and an invocation context is active, with the instance level set at initialization as the fallback. A private #getLogLevel() accessor resolves reads (the level getter, getLevelName(), the print gate, and the sampling refresh guard), and a private #setLogLevel() routes writes: inside an invocation context they go to the InvokeStore, outside one (e.g. during init) they set the instance level as before.
  • The cold-start refresh guard (refreshedTimes) stays environment-wide, so only the first invocation of an environment keeps the boot-time sampling decision and warm invocations re-roll as they do today.
  • The ALC short-circuit and constructor-time level initialization are unchanged; they write the instance level, which is environment-wide configuration.

Added regression tests in tests/unit/concurrency/sampling.test.ts:

  • interleaved invocations: a sampled-in invocation keeps emitting debug logs after a concurrent sampled-out invocation's refresh, and the sampled-out invocation's debug logs stay suppressed (fails on main)
  • an invocation-scoped setLogLevel('DEBUG') does not leak into later invocations (fails on main)
  • reading and setting the log level throws when concurrency is enabled but the InvokeStore is unavailable, matching the attribute store behavior
  • setLogLevel() outside an invocation context sets the instance level

All existing logger unit tests pass unchanged (228 tests), including the existing non-concurrent sampling suite, with 100% coverage on Logger.ts.

Issue number: closes #5542


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…concurrency

refreshSampleRateCalculation() runs once per invocation and wrote its random
sampling decision into the instance-level log level shared by all concurrent
invocations. Under Lambda Managed Instances concurrency, whichever
invocation's refresh ran last won for every in-flight invocation: sampled-in
invocations had their debug logs silently dropped, and sampled-out
invocations emitted debug logs they were never sampled for.

Store the effective log level in the InvokeStore when concurrency is
enabled and an invocation context is active, falling back to the instance
level set at initialization. The cold-start refresh guard stays
environment-wide so only the first invocation of an environment keeps the
boot-time sampling decision.

Closes aws-powertools#5542
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L PRs between 100-499 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Logger debug sampling decision mutates shared log level across concurrent invocations

1 participant