Skip to content

Commit 55433a3

Browse files
pankajagrawal16Pankaj Agrawal
and
Pankaj Agrawal
authored
feat: Clear logger state (#453)
* capture correlation ids * chore: docs and javadocs update * support any Json Pointer path * docs: correlation id extraction * feat: ability to clear state of logger on each request for custom keys * docs: ability to clear state of logger on each request for custom keys Co-authored-by: Pankaj Agrawal <[email protected]>
1 parent 3cf0724 commit 55433a3

File tree

5 files changed

+135
-0
lines changed

5 files changed

+135
-0
lines changed

docs/core/logging.md

+64
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ for known event sources, where either a request ID or X-Ray Trace ID are present
224224

225225
## Appending additional keys
226226

227+
!!! info "Custom keys are persisted across warm invocations"
228+
Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [`clearState=true`](#clearing-all-state).
229+
227230
You can append your own keys to your existing logs via `appendKey`.
228231

229232
=== "App.java"
@@ -286,6 +289,67 @@ You can remove any additional key from entry using `LoggingUtils.removeKeys()`.
286289
}
287290
```
288291

292+
### Clearing all state
293+
294+
Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html),
295+
this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use
296+
`clearState=true` attribute on `@Logging` annotation.
297+
298+
299+
=== "App.java"
300+
301+
```java hl_lines="8 12"
302+
/**
303+
* Handler for requests to Lambda function.
304+
*/
305+
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
306+
307+
Logger log = LogManager.getLogger();
308+
309+
@Logging(clearState = true)
310+
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
311+
...
312+
if(input.getHeaders().get("someSpecialHeader")) {
313+
LoggingUtils.appendKey("specialKey", "value");
314+
}
315+
316+
log.info("Collecting payment");
317+
...
318+
}
319+
}
320+
```
321+
=== "#1 Request"
322+
323+
```json hl_lines="11"
324+
{
325+
"level": "INFO",
326+
"message": "Collecting payment",
327+
"timestamp": "2021-05-03 11:47:12,494+0200",
328+
"service": "payment",
329+
"coldStart": true,
330+
"functionName": "test",
331+
"functionMemorySize": 128,
332+
"functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
333+
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
334+
"specialKey": "value"
335+
}
336+
```
337+
338+
=== "#2 Request"
339+
340+
```json
341+
{
342+
"level": "INFO",
343+
"message": "Collecting payment",
344+
"timestamp": "2021-05-03 11:47:12,494+0200",
345+
"service": "payment",
346+
"coldStart": true,
347+
"functionName": "test",
348+
"functionMemorySize": 128,
349+
"functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
350+
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72"
351+
}
352+
```
289353

290354
## Override default object mapper
291355

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

+7
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@
7373
* @see <a href=https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03/>
7474
*/
7575
String correlationIdPath() default "";
76+
77+
/**
78+
* Logger is commonly initialized in the global scope.
79+
* Due to Lambda Execution Context reuse, this means that custom keys can be persisted across invocations.
80+
* Set this attribute to true if you want all custom keys to be deleted on each request.
81+
*/
82+
boolean clearState() default false;
7683
}

powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.logging.log4j.Level;
3030
import org.apache.logging.log4j.LogManager;
3131
import org.apache.logging.log4j.Logger;
32+
import org.apache.logging.log4j.ThreadContext;
3233
import org.apache.logging.log4j.core.LoggerContext;
3334
import org.apache.logging.log4j.core.config.Configurator;
3435
import org.apache.logging.log4j.core.util.IOUtils;
@@ -103,6 +104,10 @@ public Object around(ProceedingJoinPoint pjp,
103104

104105
Object proceed = pjp.proceed(proceedArgs);
105106

107+
if(logging.clearState()) {
108+
ThreadContext.clearMap();
109+
}
110+
106111
coldStartDone();
107112
return proceed;
108113
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020 Amazon.com, Inc. or its affiliates.
3+
* Licensed under the Apache License, Version 2.0 (the
4+
* "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
* limitations under the License.
12+
*
13+
*/
14+
package software.amazon.lambda.powertools.logging.handlers;
15+
16+
import com.amazonaws.services.lambda.runtime.Context;
17+
import com.amazonaws.services.lambda.runtime.RequestHandler;
18+
import org.apache.logging.log4j.LogManager;
19+
import org.apache.logging.log4j.Logger;
20+
import software.amazon.lambda.powertools.logging.Logging;
21+
import software.amazon.lambda.powertools.logging.LoggingUtils;
22+
23+
public class PowerLogToolEnabledWithClearState implements RequestHandler<Object, Object> {
24+
private final Logger LOG = LogManager.getLogger(PowerLogToolEnabledWithClearState.class);
25+
public static int COUNT = 1;
26+
@Override
27+
@Logging(clearState = true)
28+
public Object handleRequest(Object input, Context context) {
29+
if(COUNT == 1) {
30+
LoggingUtils.appendKey("TestKey", "TestValue");
31+
}
32+
LOG.info("Test event");
33+
COUNT++;
34+
return null;
35+
}
36+
}

powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java

+23
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import java.nio.file.Files;
2626
import java.nio.file.Paths;
2727
import java.nio.file.StandardOpenOption;
28+
import java.util.List;
2829
import java.util.Map;
30+
import java.util.stream.Collectors;
2931

3032
import com.amazonaws.services.lambda.runtime.Context;
3133
import com.amazonaws.services.lambda.runtime.RequestHandler;
@@ -52,6 +54,7 @@
5254
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayRestApiCorrelationId;
5355
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled;
5456
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream;
57+
import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledWithClearState;
5558
import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled;
5659
import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream;
5760
import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabled;
@@ -289,6 +292,26 @@ void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event)
289292
.containsEntry("correlation_id", event.getHeaders().get("x-amzn-trace-id"));
290293
}
291294

295+
@Test
296+
void shouldLogAndClearLogContextOnEachRequest() throws IOException {
297+
requestHandler = new PowerLogToolEnabledWithClearState();
298+
S3EventNotification s3EventNotification = s3EventNotification();
299+
300+
requestHandler.handleRequest(s3EventNotification, context);
301+
requestHandler.handleRequest(s3EventNotification, context);
302+
303+
List<String> logLines = Files.lines(Paths.get("target/logfile.json")).collect(Collectors.toList());
304+
Map<String, Object> invokeLog = parseToMap(logLines.get(0));
305+
306+
assertThat(invokeLog)
307+
.containsEntry("TestKey", "TestValue");
308+
309+
invokeLog = parseToMap(logLines.get(1));
310+
311+
assertThat(invokeLog)
312+
.doesNotContainKey("TestKey");
313+
}
314+
292315
private void setupContext() {
293316
when(context.getFunctionName()).thenReturn("testFunction");
294317
when(context.getInvokedFunctionArn()).thenReturn("testArn");

0 commit comments

Comments
 (0)