Skip to content

Commit 18745f5

Browse files
committed
feat(logging): Use per-context replay status for log suppression
1 parent dbd1108 commit 18745f5

2 files changed

Lines changed: 40 additions & 7 deletions

File tree

sdk/src/main/java/software/amazon/lambda/durable/logging/DurableLogger.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ public void error(String message, Throwable t) {
9393
}
9494

9595
private boolean shouldSuppress() {
96-
return context.getDurableConfig().getLoggerConfig().suppressReplayLogs()
97-
&& context.getExecutionManager().isReplaying();
96+
return context.getDurableConfig().getLoggerConfig().suppressReplayLogs() && context.isReplaying();
9897
}
9998

10099
private void log(Runnable logAction) {

sdk/src/test/java/software/amazon/lambda/durable/logging/DurableLoggerTest.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ void setUp() {
4242
}
4343

4444
private DurableLogger createLogger(Mode mode, Suppression suppression) {
45-
when(mockExecutionManager.isReplaying()).thenReturn(mode == Mode.REPLAYING);
45+
when(mockExecutionManager.hasOperationsForContext(null)).thenReturn(mode == Mode.REPLAYING);
4646
return new DurableLogger(mockLogger, createDurableContext(REQUEST_ID, suppression));
4747
}
4848

@@ -104,7 +104,7 @@ void setsExecutionMdcInConstructor() {
104104
void setStepThreadPropertiesSetsMdc() {
105105
try (MockedStatic<MDC> mdcMock = mockStatic(MDC.class)) {
106106
mdcMock.clearInvocations();
107-
when(mockExecutionManager.isReplaying()).thenReturn(false);
107+
when(mockExecutionManager.hasOperationsForContext(null)).thenReturn(false);
108108
var logger = new DurableLogger(
109109
mockLogger,
110110
createDurableContext(REQUEST_ID, Suppression.ENABLED)
@@ -130,13 +130,17 @@ void clearThreadPropertiesRemovesMdc() {
130130

131131
@Test
132132
void replayModeTransitionAllowsSubsequentLogs() {
133-
when(mockExecutionManager.isReplaying()).thenReturn(true, false);
134-
var logger = new DurableLogger(mockLogger, createDurableContext(REQUEST_ID, Suppression.ENABLED));
133+
when(mockExecutionManager.hasOperationsForContext(null)).thenReturn(true);
134+
var durableContext = createDurableContext(REQUEST_ID, Suppression.ENABLED);
135+
var logger = new DurableLogger(mockLogger, durableContext);
135136

136137
// During replay - suppressed
137138
logger.info("suppressed");
138139
verify(mockLogger, never()).info(anyString(), any(Object[].class));
139140

141+
// Transition to execution mode at per-context level
142+
durableContext.setExecutionMode();
143+
140144
// After transition to execution mode - logged
141145
logger.info("logged after transition");
142146
verify(mockLogger).info(eq("logged after transition"), any(Object[].class));
@@ -163,10 +167,40 @@ void allLogLevelsDelegateCorrectly() {
163167
verify(mockLogger).error("error with exception", exception);
164168
}
165169

170+
@Test
171+
void concurrentContextsHaveIndependentReplayState() {
172+
// Simulate two child contexts: one still replaying, one already executing
173+
when(mockExecutionManager.hasOperationsForContext(null)).thenReturn(true);
174+
when(mockExecutionManager.hasOperationsForContext("child-a")).thenReturn(false);
175+
when(mockExecutionManager.hasOperationsForContext("child-b")).thenReturn(true);
176+
177+
var rootContext = createDurableContext(REQUEST_ID, Suppression.ENABLED);
178+
var childA = rootContext.createChildContext("child-a", "branch-a", false);
179+
var childB = rootContext.createChildContext("child-b", "branch-b", false);
180+
181+
var loggerForA = mock(Logger.class);
182+
var loggerForB = mock(Logger.class);
183+
var durableLoggerA = new DurableLogger(loggerForA, childA);
184+
var durableLoggerB = new DurableLogger(loggerForB, childB);
185+
186+
// Child A is in execution mode — logs should pass through
187+
durableLoggerA.info("from branch A");
188+
verify(loggerForA).info(eq("from branch A"), any(Object[].class));
189+
190+
// Child B is still replaying — logs should be suppressed
191+
durableLoggerB.info("from branch B");
192+
verify(loggerForB, never()).info(anyString(), any(Object[].class));
193+
194+
// After child B transitions, its logs should pass through
195+
childB.setExecutionMode();
196+
durableLoggerB.info("branch B after transition");
197+
verify(loggerForB).info(eq("branch B after transition"), any(Object[].class));
198+
}
199+
166200
@Test
167201
void handlesNullRequestId() {
168202
try (MockedStatic<MDC> mdcMock = mockStatic(MDC.class)) {
169-
when(mockExecutionManager.isReplaying()).thenReturn(false);
203+
when(mockExecutionManager.hasOperationsForContext(null)).thenReturn(false);
170204
new DurableLogger(mockLogger, createDurableContext(null, Suppression.DISABLED));
171205

172206
mdcMock.verify(() -> MDC.put(DurableLogger.MDC_EXECUTION_ARN, EXECUTION_ARN));

0 commit comments

Comments
 (0)