Skip to content

Commit

Permalink
[alc] Log4j2 support (#37) (#454)
Browse files Browse the repository at this point in the history
* [alc] Log4j2 support

* [log4j2] bump aws-lambda-java-core deps to 1.2.3

* [log4j2] version bump and doc updates

---------

Co-authored-by: Daniel Torok <[email protected]>
  • Loading branch information
dtorok and dtorok authored Nov 3, 2023
1 parent 04b6e96 commit 5a94e19
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 19 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Example request handler
public class Handler implements RequestHandler<Map<String, String>, String>{
@Override
public String handleRequest(Map<String, String> event, Context context) {

}
}
```
Expand All @@ -32,7 +32,7 @@ Example request stream handler
public class HandlerStream implements RequestStreamHandler {
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {

}
}
```
Expand Down Expand Up @@ -101,7 +101,7 @@ public void testInjectSQSEvent(SQSEvent event) {
## aws-lambda-java-events-sdk-transformer

This package provides helper classes/methods to use alongside `aws-lambda-java-events` in order to transform
Lambda input event model objects into SDK-compatible output model objects.
Lambda input event model objects into SDK-compatible output model objects.
See the [documentation](aws-lambda-java-events-sdk-transformer/README.md) for more information.

- [Release Notes](aws-lambda-java-events-sdk-transformer/RELEASE.CHANGELOG.md)
Expand All @@ -125,7 +125,7 @@ See the [README](aws-lambda-java-log4j2/README.md) or the [official documentatio
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-log4j2</artifactId>
<version>1.5.1</version>
<version>1.6.0</version>
</dependency>
```

Expand Down
25 changes: 16 additions & 9 deletions aws-lambda-java-log4j2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Example for Maven pom.xml
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-log4j2</artifactId>
<version>1.5.1</version>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand Down Expand Up @@ -65,10 +65,10 @@ If using maven shade plugin, set the plugin configuration as follows
If you are using the [John Rengelman](https://github.com/johnrengelman/shadow) Gradle shadow plugin, then the plugin configuration is as follows:

```groovy
dependencies{
...
implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.5.1'
implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.6.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion
}
Expand All @@ -83,8 +83,8 @@ shadowJar {
build.dependsOn(shadowJar)
```
If you are using the `sam build` and `sam deploy` commands to deploy your lambda function, then you don't

If you are using the `sam build` and `sam deploy` commands to deploy your lambda function, then you don't
need to use the shadow jar plugin. The `sam` cli-tool merges itself the `Log4j2Plugins.dat`
files.

Expand All @@ -94,12 +94,17 @@ Add the following file `<project-dir>/src/main/resources/log4j2.xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="com.amazonaws.services.lambda.runtime.log4j2">
<Configuration>
<Appenders>
<Lambda name="Lambda">
<PatternLayout>
<Lambda name="Lambda" format="${env:AWS_LAMBDA_LOG_FORMAT:-TEXT}">
<LambdaTextFormat>
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n</pattern>
</PatternLayout>
</PatternLayout>
</LambdaTextFormat>
<LambdaJSONFormat>
<JsonTemplateLayout eventTemplateUri="classpath:LambdaLayout.json" />
</LambdaJSONFormat>
</Lambda>
</Appenders>
<Loggers>
Expand All @@ -110,6 +115,8 @@ Add the following file `<project-dir>/src/main/resources/log4j2.xml`
</Configuration>
```

If the `AWS_LAMBDA_LOG_FORMAT` is set to `JSON`, the `LambdaJSONFormat` formatter will be applied, otherwise the `LambdaTextFormat`.

### 3. Example code

```java
Expand Down
4 changes: 4 additions & 0 deletions aws-lambda-java-log4j2/RELEASE.CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### October 24, 2023
`1.6.0`:
- Log level and log format support

### January 04, 2022
`1.5.1`:
- Updated `log4j-core` and `log4j-api` dependencies to `2.17.1`
Expand Down
4 changes: 2 additions & 2 deletions aws-lambda-java-log4j2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-log4j2</artifactId>
<version>1.5.1</version>
<version>1.6.0</version>
<packaging>jar</packaging>

<name>AWS Lambda Java Log4j 2.x Libraries</name>
Expand Down Expand Up @@ -48,7 +48,7 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.2</version>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

import com.amazonaws.services.lambda.runtime.LambdaRuntime;
import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal;

import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
import com.amazonaws.services.lambda.runtime.logging.LogLevel;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginElement;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
* Class to append log4j2 logs from AWS Lambda function to CloudWatch
Expand All @@ -20,27 +28,53 @@
@Plugin(name = LambdaAppender.PLUGIN_NAME, category = LambdaAppender.PLUGIN_CATEGORY,
elementType = LambdaAppender.PLUGIN_TYPE, printObject = true)
public class LambdaAppender extends AbstractAppender {
static {
LambdaRuntimeInternal.setUseLog4jAppender(true);
}

public static final String PLUGIN_NAME = "Lambda";
public static final String PLUGIN_CATEGORY = "Core";
public static final String PLUGIN_TYPE = "appender";

private LambdaLogger logger = LambdaRuntime.getLogger();

private static LogFormat logFormat = LogFormat.TEXT;

private static final Map<Level, LogLevel> logLevelMapper = new HashMap<Level, LogLevel>() {{
put(Level.TRACE, LogLevel.TRACE);
put(Level.DEBUG, LogLevel.DEBUG);
put(Level.INFO, LogLevel.INFO);
put(Level.WARN, LogLevel.WARN);
put(Level.ERROR, LogLevel.ERROR);
put(Level.FATAL, LogLevel.FATAL);
}};

/**
* Builder class that follows log4j2 plugin convention
* @param <B> Generic Builder class
*/
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
implements org.apache.logging.log4j.core.util.Builder<LambdaAppender> {

@PluginAttribute(value = "format", defaultString = "TEXT")
LogFormat logFormat;
@PluginElement("LambdaTextFormat")
private LambdaTextFormat lambdaTextFormat;
@PluginElement("LambdaJsonFormat")
private LambdaJsonFormat lambdaJsonFormat;

/**
* creates a new LambdaAppender
* @return a new LambdaAppender
*/
public LambdaAppender build() {
return new LambdaAppender(super.getName(), super.getFilter(), super.getOrCreateLayout(),
super.isIgnoreExceptions());
Layout<?> layout;
if (logFormat == LogFormat.TEXT) {
layout = lambdaTextFormat != null ? lambdaTextFormat.getLayout() : super.getOrCreateLayout();
} else {
layout = lambdaJsonFormat != null ? lambdaJsonFormat.getLayout() : super.getOrCreateLayout();
}
return new LambdaAppender(super.getName(), super.getFilter(), layout, super.isIgnoreExceptions());
}
}

Expand All @@ -63,14 +97,22 @@ public static <B extends Builder<B>> B newBuilder() {
*/
private LambdaAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {
super(name, filter, layout, ignoreExceptions);
LambdaRuntimeInternal.setUseLog4jAppender(true);
}

/**
* Converts log4j Level into Lambda LogLevel
* @param level the log4j log level
* @return Lambda log leve
*/
private LogLevel toLambdaLogLevel(Level level) {
return logLevelMapper.getOrDefault(level, LogLevel.UNDEFINED);
}

/**
* Append log event to System.out
* @param event log4j event
*/
public void append(LogEvent event) {
logger.log(super.getLayout().toByteArray(event));
logger.log(getLayout().toByteArray(event), toLambdaLogLevel(event.getLevel()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

package com.amazonaws.services.lambda.runtime.log4j2;

import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

import java.io.Serializable;

@Plugin(name = "LambdaJsonFormat", category = "core", printObject = true)
public class LambdaJsonFormat {

private Layout layout;

@PluginFactory
public static LambdaJsonFormat createNode(@PluginElement("Layout") Layout<? extends Serializable> layout) {
return new LambdaJsonFormat(layout);
}

private LambdaJsonFormat(Layout layout) {
this.layout = layout;
}

public Layout getLayout() {
return layout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */

package com.amazonaws.services.lambda.runtime.log4j2;

import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

import java.io.Serializable;

@Plugin(name = "LambdaTextFormat", category = "core", printObject = true)
public class LambdaTextFormat {

private Layout layout;

@PluginFactory
public static LambdaTextFormat createNode(@PluginElement("Layout") Layout<? extends Serializable> layout) {
return new LambdaTextFormat(layout);
}

private LambdaTextFormat(Layout layout) {
this.layout = layout;
}

public Layout getLayout() {
return layout;
}
}
39 changes: 39 additions & 0 deletions aws-lambda-java-log4j2/src/main/resources/LambdaLayout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"timestamp": {
"$resolver": "timestamp",
"pattern": {
"format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"timeZone": "UTC"
}
},
"level": {
"$resolver": "level",
"field": "name"
},
"message": {
"$resolver": "message"
},
"logger": {
"$resolver": "logger",
"field": "name"
},

"errorType": {
"$resolver": "exception",
"field": "className"
},
"errorMessage": {
"$resolver": "exception",
"field": "message"
},
"stackTrace": {
"$resolver": "exception",
"field": "stackTrace"
},

"labels": {
"$resolver": "mdc",
"flatten": true,
"stringified": true
}
}

0 comments on commit 5a94e19

Please sign in to comment.