Skip to content

Commit 84de3dd

Browse files
committed
fix: align log metadata with other SDKs
1 parent da837f8 commit 84de3dd

17 files changed

Lines changed: 364 additions & 211 deletions

File tree

examples/src/main/java/software/amazon/lambda/durable/examples/general/LoggingExample.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package software.amazon.lambda.durable.examples.general;
44

5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
57
import software.amazon.lambda.durable.DurableContext;
68
import software.amazon.lambda.durable.DurableHandler;
79
import software.amazon.lambda.durable.examples.types.GreetingRequest;
@@ -13,15 +15,16 @@
1315
* in log entries via MDC. By default, logs are suppressed during replay to avoid duplicates.
1416
*/
1517
public class LoggingExample extends DurableHandler<GreetingRequest, String> {
18+
Logger logger = LoggerFactory.getLogger(LoggingExample.class);
1619

1720
@Override
1821
public String handleRequest(GreetingRequest input, DurableContext context) {
1922
// Log at execution level (outside any step)
20-
context.getLogger().info("Processing greeting for: {}", input.getName());
23+
context.getLogger(logger).info("Processing greeting for: {}", input.getName());
2124

2225
// Step 1: Create greeting - logs inside step include operation context
2326
var greeting = context.step("create-greeting", String.class, ctx -> {
24-
ctx.getLogger().info("Creating greeting message");
27+
ctx.getLogger(logger).info("Creating greeting message");
2528
return "Hello, " + input.getName();
2629
});
2730

examples/src/test/java/software/amazon/lambda/durable/examples/CloudBasedIntegrationTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,14 @@ void testWaitAtLeastInProcessExample() {
253253
assertTrue(asyncOp.getStepResult(String.class).contains("Processed: TestUser"));
254254
}
255255

256+
@Test
257+
void testLoggingExample() {
258+
var runner = CloudDurableTestRunner.create(
259+
arn("logging-example"), GreetingRequest.class, String.class, lambdaClient);
260+
var result = runner.run(new GreetingRequest("TestUser"));
261+
assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());
262+
}
263+
256264
@Test
257265
void testGenericTypesExample() {
258266
var runner = CloudDurableTestRunner.create(

sdk/src/main/java/software/amazon/lambda/durable/ParallelDurableFuture.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import java.util.function.Function;
66
import software.amazon.lambda.durable.config.ParallelBranchConfig;
77
import software.amazon.lambda.durable.model.ParallelResult;
8+
import software.amazon.lambda.durable.model.SafeCloseable;
89

910
/** User-facing context for managing parallel branch execution within a durable function. */
10-
public interface ParallelDurableFuture extends AutoCloseable, DurableFuture<ParallelResult> {
11+
public interface ParallelDurableFuture extends SafeCloseable, DurableFuture<ParallelResult> {
1112

1213
/**
1314
* Registers and immediately starts a branch (respects maxConcurrency).
@@ -68,7 +69,4 @@ default <T> DurableFuture<T> branch(
6869
*/
6970
<T> DurableFuture<T> branch(
7071
String name, TypeToken<T> resultType, Function<DurableContext, T> func, ParallelBranchConfig config);
71-
72-
/** Calls {@link #get()} if not already called. Guarantees that the context is closed. */
73-
void close();
7472
}

sdk/src/main/java/software/amazon/lambda/durable/context/BaseContext.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@
33
package software.amazon.lambda.durable.context;
44

55
import com.amazonaws.services.lambda.runtime.Context;
6+
import org.slf4j.Logger;
67
import software.amazon.lambda.durable.DurableConfig;
78
import software.amazon.lambda.durable.logging.DurableLogger;
89

9-
public interface BaseContext extends AutoCloseable {
10+
public interface BaseContext {
1011
/**
1112
* Gets a logger with additional information of the current execution context.
1213
*
1314
* @return a DurableLogger instance
1415
*/
1516
DurableLogger getLogger();
1617

18+
/**
19+
* Gets a logger with additional information of the current execution context.
20+
*
21+
* @param delegate the logger to wrap
22+
* @return a DurableLogger instance
23+
*/
24+
DurableLogger getLogger(Logger delegate);
25+
1726
/**
1827
* Returns the AWS Lambda runtime context.
1928
*
@@ -46,7 +55,4 @@ public interface BaseContext extends AutoCloseable {
4655

4756
/** Returns whether this context is currently in replay mode. */
4857
boolean isReplaying();
49-
50-
/** Closes this context. */
51-
void close();
5258
}

sdk/src/main/java/software/amazon/lambda/durable/context/BaseContextImpl.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
package software.amazon.lambda.durable.context;
44

55
import com.amazonaws.services.lambda.runtime.Context;
6+
import org.slf4j.Logger;
67
import software.amazon.lambda.durable.DurableConfig;
78
import software.amazon.lambda.durable.execution.ExecutionManager;
89
import software.amazon.lambda.durable.execution.ThreadType;
10+
import software.amazon.lambda.durable.logging.DurableLogger;
911

10-
public abstract class BaseContextImpl implements AutoCloseable, BaseContext {
12+
public abstract class BaseContextImpl implements BaseContext {
1113
private final ExecutionManager executionManager;
1214
private final DurableConfig durableConfig;
1315
private final Context lambdaContext;
@@ -109,4 +111,12 @@ public boolean isReplaying() {
109111
public void setExecutionMode() {
110112
this.isReplaying = false;
111113
}
114+
115+
public DurableLogger getLogger() {
116+
return DurableLogger.INSTANCE;
117+
}
118+
119+
public DurableLogger getLogger(Logger delegate) {
120+
return new DurableLogger(delegate);
121+
}
112122
}

sdk/src/main/java/software/amazon/lambda/durable/context/DurableContextImpl.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.util.function.BiConsumer;
1111
import java.util.function.BiFunction;
1212
import java.util.function.Function;
13-
import org.slf4j.LoggerFactory;
1413
import software.amazon.lambda.durable.DurableCallbackFuture;
1514
import software.amazon.lambda.durable.DurableConfig;
1615
import software.amazon.lambda.durable.DurableContext;
@@ -32,7 +31,6 @@
3231
import software.amazon.lambda.durable.execution.OperationIdGenerator;
3332
import software.amazon.lambda.durable.execution.SuspendExecutionException;
3433
import software.amazon.lambda.durable.execution.ThreadType;
35-
import software.amazon.lambda.durable.logging.DurableLogger;
3634
import software.amazon.lambda.durable.model.MapResult;
3735
import software.amazon.lambda.durable.model.OperationIdentifier;
3836
import software.amazon.lambda.durable.model.OperationSubType;
@@ -62,7 +60,6 @@ public class DurableContextImpl extends BaseContextImpl implements DurableContex
6260
private final OperationIdGenerator operationIdGenerator;
6361
private final DurableContextImpl parentContext;
6462
private final boolean isVirtual;
65-
private volatile DurableLogger logger;
6663

6764
/** Shared initialization — sets all fields. */
6865
private DurableContextImpl(
@@ -430,30 +427,6 @@ private static <T> T executeRetryLoop(
430427
}
431428

432429
// =============== accessors ================
433-
@Override
434-
public DurableLogger getLogger() {
435-
// lazy initialize logger
436-
if (logger == null) {
437-
synchronized (this) {
438-
if (logger == null) {
439-
logger = new DurableLogger(LoggerFactory.getLogger(DurableContext.class), this);
440-
}
441-
}
442-
}
443-
return logger;
444-
}
445-
446-
/**
447-
* Clears the logger's thread properties. Called during context destruction to prevent memory leaks and ensure clean
448-
* state for subsequent executions.
449-
*/
450-
@Override
451-
public void close() {
452-
if (logger != null) {
453-
logger.close();
454-
}
455-
}
456-
457430
/**
458431
* Get the next operationId. Returns a globally unique operation ID by hashing a sequential operation counter. For
459432
* root contexts, the counter value is hashed directly (e.g. "1", "2", "3"). For child contexts, the values are

sdk/src/main/java/software/amazon/lambda/durable/context/StepContextImpl.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
package software.amazon.lambda.durable.context;
44

55
import com.amazonaws.services.lambda.runtime.Context;
6-
import org.slf4j.LoggerFactory;
76
import software.amazon.lambda.durable.DurableConfig;
87
import software.amazon.lambda.durable.StepContext;
98
import software.amazon.lambda.durable.execution.ExecutionManager;
109
import software.amazon.lambda.durable.execution.ThreadType;
11-
import software.amazon.lambda.durable.logging.DurableLogger;
1210

1311
/**
1412
* Context available inside a step operation's user function.
@@ -17,7 +15,6 @@
1715
* {@link BaseContext} for thread lifecycle management.
1816
*/
1917
public class StepContextImpl extends BaseContextImpl implements StepContext {
20-
private volatile DurableLogger logger;
2118
private final int attempt;
2219

2320
/**
@@ -46,25 +43,4 @@ protected StepContextImpl(
4643
public int getAttempt() {
4744
return attempt;
4845
}
49-
50-
@Override
51-
public DurableLogger getLogger() {
52-
// lazy initialize logger
53-
if (logger == null) {
54-
synchronized (this) {
55-
if (logger == null) {
56-
logger = new DurableLogger(LoggerFactory.getLogger(StepContext.class), this);
57-
}
58-
}
59-
}
60-
return logger;
61-
}
62-
63-
/** Closes the logger for this context. */
64-
@Override
65-
public void close() {
66-
if (logger != null) {
67-
logger.close();
68-
}
69-
}
7046
}

sdk/src/main/java/software/amazon/lambda/durable/execution/DurableExecutor.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import software.amazon.lambda.durable.exception.DurableOperationException;
2323
import software.amazon.lambda.durable.exception.IllegalDurableOperationException;
2424
import software.amazon.lambda.durable.exception.UnrecoverableDurableExecutionException;
25+
import software.amazon.lambda.durable.logging.DurableLogger;
2526
import software.amazon.lambda.durable.model.DurableExecutionInput;
2627
import software.amazon.lambda.durable.model.DurableExecutionOutput;
2728
import software.amazon.lambda.durable.plugin.InvocationEndInfo;
@@ -69,9 +70,9 @@ public static <I, O> DurableExecutionOutput execute(
6970

7071
var userInput = extractUserInput(
7172
executionManager.getExecutionOperation(), config.getSerDes(), inputType);
72-
// use try-with-resources to clear logger properties
73-
try (var context =
74-
DurableContextImpl.createRootContext(executionManager, config, lambdaContext)) {
73+
var context = DurableContextImpl.createRootContext(executionManager, config, lambdaContext);
74+
// use a try-with-resources to clear logger properties
75+
try (var ignored = DurableLogger.attachContext(context)) {
7576
return handler.apply(userInput, context);
7677
}
7778
},

sdk/src/main/java/software/amazon/lambda/durable/execution/ExecutionManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import software.amazon.lambda.durable.DurableConfig;
2525
import software.amazon.lambda.durable.exception.UnrecoverableDurableExecutionException;
2626
import software.amazon.lambda.durable.model.DurableExecutionInput;
27+
import software.amazon.lambda.durable.model.SafeCloseable;
2728
import software.amazon.lambda.durable.operation.BaseDurableOperation;
2829

2930
/**
@@ -47,7 +48,7 @@
4748
*
4849
* @see InternalExecutor
4950
*/
50-
public class ExecutionManager implements AutoCloseable {
51+
public class ExecutionManager implements SafeCloseable {
5152

5253
private static final Logger logger = LoggerFactory.getLogger(ExecutionManager.class);
5354

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

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,66 @@
33
package software.amazon.lambda.durable.logging;
44

55
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
67
import org.slf4j.MDC;
78
import software.amazon.lambda.durable.DurableContext;
89
import software.amazon.lambda.durable.StepContext;
9-
import software.amazon.lambda.durable.context.BaseContextImpl;
10+
import software.amazon.lambda.durable.context.BaseContext;
11+
import software.amazon.lambda.durable.model.SafeCloseable;
1012

1113
/**
1214
* Logger wrapper that adds durable execution context to log entries via MDC and optionally suppresses logs during
1315
* replay.
1416
*/
1517
public class DurableLogger {
16-
static final String MDC_EXECUTION_ARN = "durableExecutionArn";
18+
static final String MDC_DURABLE_EXECUTION_ARN = "durableExecutionArn";
19+
static final String MDC_EXECUTION_ARN = "executionArn";
1720
static final String MDC_REQUEST_ID = "requestId";
1821
static final String MDC_OPERATION_ID = "operationId";
1922
static final String MDC_CONTEXT_ID = "contextId";
2023
static final String MDC_OPERATION_NAME = "operationName";
2124
static final String MDC_CONTEXT_NAME = "contextName";
2225
static final String MDC_ATTEMPT = "attempt";
2326

27+
public static final DurableLogger INSTANCE = new DurableLogger(LoggerFactory.getLogger(DurableLogger.class));
28+
private static final SafeCloseable AUTO_CLOSER = DurableLogger::detachContext;
29+
private static final ThreadLocal<BaseContext> CONTEXT = new ThreadLocal<>();
30+
2431
private final Logger delegate;
25-
private final BaseContextImpl context;
2632

2733
/**
2834
* Creates a DurableLogger wrapping the given SLF4J logger with execution context MDC entries.
2935
*
3036
* @param delegate the SLF4J logger to wrap
31-
* @param context the durable execution context providing MDC values
3237
*/
33-
public DurableLogger(Logger delegate, BaseContextImpl context) {
38+
public DurableLogger(Logger delegate) {
3439
this.delegate = delegate;
35-
this.context = context;
40+
}
41+
42+
public static SafeCloseable attachContext(BaseContext context) {
43+
CONTEXT.set(context);
44+
if (context != null) {
45+
injectMdcProperties(context);
46+
}
47+
return AUTO_CLOSER;
48+
}
49+
50+
public static void detachContext() {
51+
if (CONTEXT.get() != null) {
52+
CONTEXT.remove();
53+
MDC.clear();
54+
}
55+
}
56+
57+
private static void injectMdcProperties(BaseContext context) {
58+
var config = context.getDurableConfig().getLoggerConfig();
3659

3760
// execution arn
38-
MDC.put(MDC_EXECUTION_ARN, context.getExecutionArn());
61+
if (config.oldKeyNames()) {
62+
MDC.put(MDC_DURABLE_EXECUTION_ARN, context.getExecutionArn());
63+
} else {
64+
MDC.put(MDC_EXECUTION_ARN, context.getExecutionArn());
65+
}
3966

4067
// lambda request id
4168
var requestId =
@@ -47,10 +74,18 @@ public DurableLogger(Logger delegate, BaseContextImpl context) {
4774
if (context instanceof DurableContext) {
4875
// context thread - context id and name
4976
if (context.getContextId() != null) {
50-
MDC.put(MDC_CONTEXT_ID, context.getContextId());
77+
if (config.oldKeyNames()) {
78+
MDC.put(MDC_CONTEXT_ID, context.getContextId());
79+
} else {
80+
MDC.put(MDC_OPERATION_ID, context.getContextId());
81+
}
5182
}
5283
if (context.getContextName() != null) {
53-
MDC.put(MDC_CONTEXT_NAME, context.getContextName());
84+
if (config.oldKeyNames()) {
85+
MDC.put(MDC_CONTEXT_NAME, context.getContextName());
86+
} else {
87+
MDC.put(MDC_OPERATION_NAME, context.getContextName());
88+
}
5489
}
5590
} else if (context instanceof StepContext stepContext) {
5691
// In step context, context id is the operation id, context name is the operation name
@@ -63,11 +98,6 @@ public DurableLogger(Logger delegate, BaseContextImpl context) {
6398
}
6499
}
65100

66-
/** Clears all MDC entries. User set MDC entries will also be removed as the thread will not be used anymore. */
67-
public void close() {
68-
MDC.clear();
69-
}
70-
71101
public void trace(String format, Object... args) {
72102
log(() -> delegate.trace(format, args));
73103
}
@@ -92,13 +122,13 @@ public void error(String message, Throwable t) {
92122
log(() -> delegate.error(message, t));
93123
}
94124

95-
private boolean shouldSuppress() {
96-
return context.getDurableConfig().getLoggerConfig().suppressReplayLogs()
97-
&& context.getExecutionManager().isReplaying();
125+
private boolean shouldSuppress(BaseContext context) {
126+
return context.getDurableConfig().getLoggerConfig().suppressReplayLogs() && context.isReplaying();
98127
}
99128

100129
private void log(Runnable logAction) {
101-
if (!shouldSuppress()) {
130+
var threadLocalContext = CONTEXT.get();
131+
if (threadLocalContext == null || !shouldSuppress(threadLocalContext)) {
102132
logAction.run();
103133
}
104134
}

0 commit comments

Comments
 (0)