cloudwatchlogs-java-appender Build Status GitHub license

The Boxfuse Java log appender for AWS CloudWatch Logs is a Logback and Log4J2 appender that ships your log events directly and securely to AWS CloudWatch Logs via HTTPS.

All log events are structured and standardized. Each Boxfuse environment maps to an AWS CloudWatch Logs LogGroup which contains one LogStream per application.

More info: https://boxfuse.com/blog/cloudwatch-logs

LogGroup and LogStream overview

Supported logging systems

Installation

To include the Boxfuse Java log appender for AWS CloudWatch Logs in your application all you need to do is include the dependency in your build file.

Maven

Start by adding the Boxfuse Maven repository to your list of repositories in your pom.xml:

<repositories>
    <repository>
        <id>central</id>
        <url>http://repo1.maven.org/maven2/</url>
    </repository>
    <repository>
        <id>boxfuse-repo</id>
        <url>https://files.boxfuse.com</url>
    </repository>
</repositories>

Then add the dependency:

<dependency>
    <groupId>com.boxfuse.cloudwatchlogs</groupId>
    <artifactId>cloudwatchlogs-java-appender</artifactId>
    <version>1.1.9.62</version>
</dependency>

Gradle

Start by adding the Boxfuse Maven repository to your list of repositories in your build.gradle:

repositories {
    mavenCentral()
    maven {
        url "https://files.boxfuse.com"
    }
}

Then add the dependency:

dependencies {
    compile 'com.boxfuse.cloudwatchlogs:cloudwatchlogs-java-appender:1.1.9.62'
}

Transitive dependencies

Besides Logback or Log4J2 this appender also requires the following dependency (declared as a transitive dependency in the pom.xml):

com.amazonaws:aws-java-sdk-logs:1.1.143 (or newer)

Usage

To use the appender you must add it to the configuration of your logging system.

Logback

Add the appender to your logback.xml file at the root of your classpath. In a Maven or Gradle project you can find it under src/main/resources :

<configuration>
    <appender name="Boxfuse-CloudwatchLogs" class="com.boxfuse.cloudwatchlogs.logback.CloudwatchLogsLogbackAppender">
        <!-- Optional config parameters -->
        <config>
            <!-- Whether to fall back to stdout instead of disabling the appender when running outside of a Boxfuse instance. Default: false -->
            <stdoutFallback>false</stdoutFallback>

            <!-- The maximum size of the async log event queue. Default: 1000000.
                 Increase to avoid dropping log events at very high throughput.
                 Decrease to reduce maximum memory usage at the risk if the occasional log event drop when it gets full. -->
            <maxEventQueueSize>1000000</maxEventQueueSize>

            <!-- The default maximum delay in milliseconds before forcing a flush of the buffered log events to CloudWatch Logs. Default: 500. -->
            <maxFlushDelay>500</maxFlushDelay>

            <!-- Custom MDC keys to include in the log events along with their values. -->            
            <customMdcKey>my-custom-key</customMdcKey>
            <customMdcKey>my-other-key</customMdcKey>

            <!-- The AWS CloudWatch Logs LogGroup to use. This is determined automatically within Boxfuse environments. -->
            <!--
            <logGroup>my-custom-log-group</logGroup>
            -->
        </config>    
    </appender>

    <root level="debug">
        <appender-ref ref="Boxfuse-CloudwatchLogs" />
    </root>
</configuration>

Log4J2

Add the appender to your log4j2.xml file at the root of your classpath. In a Maven or Gradle project you can find it under src/main/resources :

<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="com.boxfuse.cloudwatchlogs.log4j2">
    <Appenders>
        <Boxfuse-CloudwatchLogs>
            <!-- Optional config parameters -->

            <!-- Whether to fall back to stdout instead of disabling the appender when running outside of a Boxfuse instance. Default: false -->
            <stdoutFallback>false</stdoutFallback>

            <!-- The maximum size of the async log event queue. Default: 1000000.
                 Increase to avoid dropping log events at very high throughput.
                 Decrease to reduce maximum memory usage at the risk if the occasional log event drop when it gets full. -->
            <maxEventQueueSize>1000000</maxEventQueueSize>

            <!-- The default maximum delay in milliseconds before forcing a flush of the buffered log events to CloudWatch Logs. Default: 500. -->
            <maxFlushDelay>500</maxFlushDelay>

            <!-- Custom MDC (ThreadContext) keys to include in the log events along with their values. -->            
            <customMdcKey key="my-custom-key"/>
            <customMdcKey key="my-other-key"/>

            <!-- The AWS CloudWatch Logs LogGroup to use. This is determined automatically within Boxfuse environments. -->
            <!--
            <logGroup>my-custom-log-group</logGroup>
            -->
        </Boxfuse-CloudwatchLogs>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Boxfuse-CloudwatchLogs"/>
        </Root>
    </Loggers>
</Configuration>

Standardized Structured Logging

All log events are structured and standardized. What this means is that instead of shipping log events as strings like this:

2014-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]

events are shipped as JSON documents will all required metadata:

{
    "image": "myuser/myapp:123",
    "instance": "i-607b5ddc",
    "level": "INFO",
    "logger": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping",
    "message": "Mapping filter: 'hiddenHttpMethodFilter' to: [/*]",
    "thread": "main"
}

This has several advantages:

Log streams and log groups

When the appender is run within a Boxfuse instance, it will send the log events to the AWS CloudWatch Logs log group for the current Boxfuse environment. Within that log group the events will be placed in the log stream for the current Boxfuse application.

Automatically populated attributes

A number of log event attributes are populated automatically when the appender is run within a Boxfuse instance:

When logging a message from your code using SLF4J as follows:

Logger log = LoggerFactory.getLogger(MyClass.class);
...
log.info("My log message");

the timestamp of the log event is added to its metadata and the following attributes are also automatically extracted:

When using an SLF4J marker you can also make it much easier to filter specific event types. The following code:

Logger log = LoggerFactory.getLogger(MyClass.class);
Marker USER_CREATED = MarkerFactory.getMarker("USER_CREATED");
String username = "MyUser";
...
log.info(USER_CREATED, "Created user {}", username);

now also automatically defines an additional log event attribute:

Optional additional attributes

Additionally a number of optional attributes can also be defined via MDC to provide further information of the log event:

They are populated in the MDC as follows:

MDC.put(CloudwatchLogsMDCPropertyNames.ACCOUNT, "MyCurrentAccount");
MDC.put(CloudwatchLogsMDCPropertyNames.ACTION, "order-12345");
MDC.put(CloudwatchLogsMDCPropertyNames.USER, "MyUser");
MDC.put(CloudwatchLogsMDCPropertyNames.SESSION, "session-9876543210");
MDC.put(CloudwatchLogsMDCPropertyNames.REQUEST, "req-111222333");

When finishing processing (after sending out a response for example) they should be cleaned up again to prevent mixups:

MDC.remove(CloudwatchLogsMDCPropertyNames.ACCOUNT);
MDC.remove(CloudwatchLogsMDCPropertyNames.ACTION);
MDC.remove(CloudwatchLogsMDCPropertyNames.USER);
MDC.remove(CloudwatchLogsMDCPropertyNames.SESSION);
MDC.remove(CloudwatchLogsMDCPropertyNames.REQUEST);

In a microservices architecture these attributes should be included in all requests sent between systems, to ensure they can be put in the MDC by each individual service in order to be correlated later. This is very powerful as it allows you to retrieve all the logs pertaining for example to a specific request across all microservices in your environment.

Implementation

The log events are shipped asynchronously on a separate background thread, leaving the performance of your application thread unaffected. To make this possible the appender buffers your messages in a concurrent bounded queue. By default the buffer allows for 1,000,000 messages. If the buffer fills up it will not expand further. This is done to prevent OutOfMemoryErrors. Instead log events are dropped in a FIFO fashion.

If you are seeing dropped messages without having been affected by AWS CloudWatch Logs availability issues, you should consider increasing maxEventQueueSize in the config to allow more log events to be buffered before having to drop them.

Version History

1.1.9.62 (2018-02-01)

1.1.8.60 (2018-01-22)

1.1.7.56 (2018-01-08)

1.1.6.49 (2017-09-19)

1.1.5.46 (2017-06-09)

1.1.4.40 (2017-06-08)

1.1.3.33 (2017-05-16)

1.1.2.30 (2017-05-15)

1.1.1.29 (2017-03-14)

1.1.0.23 (2017-03-02)

1.0.3.20 (2017-01-04)

1.0.2 (2016-11-02)

License

Copyright (C) 2018 Boxfuse GmbH

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.