Skip to content

Commit

Permalink
logback implementation of the logging module
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromevdl committed Nov 22, 2022
1 parent 4663044 commit db6f8b7
Show file tree
Hide file tree
Showing 10 changed files with 872 additions and 0 deletions.
137 changes: 137 additions & 0 deletions powertools-logging-logback/LambdaJsonLayout.java
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;
}

}
113 changes: 113 additions & 0 deletions powertools-logging-logback/pom.xml
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>
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;
}
}
Loading

0 comments on commit db6f8b7

Please sign in to comment.