Skip to content

Commit 6e588dc

Browse files
committed
logback implementation of the logging module
1 parent 61153ad commit 6e588dc

File tree

10 files changed

+872
-0
lines changed

10 files changed

+872
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package software.amazon.lambda.powertools.logging;
2+
3+
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
4+
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
5+
import ch.qos.logback.classic.spi.ILoggingEvent;
6+
import ch.qos.logback.classic.spi.IThrowableProxy;
7+
import ch.qos.logback.classic.spi.ThrowableProxy;
8+
import ch.qos.logback.core.LayoutBase;
9+
10+
/**
11+
* Custom layout for logback that encodes logs in JSON format.
12+
* It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library.
13+
*/
14+
public class LambdaJsonLayout extends LayoutBase<ILoggingEvent> {
15+
private final static String CONTENT_TYPE = "application/json";
16+
private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter();
17+
private ThrowableHandlingConverter throwableConverter;
18+
private String timestampFormat;
19+
private String timestampFormatTimezoneId;
20+
private boolean includeThreadInfo;
21+
22+
@Override
23+
public String doLayout(ILoggingEvent event) {
24+
StringBuilder builder = new StringBuilder(256);
25+
LambdaJsonSerializer.serializeObjectStart(builder);
26+
LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel());
27+
LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage());
28+
IThrowableProxy throwableProxy = event.getThrowableProxy();
29+
if (throwableProxy != null) {
30+
if (throwableConverter != null) {
31+
LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString());
32+
} else if (throwableProxy instanceof ThrowableProxy) {
33+
LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable());
34+
} else {
35+
LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString());
36+
}
37+
}
38+
LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap());
39+
if (includeThreadInfo) {
40+
LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName());
41+
LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId()));
42+
LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority()));
43+
}
44+
LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId);
45+
LambdaJsonSerializer.serializeObjectEnd(builder);
46+
return builder.toString();
47+
}
48+
49+
@Override
50+
public String getContentType() {
51+
return CONTENT_TYPE;
52+
}
53+
54+
public void setTimestampFormat(String timestampFormat) {
55+
this.timestampFormat = timestampFormat;
56+
}
57+
58+
public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) {
59+
this.timestampFormatTimezoneId = timestampFormatTimezoneId;
60+
}
61+
62+
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
63+
this.throwableConverter = throwableConverter;
64+
}
65+
66+
// public static final String INSTANT_ATTR_NAME = "instant";
67+
// public static final String EPOCH_SEC_ATTR_NAME = "epochSecond";
68+
// public static final String NANO_SEC_ATTR_NAME = "nanoOfSecond";
69+
// public static final String LOGGER_FQCN_ATTR_NAME = "loggerFqcn";
70+
// public static final String LOGGER_ATTR_NAME = "loggerName";
71+
// public static final String THREAD_ID_ATTR_NAME = "threadId";
72+
// public static final String THREAD_PRIORITY_ATTR_NAME = "threadPriority";
73+
//
74+
// private boolean includePowertools;
75+
// private boolean includeInstant;
76+
// private boolean includeThreadInfo;
77+
//
78+
// public LambdaJsonLayout() {
79+
// super();
80+
// this.includeInstant = true;
81+
// this.includePowertools = true;
82+
// this.includeThreadInfo = true;
83+
// }
84+
//
85+
// @Override
86+
// protected Map<String, Object> toJsonMap(ILoggingEvent event) {
87+
// Map<String, Object> map = new LinkedHashMap<>();
88+
// addTimestamp(TIMESTAMP_ATTR_NAME, this.includeTimestamp, event.getTimeStamp(), map);
89+
// addInstant(this.includeInstant, event.getTimeStamp(), event.getNanoseconds(), map);
90+
// add(THREAD_ATTR_NAME, this.includeThreadName || this.includeThreadInfo, event.getThreadName(), map);
91+
// add(LEVEL_ATTR_NAME, this.includeLevel, String.valueOf(event.getLevel()), map);
92+
// add(LOGGER_ATTR_NAME, this.includeLoggerName, event.getLoggerName(), map);
93+
// add(FORMATTED_MESSAGE_ATTR_NAME, this.includeFormattedMessage, event.getFormattedMessage(), map);
94+
// addThrowableInfo(EXCEPTION_ATTR_NAME, this.includeException, event, map);
95+
// // contextStack ?
96+
// // endOfBatch ?
97+
// map.put(LOGGER_FQCN_ATTR_NAME, "ch.qos.logback.classic.Logger");
98+
// add(THREAD_ID_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getId()), map);
99+
// add(THREAD_PRIORITY_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getPriority()), map);
100+
// addPowertools(this.includePowertools, event.getMDCPropertyMap(), map);
101+
// return map;
102+
// }
103+
//
104+
// private void addPowertools(boolean includePowertools, Map<String, String> mdcPropertyMap, Map<String, Object> map) {
105+
// TreeMap<String, String> sortedMap = new TreeMap<>(mdcPropertyMap);
106+
// List<String> powertoolsFields = DefaultLambdaFields.stringValues();
107+
//
108+
// sortedMap.forEach((k, v) -> {
109+
// if (includePowertools || !powertoolsFields.contains(k)) {
110+
// map.put(k, v);
111+
// }
112+
// });
113+
//
114+
// }
115+
//
116+
// private void addInstant(boolean includeInstant, long timeStamp, int nanoseconds, Map<String, Object> map) {
117+
// if (includeInstant) {
118+
// Map<String, Object> instantMap = new LinkedHashMap<>();
119+
// instantMap.put(EPOCH_SEC_ATTR_NAME, timeStamp / 1000);
120+
// instantMap.put(NANO_SEC_ATTR_NAME, nanoseconds);
121+
// map.put(LambdaJsonLayout.INSTANT_ATTR_NAME, instantMap);
122+
// }
123+
// }
124+
//
125+
// public void setIncludeInstant(boolean includeInstant) {
126+
// this.includeInstant = includeInstant;
127+
// }
128+
//
129+
// public void setIncludePowertools(boolean includePowertools) {
130+
// this.includePowertools = includePowertools;
131+
// }
132+
//
133+
public void setIncludeThreadInfo(boolean includeThreadInfo) {
134+
this.includeThreadInfo = includeThreadInfo;
135+
}
136+
137+
}

powertools-logging-logback/pom.xml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>powertools-parent</artifactId>
7+
<groupId>software.amazon.lambda</groupId>
8+
<version>1.12.3</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>powertools-logging-logback</artifactId>
13+
<name>AWS Lambda Powertools for Java library Logging with LogBack</name>
14+
<description>
15+
A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier.
16+
</description>
17+
<url>https://aws.amazon.com/lambda/</url>
18+
<issueManagement>
19+
<system>GitHub Issues</system>
20+
<url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url>
21+
</issueManagement>
22+
<scm>
23+
<url>https://github.com/awslabs/aws-lambda-powertools-java.git</url>
24+
</scm>
25+
<developers>
26+
<developer>
27+
<name>AWS Lambda Powertools team</name>
28+
<organization>Amazon Web Services</organization>
29+
<organizationUrl>https://aws.amazon.com/</organizationUrl>
30+
</developer>
31+
</developers>
32+
33+
<distributionManagement>
34+
<snapshotRepository>
35+
<id>ossrh</id>
36+
<url>https://aws.oss.sonatype.org/content/repositories/snapshots</url>
37+
</snapshotRepository>
38+
</distributionManagement>
39+
40+
41+
<dependencies>
42+
<dependency>
43+
<groupId>software.amazon.lambda</groupId>
44+
<artifactId>powertools-logging</artifactId>
45+
<version>${version}</version>
46+
</dependency>
47+
<dependency>
48+
<groupId>ch.qos.logback</groupId>
49+
<artifactId>logback-classic</artifactId>
50+
<version>1.3.4</version> <!-- v1.3.x compatible with JDK 1.8, v1.4.x only compatible with JDK 11 -->
51+
<scope>provided</scope> <!-- provided to let users change to 1.4.x when using JDK 11 -->
52+
<exclusions>
53+
<exclusion>
54+
<groupId>com.sun.mail</groupId>
55+
<artifactId>javax.mail</artifactId>
56+
</exclusion>
57+
</exclusions>
58+
</dependency>
59+
60+
<!-- Test dependencies -->
61+
<dependency>
62+
<groupId>org.junit.jupiter</groupId>
63+
<artifactId>junit-jupiter-api</artifactId>
64+
<scope>test</scope>
65+
</dependency>
66+
<dependency>
67+
<groupId>org.junit.jupiter</groupId>
68+
<artifactId>junit-jupiter-engine</artifactId>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>org.apache.commons</groupId>
73+
<artifactId>commons-lang3</artifactId>
74+
<scope>test</scope>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.mockito</groupId>
78+
<artifactId>mockito-core</artifactId>
79+
<scope>test</scope>
80+
</dependency>
81+
<dependency>
82+
<groupId>org.mockito</groupId>
83+
<artifactId>mockito-inline</artifactId>
84+
<scope>test</scope>
85+
</dependency>
86+
<dependency>
87+
<groupId>org.aspectj</groupId>
88+
<artifactId>aspectjweaver</artifactId>
89+
<scope>test</scope>
90+
</dependency>
91+
<dependency>
92+
<groupId>org.assertj</groupId>
93+
<artifactId>assertj-core</artifactId>
94+
<scope>test</scope>
95+
</dependency>
96+
<dependency>
97+
<groupId>com.amazonaws</groupId>
98+
<artifactId>aws-lambda-java-events</artifactId>
99+
<scope>test</scope>
100+
</dependency>
101+
<dependency>
102+
<groupId>com.amazonaws</groupId>
103+
<artifactId>aws-lambda-java-tests</artifactId>
104+
<scope>test</scope>
105+
</dependency>
106+
<dependency>
107+
<groupId>org.skyscreamer</groupId>
108+
<artifactId>jsonassert</artifactId>
109+
<scope>test</scope>
110+
</dependency>
111+
</dependencies>
112+
113+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package software.amazon.lambda.powertools.logging;
2+
3+
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
4+
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
5+
import ch.qos.logback.classic.spi.ILoggingEvent;
6+
import ch.qos.logback.classic.spi.IThrowableProxy;
7+
import ch.qos.logback.classic.spi.ThrowableProxy;
8+
import ch.qos.logback.core.encoder.EncoderBase;
9+
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor;
10+
import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer;
11+
12+
import java.util.Map;
13+
14+
import static java.nio.charset.StandardCharsets.UTF_8;
15+
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*;
16+
17+
/**
18+
* This class will encode the logback event into the format expected by the ECS service (ElasticSearch).
19+
* <br/>
20+
* Inspired from <code>co.elastic.logging.logback.EcsEncoder</code>, this class doesn't use
21+
* any JSON (de)serialization library (Jackson, Gson, etc.) or Elastic library to avoid the dependency.
22+
* <br/>
23+
* This encoder also adds cloud information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-cloud.html">doc</a>)
24+
* and Lambda function information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-faas.html">doc</a>, currently in beta).
25+
*/
26+
public class LambdaEcsEncoder extends EncoderBase<ILoggingEvent> {
27+
28+
protected static final String ECS_VERSION = "1.2.0";
29+
protected static final String CLOUD_PROVIDER = "aws";
30+
protected static final String CLOUD_SERVICE = "lambda";
31+
32+
private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter();
33+
protected ThrowableHandlingConverter throwableConverter = null;
34+
35+
@Override
36+
public byte[] headerBytes() {
37+
return null;
38+
}
39+
40+
@Override
41+
public byte[] encode(ILoggingEvent event) {
42+
Map<String, String> mdcPropertyMap = event.getMDCPropertyMap();
43+
44+
StringBuilder builder = new StringBuilder(256);
45+
LambdaEcsSerializer.serializeObjectStart(builder);
46+
LambdaEcsSerializer.serializeTimestamp(builder, event.getTimeStamp(), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "UTC");
47+
LambdaEcsSerializer.serializeEcsVersion(builder, ECS_VERSION);
48+
LambdaEcsSerializer.serializeLogLevel(builder, event.getLevel());
49+
LambdaEcsSerializer.serializeFormattedMessage(builder, event.getFormattedMessage());
50+
LambdaEcsSerializer.serializeServiceName(builder, LambdaHandlerProcessor.serviceName());
51+
LambdaEcsSerializer.serializeServiceVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName()));
52+
// TODO : Environment ?
53+
LambdaEcsSerializer.serializeEventDataset(builder, LambdaHandlerProcessor.serviceName());
54+
LambdaEcsSerializer.serializeThreadName(builder, event.getThreadName());
55+
LambdaEcsSerializer.serializeLoggerName(builder, event.getLoggerName());
56+
IThrowableProxy throwableProxy = event.getThrowableProxy();
57+
if (throwableProxy != null) {
58+
if (throwableConverter != null) {
59+
LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event));
60+
} else if (throwableProxy instanceof ThrowableProxy) {
61+
LambdaEcsSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable());
62+
} else {
63+
LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event));
64+
}
65+
}
66+
LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER);
67+
LambdaEcsSerializer.serializeCloudService(builder, CLOUD_SERVICE);
68+
String arn = mdcPropertyMap.get(FUNCTION_ARN.getName());
69+
if (arn != null) {
70+
String[] arnParts = arn.split(":");
71+
LambdaEcsSerializer.serializeCloudRegion(builder, arnParts[3]);
72+
LambdaEcsSerializer.serializeCloudAccountId(builder, arnParts[4]);
73+
}
74+
LambdaEcsSerializer.serializeFunctionId(builder, arn);
75+
LambdaEcsSerializer.serializeFunctionName(builder, mdcPropertyMap.get(FUNCTION_NAME.getName()));
76+
LambdaEcsSerializer.serializeFunctionVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName()));
77+
LambdaEcsSerializer.serializeFunctionMemory(builder, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName()));
78+
LambdaEcsSerializer.serializeFunctionExecutionId(builder, mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName()));
79+
LambdaEcsSerializer.serializeColdStart(builder, mdcPropertyMap.get(FUNCTION_COLD_START.getName()));
80+
LambdaEcsSerializer.serializeAdditionalFields(builder, event.getMDCPropertyMap());
81+
LambdaEcsSerializer.serializeTraceId(builder, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName()));
82+
LambdaEcsSerializer.serializeObjectEnd(builder);
83+
return builder.toString().getBytes(UTF_8);
84+
}
85+
86+
@Override
87+
public byte[] footerBytes() {
88+
return null;
89+
}
90+
91+
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
92+
this.throwableConverter = throwableConverter;
93+
}
94+
}

0 commit comments

Comments
 (0)