1. Introduction

Micronaut Log4AWS simplifies integration of AWS Lambda functions written with Micronaut into Sentry service.

2. Installation

First, add this dependency to your build file.

Gradle Installation
repositories {
    mavenCentral()
}

dependencies {
    implementation "com.agorapulse:micronaut-log4aws:4.0.0"
}
There are variants for older Micronaut versions as well. You can use micronaut-1.0 or micronaut-2.0 suffix for different versions of Micronaut.

Also be sure that your build file does not contain any reference to Logback:

Build File Cleanup
dependencies {
    // remove following line from your build file if present
    runtime "ch.qos.logback:logback-classic:1.2.3"
}

Then remove Logback configuration file from src/main/resources/logback.groovy and replace it with a new file log4j2.xml with following content. Feel free to update the file to fit your needs.

Log4J2 Configuration
<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="org.apache.logging.log4j.core,io.sentry.log4j2,com.amazonaws.services.lambda.runtime.log4j2">
    <Appenders>
        <Lambda name="Lambda">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n"/>
        </Lambda>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <Sentry name="Sentry"/>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Lambda"/>
            <!-- Note that the Sentry logging threshold is overridden to the WARN level -->
            <AppenderRef ref="Sentry" level="warn"/>
        </Root>
        <Logger name="io.sentry" level="warn" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <Logger name="software.amazon" level="warn" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
    </Loggers>
</Configuration>
The library can configure Sentry appender for you if you don’t specify it in the configuration file.

Finally, create src/main/resources/sentry.properties for sentry configuration:

sentry.properties
# consider adding your packages here to enable filtering of stack traces in Sentry
# in-app-includes=agorapulse,com.agorapulse

3. Usage

3.1. Sentry Integration

Sentry logger is initialised automatically at application startup. Sentry’s IHub bean is available for injection to access the lower-level API.

Sentry events are enriched with the following information if available:

  • aws_region

  • aws_default_region

  • lambda_function_name

  • lambda_function_version

  • lambda_handler

  • lambda_execution_environment

  • lambda_log_group_name

  • lambda_log_stream_name

  • lambda_memory_size

  • req.path

  • req.method

  • req.remoteHost

  • req.serverHost

  • req.parameters

  • req.headers

3.2. Ensure Exception Being Logged

3.2.1. Using @LogError

All the errors have to be logged to be propagated to Sentry. You can use LogErrors around advice with your entry-point method to simplify the logging boilerplate:

Using LogError Annotation
/**
* This class is an entrypoint called by AWS Lambda. Keep the code to minimum.
* Method handleRequest is called directly by the AWS Lambda container.
*/
class ReportsExporter extends FunctionInitializer implements RequestHandler<SQSEvent, Void> {

    @Inject private ReportsService reportsService;

    Void handleRequest(SQSEvent event, Context context) {
        return reportsService.handlerRequest(event);
    }

}

/**
* This class is handled by Micronaut and can use the advice.
*/
@Singleton
class ReportsService implements RequestHandler<SQSEvent, Void> {

    @LogError
    Void handleRequest(SQSEvent event, Context context) {
        return reportsService.handlerRequest(event);
    }

}
The annotation applied to the function class itself has no effect as the function class is executed by AWS Lambda container.

3.2.2. Using LoggingFunctionInitializer

You can extend LoggingFunctionInitializer instead of FunctionInitializer to ensure the problems with function initialization are logged.

3.2.3. Using Logging Request Handlers

You can use Logging class to ensure the errors are logged properly:

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class LoggingRequestHandler extends RequestHandler<Input, Output> {

    @Override
    public Output handleRequest(Input input, Context context) {
        return Logging.callAndRethrow(getClass(), "Exception running handler", () -> doHandleRequest(input, context));
    }

    private Output doHandleRequest(Input input, Context context) {
        // ...
    }

}