Skip to content

Commit 6c5450d

Browse files
committed
Add withLogging callback based interface to PowertoolsLogging.
1 parent a780741 commit 6c5450d

File tree

3 files changed

+176
-9
lines changed

3 files changed

+176
-9
lines changed

powertools-e2e-tests/handlers/logging-functional/src/main/java/software/amazon/lambda/powertools/e2e/Function.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,20 @@
2121
import com.amazonaws.services.lambda.runtime.Context;
2222
import com.amazonaws.services.lambda.runtime.RequestHandler;
2323

24-
import software.amazon.lambda.powertools.logging.Logging;
2524
import software.amazon.lambda.powertools.logging.PowertoolsLogging;
2625

2726
public class Function implements RequestHandler<Input, String> {
2827
private static final Logger LOG = LoggerFactory.getLogger(Function.class);
2928

3029
public String handleRequest(Input input, Context context) {
31-
PowertoolsLogging.initializeLogging(context);
30+
return PowertoolsLogging.withLogging(context, () -> {
31+
input.getKeys().forEach(MDC::put);
32+
LOG.info(input.getMessage());
3233

33-
input.getKeys().forEach(MDC::put);
34-
LOG.info(input.getMessage());
34+
// Flush buffer manually since we buffer at INFO level to test log buffering
35+
PowertoolsLogging.flushBuffer();
3536

36-
// Flush buffer manually since we buffer at INFO level to test log buffering
37-
PowertoolsLogging.flushBuffer();
38-
39-
return "OK";
37+
return "OK";
38+
});
4039
}
4140
}

powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/PowertoolsLogging.java

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Locale;
3131
import java.util.Random;
3232
import java.util.concurrent.atomic.AtomicBoolean;
33+
import java.util.function.Supplier;
3334

3435
import org.slf4j.Logger;
3536
import org.slf4j.LoggerFactory;
@@ -140,6 +141,9 @@ public static void clearBuffer() {
140141
* This method should be called at the beginning of your Lambda handler to set up
141142
* logging context with Lambda function information, trace ID, and service name.
142143
*
144+
* <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use
145+
* {@link #withLogging(Context, Supplier)} to handle cleanup automatically.</p>
146+
*
143147
* @param context the Lambda context provided by AWS Lambda runtime
144148
*/
145149
public static void initializeLogging(Context context) {
@@ -151,6 +155,9 @@ public static void initializeLogging(Context context) {
151155
* This method sets up logging context and optionally enables DEBUG logging
152156
* based on the provided sampling rate.
153157
*
158+
* <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use
159+
* {@link #withLogging(Context, double, Supplier)} to handle cleanup automatically.</p>
160+
*
154161
* @param context the Lambda context provided by AWS Lambda runtime
155162
* @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0)
156163
*/
@@ -163,6 +170,9 @@ public static void initializeLogging(Context context, double samplingRate) {
163170
* This method sets up logging context and extracts correlation ID from the event
164171
* using the provided JSON path.
165172
*
173+
* <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use
174+
* {@link #withLogging(Context, String, Object, Supplier)} to handle cleanup automatically.</p>
175+
*
166176
* @param context the Lambda context provided by AWS Lambda runtime
167177
* @param correlationIdPath JSON path to extract correlation ID from event
168178
* @param event the Lambda event object
@@ -177,7 +187,10 @@ public static void initializeLogging(Context context, String correlationIdPath,
177187
* configures sampling rate for DEBUG logging, and optionally extracts
178188
* correlation ID from the event.
179189
*
180-
* This method is tread-safe.
190+
* <p>Important: Call {@link #clearState(boolean)} at the end of your handler or use
191+
* {@link #withLogging(Context, double, String, Object, Supplier)} to handle cleanup automatically.</p>
192+
*
193+
* <p>This method is thread-safe.</p>
181194
*
182195
* @param context the Lambda context provided by AWS Lambda runtime
183196
* @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0)
@@ -278,4 +291,79 @@ public static void clearState(boolean clearMdcState) {
278291
clearBuffer();
279292
SAMPLER.remove();
280293
}
294+
295+
/**
296+
* Executes code with logging context initialized and automatically clears state.
297+
*
298+
* @param context the Lambda context provided by AWS Lambda runtime
299+
* @param supplier the code to execute with logging context
300+
* @param <T> the return type
301+
* @return the result of the supplier execution
302+
*/
303+
public static <T> T withLogging(Context context, Supplier<T> supplier) {
304+
initializeLogging(context);
305+
try {
306+
return supplier.get();
307+
} finally {
308+
clearState(true);
309+
}
310+
}
311+
312+
/**
313+
* Executes code with logging context initialized with sampling rate and automatically clears state.
314+
*
315+
* @param context the Lambda context provided by AWS Lambda runtime
316+
* @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0)
317+
* @param supplier the code to execute with logging context
318+
* @param <T> the return type
319+
* @return the result of the supplier execution
320+
*/
321+
public static <T> T withLogging(Context context, double samplingRate, Supplier<T> supplier) {
322+
initializeLogging(context, samplingRate);
323+
try {
324+
return supplier.get();
325+
} finally {
326+
clearState(true);
327+
}
328+
}
329+
330+
/**
331+
* Executes code with logging context initialized with correlation ID extraction and automatically clears state.
332+
*
333+
* @param context the Lambda context provided by AWS Lambda runtime
334+
* @param correlationIdPath JSON path to extract correlation ID from event
335+
* @param event the Lambda event object
336+
* @param supplier the code to execute with logging context
337+
* @param <T> the return type
338+
* @return the result of the supplier execution
339+
*/
340+
public static <T> T withLogging(Context context, String correlationIdPath, Object event, Supplier<T> supplier) {
341+
initializeLogging(context, correlationIdPath, event);
342+
try {
343+
return supplier.get();
344+
} finally {
345+
clearState(true);
346+
}
347+
}
348+
349+
/**
350+
* Executes code with logging context initialized with full configuration and automatically clears state.
351+
*
352+
* @param context the Lambda context provided by AWS Lambda runtime
353+
* @param samplingRate sampling rate for DEBUG logging (0.0 to 1.0)
354+
* @param correlationIdPath JSON path to extract correlation ID from event (can be null)
355+
* @param event the Lambda event object (required if correlationIdPath is provided)
356+
* @param supplier the code to execute with logging context
357+
* @param <T> the return type
358+
* @return the result of the supplier execution
359+
*/
360+
public static <T> T withLogging(Context context, double samplingRate, String correlationIdPath, Object event,
361+
Supplier<T> supplier) {
362+
initializeLogging(context, samplingRate, correlationIdPath, event);
363+
try {
364+
return supplier.get();
365+
} finally {
366+
clearState(true);
367+
}
368+
}
281369
}

powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/PowertoolsLoggingTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,86 @@ void initializeLogging_concurrentCalls_shouldBeThreadSafe() throws InterruptedEx
425425
assertThat(coldStartCount).isEqualTo(1);
426426
}
427427

428+
@Test
429+
void withLogging_basicUsage_shouldInitializeAndCleanup() {
430+
// WHEN
431+
String result = PowertoolsLogging.withLogging(context, () -> {
432+
assertThat(MDC.get(PowertoolsLoggedFields.FUNCTION_NAME.getName())).isEqualTo("test-function");
433+
return "test-result";
434+
});
435+
436+
// THEN
437+
assertThat(result).isEqualTo("test-result");
438+
assertThat(MDC.getCopyOfContextMap()).isNull();
439+
assertThat(testManager.isBufferCleared()).isTrue();
440+
}
441+
442+
@Test
443+
void withLogging_withSamplingRate_shouldSetSamplingRateAndCleanup() {
444+
// WHEN
445+
String result = PowertoolsLogging.withLogging(context, 0.5, () -> {
446+
assertThat(MDC.get(PowertoolsLoggedFields.SAMPLING_RATE.getName())).isEqualTo("0.5");
447+
return "sampled-result";
448+
});
449+
450+
// THEN
451+
assertThat(result).isEqualTo("sampled-result");
452+
assertThat(MDC.getCopyOfContextMap()).isNull();
453+
}
454+
455+
@Test
456+
void withLogging_withCorrelationId_shouldExtractCorrelationIdAndCleanup() {
457+
// GIVEN
458+
Map<String, Object> event = Map.of("requestId", "correlation-123");
459+
460+
// WHEN
461+
Integer result = PowertoolsLogging.withLogging(context, "requestId", event, () -> {
462+
assertThat(MDC.get(PowertoolsLoggedFields.CORRELATION_ID.getName())).isEqualTo("correlation-123");
463+
return 42;
464+
});
465+
466+
// THEN
467+
assertThat(result).isEqualTo(42);
468+
assertThat(MDC.getCopyOfContextMap()).isNull();
469+
}
470+
471+
@Test
472+
void withLogging_withFullConfiguration_shouldSetAllFieldsAndCleanup() {
473+
// GIVEN
474+
Map<String, Object> event = Map.of("id", "full-correlation");
475+
476+
// WHEN
477+
Boolean result = PowertoolsLogging.withLogging(context, 0.8, "id", event, () -> {
478+
Map<String, String> mdcMap = MDC.getCopyOfContextMap();
479+
assertThat(mdcMap)
480+
.containsEntry(PowertoolsLoggedFields.FUNCTION_NAME.getName(), "test-function")
481+
.containsEntry(PowertoolsLoggedFields.CORRELATION_ID.getName(), "full-correlation")
482+
.containsEntry(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.8");
483+
return true;
484+
});
485+
486+
// THEN
487+
assertThat(result).isTrue();
488+
assertThat(MDC.getCopyOfContextMap()).isNull();
489+
}
490+
491+
@Test
492+
void withLogging_whenSupplierThrowsException_shouldStillCleanup() {
493+
// WHEN & THEN
494+
try {
495+
PowertoolsLogging.withLogging(context, () -> {
496+
assertThat(MDC.get(PowertoolsLoggedFields.FUNCTION_NAME.getName())).isEqualTo("test-function");
497+
throw new RuntimeException("test exception");
498+
});
499+
} catch (RuntimeException e) {
500+
assertThat(e.getMessage()).isEqualTo("test exception");
501+
}
502+
503+
// THEN - cleanup should still happen
504+
assertThat(MDC.getCopyOfContextMap()).isNull();
505+
assertThat(testManager.isBufferCleared()).isTrue();
506+
}
507+
428508
private void reinitializeLogLevel() {
429509
try {
430510
Method initializeLogLevel = PowertoolsLogging.class.getDeclaredMethod("initializeLogLevel");

0 commit comments

Comments
 (0)