Skip to content

Commit e309122

Browse files
pankajagrawal16Pankaj Agrawal
and
Pankaj Agrawal
authored
feat:Correlation id injection (#448)
* capture correlation ids * chore: docs and javadocs update * support any Json Pointer path * docs: correlation id extraction Co-authored-by: Pankaj Agrawal <[email protected]>
1 parent 2aae5c6 commit e309122

File tree

14 files changed

+453
-8
lines changed

14 files changed

+453
-8
lines changed

docs/core/logging.md

+99
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,105 @@ to customise what is logged.
123123
}
124124
```
125125

126+
## Setting a Correlation ID
127+
128+
You can set a Correlation ID using `correlationIdPath` attribute by passing a [JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"}.
129+
130+
=== "App.java"
131+
132+
```java hl_lines="8"
133+
/**
134+
* Handler for requests to Lambda function.
135+
*/
136+
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
137+
138+
Logger log = LogManager.getLogger();
139+
140+
@Logging(correlationIdPath = "/headers/my_request_id_header")
141+
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
142+
...
143+
log.info("Collecting payment")
144+
...
145+
}
146+
}
147+
```
148+
=== "Example Event"
149+
150+
```json hl_lines="3"
151+
{
152+
"headers": {
153+
"my_request_id_header": "correlation_id_value"
154+
}
155+
}
156+
```
157+
158+
=== "Example CloudWatch Logs excerpt"
159+
160+
```json hl_lines="11"
161+
{
162+
"level": "INFO",
163+
"message": "Collecting payment",
164+
"timestamp": "2021-05-03 11:47:12,494+0200",
165+
"service": "payment",
166+
"coldStart": true,
167+
"functionName": "test",
168+
"functionMemorySize": 128,
169+
"functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
170+
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
171+
"correlation_id": "correlation_id_value"
172+
}
173+
```
174+
We provide [built-in JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"}
175+
for known event sources, where either a request ID or X-Ray Trace ID are present.
176+
177+
=== "App.java"
178+
179+
```java hl_lines="10"
180+
import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants;
181+
182+
/**
183+
* Handler for requests to Lambda function.
184+
*/
185+
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
186+
187+
Logger log = LogManager.getLogger();
188+
189+
@Logging(correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST)
190+
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
191+
...
192+
log.info("Collecting payment")
193+
...
194+
}
195+
}
196+
```
197+
198+
=== "Example Event"
199+
200+
```json hl_lines="3"
201+
{
202+
"requestContext": {
203+
"requestId": "correlation_id_value"
204+
}
205+
}
206+
```
207+
208+
=== "Example CloudWatch Logs excerpt"
209+
210+
```json hl_lines="11"
211+
{
212+
"level": "INFO",
213+
"message": "Collecting payment",
214+
"timestamp": "2021-05-03 11:47:12,494+0200",
215+
"service": "payment",
216+
"coldStart": true,
217+
"functionName": "test",
218+
"functionMemorySize": 128,
219+
"functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
220+
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
221+
"correlation_id": "correlation_id_value"
222+
}
223+
```
224+
126225
## Appending additional keys
127226

128227
You can append your own keys to your existing logs via `appendKey`.

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@
226226
<artifactId>aspectjtools</artifactId>
227227
<version>${aspectj.version}</version>
228228
</dependency>
229+
<dependency>
230+
<groupId>com.amazonaws</groupId>
231+
<artifactId>aws-lambda-java-tests</artifactId>
232+
<version>1.0.2</version>
233+
<scope>test</scope>
234+
</dependency>
229235
</dependencies>
230236
</dependencyManagement>
231237

powertools-logging/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@
112112
<artifactId>aws-lambda-java-events</artifactId>
113113
<scope>test</scope>
114114
</dependency>
115+
<dependency>
116+
<groupId>com.amazonaws</groupId>
117+
<artifactId>aws-lambda-java-tests</artifactId>
118+
<scope>test</scope>
119+
</dependency>
115120
<dependency>
116121
<groupId>org.skyscreamer</groupId>
117122
<artifactId>jsonassert</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package software.amazon.lambda.powertools.logging;
2+
3+
/**
4+
* Supported Event types from which Correlation ID can be extracted
5+
*/
6+
public class CorrelationIdPathConstants {
7+
/**
8+
* To use when function is expecting API Gateway Rest API Request event
9+
*/
10+
public static final String API_GATEWAY_REST = "/requestContext/requestId";
11+
/**
12+
* To use when function is expecting API Gateway HTTP API Request event
13+
*/
14+
public static final String API_GATEWAY_HTTP = "/requestContext/requestId";
15+
/**
16+
* To use when function is expecting Application Load balancer Request event
17+
*/
18+
public static final String APPLICATION_LOAD_BALANCER = "/headers/x-amzn-trace-id";
19+
/**
20+
* To use when function is expecting Event Bridge Request event
21+
*/
22+
public static final String EVENT_BRIDGE = "/id";
23+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,10 @@
6767
boolean logEvent() default false;
6868

6969
double samplingRate() default 0;
70+
71+
/**
72+
* Json Pointer path to extract correlation id from.
73+
* @see <a href=https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03/>
74+
*/
75+
String correlationIdPath() default "";
7076
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static void appendKey(String key, String value) {
4343
}
4444

4545

46+
4647
/**
4748
* Appends additional key and value to each log entry made. Duplicate values
4849
* for the same key will be replaced with the latest.
@@ -72,6 +73,15 @@ public static void removeKeys(String... keys) {
7273
ThreadContext.removeAll(asList(keys));
7374
}
7475

76+
/**
77+
* Sets correlation id attribute on the logs.
78+
*
79+
* @param value The value of the correlation id
80+
*/
81+
public static void setCorrelationId(String value) {
82+
ThreadContext.put("correlation_id", value);
83+
}
84+
7585
/**
7686
* Sets the instance of ObjectMapper object which is used for serialising event when
7787
* {@code @Logging(logEvent = true)}.

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

+63-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import java.util.Optional;
2424
import java.util.Random;
2525

26+
import com.fasterxml.jackson.core.JsonPointer;
2627
import com.fasterxml.jackson.core.JsonProcessingException;
28+
import com.fasterxml.jackson.databind.JsonNode;
2729
import org.apache.logging.log4j.Level;
2830
import org.apache.logging.log4j.LogManager;
2931
import org.apache.logging.log4j.Logger;
@@ -35,6 +37,7 @@
3537
import org.aspectj.lang.annotation.Aspect;
3638
import org.aspectj.lang.annotation.Pointcut;
3739
import software.amazon.lambda.powertools.logging.Logging;
40+
import software.amazon.lambda.powertools.logging.LoggingUtils;
3841

3942
import static java.nio.charset.StandardCharsets.UTF_8;
4043
import static java.util.Optional.empty;
@@ -94,6 +97,10 @@ public Object around(ProceedingJoinPoint pjp,
9497
proceedArgs = logEvent(pjp);
9598
}
9699

100+
if (!logging.correlationIdPath().isEmpty()) {
101+
proceedArgs = captureCorrelationId(logging.correlationIdPath(), pjp);
102+
}
103+
97104
Object proceed = pjp.proceed(proceedArgs);
98105

99106
coldStartDone();
@@ -160,18 +167,55 @@ private Object[] logEvent(final ProceedingJoinPoint pjp) {
160167
return args;
161168
}
162169

163-
private Object[] logFromInputStream(final ProceedingJoinPoint pjp) {
170+
private Object[] captureCorrelationId(final String correlationIdPath,
171+
final ProceedingJoinPoint pjp) {
164172
Object[] args = pjp.getArgs();
173+
if (isHandlerMethod(pjp)) {
174+
if (placedOnRequestHandler(pjp)) {
175+
Object arg = pjp.getArgs()[0];
176+
JsonNode jsonNode = objectMapper().valueToTree(arg);
165177

166-
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
167-
OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8);
168-
InputStreamReader reader = new InputStreamReader((InputStream) pjp.getArgs()[0], UTF_8)) {
178+
setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode);
169179

170-
IOUtils.copy(reader, writer);
171-
writer.flush();
172-
byte[] bytes = out.toByteArray();
173-
args[0] = new ByteArrayInputStream(bytes);
180+
return args;
181+
}
182+
183+
if (placedOnStreamHandler(pjp)) {
184+
try {
185+
byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]);
186+
JsonNode jsonNode = objectMapper().readTree(bytes);
187+
args[0] = new ByteArrayInputStream(bytes);
188+
189+
setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode);
190+
191+
return args;
192+
} catch (IOException e) {
193+
Logger log = logger(pjp);
194+
log.warn("Failed to capture correlation id on event from supplied input stream.", e);
195+
}
196+
}
197+
}
198+
199+
return args;
200+
}
174201

202+
private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPoint pjp, JsonNode jsonNode) {
203+
JsonNode node = jsonNode.at(JsonPointer.compile(correlationIdPath));
204+
205+
String asText = node.asText();
206+
if (null != asText && !asText.isEmpty()) {
207+
LoggingUtils.setCorrelationId(asText);
208+
} else {
209+
logger(pjp).debug("Unable to extract any correlation id. Is your function expecting supported event type?");
210+
}
211+
}
212+
213+
private Object[] logFromInputStream(final ProceedingJoinPoint pjp) {
214+
Object[] args = pjp.getArgs();
215+
216+
try {
217+
byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]);
218+
args[0] = new ByteArrayInputStream(bytes);
175219
Logger log = logger(pjp);
176220

177221
asJson(pjp, objectMapper().readValue(bytes, Map.class))
@@ -185,6 +229,17 @@ private Object[] logFromInputStream(final ProceedingJoinPoint pjp) {
185229
return args;
186230
}
187231

232+
private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws IOException {
233+
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
234+
InputStreamReader reader = new InputStreamReader(inputStream)) {
235+
OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8);
236+
237+
IOUtils.copy(reader, writer);
238+
writer.flush();
239+
return out.toByteArray();
240+
}
241+
}
242+
188243
private Optional<String> asJson(final ProceedingJoinPoint pjp,
189244
final Object target) {
190245
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
19+
import org.apache.logging.log4j.LogManager;
20+
import org.apache.logging.log4j.Logger;
21+
import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants;
22+
import software.amazon.lambda.powertools.logging.Logging;
23+
24+
import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_REST;
25+
import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.APPLICATION_LOAD_BALANCER;
26+
27+
public class PowerLogToolAlbCorrelationId implements RequestHandler<ApplicationLoadBalancerRequestEvent, Object> {
28+
private final Logger LOG = LogManager.getLogger(PowerLogToolAlbCorrelationId.class);
29+
30+
@Override
31+
@Logging(correlationIdPath = APPLICATION_LOAD_BALANCER)
32+
public Object handleRequest(ApplicationLoadBalancerRequestEvent input, Context context) {
33+
LOG.info("Test event");
34+
LOG.debug("Test debug event");
35+
return null;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
19+
import org.apache.logging.log4j.LogManager;
20+
import org.apache.logging.log4j.Logger;
21+
import software.amazon.lambda.powertools.logging.Logging;
22+
23+
import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_HTTP;
24+
25+
public class PowerLogToolApiGatewayHttpApiCorrelationId implements RequestHandler<APIGatewayV2HTTPEvent, Object> {
26+
private final Logger LOG = LogManager.getLogger(PowerLogToolApiGatewayHttpApiCorrelationId.class);
27+
28+
@Override
29+
@Logging(correlationIdPath = API_GATEWAY_HTTP)
30+
public Object handleRequest(APIGatewayV2HTTPEvent input, Context context) {
31+
LOG.info("Test event");
32+
LOG.debug("Test debug event");
33+
return null;
34+
}
35+
}

0 commit comments

Comments
 (0)