Skip to content

Commit 1478899

Browse files
Support for logging event (#11)
1 parent 810ffb5 commit 1478899

File tree

8 files changed

+192
-11
lines changed

8 files changed

+192
-11
lines changed

example/HelloWorldFunction/src/main/java/helloworld/App.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatew
2323

2424
Logger log = LogManager.getLogger();
2525

26-
@PowerToolsLogging
26+
@PowerToolsLogging(logEvent = true)
2727
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
2828
Map<String, String> headers = new HashMap<>();
2929
headers.put("Content-Type", "application/json");
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package helloworld;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
import java.util.Map;
7+
8+
import com.amazonaws.services.lambda.runtime.Context;
9+
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import software.aws.lambda.logging.PowerToolsLogging;
12+
13+
public class AppStream implements RequestStreamHandler {
14+
private static final ObjectMapper mapper = new ObjectMapper();
15+
16+
@Override
17+
@PowerToolsLogging(logEvent = true)
18+
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
19+
Map map = mapper.readValue(input, Map.class);
20+
21+
System.out.println(map.size());
22+
}
23+
}

example/template.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ Resources:
2828
Path: /hello
2929
Method: get
3030

31+
HelloWorldStreamFunction:
32+
Type: AWS::Serverless::Function
33+
Properties:
34+
CodeUri: HelloWorldFunction
35+
Handler: helloworld.AppStream::handleRequest
36+
Runtime: java8
37+
MemorySize: 512
38+
Environment:
39+
Variables:
40+
PARAM1: VALUE
41+
Events:
42+
HelloWorld:
43+
Type: Api
44+
Properties:
45+
Path: /hellostream
46+
Method: get
47+
3148
Outputs:
3249
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
3350
# Find out more about other implicit resources you can reference within SAM
Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package software.aws.lambda.logging;
22

3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.io.InputStreamReader;
8+
import java.io.OutputStreamWriter;
9+
import java.util.Map;
310
import java.util.Optional;
411

512
import com.amazonaws.services.lambda.runtime.Context;
613
import com.amazonaws.services.lambda.runtime.RequestHandler;
714
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
15+
import com.fasterxml.jackson.databind.ObjectMapper;
16+
import org.apache.logging.log4j.LogManager;
17+
import org.apache.logging.log4j.Logger;
818
import org.apache.logging.log4j.ThreadContext;
19+
import org.apache.logging.log4j.core.util.IOUtils;
920
import org.aspectj.lang.ProceedingJoinPoint;
1021
import org.aspectj.lang.annotation.Around;
1122
import org.aspectj.lang.annotation.Aspect;
@@ -17,14 +28,16 @@
1728
@Aspect
1829
public final class LambdaAspect {
1930
static Boolean IS_COLD_START = null;
31+
private static final ObjectMapper mapper = new ObjectMapper();
2032

2133
@Pointcut("@annotation(powerToolsLogging)")
2234
public void callAt(PowerToolsLogging powerToolsLogging) {
2335
}
2436

25-
@Around(value = "callAt(powerToolsLogging)")
37+
@Around(value = "callAt(powerToolsLogging)", argNames = "pjp,powerToolsLogging")
2638
public Object around(ProceedingJoinPoint pjp,
2739
PowerToolsLogging powerToolsLogging) throws Throwable {
40+
Object[] proceedArgs = pjp.getArgs();
2841

2942
extractContext(pjp)
3043
.ifPresent(context -> {
@@ -34,24 +47,84 @@ public Object around(ProceedingJoinPoint pjp,
3447

3548
IS_COLD_START = false;
3649

50+
if (powerToolsLogging.logEvent()) {
51+
proceedArgs = logEvent(pjp);
52+
}
3753

38-
return pjp.proceed();
54+
return pjp.proceed(proceedArgs);
3955
}
4056

4157
private Optional<Context> extractContext(ProceedingJoinPoint pjp) {
4258

43-
if ("handleRequest".equals(pjp.getSignature().getName())) {
44-
if (RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType())
45-
&& pjp.getArgs().length == 2 && pjp.getArgs()[1] instanceof Context) {
59+
if (isHandlerMethod(pjp)) {
60+
if (placedOnRequestHandler(pjp)) {
4661
return of((Context) pjp.getArgs()[1]);
4762
}
4863

49-
if (RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType())
50-
&& pjp.getArgs().length == 3 && pjp.getArgs()[2] instanceof Context) {
64+
if (placedOnStreamHandler(pjp)) {
5165
return of((Context) pjp.getArgs()[2]);
5266
}
5367
}
5468

5569
return empty();
5670
}
71+
72+
private Object[] logEvent(ProceedingJoinPoint pjp) {
73+
Object[] args = pjp.getArgs();
74+
75+
if (isHandlerMethod(pjp)) {
76+
if (placedOnRequestHandler(pjp)) {
77+
Logger log = logger(pjp);
78+
log.info(pjp.getArgs()[0]);
79+
}
80+
81+
if (placedOnStreamHandler(pjp)) {
82+
args = logFromInputStream(pjp);
83+
}
84+
}
85+
86+
return args;
87+
}
88+
89+
private Object[] logFromInputStream(ProceedingJoinPoint pjp) {
90+
Object[] args = pjp.getArgs();
91+
92+
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
93+
OutputStreamWriter writer = new OutputStreamWriter(out);
94+
InputStreamReader reader = new InputStreamReader((InputStream) pjp.getArgs()[0])) {
95+
96+
IOUtils.copy(reader, writer);
97+
writer.flush();
98+
byte[] bytes = out.toByteArray();
99+
args[0] = new ByteArrayInputStream(bytes);
100+
101+
Logger log = logger(pjp);
102+
log.info(mapper.readValue(bytes, Map.class));
103+
104+
} catch (IOException e) {
105+
Logger log = logger(pjp);
106+
log.debug("Failed to log event from supplied input stream.", e);
107+
}
108+
109+
return args;
110+
}
111+
112+
private Logger logger(ProceedingJoinPoint pjp) {
113+
return LogManager.getLogger(pjp.getSignature().getDeclaringType());
114+
}
115+
116+
private boolean isHandlerMethod(ProceedingJoinPoint pjp) {
117+
return "handleRequest".equals(pjp.getSignature().getName());
118+
}
119+
120+
private boolean placedOnRequestHandler(ProceedingJoinPoint pjp) {
121+
return RequestHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType())
122+
&& pjp.getArgs().length == 2 && pjp.getArgs()[1] instanceof Context;
123+
}
124+
125+
private boolean placedOnStreamHandler(ProceedingJoinPoint pjp) {
126+
return RequestStreamHandler.class.isAssignableFrom(pjp.getSignature().getDeclaringType())
127+
&& pjp.getArgs().length == 3 && pjp.getArgs()[0] instanceof InputStream
128+
&& pjp.getArgs()[2] instanceof Context;
129+
}
57130
}

src/main/java/software/aws/lambda/logging/PowerToolsLogging.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
@Retention(RetentionPolicy.RUNTIME)
99
@Target(ElementType.METHOD)
1010
public @interface PowerToolsLogging {
11+
12+
boolean logEvent() default false;
1113
}

src/test/java/software/aws/lambda/logging/LambdaAspectTest.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package software.aws.lambda.logging;
22

3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
35
import java.io.IOException;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.HashMap;
8+
import java.util.Map;
49

510
import com.amazonaws.services.lambda.runtime.Context;
611
import com.amazonaws.services.lambda.runtime.RequestHandler;
712
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
814
import org.apache.logging.log4j.ThreadContext;
915
import org.junit.jupiter.api.BeforeEach;
1016
import org.junit.jupiter.api.Test;
@@ -49,7 +55,7 @@ void shouldSetLambdaContextWhenEnabled() {
4955
void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException {
5056
requestStreamHandler = new PowerLogToolEnabledForStream();
5157

52-
requestStreamHandler.handleRequest(null, null, context);
58+
requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), null, context);
5359

5460
assertThat(ThreadContext.getImmutableContext())
5561
.hasSize(5)
@@ -62,13 +68,13 @@ void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException {
6268

6369
@Test
6470
void shouldSetColdStartFlag() throws IOException {
65-
requestStreamHandler.handleRequest(null, null, context);
71+
requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), null, context);
6672

6773
assertThat(ThreadContext.getImmutableContext())
6874
.hasSize(5)
6975
.containsEntry("coldStart", "true");
7076

71-
requestStreamHandler.handleRequest(null, null, context);
77+
requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), null, context);
7278

7379
assertThat(ThreadContext.getImmutableContext())
7480
.hasSize(5)
@@ -105,6 +111,33 @@ void shouldHaveNoEffectIfNotUsedOnLambdaHandler() {
105111
.isEmpty();
106112
}
107113

114+
@Test
115+
void shouldLogEventForHandler() {
116+
requestHandler = new PowerToolLogEventEnabled();
117+
118+
requestHandler.handleRequest(new Object(), context);
119+
120+
assertThat(ThreadContext.getImmutableContext())
121+
.hasSize(5);
122+
}
123+
124+
@Test
125+
void shouldLogEventForStreamAndLambdaStreamIsValid() throws IOException {
126+
requestStreamHandler = new PowerToolLogEventEnabledForStream();
127+
ByteArrayOutputStream output = new ByteArrayOutputStream();
128+
129+
Map<String, String> testPayload = new HashMap<>();
130+
testPayload.put("test", "payload");
131+
132+
requestStreamHandler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(testPayload)), output, context);
133+
134+
assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8))
135+
.isEqualTo("{\"test\":\"payload\"}");
136+
137+
assertThat(ThreadContext.getImmutableContext())
138+
.hasSize(5);
139+
}
140+
108141
private void setupContext() {
109142
when(context.getFunctionName()).thenReturn("testFunction");
110143
when(context.getInvokedFunctionArn()).thenReturn("testArn");
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package software.aws.lambda.logging;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.amazonaws.services.lambda.runtime.RequestHandler;
5+
6+
public class PowerToolLogEventEnabled implements RequestHandler<Object, Object> {
7+
8+
@PowerToolsLogging(logEvent = true)
9+
@Override
10+
public Object handleRequest(Object input, Context context) {
11+
return null;
12+
}
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package software.aws.lambda.logging;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
import java.util.Map;
7+
8+
import com.amazonaws.services.lambda.runtime.Context;
9+
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
12+
public class PowerToolLogEventEnabledForStream implements RequestStreamHandler {
13+
14+
@PowerToolsLogging(logEvent = true)
15+
@Override
16+
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
17+
ObjectMapper mapper = new ObjectMapper();
18+
mapper.writeValue(output, mapper.readValue(input, Map.class));
19+
}
20+
}

0 commit comments

Comments
 (0)