Skip to content

Cherry-pick replay-aware logger into feature branch#2405

Merged
GarrettBeatty merged 2 commits into
feature/durablefunctionfrom
gcbeatty/cherry-pick-replay-logger
Jun 3, 2026
Merged

Cherry-pick replay-aware logger into feature branch#2405
GarrettBeatty merged 2 commits into
feature/durablefunctionfrom
gcbeatty/cherry-pick-replay-logger

Conversation

@GarrettBeatty
Copy link
Copy Markdown
Contributor

Summary

Test plan

  • Conformance test suite passes test 1-7 (StepLogging) with this change
  • Existing tests continue to pass

Implement context.Logger, the replay-aware ILogger described in
Docs/durable-execution-design.md and shipped by the Python / Java / JS
reference SDKs. Messages emitted while the workflow is replaying prior
operations are suppressed, so a 30-step workflow re-invoked 30 times
emits each LogInformation line once instead of 30 times.

Public API:
- IDurableContext.Logger — was NullLogger.Instance, now a replay-safe
  ILogger backed by Amazon.Lambda.Core.LambdaLogger so logs flow into
  the standard runtime pipeline (JSON when AWS_LAMBDA_LOG_FORMAT=JSON,
  level-filtered by AWS_LAMBDA_LOG_LEVEL).
- IDurableContext.ConfigureLogger(LoggerConfig) — swap the inner
  ILogger (Serilog, Powertools, etc.) and/or disable replay-aware
  filtering (ModeAware = false) for debugging. Matches the API shape
  documented in the design doc.

Internals:
- ReplayAwareLogger — ILogger decorator that consults
  ExecutionState.IsReplaying on every Log call. Short-circuits both
  Log<TState> and IsEnabled during replay so LoggerExtensions.LogXxx
  doesn't even format the string. BeginScope always passes through so
  the scope stack stays balanced.
- LambdaCoreLogger — minimal in-package adapter that delegates to
  Amazon.Lambda.Core.LambdaLogger.Log. Avoids forcing a dependency on
  Amazon.Lambda.Logging.AspNetCore.
- DurableFunction.WrapAsyncCore opens a BeginScope around the workflow
  body carrying durableExecutionArn + awsRequestId. StepOperation
  opens a per-step scope (operationId, operationName, attempt) around
  the user-func invocation only. Structured log providers (the
  runtime's JSON formatter, Serilog, etc.) tag every log line emitted
  by user code with that metadata automatically.

Tests:
- ReplayAwareLoggerTests — 7 unit tests: replay suppression, execution
  passthrough, ModeAware=false, IsEnabled short-circuit, scope
  passthrough, mid-workflow REPLAY→NEW transition (mirrors Python's
  test_logger_replay_then_new_logging).
- DurableContextTests — coverage for the default logger, ConfigureLogger
  with a custom logger, and ConfigureLogger { ModeAware = false }
  enabling logs during replay.
- ReplayAwareLoggerTest (integration) — deploys a Step → Wait → Step
  workflow that pairs each context.Logger.LogInformation line with a
  Console.WriteLine "control" line. After the durable execution
  completes, queries CloudWatch Logs and asserts each replay-aware
  line appears exactly once across both invocations while each control
  line appears once per invocation, proving the suppression works
  end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Forward template + args from LambdaCoreLogger

When state is FormattedLogValues, extract {OriginalFormat} and pass the
original template + named-argument values through to LambdaLogger.Log
instead of pre-rendering. Mirrors the pattern in
Amazon.Lambda.Logging.AspNetCore.LambdaILogger so the runtime's JSON
formatter can surface {OrderId}-style placeholders as top-level
structured attributes.

Make LambdaCoreLogger scope-aware

BeginScope now maintains an AsyncLocal chain of scope state. On Log,
KVP-shaped scope state is appended to the template as named placeholders
(inner→outer order, inner wins on key collision; explicit message args
win over scope keys). The runtime's JSON formatter promotes the keys
to top-level fields, so durableExecutionArn / operationId / etc. show
up as structured attributes without callers having to swap in a
third-party logger.

Unit tests cover ordering, nested scopes, message-arg precedence,
AsyncLocal isolation, and non-KVP fallback. The integration test now
sets AWS_LAMBDA_LOG_FORMAT=JSON, adds a step-internal log line, and
asserts the scope-derived fields land on the parsed JSON record.
@GarrettBeatty GarrettBeatty requested review from a team as code owners June 3, 2026 16:20
@GarrettBeatty GarrettBeatty requested review from normj and philasmar and removed request for a team June 3, 2026 16:20
# etc.) appear as top-level fields.
ENV AWS_LAMBDA_LOG_FORMAT=JSON

ENTRYPOINT ["/var/task/bootstrap"]
@GarrettBeatty GarrettBeatty added the Release Not Needed Add this label if a PR does not need to be released. label Jun 3, 2026
@GarrettBeatty GarrettBeatty merged commit 72049dd into feature/durablefunction Jun 3, 2026
3 of 6 checks passed
@GarrettBeatty GarrettBeatty deleted the gcbeatty/cherry-pick-replay-logger branch June 3, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Release Not Needed Add this label if a PR does not need to be released.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants