Git Product home page Git Product logo

cloudwatchlogs-java-appender's Introduction

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:

  • It cleanly separates presentation and formatting from log event content
  • Log events are now machine searchable
  • All log events from all applications now have exactly the same attributes, which enables searches across application boundaries

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:

  • image is the current Boxfuse image
  • instance is the current AWS instance id

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:

  • level is the log level (INFO in this case)
  • logger is the logger used (com.mypkg.MyClass in this case)
  • thread that was logged from (main for the main application thread)
  • message is the actual log message (My log message in this case)

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:

  • event which is the exact type of the event, making it easy to search and filter for this (USER_CREATED in this case)

Optional additional attributes

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

  • account is the current account in the system
  • action is the current action in the system (for grouping log events all related to the same domain-specific thing like the current order for example)
  • user is the user of the account (for systems with the concept of teams or multiple users per account)
  • session is the ID of the current session of the user
  • request is the ID of the request

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)

  • Fixed stdoutFallback handling

1.1.8.60 (2018-01-22)

  • Improved polling logic under high load
  • Added optional maxFlushDelay configuration param
  • Added optional customMdcKey configuration param

1.1.7.56 (2018-01-08)

  • Added thread name
  • Improved polling logic
  • Added optional logGroup configuration param

1.1.6.49 (2017-09-19)

  • Fixed: Handling of DataAlreadyAcceptedException

1.1.5.46 (2017-06-09)

  • Prevent creation of AWS CloudWatch Logs client when disabled

1.1.4.40 (2017-06-08)

  • Fixed: Flushing under high load caused maximum batch size to be exceeded
  • Fixed: Maximum batch size restored to 1,048,576 bytes
  • Added warning when an individual message exceeds the maximum allowed batch size

1.1.3.33 (2017-05-16)

  • Fixed: Reduced maximum batch size to 1,000,000 bytes to avoid occasional batch size exceeded errors

1.1.2.30 (2017-05-15)

  • Fixed: Better handling of temporary network connectivity loss

1.1.1.29 (2017-03-14)

  • Fixed: Exception name is now part of the message along with the stacktrace

1.1.0.23 (2017-03-02)

  • Added stdoutFallback configuration property
  • Fixed: Maximum batch size enforcement before flushing events to CloudWatch Logs

1.0.3.20 (2017-01-04)

  • Fixed: Do not let log thread die after an exception / auto-restart if possible
  • Fixed: Enforce that all events within a single PutLogEvents call are always chronological

1.0.2 (2016-11-02)

  • Initial release

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.

cloudwatchlogs-java-appender's People

Contributors

henri-tremblay avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

cloudwatchlogs-java-appender's Issues

Log Message Contains All Metadata

I upgrade to 1.1.8.60 to get a hold of the custom MDC fields (thank you by the way)

However since, my messages in cloud watch are hard to read because they now contain all the meta data + the actual log message.

Before (version 1.1.5.46):
{
"image": "[imagename]",
"instance": "i-xxxxxxxx",
"level": "WARN",
"logger": "[logger]",
"message": "[actual log message",
"thread": "[threadid]"
}
Nice and clean...

After:

{
"instance": "i-xxxxx",
"image": "[image]",
"level": "INFO",
"message": "2018-02-01 18:07:27.579 boxfuse/xxx [application] {"image":"[boxfuse image]","instance":"i-xxxxxx","level":"INFO","logger":"[logger]","message":"[THE LOG MESSAGE"}"
}

Logback configuration is as follows:

<appender name="cloud-watch" class="com.boxfuse.cloudwatchlogs.logback.CloudwatchLogsLogbackAppender">
	<config>
		<stdoutFallback>true</stdoutFallback>
		<customMdcKey>my-custom-mdc-key</customMdcKey>
	</config>
</appender>

too high logging throughput makes our app unresponsive

We had a hard time last week to understand why our system went down regularly. But finally we managed by some black box testing to find at least which component seems to be responsible for our frequent downtimes.

It turns out that the log appender seems not to handle our amount of logs (>=20logs/sec) we are sending to it. When we send the logs, the app is not responding anymore and thus boxfuse eventually redeploys it.

We still wonder how this can be the case as the appender should be able to handle much more throughput. We are using the latest version 1.1.3.33, Unfortunately, we can't tell more about the exact error as we won't see anything in the logs nor can't we access the application. What we can tell from our debugging with New Relic and jxm though, is that there seems every thing alright with the memory, threads and open connections on our server.

We found that the logappender is to blame for our downtime when we reduced the functionality of some of our components to only perform some logging and nothing else. When we finally also disabled that, our system became stable again.

To be precisely, I can change the behavior of our backend from a mostly stable to an unstable system by changing:

<!-- boxfuse aws -->
  <appender name="Boxfuse-CloudwatchLogs" class="com.boxfuse.cloudwatchlogs.logback.CloudwatchLogsLogbackAppender"/>

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

to

<!-- boxfuse aws -->
  <appender name="Boxfuse-CloudwatchLogs" class="com.boxfuse.cloudwatchlogs.logback.CloudwatchLogsLogbackAppender"/>

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

Publish to Maven Central

Please publish these appenders to Maven Central, so that the community can benefit from them without depending on your Maven repository.

Configure Custom MDC Fields

@axelfontaine
Would be it possible to configure the appender in logback.xml so that it will include a named MDC attribute (that is outside of the list of pre-determined MDC attributes)

<appender name="cloud-watch" class="com.boxfuse.cloudwatchlogs.logback.CloudwatchLogsLogbackAppender">
	<mdc key="my-mdc-key"/>
</appender>

Issue with dropping off/losing logs when pushing to cloudwatch at the end of program execution

Hi boxfuse team -

I tried creating a generic version of cloudwatch logs appender using boxfuse's appender as a starting point. I faced several issues during the process - one of them being that - some of the logs are dropped off and are not fed to CloudWatch. This is because the while loop in the run() method of the CloudwatchLogsLogEventPutter.java is polling for one event while there could be 100's of events in the BlockingQueue and it is very much possible that the program execution stops before the while loop can run as many times as there are log events in the queue. To fix this, I had modified the design to use BlockingQueue.drainTo(collection) approach and push all logs to cloudwatch at one go. Please see https://stackoverflow.com/a/48230308/829542 for implementation info. Hope this helps in enhancing the appender on your end for boxfuse environments. Thanks.

Document minimum version requirements for logging libraries

For example, with Log4j2 ver. 2.6.2 (which we were using in production), appender failed on first logging statement with Exception in thread "main" java.lang.NoSuchMethodError: org.apache.logging.log4j.core.LogEvent.getContextData()Lorg/apache/logging/log4j/util/ReadOnlyStringMap; - which is understandable, as it was only introduced in ver. 2.7, as far as I can see.

Handle DataAlreadyAcceptedException

To auto-recover from errors such as

com.amazonaws.services.logs.model.DataAlreadyAcceptedException: The given batch of log events has already been accepted. The next batch can be sent with sequenceToken: 49576308050167475836136287314051956388877295249203948850 (Service: AWSLogs; Status Code: 400; Error Code: DataAlreadyAcceptedException; Request ID: 0394f028-9c6d-11e7-818f-cdfcfc027d79)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.