Skip to content

Commit 500ad22

Browse files
committed
complete tests for log4j implementation
1 parent d66bda5 commit 500ad22

File tree

10 files changed

+332
-43
lines changed

10 files changed

+332
-43
lines changed

powertools-logging/powertools-logging-log4j/pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,45 @@
117117
<artifactId>jsonassert</artifactId>
118118
<scope>test</scope>
119119
</dependency>
120+
<dependency>
121+
<groupId>org.junit-pioneer</groupId>
122+
<artifactId>junit-pioneer</artifactId>
123+
<scope>test</scope>
124+
</dependency>
120125
</dependencies>
121126

122127
<build>
123128
<plugins>
129+
<plugin>
130+
<groupId>dev.aspectj</groupId>
131+
<artifactId>aspectj-maven-plugin</artifactId>
132+
<version>1.13.1</version>
133+
<configuration>
134+
<source>${maven.compiler.source}</source>
135+
<target>${maven.compiler.target}</target>
136+
<complianceLevel>${maven.compiler.target}</complianceLevel>
137+
<aspectLibraries>
138+
<aspectLibrary>
139+
<groupId>software.amazon.lambda</groupId>
140+
<artifactId>powertools-logging</artifactId>
141+
</aspectLibrary>
142+
</aspectLibraries>
143+
</configuration>
144+
<executions>
145+
<execution>
146+
<goals>
147+
<goal>compile</goal>
148+
</goals>
149+
</execution>
150+
</executions>
151+
<dependencies>
152+
<dependency>
153+
<groupId>org.aspectj</groupId>
154+
<artifactId>aspectjtools</artifactId>
155+
<version>${aspectj.version}</version>
156+
</dependency>
157+
</dependencies>
158+
</plugin>
124159
<plugin>
125160
<groupId>org.apache.maven.plugins</groupId>
126161
<artifactId>maven-checkstyle-plugin</artifactId>
Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@
1212
*
1313
*/
1414

15-
package software.amazon.lambda.powertools.logging.internal;
15+
package org.apache.logging.log4j.layout.template.json.resolver;
1616

1717
import org.apache.logging.log4j.core.LogEvent;
18-
import org.apache.logging.log4j.layout.template.json.resolver.EventResolver;
19-
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig;
2018
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
2119
import org.apache.logging.log4j.util.ReadOnlyStringMap;
20+
import software.amazon.lambda.powertools.common.internal.LambdaConstants;
21+
import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields;
2222

23+
/**
24+
* Custom {@link org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver}
25+
* used by {@link org.apache.logging.log4j.layout.template.json.JsonTemplateLayout}
26+
* to be able to recognize powertools fields in the LambdaJsonLayout.json file.
27+
*/
2328
final class PowertoolsResolver implements EventResolver {
2429

2530
private static final EventResolver COLD_START_RESOLVER = new EventResolver() {
@@ -87,7 +92,11 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
8792
public boolean isResolvable(LogEvent logEvent) {
8893
final String samplingRate =
8994
logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName());
90-
return null != samplingRate;
95+
try {
96+
return (null != samplingRate && Float.parseFloat(samplingRate) > 0.f);
97+
} catch (NumberFormatException nfe) {
98+
return false;
99+
}
91100
}
92101

93102
@Override
@@ -98,12 +107,21 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
98107
}
99108
};
100109

101-
private static final EventResolver XRAY_TRACE_RESOLVER =
102-
(final LogEvent logEvent, final JsonWriter jsonWriter) -> {
103-
final String traceId =
104-
logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName());
105-
jsonWriter.writeString(traceId);
106-
};
110+
private static final EventResolver XRAY_TRACE_RESOLVER = new EventResolver() {
111+
@Override
112+
public boolean isResolvable(LogEvent logEvent) {
113+
final String traceId =
114+
logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName());
115+
return null != traceId;
116+
}
117+
118+
@Override
119+
public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
120+
final String traceId =
121+
logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName());
122+
jsonWriter.writeString(traceId);
123+
}
124+
};
107125

108126
private static final EventResolver SERVICE_RESOLVER =
109127
(final LogEvent logEvent, final JsonWriter jsonWriter) -> {
@@ -113,7 +131,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
113131

114132
private static final EventResolver REGION_RESOLVER =
115133
(final LogEvent logEvent, final JsonWriter jsonWriter) ->
116-
jsonWriter.writeString(System.getenv("AWS_REGION"));
134+
jsonWriter.writeString(System.getenv(LambdaConstants.AWS_REGION_ENV));
117135

118136
private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() {
119137
@Override
@@ -132,7 +150,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
132150
private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER =
133151
(LogEvent logEvent, JsonWriter jsonWriter) -> {
134152
StringBuilder stringBuilder = jsonWriter.getStringBuilder();
135-
// remove dummy field to kick inn powertools resolver
153+
// remove dummy field to kick in powertools resolver
136154
stringBuilder.setLength(stringBuilder.length() - 4);
137155

138156
// Inject all the context information.
@@ -195,10 +213,6 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
195213
}
196214
}
197215

198-
static String getName() {
199-
return "powertools";
200-
}
201-
202216
@Override
203217
public void resolve(LogEvent value, JsonWriter jsonWriter) {
204218
internalResolver.resolve(value, jsonWriter);
@@ -207,6 +221,6 @@ public void resolve(LogEvent value, JsonWriter jsonWriter) {
207221
@Override
208222
public boolean isResolvable(LogEvent value) {
209223
ReadOnlyStringMap contextData = value.getContextData();
210-
return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable();
224+
return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable(value);
211225
}
212226
}
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,17 @@
1212
*
1313
*/
1414

15-
package software.amazon.lambda.powertools.logging.internal;
15+
package org.apache.logging.log4j.layout.template.json.resolver;
1616

1717
import org.apache.logging.log4j.core.LogEvent;
1818
import org.apache.logging.log4j.core.config.plugins.Plugin;
1919
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
20-
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
21-
import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
22-
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
23-
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig;
24-
import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory;
2520

2621
@Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY)
2722
public final class PowertoolsResolverFactory implements EventResolverFactory {
2823

2924
private static final PowertoolsResolverFactory INSTANCE = new PowertoolsResolverFactory();
25+
private static final String RESOLVER_NAME = "powertools";
3026

3127
private PowertoolsResolverFactory() {
3228
}
@@ -38,7 +34,7 @@ public static PowertoolsResolverFactory getInstance() {
3834

3935
@Override
4036
public String getName() {
41-
return PowertoolsResolver.getName();
37+
return RESOLVER_NAME;
4238
}
4339

4440
@Override

powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323
"$resolver": "powertools",
2424
"field": "service_version"
2525
},
26-
"event.dataset": {
27-
"$resolver": "powertools",
28-
"field": "service_name"
29-
},
3026
"process.thread.name": {
3127
"$resolver": "thread",
3228
"field": "name"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2023 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+
15+
package org.apache.logging.log4j.layout.template.json.resolver;
16+
17+
import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.contentOf;
20+
import static org.mockito.Mockito.when;
21+
import static org.mockito.MockitoAnnotations.openMocks;
22+
23+
import com.amazonaws.services.lambda.runtime.Context;
24+
import java.io.File;
25+
import java.io.IOException;
26+
import java.nio.channels.FileChannel;
27+
import java.nio.file.NoSuchFileException;
28+
import java.nio.file.Paths;
29+
import java.nio.file.StandardOpenOption;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
import org.junitpioneer.jupiter.SetEnvironmentVariable;
33+
import org.mockito.Mock;
34+
import org.slf4j.MDC;
35+
import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor;
36+
import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled;
37+
38+
class PowerToolsResolverFactoryTest {
39+
40+
@Mock
41+
private Context context;
42+
43+
@BeforeEach
44+
void setUp() throws IllegalAccessException, IOException {
45+
openMocks(this);
46+
MDC.clear();
47+
writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true);
48+
setupContext();
49+
// Make sure file is cleaned up before running tests
50+
try {
51+
FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
52+
FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close();
53+
} catch (NoSuchFileException e) {
54+
// file may not exist on the first launch
55+
}
56+
}
57+
58+
@Test
59+
@SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4j")
60+
@SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001")
61+
@SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678")
62+
void shouldLogInJsonFormat() {
63+
PowertoolsLogEnabled handler = new PowertoolsLogEnabled();
64+
handler.handleRequest("Input", context);
65+
66+
File logFile = new File("target/logfile.json");
67+
assertThat(contentOf(logFile)).startsWith(
68+
"{\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"level\":\"INFO\",\"message\":\"Test event\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":")
69+
.endsWith("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n");
70+
}
71+
72+
@Test
73+
@SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1")
74+
@SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4jEcs")
75+
@SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678")
76+
void shouldLogInEcsFormat() {
77+
PowertoolsLogEnabled handler = new PowertoolsLogEnabled();
78+
handler.handleRequest("Input", context);
79+
80+
File logFile = new File("target/ecslogfile.json");
81+
assertThat(contentOf(logFile)).endsWith(
82+
"\"ecs.version\":\"1.2.0\",\"log.level\":\"INFO\",\"message\":\"Test event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"process.thread.name\":\"main\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.coldstart\":true,\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.memory\":1024,\"faas.name\":\"testFunction\",\"faas.execution\":\"RequestId\",\"faas.version\":\"1\",\"myKey\":\"myValue\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\"}\n")
83+
.startsWith("{\"@timestamp\":\"");
84+
}
85+
86+
private void setupContext() {
87+
when(context.getFunctionName()).thenReturn("testFunction");
88+
when(context.getInvokedFunctionArn()).thenReturn(
89+
"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1");
90+
when(context.getFunctionVersion()).thenReturn("1");
91+
when(context.getMemoryLimitInMB()).thenReturn(1024);
92+
when(context.getAwsRequestId()).thenReturn("RequestId");
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2023 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+
15+
package org.apache.logging.log4j.layout.template.json.resolver;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN;
19+
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START;
20+
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE;
21+
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE;
22+
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
26+
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
27+
import org.apache.logging.log4j.util.SortedArrayStringMap;
28+
import org.apache.logging.log4j.util.StringMap;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.EnumSource;
32+
import org.junitpioneer.jupiter.SetEnvironmentVariable;
33+
import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields;
34+
35+
class PowertoolsResolverTest {
36+
37+
@ParameterizedTest
38+
@EnumSource(value = PowertoolsLoggedFields.class,
39+
mode = EnumSource.Mode.EXCLUDE,
40+
names = {"FUNCTION_MEMORY_SIZE", "SAMPLING_RATE", "FUNCTION_COLD_START"})
41+
void shouldResolveFunctionStringInfo(PowertoolsLoggedFields field) {
42+
String result = resolveField(field.getName(), "value");
43+
assertThat(result).isEqualTo("\"value\"");
44+
}
45+
46+
@Test
47+
void shouldResolveMemorySize() {
48+
String result = resolveField(FUNCTION_MEMORY_SIZE.getName(), "42");
49+
assertThat(result).isEqualTo("42");
50+
}
51+
52+
@Test
53+
void shouldResolveSamplingRate() {
54+
String result = resolveField(SAMPLING_RATE.getName(), "0.4");
55+
assertThat(result).isEqualTo("0.4");
56+
}
57+
58+
@Test
59+
void shouldResolveColdStart() {
60+
String result = resolveField(FUNCTION_COLD_START.getName(), "true");
61+
assertThat(result).isEqualTo("true");
62+
}
63+
64+
@Test
65+
void shouldResolveAccountId() {
66+
String result = resolveField(FUNCTION_ARN.getName(), "account_id", "arn:aws:lambda:us-east-2:123456789012:function:my-function");
67+
assertThat(result).isEqualTo("\"123456789012\"");
68+
}
69+
70+
@Test
71+
@SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-2")
72+
void shouldResolveRegion() {
73+
String result = resolveField("region", "dummy, will use the env var");
74+
assertThat(result).isEqualTo("\"eu-central-2\"");
75+
}
76+
77+
private static String resolveField(String field, String value) {
78+
return resolveField(field, field, value);
79+
}
80+
81+
private static String resolveField(String data, String field, String value) {
82+
Map<String, Object> configMap = new HashMap<>();
83+
configMap.put("field", field);
84+
85+
TemplateResolverConfig config = new TemplateResolverConfig(configMap);
86+
PowertoolsResolver resolver = new PowertoolsResolver(config);
87+
JsonWriter writer = JsonWriter
88+
.newBuilder()
89+
.setMaxStringLength(1000)
90+
.setTruncatedStringSuffix("")
91+
.build();
92+
93+
StringMap contextMap = new SortedArrayStringMap();
94+
contextMap.putValue(data, value);
95+
96+
Log4jLogEvent logEvent = Log4jLogEvent.newBuilder().setContextData(contextMap).build();
97+
if (resolver.isResolvable(logEvent)) {
98+
resolver.resolve(logEvent, writer);
99+
}
100+
101+
return writer.getStringBuilder().toString();
102+
}
103+
}

0 commit comments

Comments
 (0)