Skip to content

An SLF4J Logger implementation for AWS Lambda and CloudWatch.

License

Notifications You must be signed in to change notification settings

vitalijr2/aws-lambda-slf4j

Repository files navigation

SLF4J for AWS Lambda

An SLF4J Logger implementation for AWS Lambda and CloudWatch.

Java Version License
GitHub master check runs Codacy Badge Codacy Coverage GitHub commit activity Today's hits

Logger Release Javadoc
aws-lambda-slf4j-logger Maven Central Javadoc
aws-lambda-slf4j-json-logger Maven Central Javadoc

Table of Contents

Created by gh-md-toc

Getting started

Yet another SLF4J Simple, isn't it?

No, it isn't.

This implementation supports MDC to print out AWS request ID in start of every logging record and supports Markers too. And the killer feature: it solves the CRLF issue described by Frank Afriat in Solving the Java Aws Lambda logging problem - you don't have to prepare logging messages and stacktraces to log them on CloudWatch Logs.

The footprint of aws-lambda-slf4j-logger (36K) and aws-lambda-slf4j-json-logger (116K) are the same as slf4j-simple (16K) and much smaller than logback (888K).

Usage

There is a great original manual.

The sample code:

@Override
public String handleRequest(Map<String, Object> input, Context context) {
  MDC.put("@aws-request-id@", context.getAwsRequestId());

  logger.trace("trace message");
  logger.debug("debug message");
  logger.info("info message");
  logger.warn("warning message");
  logger.error("error message");

  var marker = new BasicMarkerFactory().getMarker("important");

  Stream.of("\n", "\r\n", "\r").forEach(injection -> {
    logger.trace(marker, "CRLF{}injection", injection);
  });

  logger.warn("printable stacktrace", new Throwable("Printable Stacktrace Demo"));
  return "done";
}

The log with aws-lambda-slf4j-logger:

983f71e5-9091-443b-8c01-6668120c0e5d INFO io.github.vitalijr2.slf4j_demo.BotHandler - info message
983f71e5-9091-443b-8c01-6668120c0e5d WARN io.github.vitalijr2.slf4j_demo.BotHandler - warning message
983f71e5-9091-443b-8c01-6668120c0e5d ERROR io.github.vitalijr2.slf4j_demo.BotHandler - error message
983f71e5-9091-443b-8c01-6668120c0e5d TRACE io.github.vitalijr2.slf4j_demo.BotHandler - CRLF
injection
983f71e5-9091-443b-8c01-6668120c0e5d TRACE io.github.vitalijr2.slf4j_demo.BotHandler - CRLF
injection
983f71e5-9091-443b-8c01-6668120c0e5d TRACE io.github.vitalijr2.slf4j_demo.BotHandler - CRLF
injection
983f71e5-9091-443b-8c01-6668120c0e5d WARN io.github.vitalijr2.slf4j_demo.BotHandler - printable stacktrace

There is a JSON option with aws-lambda-slf4j-json-logger:

{
  "level": "INFO",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "info message",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "level": "WARN",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "warning message",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "level": "ERROR",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "error message",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "level": "TRACE",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "CRLF\ninjection",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "level": "TRACE",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "CRLF\r\ninjection",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "level": "TRACE",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "CRLF\rinjection",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}
{
  "stack-trace": "java.lang.Throwable: Printable Stacktrace Demo\n\tat io.github.vitalijr2.slf4j_demo.BotHandler.handleRequest(BotHandler.java:36)\n\tat io.github.vitalijr2.slf4j_demo.BotHandler.handleRequest(BotHandler.java:12)\n\tat lambdainternal.EventHandlerLoader$PojoHandlerAsStreamHandler.handleRequest(EventHandlerLoader.java:205)\n\tat lambdainternal.EventHandlerLoader$2.call(EventHandlerLoader.java:905)\n\tat lambdainternal.AWSLambda.startRuntime(AWSLambda.java:261)\n\tat lambdainternal.AWSLambda.startRuntime(AWSLambda.java:200)\n\tat lambdainternal.AWSLambda.main(AWSLambda.java:194)\n",
  "level": "WARN",
  "logname": "io.github.vitalijr2.slf4j_demo.BotHandler",
  "message": "printable stacktrace",
  "aws-request-id": "7b9af47e-d861-44b4-bde7-fa2e84ffb7cf"
}

CloudWatch logs

Configuration

The configuration is similar to SLF4J Simple.

It looks for the lambda-logger.properties resource and read properties:

  • dateTimeFormat - The date and time format to be used in the output messages. The pattern describing the date and time format is defined by SimpleDateFormat. If the format is not specified or is invalid, the number of milliseconds since start up will be output.
  • defaultLogLevel - Default log level for all instances of LambdaLogger. Must be one of (trace, debug, info, warn, error), a value is case-insensitive. If not specified, defaults to info.
  • levelInBrackets - Should the level string be output in brackets? Defaults to false.
  • log.a.b.c - Logging detail level for a LambdaLogger instance named a.b.c.
  • requestId - Set the context name of AWS request ID. Defaults to AWS_REQUEST_ID.
  • showDateTime - Set to true if you want the current date and time to be included in output messages. Defaults to false.
  • showLogName - Set to true if you want the Logger instance name to be included in output messages. Defaults to true.
  • showShortLogName - Set to true if you want the last component of the name to be included in output messages. Defaults to false.
  • showThreadId - If you would like to output the current thread id, then set to true. Defaults to false.
  • showThreadName - Set to true if you want to output the current thread name. Defaults to false.

The environment variables overrides the properties: LOG_AWS_REQUEST_ID, LOG_DATE_TIME_FORMAT, LOG_DEFAULT_LEVEL, LOG_LEVEL_IN_BRACKETS, LOG_SHOW_DATE_TIME, LOG_SHOW_NAME, LOG_SHOW_SHORT_NAME, LOG_SHOW_THREAD_ID, LOG_SHOW_THREAD_NAME.

Setup a provider by a system property

SLF4J 2.0.9 allows a provider to be explicitly specified. Since AWS Lambda Environment does not provide any configuration options to set what options should be used on startup, the trick is to use the JAVA_TOOL_OPTIONS environment variable.

If your AWS Java Lambda has some SLF4J providers and you want to point one of them, that needs to setup a System property named slf4j.provider, as in the following:

Using JAVA_TOOL_OPTIONS to setup a provider

Other solutions

Other AWS centric loggers are SLF4J/Logback Appender, slf4j-simple-lambda, Logback's CloudWatch appender and CloudWatchLogs Java appender.

Contributing

Please read Contributing.

History

See Changelog

Authors and acknowledgment

  • Replace the custom output to stdout with AWS LambdaLogger, the main idea from @igorakkerman SLF4J/Logback Appender.

License

Copyright 2022-2024 Vitalij Berdinskih

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Apache License v2.0