Solving the Java Aws Lambda logging problem

via maven, docker, GraalVM and sfl4j-simple-lambda

Note

This story is the following of my previous one Let’s compile your Aws Lambda in Java, I am building on it so it could be the place to start if you don’t know about GraalVM native-image or need more info on the Aws Lambda Execution Environment…

Introduction

Logs are crucial in production environment and also during development but the task is not easy even in full java, even following official recommendations from AWS.

Logging requirements

R1: Use a modern logging framework

We need the possibility to log using different Log levels (INFO, DEBUG, WARN, ERROR…) and to be able to configure which one is active.

R2: Be able to easily find something in the logs

Logs are going naturally to AWS Cloudwatch. We expect one call to log will result to one Cloudwatch Event/Entry.

R3: Be able to get logs also from the libraries we use

In particular, we need to be able to get logs from the Java Aws Sdk v2.

R4: Be able to identify several Log entries as coming from same Business Transaction

As every call to Lambda has a unique AWSRequestId, it could be very useful to add this id to every log entry, to identify them as coming as the same business transaction.

R5: Finding a solution that is native-image compatible.

Using a complex logging framework is a real challenge when trying to compile. Our solution must be compatible for the Java or the Custom Lambda Runtime Environment.

The problems

  • Aws Lambda Execution environment is providing us a Context object having a method getLogger() giving a LambdaLogger whose methods doesn’t have any concept of Log Levels…
  • It is a known issue that sometimes your call to LambdaLogger.log() is creating several Log entries for each line of a multi-line log. See this question in stackoverflow.
  • The AWS Context Object is giving access to the AWSRequestId, but adding it manually to every log entry is no-go option.

Investigating on the multi-line logging problem

It took me several days to find the origin of the problem.

  • The Custom Lambda Environment issues

The Java Lambda Environment classloading issue

After deploying your deployment package, the Java Lambda Execution environment is loading your code inside a custom classloader whose parent is the System Classloader:

The Custom Lambda Environment issues

When compiling your lambda for use in the Custom Lambda Execution Environment, you need to include the Lambda RIE in your compilation.

My Complete Solution

As the solution provided by AWS is not suitable for native-compilation. I tried to provide my own solution based on Slf4j with logback, or log4j2 or slf4j-simple but without success because of native compilation problems or because it was not solving the multi-line logging problem.

  • Allow logging from your code via slf4j
  • Allow logging from AWS Sdk via log4j-over-slf4j
  • graalVM native-image friendly
  • Support for AWSRequestId
  • Allow env properties with underscore instead of point to be compatible with AWS lambda env property naming rules

Setup your dependencies

A complete Example with Logs

This example is using dynamodb, and slf4j-simple-lambda for both:

  • The Custom Execution Environment

The Java Execution Environment

Simply run this:

mvn package
hello-lambda-custom-with-logs-1.0-SNAPSHOT-deployment_package.zip
example.App::sayHello 

The Custom Execution Environment

First run:

mvn validate
docker build -f target/Dockerfile -t hello-lambda:latest .
docker cp $(docker create hello-lambda:latest):/function.zip .

Conclusion

I let you discover the code of the project for understanding how I did it.

Founder and CTO of Solusoft.Tech, Java Expert & Architect, AWS Serverless happy user and flutter early adopter.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store