-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
logback implementation of the logging module
- Loading branch information
Showing
10 changed files
with
872 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package software.amazon.lambda.powertools.logging; | ||
|
||
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; | ||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter; | ||
import ch.qos.logback.classic.spi.ILoggingEvent; | ||
import ch.qos.logback.classic.spi.IThrowableProxy; | ||
import ch.qos.logback.classic.spi.ThrowableProxy; | ||
import ch.qos.logback.core.LayoutBase; | ||
|
||
/** | ||
* Custom layout for logback that encodes logs in JSON format. | ||
* It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. | ||
*/ | ||
public class LambdaJsonLayout extends LayoutBase<ILoggingEvent> { | ||
private final static String CONTENT_TYPE = "application/json"; | ||
private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); | ||
private ThrowableHandlingConverter throwableConverter; | ||
private String timestampFormat; | ||
private String timestampFormatTimezoneId; | ||
private boolean includeThreadInfo; | ||
|
||
@Override | ||
public String doLayout(ILoggingEvent event) { | ||
StringBuilder builder = new StringBuilder(256); | ||
LambdaJsonSerializer.serializeObjectStart(builder); | ||
LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel()); | ||
LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); | ||
IThrowableProxy throwableProxy = event.getThrowableProxy(); | ||
if (throwableProxy != null) { | ||
if (throwableConverter != null) { | ||
LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); | ||
} else if (throwableProxy instanceof ThrowableProxy) { | ||
LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); | ||
} else { | ||
LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); | ||
} | ||
} | ||
LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); | ||
if (includeThreadInfo) { | ||
LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName()); | ||
LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); | ||
LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority())); | ||
} | ||
LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId); | ||
LambdaJsonSerializer.serializeObjectEnd(builder); | ||
return builder.toString(); | ||
} | ||
|
||
@Override | ||
public String getContentType() { | ||
return CONTENT_TYPE; | ||
} | ||
|
||
public void setTimestampFormat(String timestampFormat) { | ||
this.timestampFormat = timestampFormat; | ||
} | ||
|
||
public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { | ||
this.timestampFormatTimezoneId = timestampFormatTimezoneId; | ||
} | ||
|
||
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { | ||
this.throwableConverter = throwableConverter; | ||
} | ||
|
||
// public static final String INSTANT_ATTR_NAME = "instant"; | ||
// public static final String EPOCH_SEC_ATTR_NAME = "epochSecond"; | ||
// public static final String NANO_SEC_ATTR_NAME = "nanoOfSecond"; | ||
// public static final String LOGGER_FQCN_ATTR_NAME = "loggerFqcn"; | ||
// public static final String LOGGER_ATTR_NAME = "loggerName"; | ||
// public static final String THREAD_ID_ATTR_NAME = "threadId"; | ||
// public static final String THREAD_PRIORITY_ATTR_NAME = "threadPriority"; | ||
// | ||
// private boolean includePowertools; | ||
// private boolean includeInstant; | ||
// private boolean includeThreadInfo; | ||
// | ||
// public LambdaJsonLayout() { | ||
// super(); | ||
// this.includeInstant = true; | ||
// this.includePowertools = true; | ||
// this.includeThreadInfo = true; | ||
// } | ||
// | ||
// @Override | ||
// protected Map<String, Object> toJsonMap(ILoggingEvent event) { | ||
// Map<String, Object> map = new LinkedHashMap<>(); | ||
// addTimestamp(TIMESTAMP_ATTR_NAME, this.includeTimestamp, event.getTimeStamp(), map); | ||
// addInstant(this.includeInstant, event.getTimeStamp(), event.getNanoseconds(), map); | ||
// add(THREAD_ATTR_NAME, this.includeThreadName || this.includeThreadInfo, event.getThreadName(), map); | ||
// add(LEVEL_ATTR_NAME, this.includeLevel, String.valueOf(event.getLevel()), map); | ||
// add(LOGGER_ATTR_NAME, this.includeLoggerName, event.getLoggerName(), map); | ||
// add(FORMATTED_MESSAGE_ATTR_NAME, this.includeFormattedMessage, event.getFormattedMessage(), map); | ||
// addThrowableInfo(EXCEPTION_ATTR_NAME, this.includeException, event, map); | ||
// // contextStack ? | ||
// // endOfBatch ? | ||
// map.put(LOGGER_FQCN_ATTR_NAME, "ch.qos.logback.classic.Logger"); | ||
// add(THREAD_ID_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getId()), map); | ||
// add(THREAD_PRIORITY_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getPriority()), map); | ||
// addPowertools(this.includePowertools, event.getMDCPropertyMap(), map); | ||
// return map; | ||
// } | ||
// | ||
// private void addPowertools(boolean includePowertools, Map<String, String> mdcPropertyMap, Map<String, Object> map) { | ||
// TreeMap<String, String> sortedMap = new TreeMap<>(mdcPropertyMap); | ||
// List<String> powertoolsFields = DefaultLambdaFields.stringValues(); | ||
// | ||
// sortedMap.forEach((k, v) -> { | ||
// if (includePowertools || !powertoolsFields.contains(k)) { | ||
// map.put(k, v); | ||
// } | ||
// }); | ||
// | ||
// } | ||
// | ||
// private void addInstant(boolean includeInstant, long timeStamp, int nanoseconds, Map<String, Object> map) { | ||
// if (includeInstant) { | ||
// Map<String, Object> instantMap = new LinkedHashMap<>(); | ||
// instantMap.put(EPOCH_SEC_ATTR_NAME, timeStamp / 1000); | ||
// instantMap.put(NANO_SEC_ATTR_NAME, nanoseconds); | ||
// map.put(LambdaJsonLayout.INSTANT_ATTR_NAME, instantMap); | ||
// } | ||
// } | ||
// | ||
// public void setIncludeInstant(boolean includeInstant) { | ||
// this.includeInstant = includeInstant; | ||
// } | ||
// | ||
// public void setIncludePowertools(boolean includePowertools) { | ||
// this.includePowertools = includePowertools; | ||
// } | ||
// | ||
public void setIncludeThreadInfo(boolean includeThreadInfo) { | ||
this.includeThreadInfo = includeThreadInfo; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>powertools-parent</artifactId> | ||
<groupId>software.amazon.lambda</groupId> | ||
<version>1.12.3</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>powertools-logging-logback</artifactId> | ||
<name>AWS Lambda Powertools for Java library Logging with LogBack</name> | ||
<description> | ||
A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. | ||
</description> | ||
<url>https://aws.amazon.com/lambda/</url> | ||
<issueManagement> | ||
<system>GitHub Issues</system> | ||
<url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> | ||
</issueManagement> | ||
<scm> | ||
<url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> | ||
</scm> | ||
<developers> | ||
<developer> | ||
<name>AWS Lambda Powertools team</name> | ||
<organization>Amazon Web Services</organization> | ||
<organizationUrl>https://aws.amazon.com/</organizationUrl> | ||
</developer> | ||
</developers> | ||
|
||
<distributionManagement> | ||
<snapshotRepository> | ||
<id>ossrh</id> | ||
<url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> | ||
</snapshotRepository> | ||
</distributionManagement> | ||
|
||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>software.amazon.lambda</groupId> | ||
<artifactId>powertools-logging</artifactId> | ||
<version>${version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-classic</artifactId> | ||
<version>1.3.4</version> <!-- v1.3.x compatible with JDK 1.8, v1.4.x only compatible with JDK 11 --> | ||
<scope>provided</scope> <!-- provided to let users change to 1.4.x when using JDK 11 --> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>com.sun.mail</groupId> | ||
<artifactId>javax.mail</artifactId> | ||
</exclusion> | ||
</exclusions> | ||
</dependency> | ||
|
||
<!-- Test dependencies --> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-engine</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-lang3</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-inline</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.aspectj</groupId> | ||
<artifactId>aspectjweaver</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.amazonaws</groupId> | ||
<artifactId>aws-lambda-java-events</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.amazonaws</groupId> | ||
<artifactId>aws-lambda-java-tests</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.skyscreamer</groupId> | ||
<artifactId>jsonassert</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
94 changes: 94 additions & 0 deletions
94
...ing-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package software.amazon.lambda.powertools.logging; | ||
|
||
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; | ||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter; | ||
import ch.qos.logback.classic.spi.ILoggingEvent; | ||
import ch.qos.logback.classic.spi.IThrowableProxy; | ||
import ch.qos.logback.classic.spi.ThrowableProxy; | ||
import ch.qos.logback.core.encoder.EncoderBase; | ||
import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; | ||
import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer; | ||
|
||
import java.util.Map; | ||
|
||
import static java.nio.charset.StandardCharsets.UTF_8; | ||
import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*; | ||
|
||
/** | ||
* This class will encode the logback event into the format expected by the ECS service (ElasticSearch). | ||
* <br/> | ||
* Inspired from <code>co.elastic.logging.logback.EcsEncoder</code>, this class doesn't use | ||
* any JSON (de)serialization library (Jackson, Gson, etc.) or Elastic library to avoid the dependency. | ||
* <br/> | ||
* This encoder also adds cloud information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-cloud.html">doc</a>) | ||
* and Lambda function information (see <a href="https://www.elastic.co/guide/en/ecs/current/ecs-faas.html">doc</a>, currently in beta). | ||
*/ | ||
public class LambdaEcsEncoder extends EncoderBase<ILoggingEvent> { | ||
|
||
protected static final String ECS_VERSION = "1.2.0"; | ||
protected static final String CLOUD_PROVIDER = "aws"; | ||
protected static final String CLOUD_SERVICE = "lambda"; | ||
|
||
private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); | ||
protected ThrowableHandlingConverter throwableConverter = null; | ||
|
||
@Override | ||
public byte[] headerBytes() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public byte[] encode(ILoggingEvent event) { | ||
Map<String, String> mdcPropertyMap = event.getMDCPropertyMap(); | ||
|
||
StringBuilder builder = new StringBuilder(256); | ||
LambdaEcsSerializer.serializeObjectStart(builder); | ||
LambdaEcsSerializer.serializeTimestamp(builder, event.getTimeStamp(), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "UTC"); | ||
LambdaEcsSerializer.serializeEcsVersion(builder, ECS_VERSION); | ||
LambdaEcsSerializer.serializeLogLevel(builder, event.getLevel()); | ||
LambdaEcsSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); | ||
LambdaEcsSerializer.serializeServiceName(builder, LambdaHandlerProcessor.serviceName()); | ||
LambdaEcsSerializer.serializeServiceVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); | ||
// TODO : Environment ? | ||
LambdaEcsSerializer.serializeEventDataset(builder, LambdaHandlerProcessor.serviceName()); | ||
LambdaEcsSerializer.serializeThreadName(builder, event.getThreadName()); | ||
LambdaEcsSerializer.serializeLoggerName(builder, event.getLoggerName()); | ||
IThrowableProxy throwableProxy = event.getThrowableProxy(); | ||
if (throwableProxy != null) { | ||
if (throwableConverter != null) { | ||
LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event)); | ||
} else if (throwableProxy instanceof ThrowableProxy) { | ||
LambdaEcsSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); | ||
} else { | ||
LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event)); | ||
} | ||
} | ||
LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER); | ||
LambdaEcsSerializer.serializeCloudService(builder, CLOUD_SERVICE); | ||
String arn = mdcPropertyMap.get(FUNCTION_ARN.getName()); | ||
if (arn != null) { | ||
String[] arnParts = arn.split(":"); | ||
LambdaEcsSerializer.serializeCloudRegion(builder, arnParts[3]); | ||
LambdaEcsSerializer.serializeCloudAccountId(builder, arnParts[4]); | ||
} | ||
LambdaEcsSerializer.serializeFunctionId(builder, arn); | ||
LambdaEcsSerializer.serializeFunctionName(builder, mdcPropertyMap.get(FUNCTION_NAME.getName())); | ||
LambdaEcsSerializer.serializeFunctionVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); | ||
LambdaEcsSerializer.serializeFunctionMemory(builder, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName())); | ||
LambdaEcsSerializer.serializeFunctionExecutionId(builder, mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName())); | ||
LambdaEcsSerializer.serializeColdStart(builder, mdcPropertyMap.get(FUNCTION_COLD_START.getName())); | ||
LambdaEcsSerializer.serializeAdditionalFields(builder, event.getMDCPropertyMap()); | ||
LambdaEcsSerializer.serializeTraceId(builder, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName())); | ||
LambdaEcsSerializer.serializeObjectEnd(builder); | ||
return builder.toString().getBytes(UTF_8); | ||
} | ||
|
||
@Override | ||
public byte[] footerBytes() { | ||
return null; | ||
} | ||
|
||
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { | ||
this.throwableConverter = throwableConverter; | ||
} | ||
} |
Oops, something went wrong.