Add replay-aware logger to Amazon.Lambda.DurableExecution#2371
Draft
GarrettBeatty wants to merge 1 commit into
Draft
Add replay-aware logger to Amazon.Lambda.DurableExecution#2371GarrettBeatty wants to merge 1 commit into
GarrettBeatty wants to merge 1 commit into
Conversation
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>
|
|
||
| COPY bin/publish/ ${LAMBDA_TASK_ROOT} | ||
|
|
||
| ENTRYPOINT ["/var/task/bootstrap"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked PRs:
What
Implements
context.Logger, the replay-awareILoggerdescribed inDocs/durable-execution-design.mdand shipped by the Python / Java / JavaScript reference SDKs.Public API surface introduced:
IDurableContext.LoggerILogger(wasNullLogger.Instance).IDurableContext.ConfigureLogger(LoggerConfig)LoggerConfigCustomLogger+ModeAwareconfiguration record.Why
Without replay-aware logging, every
Console.WriteLine(or any non-suppressing logger) repeats on every replay invocation. A 30-step workflow re-invoked 30 times produces 30 copies of every log line — noisy at best, misleading at worst. The reference SDKs all solve this by reading replay state on each log call and suppressing emission while the workflow is re-deriving prior operations from checkpoint state. This PR ports that behavior to .NET on top of the per-operation replay tracker introduced in #2360.How
ReplayAwareLogger. An
ILoggerdecorator that consultsExecutionState.IsReplayingon every call. Short-circuits bothLog<TState>andIsEnabledduring replay soLoggerExtensions.LogXxxdoesn't even format the message string.BeginScopealways passes through so the scope stack stays balanced — suppression only applies at log emission.Default inner logger.
LambdaCoreLogger— a minimal in-package adapter that delegates toAmazon.Lambda.Core.LambdaLogger.Log, so logs flow into the standard Lambda runtime pipeline (JSON whenAWS_LAMBDA_LOG_FORMAT=JSON, level-filtered byAWS_LAMBDA_LOG_LEVEL). Avoids forcing a dependency onAmazon.Lambda.Logging.AspNetCore. Users who want Serilog/Powertools/etc. swap their own logger viaConfigureLogger.Metadata scopes.
DurableFunction.WrapAsyncCoreopens aBeginScopearound the workflow body carryingdurableExecutionArn+awsRequestId.StepOperationopens a per-step scope (operationId,operationName,attempt) around the user-func invocation only. Structured log providers tag every log line emitted by user code with that metadata automatically.Key files:
LoggerConfig.cs— public configuration typeInternal/ReplayAwareLogger.cs— the decoratorInternal/LambdaCoreLogger.cs— default inner loggerDurableContext.cs— replacesNullLoggerdefault; implementsConfigureLoggerDurableFunction.cs— execution-level scopeInternal/StepOperation.cs— step-level scope around user funcTesting
Unit tests (10 new in
Amazon.Lambda.DurableExecution.Tests):ReplayAwareLoggerTests(7) — replay suppression, execution passthrough,ModeAware=false,IsEnabledshort-circuit,BeginScopepassthrough, mid-workflow REPLAY → NEW transition (mirrors Python'stest_logger_replay_then_new_logging).DurableContextTests(3) —Logger_Default_IsReplayAwareLogger,ConfigureLogger_WithCustomLogger_ReachesUserLogger,ConfigureLogger_ModeAwareFalse_LogsDuringReplay.Integration test (
ReplayAwareLoggerTestinAmazon.Lambda.DurableExecution.IntegrationTests):End-to-end proof on real AWS infra. Deploys a
step → wait(3s) → stepworkflow that pairs eachcontext.Logger.LogInformationline with aConsole.WriteLine"control" line. After the durable execution completes (across two invocations driven by the wait), queries CloudWatch Logs and asserts:This pins the suppression contract end-to-end against the actual durable-execution service.
Out of scope (follow-up PRs)
MapAsync/ParallelAsync/RunInChildContextAsync/WaitForConditionAsyncCallbackAsync,InvokeAsyncDefaultJsonCheckpointSerializer[DurableExecution]attributeDurableTestRunner/Amazon.Lambda.DurableExecution.Testingpackagedotnet new lambda.DurableFunctionblueprint