├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE.MIT ├── README.md ├── logback-ext-aws-core ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── aws │ └── core │ ├── AbstractAwsEncodingStringAppender.java │ ├── AwsSupport.java │ ├── InternalSdkLoggingFilter.java │ └── LoggingEventHandler.java ├── logback-ext-cloudwatch-appender ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── cloudwatch │ └── appender │ ├── AbstractCloudWatchAppender.java │ ├── CloudWatchAccessAppender.java │ └── CloudWatchAppender.java ├── logback-ext-core ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── core │ ├── AppenderExecutors.java │ ├── ByteArrayPayloadConverter.java │ ├── CharacterEncoder.java │ ├── CommonEventAttributes.java │ ├── ContextAwareExecutorService.java │ ├── EncodingStringAppender.java │ ├── FieldNames.java │ ├── PayloadConverter.java │ └── StringPayloadConverter.java ├── logback-ext-dynamodb-appender ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── dynamodb │ └── appender │ ├── AsyncDynamoDbAppender.java │ ├── CapitalizingFieldNames.java │ └── DynamoDbAppender.java ├── logback-ext-jackson ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── eluder │ │ └── logback │ │ └── ext │ │ └── jackson │ │ ├── JacksonEncoder.java │ │ └── JsonWriter.java │ └── test │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── jackson │ ├── JacksonEncoderTest.java │ └── JsonWriterTest.java ├── logback-ext-kinesis-appender ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── kinesis │ └── appender │ ├── AsyncKinesisAppender.java │ └── KinesisAppender.java ├── logback-ext-lmax-appender ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── eluder │ │ └── logback │ │ └── ext │ │ └── lmax │ │ └── appender │ │ ├── AccessEventDisruptorAppender.java │ │ ├── DelegatingDisruptorAppender.java │ │ ├── DisruptorAppender.java │ │ ├── LoggingEventDisruptorAppender.java │ │ └── WaitStrategyFactory.java │ └── test │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── lmax │ └── appender │ ├── AppenderBenchmark.java │ ├── AsyncAppenderBenchmark.java │ ├── DisruptorAppenderBenchmark.java │ └── LoggingEventDisruptorAppenderPerf.java ├── logback-ext-sns-appender ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── sns │ └── appender │ ├── AsyncSnsAppender.java │ └── SnsAppender.java ├── logback-ext-sqs-appender ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── eluder │ └── logback │ └── ext │ └── sqs │ └── appender │ ├── AsyncSqsAppender.java │ └── SqsAppender.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | */target/ 3 | 4 | # Eclipse 5 | .settings 6 | .project 7 | .classpath 8 | 9 | # Idea 10 | *.iml 11 | .idea 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "etc"] 2 | path = etc 3 | url = https://github.com/trautonen/etc 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk7 5 | - oraclejdk8 6 | 7 | env: 8 | global: 9 | - secure: "h9guHdZrJfVqqzYxDuM/NCuRiSPwCf8rHccSwDbsnJIr/M9u8GOm81sdg6K1wXTLNOSw/G24slZ9EyWfU9Q2mkODklEUqWnmBejwTgJsfpTzj4phdE3MmihCkO/zLLz2XC/K1g1rHwWIZ2WKWUYd81Wa1aFi+QuVG3uvupqYozY=" 10 | - secure: "hnwACXYhD0drSAk2McPyQZ9PmltFEl3m/dPuPbyx1jdC4ly7tbbeRGzomoR5wh/uiH4RaILOdl7h1PbNNPTDYQCYbXJbO1Q1jHGZpOHAna+jHzyCO9fOJLoU/0zcz/ZcJRw1kfcyjNtVYxl3Cd4XIBmPQOqYHxehRlFwfvadYpk=" 11 | 12 | before_install: 13 | - sudo pip install -q pyyaml 14 | 15 | before_script: 16 | - python etc/travis-sonatype.py 17 | 18 | script: python etc/travis-build.py --settings ~/.m2/sonatype.xml 19 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 - 2015, Tapio Rautonen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logback Extensions 2 | ================== 3 | 4 | [![Build Status](https://img.shields.io/travis/trautonen/logback-ext.svg?style=flat-square)](https://travis-ci.org/trautonen/logback-ext) 5 | ![License](https://img.shields.io/github/license/trautonen/logback-ext.svg?style=flat-square) 6 | 7 | Extensions for Logback logging library mainly for appenders aimed for Amazon Web Services, 8 | including CloudWatch Logs, DynamoDB, Kinesis, SNS and SQS appenders. Contains also high 9 | performance asynchronous appender based on LMAX disrupotr and some utilities like Jackson JSON 10 | encoder. 11 | 12 | 13 | ## Using Logback Extensions 14 | 15 | Logback Extensions requires Java 7 or newer. Include desired modules in your project's 16 | dependency management and configure the appenders or encoders using Logback's XML configutation 17 | or Java API. 18 | 19 | 20 | ### Modules 21 | 22 | All modules belong to group `org.eluder.logback`. See each module for specific documentation. 23 | 24 | * Extensions core module: [logback-ext-core](logback-ext-core/) 25 | * AWS core module: [logback-ext-aws-core](logback-ext-aws-core/) 26 | * Jackson JSON encoder: [logback-ext-jackson](logback-ext-jackson/) 27 | * LMAX Disruptor appender: [logback-ext-lmax-appender](logback-ext-lmax-appender/) 28 | * CloudWatch appender: [logback-ext-cloudwatch-appender](logback-ext-cloudwatch-appender/) 29 | * DynamoDB appender: [logback-ext-dynamodb-appender](logback-ext-dynamodb-appender/) 30 | * Kinesis appender: [logback-ext-kinesis-appender](logback-ext-kinesis-appender/) 31 | * SNS appender: [logback-ext-sns-appender](logback-ext-sns-appender/) 32 | * SQS appender: [logback-ext-sqs-appender](logback-ext-sqs-appender/) 33 | 34 | 35 | ### AWS Authentication 36 | 37 | All AWS based appenders require IAM authentication. The default credentials provider from 38 | `org.eluder.logback.ext.aws.core.AwsSupport` creates a credential chain in the following order. 39 | 40 | 1. Environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_KEY` 41 | 2. System properties `aws.accessKeyId` and `aws.secretKey` 42 | 3. Appender configuration properties `accessKey` and `secretKey` 43 | 4. AWS profile configuration file `~/.aws/credentials` 44 | 5. EC2 instance role 45 | 46 | Best practice for EC2 instances is to use instance role only. With instance role no access keys or 47 | secret keys are exposed if the server is compromised. 48 | 49 | 50 | ### Continuous Integration 51 | 52 | TravisCI builds the project with Oracle JDK 7 and 8. Builds created with Oracle JDK 7 are deployed 53 | to Sonatype OSSRH. 54 | -------------------------------------------------------------------------------- /logback-ext-aws-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.eluder.logback 8 | logback-ext 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-aws-core 13 | 14 | 15 | 16 | org.eluder.logback 17 | logback-ext-core 18 | 19 | 20 | ch.qos.logback 21 | logback-core 22 | 23 | 24 | ch.qos.logback 25 | logback-classic 26 | 27 | 28 | com.amazonaws 29 | aws-java-sdk-core 30 | ${aws.version} 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /logback-ext-aws-core/src/main/java/org/eluder/logback/ext/aws/core/AbstractAwsEncodingStringAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.aws.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-aws-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.Context; 30 | import ch.qos.logback.core.filter.Filter; 31 | import ch.qos.logback.core.spi.DeferredProcessingAware; 32 | import com.amazonaws.ClientConfiguration; 33 | import com.amazonaws.auth.AWSCredentials; 34 | import com.amazonaws.auth.AWSCredentialsProvider; 35 | import org.eluder.logback.ext.core.AppenderExecutors; 36 | import org.eluder.logback.ext.core.EncodingStringAppender; 37 | 38 | import static java.lang.String.format; 39 | 40 | public abstract class AbstractAwsEncodingStringAppender extends EncodingStringAppender implements AWSCredentials { 41 | 42 | protected final AwsSupport awsSupport; 43 | protected final Filter sdkLoggingFilter; 44 | 45 | private String accessKey; 46 | private String secretKey; 47 | private int maxPayloadSize = 256; 48 | private boolean asyncParent = false; 49 | private int threadPoolSize = AppenderExecutors.DEFAULT_THREAD_POOL_SIZE; 50 | private int maxFlushTime = AppenderExecutors.DEFAULT_MAX_FLUSH_TIME; 51 | 52 | protected AbstractAwsEncodingStringAppender() { 53 | this(new AwsSupport(), new InternalSdkLoggingFilter()); 54 | } 55 | 56 | protected AbstractAwsEncodingStringAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 57 | this.awsSupport = awsSupport; 58 | this.sdkLoggingFilter = sdkLoggingFilter; 59 | addFilter(sdkLoggingFilter); 60 | } 61 | 62 | public final void setAccessKey(String accessKey) { 63 | this.accessKey = accessKey; 64 | } 65 | 66 | public final void setSecretKey(String secretKey) { 67 | this.secretKey = secretKey; 68 | } 69 | 70 | public final void setMaxPayloadSize(int maxPayloadSize) { 71 | this.maxPayloadSize = maxPayloadSize; 72 | } 73 | 74 | public final void setAsyncParent(boolean asyncParent) { 75 | this.asyncParent = asyncParent; 76 | } 77 | 78 | public final void setThreadPoolSize(int threadPoolSize) { 79 | this.threadPoolSize = threadPoolSize; 80 | } 81 | 82 | public final void setMaxFlushTime(int maxFlushTime) { 83 | this.maxFlushTime = maxFlushTime; 84 | } 85 | 86 | @Override 87 | public void setContext(Context context) { 88 | sdkLoggingFilter.setContext(context); 89 | super.setContext(context); 90 | } 91 | 92 | @Override 93 | public final String getAWSAccessKeyId() { 94 | return accessKey; 95 | } 96 | 97 | @Override 98 | public final String getAWSSecretKey() { 99 | return secretKey; 100 | } 101 | 102 | protected final int getMaxPayloadSize() { 103 | return maxPayloadSize; 104 | } 105 | 106 | protected final boolean isAsyncParent() { 107 | return asyncParent; 108 | } 109 | 110 | protected final int getThreadPoolSize() { 111 | return threadPoolSize; 112 | } 113 | 114 | protected final int getMaxFlushTime() { 115 | return maxFlushTime; 116 | } 117 | 118 | @Override 119 | public void start() { 120 | lock.lock(); 121 | try { 122 | sdkLoggingFilter.start(); 123 | doStart(); 124 | super.start(); 125 | } finally { 126 | lock.unlock(); 127 | } 128 | } 129 | 130 | @Override 131 | public void stop() { 132 | lock.lock(); 133 | try { 134 | super.stop(); 135 | doStop(); 136 | sdkLoggingFilter.stop(); 137 | } finally { 138 | lock.unlock(); 139 | } 140 | } 141 | 142 | protected abstract void doStart(); 143 | 144 | protected abstract void doStop(); 145 | 146 | protected AWSCredentialsProvider getCredentials() { 147 | return awsSupport.getCredentials(this); 148 | } 149 | 150 | protected ClientConfiguration getClientConfiguration() { 151 | return awsSupport.getClientConfiguration(); 152 | } 153 | 154 | @Override 155 | protected P convert(byte[] payload) { 156 | if (payload != null && payload.length > (maxPayloadSize * 1024)) { 157 | addWarn(format("Logging event exceeded the maximum size of %dkB", maxPayloadSize)); 158 | return null; 159 | } else { 160 | return super.convert(payload); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /logback-ext-aws-core/src/main/java/org/eluder/logback/ext/aws/core/AwsSupport.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.aws.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-aws-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import com.amazonaws.ClientConfiguration; 30 | import com.amazonaws.auth.AWSCredentials; 31 | import com.amazonaws.auth.AWSCredentialsProvider; 32 | import com.amazonaws.auth.AWSCredentialsProviderChain; 33 | import com.amazonaws.auth.EnvironmentVariableCredentialsProvider; 34 | import com.amazonaws.auth.InstanceProfileCredentialsProvider; 35 | import com.amazonaws.auth.SystemPropertiesCredentialsProvider; 36 | import com.amazonaws.auth.profile.ProfileCredentialsProvider; 37 | import com.amazonaws.internal.StaticCredentialsProvider; 38 | 39 | public class AwsSupport { 40 | 41 | public AWSCredentialsProvider getCredentials() { 42 | return getCredentials(null); 43 | } 44 | 45 | public AWSCredentialsProvider getCredentials(AWSCredentials credentials) { 46 | return new AWSCredentialsProviderChain( 47 | new EnvironmentVariableCredentialsProvider(), 48 | new SystemPropertiesCredentialsProvider(), 49 | new StaticCredentialsProvider(credentials == null ? new NullCredentials() : credentials), 50 | new ProfileCredentialsProvider(), 51 | new InstanceProfileCredentialsProvider() 52 | ); 53 | } 54 | 55 | public ClientConfiguration getClientConfiguration() { 56 | return new ClientConfiguration(); 57 | } 58 | 59 | private static class NullCredentials implements AWSCredentials { 60 | @Override 61 | public String getAWSAccessKeyId() { 62 | return null; 63 | } 64 | 65 | @Override 66 | public String getAWSSecretKey() { 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /logback-ext-aws-core/src/main/java/org/eluder/logback/ext/aws/core/InternalSdkLoggingFilter.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.aws.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-aws-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.spi.ILoggingEvent; 30 | import ch.qos.logback.core.filter.Filter; 31 | import ch.qos.logback.core.spi.FilterReply; 32 | 33 | public class InternalSdkLoggingFilter extends Filter { 34 | 35 | private static final String[] EXCLUDED_PACKAGES = { "org.apache.http.", "com.amazonaws." }; 36 | 37 | public InternalSdkLoggingFilter() { 38 | setName("aws-internal-logging-exclude"); 39 | } 40 | 41 | @Override 42 | public FilterReply decide(E event) { 43 | if (event instanceof ILoggingEvent) { 44 | String loggerName = ((ILoggingEvent) event).getLoggerName(); 45 | for (String exclude : EXCLUDED_PACKAGES) { 46 | if (loggerName.startsWith(exclude)) { 47 | return FilterReply.DENY; 48 | } 49 | } 50 | } 51 | return FilterReply.NEUTRAL; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /logback-ext-aws-core/src/main/java/org/eluder/logback/ext/aws/core/LoggingEventHandler.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.aws.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-aws-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.spi.ContextAware; 30 | import com.amazonaws.AmazonWebServiceRequest; 31 | import com.amazonaws.handlers.AsyncHandler; 32 | 33 | import java.util.concurrent.CountDownLatch; 34 | 35 | public class LoggingEventHandler implements AsyncHandler { 36 | 37 | protected final ContextAware contextAware; 38 | protected final CountDownLatch latch; 39 | protected final String errorMessage; 40 | 41 | public LoggingEventHandler(ContextAware contextAware, CountDownLatch latch, String errorMessage) { 42 | this.contextAware = contextAware; 43 | this.latch = latch; 44 | this.errorMessage = errorMessage; 45 | } 46 | 47 | @Override 48 | public void onError(Exception exception) { 49 | contextAware.addWarn(errorMessage, exception); 50 | latch.countDown(); 51 | } 52 | 53 | @Override 54 | public void onSuccess(REQUEST request, RESULT result) { 55 | latch.countDown(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /logback-ext-cloudwatch-appender/README.md: -------------------------------------------------------------------------------- 1 | ## CloudWatch Appender 2 | 3 | Appender that submits log events to CloudWatch Logs service. By default all appenders work in 4 | synchronous manner, except this. CloudWatch Logs is intended to accept batches of log events and 5 | due to the requirement of sequence token for batches this appender uses an internal queue similar 6 | to `ch.qos.logback.classic.AsyncAppender` and batches submits to CloudWatch logs according to 7 | appender configuration. 8 | 9 | 10 | ### Maven 11 | 12 | ```xml 13 | 14 | org.eluder.logback 15 | logback-ext-cloudwatch-appender 16 | 1.0-SNAPSHOT 17 | 18 | ``` 19 | 20 | 21 | ### Configuration 22 | 23 | Create the following appender configuration in `logback.xml` and replace the required properties 24 | `region`, `logGroup` and `logStream` with desired values and set an `encoder` that serializes 25 | the logging events. 26 | 27 | By default the appender automatically creates the log group and stream. To restrict IAM policy 28 | actions more, use `skipCreate` property as `true` and create the group and stream beforehand. 29 | 30 | ```xml 31 | 32 | eu-west-1 33 | logzgroup 34 | logzstream 35 | 36 | yyyy-MM-dd'T'HH:mm:ss.SSS 37 | 38 | 39 | ``` 40 | 41 | Complete list of the appender properties. 42 | 43 | | Property | Type | Description | 44 | | -------- | ---- | ----------- | 45 | | `region` | *string* | AWS region. | 46 | | `logGroup` | *string* | Log group name. | 47 | | `logStream` | *string* | Log stream name. | 48 | | `maxBatchSize` | *integer* | **Default: 512**
Maximum number of log events in single submit batch. | 49 | | `maxBatchTime` | *ingeger* | **Default: 1000**
Maximum time in milliseconds to collect log events to submit batch. | 50 | | `internalQueueSize` | *integer* | **Default: 8192**
Size of the internal log event queue. | 51 | | `skipCreate` | *boolean* | **Default: false**
Skip queue and stream creationg. Requires less IAM policy actions. | 52 | | `charset` | *charset* | **Default: UTF-8**
Charset for the log event encoder. | 53 | | `binary` | *boolean* | **Default: false**
Encoded data is binary and must be Base64 encoded. | 54 | | `encoder` | *encoder* | Logback encoder to serialize the logging events. | 55 | | `accessKey` | *string* | IAM access key. | 56 | | `secretKey` | *string* | IAM secret key. | 57 | | `maxPayloadSize` | *integer* | **Default: 256**
Maximum log event payload size in kilobytes. | 58 | | `maxFlushTime` | *integer* | **Default: 3000**
Maximum wait time in milliseconds to wait if internal queue is full and time to wait for the remaining queue to flush events on appender stop. | 59 | 60 | 61 | ### Required IAM policy 62 | 63 | Policy required to create the log group and stream on demand. 64 | 65 | ```json 66 | { 67 | "Version": "2012-10-17", 68 | "Statement": [ 69 | { 70 | "Effect": "Allow", 71 | "Action": [ 72 | "logs:CreateLogGroup", 73 | "logs:CreateLogStream", 74 | "logs:DescribeLogGroups", 75 | "logs:DescribeLogStreams", 76 | "logs:PutLogEvents" 77 | ], 78 | "Resource": [ 79 | "arn:aws:logs:*:*:*" 80 | ] 81 | } 82 | ] 83 | } 84 | ``` 85 | 86 | More restrictive policy for log event submits only. 87 | 88 | ```json 89 | { 90 | "Version": "2012-10-17", 91 | "Statement": [ 92 | { 93 | "Effect": "Allow", 94 | "Action": [ 95 | "logs:PutLogEvents" 96 | ], 97 | "Resource": [ 98 | "arn:aws:logs:eu-west-1:*:log-group:logzgroup:log-stream:logzstream" 99 | ] 100 | } 101 | ] 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /logback-ext-cloudwatch-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.eluder.logback 8 | logback-ext 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-cloudwatch-appender 13 | 14 | 15 | 16 | ch.qos.logback 17 | logback-core 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | true 23 | 24 | 25 | ch.qos.logback 26 | logback-access 27 | true 28 | 29 | 30 | org.eluder.logback 31 | logback-ext-core 32 | 33 | 34 | org.eluder.logback 35 | logback-ext-aws-core 36 | 37 | 38 | com.amazonaws 39 | aws-java-sdk-core 40 | ${aws.version} 41 | 42 | 43 | com.amazonaws 44 | aws-java-sdk-logs 45 | ${aws.version} 46 | 47 | 48 | com.google.guava 49 | guava 50 | ${guava.version} 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /logback-ext-cloudwatch-appender/src/main/java/org/eluder/logback/ext/cloudwatch/appender/AbstractCloudWatchAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.cloudwatch.appender; 2 | 3 | import ch.qos.logback.core.filter.Filter; 4 | import ch.qos.logback.core.spi.DeferredProcessingAware; 5 | import com.amazonaws.regions.RegionUtils; 6 | import com.amazonaws.services.logs.AWSLogsClient; 7 | import com.amazonaws.services.logs.model.*; 8 | import com.amazonaws.util.StringUtils; 9 | import com.google.common.collect.Lists; 10 | import com.google.common.collect.Ordering; 11 | import com.google.common.collect.Queues; 12 | import org.eluder.logback.ext.aws.core.AbstractAwsEncodingStringAppender; 13 | import org.eluder.logback.ext.aws.core.AwsSupport; 14 | import org.eluder.logback.ext.core.CommonEventAttributes; 15 | import org.eluder.logback.ext.core.StringPayloadConverter; 16 | 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | import java.util.concurrent.LinkedBlockingQueue; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | import static java.lang.String.format; 23 | 24 | public abstract class AbstractCloudWatchAppender extends AbstractAwsEncodingStringAppender { 25 | 26 | private static final int DEFAULT_MAX_BATCH_SIZE = 512; 27 | private static final int DEFAULT_MAX_BATCH_TIME = 1000; 28 | private static final int DEFAULT_INTERNAL_QUEUE_SIZE = 8192; 29 | private static final boolean DEFAULT_SKIP_CREATE = false; 30 | 31 | private String region; 32 | private String logGroup; 33 | private String logStream; 34 | private int maxBatchSize = DEFAULT_MAX_BATCH_SIZE; 35 | private long maxBatchTime = DEFAULT_MAX_BATCH_TIME; 36 | private int internalQueueSize = DEFAULT_INTERNAL_QUEUE_SIZE; 37 | private boolean skipCreate = DEFAULT_SKIP_CREATE; 38 | 39 | private AWSLogsClient logs; 40 | 41 | private LinkedBlockingQueue queue; 42 | private Worker worker; 43 | 44 | public AbstractCloudWatchAppender() { 45 | super(); 46 | } 47 | 48 | protected AbstractCloudWatchAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 49 | super(awsSupport, sdkLoggingFilter); 50 | } 51 | 52 | public final void setRegion(String region) { 53 | this.region = region; 54 | } 55 | 56 | public final void setLogGroup(String logGroup) { 57 | this.logGroup = logGroup; 58 | } 59 | 60 | public final void setLogStream(String logStream) { 61 | this.logStream = logStream; 62 | } 63 | 64 | public final void setMaxBatchSize(int maxBatchSize) { 65 | this.maxBatchSize = maxBatchSize; 66 | } 67 | 68 | public final void setMaxBatchTime(long maxBatchTime) { 69 | this.maxBatchTime = maxBatchTime; 70 | } 71 | 72 | public final void setInternalQueueSize(int internalQueueSize) { 73 | this.internalQueueSize = internalQueueSize; 74 | } 75 | 76 | public final void setSkipCreate(boolean skipCreate) { 77 | this.skipCreate = skipCreate; 78 | } 79 | 80 | @Override 81 | public void start() { 82 | if (RegionUtils.getRegion(region) == null) { 83 | addError(format("Region not set or invalid for appender '%s'", getName())); 84 | return; 85 | } 86 | if (StringUtils.isNullOrEmpty(logGroup)) { 87 | addError(format("Log group name not set for appender '%s'", getName())); 88 | return; 89 | } 90 | if (StringUtils.isNullOrEmpty(logStream)) { 91 | addError(format("Log stream name not set for appender '%s'", getName())); 92 | return; 93 | } 94 | setConverter(new StringPayloadConverter(getCharset(), isBinary())); 95 | super.start(); 96 | } 97 | 98 | @Override 99 | protected void doStart() { 100 | logs = new AWSLogsClient( 101 | getCredentials(), 102 | getClientConfiguration() 103 | ); 104 | logs.setRegion(RegionUtils.getRegion(region)); 105 | if (!skipCreate) { 106 | if (!logGroupExists(logGroup)) { 107 | createLogGroup(logGroup); 108 | } 109 | if (!logStreamExists(logGroup, logStream)) { 110 | createLogStream(logGroup, logStream); 111 | } 112 | } 113 | queue = new LinkedBlockingQueue<>(internalQueueSize); 114 | worker = new Worker<>(this); 115 | worker.setName(format("%s-worker", getName())); 116 | worker.setDaemon(true); 117 | worker.start(); 118 | } 119 | 120 | @Override 121 | protected void doStop() { 122 | if (worker != null) { 123 | worker.stopGracefully(); 124 | try { 125 | worker.join(getMaxFlushTime()); 126 | if (worker.isAlive()) { 127 | addWarn(format("Max queue flush timeout (%d ms) exceeded, approximately %d queued events were possibly discarded", 128 | getMaxFlushTime(), queue.size())); 129 | } 130 | } catch (InterruptedException ex) { 131 | addError(format("Stopping was interrupted, approximately %d queued events may be discarded", 132 | queue.size()), ex); 133 | } 134 | worker = null; 135 | } 136 | if (queue != null) { 137 | queue.clear(); 138 | queue = null; 139 | } 140 | if (logs != null) { 141 | logs.shutdown(); 142 | logs = null; 143 | } 144 | } 145 | 146 | @Override 147 | protected void handle(final E event, final String encoded) throws Exception { 148 | CommonEventAttributes attributes = applyCommonEventAttributes(event); 149 | InputLogEvent ile = new InputLogEvent().withTimestamp(attributes.getTimeStamp()).withMessage(encoded); 150 | if (!queue.offer(ile, getMaxFlushTime(), TimeUnit.MILLISECONDS)) { 151 | addWarn(format("No space available in internal queue after %d ms waiting, logging event was discarded", 152 | getMaxFlushTime())); 153 | } 154 | } 155 | 156 | protected abstract CommonEventAttributes applyCommonEventAttributes(final E event); 157 | 158 | protected boolean logGroupExists(String logGroup) { 159 | DescribeLogGroupsRequest request = new DescribeLogGroupsRequest().withLogGroupNamePrefix(logGroup); 160 | DescribeLogGroupsResult result = logs.describeLogGroups(request); 161 | for (LogGroup group : result.getLogGroups()) { 162 | if (logGroup.equals(group.getLogGroupName())) { 163 | return true; 164 | } 165 | } 166 | return false; 167 | } 168 | 169 | protected void createLogGroup(String logGroup) { 170 | CreateLogGroupRequest request = new CreateLogGroupRequest(logGroup); 171 | logs.createLogGroup(request); 172 | addInfo(format("Successfully created log group '%s'", logGroup)); 173 | } 174 | 175 | protected boolean logStreamExists(String logGroup, String logStream) { 176 | DescribeLogStreamsRequest request = new DescribeLogStreamsRequest().withLogGroupName(logGroup).withLogStreamNamePrefix(logStream); 177 | DescribeLogStreamsResult result = logs.describeLogStreams(request); 178 | for (LogStream stream : result.getLogStreams()) { 179 | if (logStream.equals(stream.getLogStreamName())) { 180 | return true; 181 | } 182 | } 183 | return false; 184 | } 185 | 186 | protected void createLogStream(String logGroup, String logStream) { 187 | CreateLogStreamRequest request = new CreateLogStreamRequest(logGroup, logStream); 188 | logs.createLogStream(request); 189 | addInfo(format("Successfully created log stream '%s' for group '%s'", logStream, logGroup)); 190 | } 191 | 192 | private static class Worker

extends Thread { 193 | 194 | private static final Ordering ORDERING = new Ordering() { 195 | @Override 196 | public int compare(InputLogEvent left, InputLogEvent right) { 197 | if (left.getTimestamp() < right.getTimestamp()) { 198 | return -1; 199 | } else if (left.getTimestamp() > right.getTimestamp()) { 200 | return 1; 201 | } else { 202 | return 0; 203 | } 204 | } 205 | }; 206 | 207 | private final AbstractCloudWatchAppender

parent; 208 | 209 | private String token = null; 210 | private volatile boolean started = false; 211 | 212 | Worker(AbstractCloudWatchAppender

parent) { 213 | this.parent = parent; 214 | } 215 | 216 | @Override 217 | public void run() { 218 | started = true; 219 | while (started) { 220 | List events = new LinkedList<>(); 221 | try { 222 | Queues.drain(parent.queue, events, parent.maxBatchSize, parent.maxBatchTime, TimeUnit.MILLISECONDS); 223 | handle(events); 224 | } catch (InterruptedException ex) { 225 | handle(events); 226 | } 227 | } 228 | 229 | List remaining = new LinkedList<>(); 230 | parent.queue.drainTo(remaining); 231 | for (List batch : Lists.partition(remaining, parent.maxBatchSize)) { 232 | handle(batch); 233 | } 234 | 235 | } 236 | 237 | public void stopGracefully() { 238 | started = false; 239 | } 240 | 241 | private void handle(List events) { 242 | if (!events.isEmpty()) { 243 | List sorted = ORDERING.immutableSortedCopy(events); 244 | PutLogEventsRequest request = new PutLogEventsRequest(parent.logGroup, parent.logStream, sorted); 245 | try { 246 | try { 247 | putEvents(request); 248 | } catch (DataAlreadyAcceptedException | InvalidSequenceTokenException ex) { 249 | putEvents(request); 250 | } 251 | } catch (Exception ex) { 252 | parent.addError(format("Failed to handle %d events", events.size()), ex); 253 | } 254 | } 255 | } 256 | 257 | private void putEvents(PutLogEventsRequest request) { 258 | try { 259 | PutLogEventsResult result = parent.logs.putLogEvents(request.withSequenceToken(token)); 260 | token = result.getNextSequenceToken(); 261 | } catch (DataAlreadyAcceptedException ex) { 262 | token = ex.getExpectedSequenceToken(); 263 | throw ex; 264 | } catch (InvalidSequenceTokenException ex) { 265 | token = ex.getExpectedSequenceToken(); 266 | throw ex; 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /logback-ext-cloudwatch-appender/src/main/java/org/eluder/logback/ext/cloudwatch/appender/CloudWatchAccessAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.cloudwatch.appender; 2 | 3 | import ch.qos.logback.access.spi.IAccessEvent; 4 | import ch.qos.logback.core.filter.Filter; 5 | import org.eluder.logback.ext.aws.core.AwsSupport; 6 | import org.eluder.logback.ext.core.CommonEventAttributes; 7 | 8 | public class CloudWatchAccessAppender extends AbstractCloudWatchAppender { 9 | 10 | public CloudWatchAccessAppender() { 11 | super(); 12 | } 13 | 14 | protected CloudWatchAccessAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 15 | super(awsSupport, sdkLoggingFilter); 16 | } 17 | 18 | @Override 19 | protected CommonEventAttributes applyCommonEventAttributes(final IAccessEvent event) { 20 | return new CommonEventAttributes() { 21 | @Override 22 | public String getThreadName() { 23 | return event.getThreadName(); 24 | } 25 | 26 | @Override 27 | public long getTimeStamp() { 28 | return event.getTimeStamp(); 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /logback-ext-cloudwatch-appender/src/main/java/org/eluder/logback/ext/cloudwatch/appender/CloudWatchAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.cloudwatch.appender; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.filter.Filter; 5 | import org.eluder.logback.ext.aws.core.AwsSupport; 6 | import org.eluder.logback.ext.core.CommonEventAttributes; 7 | 8 | public class CloudWatchAppender extends AbstractCloudWatchAppender { 9 | 10 | public CloudWatchAppender() { 11 | super(); 12 | } 13 | 14 | protected CloudWatchAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 15 | super(awsSupport, sdkLoggingFilter); 16 | } 17 | 18 | @Override 19 | protected CommonEventAttributes applyCommonEventAttributes(final ILoggingEvent event) { 20 | return new CommonEventAttributes() { 21 | @Override 22 | public String getThreadName() { 23 | return event.getThreadName(); 24 | } 25 | 26 | @Override 27 | public long getTimeStamp() { 28 | return event.getTimeStamp(); 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /logback-ext-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | logback-ext 9 | org.eluder.logback 10 | 1.0-SNAPSHOT 11 | 12 | 13 | logback-ext-core 14 | 15 | 16 | 17 | ch.qos.logback 18 | logback-core 19 | 20 | 21 | com.google.guava 22 | guava 23 | ${guava.version} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/AppenderExecutors.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.Appender; 30 | 31 | import java.util.concurrent.CountDownLatch; 32 | import java.util.concurrent.ExecutorService; 33 | import java.util.concurrent.Executors; 34 | import java.util.concurrent.ThreadFactory; 35 | import java.util.concurrent.TimeUnit; 36 | import java.util.concurrent.atomic.AtomicInteger; 37 | 38 | import static java.lang.String.format; 39 | 40 | public class AppenderExecutors { 41 | 42 | public static final int DEFAULT_THREAD_POOL_SIZE = 20; 43 | public static final int DEFAULT_MAX_FLUSH_TIME = 3000; // milliseconds 44 | 45 | public static ExecutorService newExecutor(Appender appender, int threadPoolSize) { 46 | final String name = appender.getName(); 47 | return Executors.newFixedThreadPool(threadPoolSize, new ThreadFactory() { 48 | 49 | private final AtomicInteger idx = new AtomicInteger(1); 50 | 51 | @Override 52 | public Thread newThread(Runnable r) { 53 | Thread thread = Executors.defaultThreadFactory().newThread(r); 54 | thread.setName(name + "-" + idx.getAndIncrement()); 55 | thread.setDaemon(true); 56 | return thread; 57 | } 58 | }); 59 | } 60 | 61 | public static void shutdown(Appender appender, ExecutorService executor, long waitMillis) { 62 | executor.shutdown(); 63 | boolean completed = awaitTermination(appender, executor, waitMillis); 64 | if (!completed) { 65 | appender.addWarn(format("Executor for %s did not shut down in %d milliseconds, " + 66 | "logging events might have been discarded", 67 | appender.getName(), waitMillis)); 68 | } 69 | } 70 | 71 | public static void awaitLatch(Appender appender, CountDownLatch latch, long waitMillis) { 72 | if (latch.getCount() > 0) { 73 | try { 74 | boolean completed = latch.await(waitMillis, TimeUnit.MILLISECONDS); 75 | if (!completed) { 76 | appender.addWarn(format("Appender '%s' did not complete sending event in %d milliseconds, " + 77 | "the event might have been lost", 78 | appender.getName(), waitMillis)); 79 | } 80 | } catch (InterruptedException ex) { 81 | appender.addWarn(format("Appender '%s' was interrupted, " + 82 | "a logging event might have been lost or shutdown was initiated", 83 | appender.getName())); 84 | Thread.currentThread().interrupt(); 85 | } 86 | } 87 | } 88 | 89 | private static boolean awaitTermination(Appender appender, ExecutorService executor, long waitMillis) { 90 | long started = System.currentTimeMillis(); 91 | try { 92 | return executor.awaitTermination(waitMillis, TimeUnit.MILLISECONDS); 93 | } catch (InterruptedException ie1) { 94 | // the worker loop is stopped by interrupt, but the remaining queue should still be handled 95 | long waited = System.currentTimeMillis() - started; 96 | if (waited < waitMillis) { 97 | try { 98 | return executor.awaitTermination(waitMillis - waited, TimeUnit.MILLISECONDS); 99 | } catch (InterruptedException ie2) { 100 | appender.addError(format("Shut down of executor for %s was interrupted", 101 | appender.getName())); 102 | } 103 | } 104 | Thread.currentThread().interrupt(); 105 | } 106 | return false; 107 | } 108 | 109 | private AppenderExecutors() { 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/ByteArrayPayloadConverter.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | public class ByteArrayPayloadConverter implements PayloadConverter { 4 | 5 | @Override 6 | public byte[] convert(byte[] payload) { 7 | return payload; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/CharacterEncoder.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.encoder.Encoder; 30 | 31 | import java.nio.charset.Charset; 32 | 33 | public interface CharacterEncoder extends Encoder { 34 | 35 | void setCharset(Charset charset); 36 | 37 | Charset getCharset(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/CommonEventAttributes.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | public interface CommonEventAttributes { 4 | 5 | String getThreadName(); 6 | 7 | long getTimeStamp(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/ContextAwareExecutorService.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.spi.ContextAware; 30 | 31 | import java.util.Collection; 32 | import java.util.List; 33 | import java.util.concurrent.Callable; 34 | import java.util.concurrent.ExecutionException; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.Future; 37 | import java.util.concurrent.TimeUnit; 38 | import java.util.concurrent.TimeoutException; 39 | 40 | public class ContextAwareExecutorService implements ExecutorService { 41 | 42 | private final ContextAware contextAware; 43 | 44 | public ContextAwareExecutorService(ContextAware contextAware) { 45 | this.contextAware = contextAware; 46 | } 47 | 48 | @Override 49 | public void shutdown() { 50 | contextAware.getContext().getExecutorService().shutdown(); 51 | } 52 | 53 | @Override 54 | public List shutdownNow() { 55 | return contextAware.getContext().getExecutorService().shutdownNow(); 56 | } 57 | 58 | @Override 59 | public boolean isShutdown() { 60 | return contextAware.getContext().getExecutorService().isShutdown(); 61 | } 62 | 63 | @Override 64 | public boolean isTerminated() { 65 | return contextAware.getContext().getExecutorService().isTerminated(); 66 | } 67 | 68 | @Override 69 | public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 70 | return contextAware.getContext().getExecutorService().awaitTermination(timeout, unit); 71 | } 72 | 73 | @Override 74 | public Future submit(Callable task) { 75 | return contextAware.getContext().getExecutorService().submit(task); 76 | } 77 | 78 | @Override 79 | public Future submit(Runnable task, T result) { 80 | return contextAware.getContext().getExecutorService().submit(task, result); 81 | } 82 | 83 | @Override 84 | public Future submit(Runnable task) { 85 | return contextAware.getContext().getExecutorService().submit(task); 86 | } 87 | 88 | @Override 89 | public List> invokeAll(Collection> tasks) throws InterruptedException { 90 | return contextAware.getContext().getExecutorService().invokeAll(tasks); 91 | } 92 | 93 | @Override 94 | public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException { 95 | return contextAware.getContext().getExecutorService().invokeAll(tasks, timeout, unit); 96 | } 97 | 98 | @Override 99 | public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { 100 | return contextAware.getContext().getExecutorService().invokeAny(tasks); 101 | } 102 | 103 | @Override 104 | public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 105 | return contextAware.getContext().getExecutorService().invokeAny(tasks, timeout, unit); 106 | } 107 | 108 | @Override 109 | public void execute(Runnable command) { 110 | contextAware.getContext().getExecutorService().execute(command); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/EncodingStringAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.Context; 30 | import ch.qos.logback.core.Layout; 31 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 32 | import ch.qos.logback.core.encoder.Encoder; 33 | import ch.qos.logback.core.encoder.LayoutWrappingEncoder; 34 | import ch.qos.logback.core.spi.DeferredProcessingAware; 35 | 36 | import java.io.ByteArrayOutputStream; 37 | import java.io.IOException; 38 | import java.nio.charset.Charset; 39 | import java.util.concurrent.locks.ReentrantLock; 40 | 41 | import static java.lang.String.format; 42 | 43 | public abstract class EncodingStringAppender extends UnsynchronizedAppenderBase { 44 | 45 | protected final ReentrantLock lock = new ReentrantLock(true); 46 | 47 | private Charset charset = Charset.forName("UTF-8"); 48 | private boolean binary = false; 49 | private Encoder encoder; 50 | private PayloadConverter

converter; 51 | 52 | public final void setCharset(Charset charset) { 53 | if (encoder instanceof LayoutWrappingEncoder) { 54 | ((LayoutWrappingEncoder) encoder).setCharset(charset); 55 | } else if (encoder instanceof CharacterEncoder) { 56 | ((CharacterEncoder) encoder).setCharset(charset); 57 | } 58 | this.charset = charset; 59 | } 60 | 61 | public final void setBinary(boolean binary) { 62 | if (binary) { 63 | addInfo(format("Appender '%s' is set to binary mode, events are converted to Base64 strings", getName())); 64 | } 65 | this.binary = binary; 66 | } 67 | 68 | public final void setEncoder(Encoder encoder) { 69 | this.encoder = encoder; 70 | setContext(context); 71 | setCharset(charset); 72 | } 73 | 74 | public final void setLayout(Layout layout) { 75 | LayoutWrappingEncoder enc = new LayoutWrappingEncoder<>(); 76 | enc.setLayout(layout); 77 | setEncoder(enc); 78 | } 79 | 80 | public final void setConverter(PayloadConverter

converter) { 81 | this.converter = converter; 82 | } 83 | 84 | @Override 85 | public void setContext(Context context) { 86 | if (encoder != null) { 87 | encoder.setContext(context); 88 | } 89 | super.setContext(context); 90 | } 91 | 92 | protected final Charset getCharset() { 93 | return charset; 94 | } 95 | 96 | protected final boolean isBinary() { 97 | return binary; 98 | } 99 | 100 | protected final Encoder getEncoder() { 101 | return encoder; 102 | } 103 | 104 | @Override 105 | public void start() { 106 | if (encoder == null) { 107 | addError(format("Encoder not set for appender '%s'", getName())); 108 | return; 109 | } 110 | if (converter == null) { 111 | addError(format("Converter not set for appender '%s'", getName())); 112 | return; 113 | } 114 | lock.lock(); 115 | try { 116 | encoder.start(); 117 | super.start(); 118 | } finally { 119 | lock.unlock(); 120 | } 121 | } 122 | 123 | @Override 124 | public void stop() { 125 | lock.lock(); 126 | try { 127 | super.stop(); 128 | if (encoder != null) { 129 | encoder.stop(); 130 | } 131 | } finally { 132 | lock.unlock(); 133 | } 134 | } 135 | 136 | @Override 137 | protected void append(E event) { 138 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 139 | encode(event, stream); 140 | doHandle(event, convert(stream.toByteArray())); 141 | } 142 | 143 | private void encode(E event, ByteArrayOutputStream stream) { 144 | lock.lock(); 145 | try { 146 | encoderInit(stream); 147 | try { 148 | doEncode(event); 149 | } finally { 150 | encoderClose(); 151 | } 152 | } finally { 153 | lock.unlock(); 154 | } 155 | } 156 | 157 | protected abstract void handle(E event, P encoded) throws Exception; 158 | 159 | protected P convert(byte[] payload) { 160 | return converter.convert(payload); 161 | } 162 | 163 | protected void doHandle(E event, P encoded) { 164 | try { 165 | if (encoded != null) { 166 | handle(event, encoded); 167 | } 168 | } catch (Exception ex) { 169 | this.started = false; 170 | addError(format("Failed to handle logging event for '%s'", getName()), ex); 171 | } 172 | } 173 | 174 | protected void doEncode(E event) { 175 | try { 176 | encoder.doEncode(event); 177 | } catch (IOException ex) { 178 | this.started = false; 179 | addError(format("Failed to encode logging event for appender '%s'", getName()), ex); 180 | } 181 | } 182 | 183 | protected void encoderInit(ByteArrayOutputStream stream) { 184 | try { 185 | encoder.init(stream); 186 | } catch (IOException ex) { 187 | this.started = false; 188 | addError(format("Failed to initialize encoder for appender '%s'", getName()), ex); 189 | } 190 | } 191 | 192 | protected void encoderClose() { 193 | try { 194 | encoder.close(); 195 | } catch (Exception ex) { 196 | this.started = false; 197 | addError(format("Failed to close encoder for appender '%s'", getName()), ex); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/FieldNames.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-core 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | public class FieldNames { 30 | 31 | public static final String IGNORE_NAME = "[ignore]"; 32 | 33 | private String timeStamp = "timeStamp"; 34 | private String level = "level"; 35 | private String levelValue = "levelValue"; 36 | private String threadName = "thread"; 37 | private String loggerName = "logger"; 38 | private String message = "message"; 39 | private String formattedMessage = "formattedMessage"; 40 | private String stackTrace = "stackTrace"; 41 | private String callerData = "caller"; 42 | private String callerClass = "class"; 43 | private String callerMethod = "method"; 44 | private String callerFile = "file"; 45 | private String callerLine = "line"; 46 | private String mdc = "mdc"; 47 | private String marker = "marker"; 48 | 49 | public String getTimeStamp() { 50 | return timeStamp; 51 | } 52 | 53 | public void setTimeStamp(String timeStamp) { 54 | this.timeStamp = timeStamp; 55 | } 56 | 57 | public String getLevel() { 58 | return level; 59 | } 60 | 61 | public void setLevel(String level) { 62 | this.level = level; 63 | } 64 | 65 | public String getLevelValue() { 66 | return levelValue; 67 | } 68 | 69 | public void setLevelValue(String levelValue) { 70 | this.levelValue = levelValue; 71 | } 72 | 73 | public String getThreadName() { 74 | return threadName; 75 | } 76 | 77 | public void setThreadName(String threadName) { 78 | this.threadName = threadName; 79 | } 80 | 81 | public String getLoggerName() { 82 | return loggerName; 83 | } 84 | 85 | public void setLoggerName(String loggerName) { 86 | this.loggerName = loggerName; 87 | } 88 | 89 | public String getMessage() { 90 | return message; 91 | } 92 | 93 | public void setMessage(String message) { 94 | this.message = message; 95 | } 96 | 97 | public String getFormattedMessage() { 98 | return formattedMessage; 99 | } 100 | 101 | public void setFormattedMessage(String formattedMessage) { 102 | this.formattedMessage = formattedMessage; 103 | } 104 | 105 | public String getStackTrace() { 106 | return stackTrace; 107 | } 108 | 109 | public void setStackTrace(String stackTrace) { 110 | this.stackTrace = stackTrace; 111 | } 112 | 113 | public String getCallerData() { 114 | return callerData; 115 | } 116 | 117 | public void setCallerData(String callerData) { 118 | this.callerData = callerData; 119 | } 120 | 121 | public String getCallerClass() { 122 | return callerClass; 123 | } 124 | 125 | public void setCallerClass(String callerClass) { 126 | this.callerClass = callerClass; 127 | } 128 | 129 | public String getCallerMethod() { 130 | return callerMethod; 131 | } 132 | 133 | public void setCallerMethod(String callerMethod) { 134 | this.callerMethod = callerMethod; 135 | } 136 | 137 | public String getCallerFile() { 138 | return callerFile; 139 | } 140 | 141 | public void setCallerFile(String callerFile) { 142 | this.callerFile = callerFile; 143 | } 144 | 145 | public String getCallerLine() { 146 | return callerLine; 147 | } 148 | 149 | public void setCallerLine(String callerLine) { 150 | this.callerLine = callerLine; 151 | } 152 | 153 | public String getMdc() { 154 | return mdc; 155 | } 156 | 157 | public void setMdc(String mdc) { 158 | this.mdc = mdc; 159 | } 160 | 161 | public String getMarker() { 162 | return marker; 163 | } 164 | 165 | public void setMarker(String marker) { 166 | this.marker = marker; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/PayloadConverter.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | public interface PayloadConverter

{ 4 | 5 | P convert(byte[] payload); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /logback-ext-core/src/main/java/org/eluder/logback/ext/core/StringPayloadConverter.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.core; 2 | 3 | import com.google.common.io.BaseEncoding; 4 | 5 | import java.nio.charset.Charset; 6 | 7 | public class StringPayloadConverter implements PayloadConverter { 8 | 9 | private final Charset charset; 10 | private final boolean binary; 11 | 12 | public StringPayloadConverter(Charset charset, boolean binary) { 13 | this.charset = charset; 14 | this.binary = binary; 15 | } 16 | 17 | @Override 18 | public String convert(byte[] payload) { 19 | if (payload == null) { 20 | return null; 21 | } else if (binary) { 22 | return BaseEncoding.base64().encode(payload); 23 | } else { 24 | return new String(payload, charset); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /logback-ext-dynamodb-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | logback-ext 8 | org.eluder.logback 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-dynamodb-appender 13 | 14 | 15 | 16 | ch.qos.logback 17 | logback-core 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | 23 | 24 | org.eluder.logback 25 | logback-ext-core 26 | 27 | 28 | org.eluder.logback 29 | logback-ext-aws-core 30 | 31 | 32 | org.eluder.logback 33 | logback-ext-jackson 34 | 35 | 36 | com.amazonaws 37 | aws-java-sdk-core 38 | ${aws.version} 39 | 40 | 41 | com.amazonaws 42 | aws-java-sdk-dynamodb 43 | ${aws.version} 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /logback-ext-dynamodb-appender/src/main/java/org/eluder/logback/ext/dynamodb/appender/AsyncDynamoDbAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.dynamodb.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-dynamodb-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.AsyncAppender; 30 | import ch.qos.logback.classic.spi.ILoggingEvent; 31 | import ch.qos.logback.core.Context; 32 | import ch.qos.logback.core.Layout; 33 | import ch.qos.logback.core.encoder.Encoder; 34 | 35 | import java.nio.charset.Charset; 36 | 37 | public class AsyncDynamoDbAppender extends AsyncAppender { 38 | 39 | private final DynamoDbAppender appender; 40 | 41 | public AsyncDynamoDbAppender() { 42 | this(new DynamoDbAppender()); 43 | } 44 | 45 | protected AsyncDynamoDbAppender(DynamoDbAppender appender) { 46 | appender.setAsyncParent(true); 47 | addAppender(appender); 48 | this.appender = appender; 49 | } 50 | 51 | public void setAccessKey(String accessKey) { 52 | appender.setAccessKey(accessKey); 53 | } 54 | 55 | public void setSecretKey(String secretKey) { 56 | appender.setSecretKey(secretKey); 57 | } 58 | 59 | public void setMaxPayloadSize(int maxPayloadSize) { 60 | appender.setMaxPayloadSize(maxPayloadSize); 61 | } 62 | 63 | public void setRegion(String region) { 64 | appender.setRegion(region); 65 | } 66 | 67 | public void setTable(String table) { 68 | appender.setTable(table); 69 | } 70 | 71 | public void setPrimaryKey(String primaryKey) { 72 | appender.setPrimaryKey(primaryKey); 73 | } 74 | 75 | public final void setThreadPoolSize(int threadPoolSize) { 76 | appender.setThreadPoolSize(threadPoolSize); 77 | } 78 | 79 | @Override 80 | public final void setMaxFlushTime(int maxFlushTime) { 81 | appender.setMaxFlushTime(maxFlushTime); 82 | // add an extra 100 millis to wait for the internal event queue handling 83 | super.setMaxFlushTime(maxFlushTime + 100); 84 | } 85 | 86 | public void setCharset(Charset charset) { 87 | appender.setCharset(charset); 88 | } 89 | 90 | public void setEncoder(Encoder encoder) { 91 | appender.setEncoder(encoder); 92 | } 93 | 94 | public void setLayout(Layout layout) { 95 | appender.setLayout(layout); 96 | } 97 | 98 | public void setBinary(boolean binary) { 99 | appender.setBinary(binary); 100 | } 101 | 102 | @Override 103 | public void setName(String name) { 104 | appender.setName(name); 105 | super.setName(name); 106 | } 107 | 108 | @Override 109 | public void setContext(Context context) { 110 | appender.setContext(context); 111 | super.setContext(context); 112 | } 113 | 114 | @Override 115 | public void start() { 116 | appender.start(); 117 | super.start(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /logback-ext-dynamodb-appender/src/main/java/org/eluder/logback/ext/dynamodb/appender/CapitalizingFieldNames.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.dynamodb.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-dynamodb-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import org.eluder.logback.ext.core.FieldNames; 30 | 31 | public class CapitalizingFieldNames extends FieldNames { 32 | 33 | public CapitalizingFieldNames() { 34 | setTimeStamp(getTimeStamp()); 35 | setLevel(getLevel()); 36 | setLevelValue(getLevelValue()); 37 | setThreadName(getThreadName()); 38 | setLoggerName(getLoggerName()); 39 | setMessage(getMessage()); 40 | setFormattedMessage(getFormattedMessage()); 41 | setStackTrace(getStackTrace()); 42 | setCallerData(getCallerData()); 43 | setCallerClass(getCallerClass()); 44 | setCallerMethod(getCallerMethod()); 45 | setCallerFile(getCallerFile()); 46 | setCallerLine(getCallerLine()); 47 | setMdc(getMdc()); 48 | setMarker(getMarker()); 49 | } 50 | 51 | @Override 52 | public void setTimeStamp(String timeStamp) { 53 | super.setTimeStamp(capitalize(timeStamp)); 54 | } 55 | 56 | @Override 57 | public void setLevel(String level) { 58 | super.setLevel(capitalize(level)); 59 | } 60 | 61 | @Override 62 | public void setLevelValue(String levelValue) { 63 | super.setLevelValue(capitalize(levelValue)); 64 | } 65 | 66 | @Override 67 | public void setThreadName(String threadName) { 68 | super.setThreadName(capitalize(threadName)); 69 | } 70 | 71 | @Override 72 | public void setLoggerName(String loggerName) { 73 | super.setLoggerName(capitalize(loggerName)); 74 | } 75 | 76 | @Override 77 | public void setMessage(String message) { 78 | super.setMessage(capitalize(message)); 79 | } 80 | 81 | @Override 82 | public void setFormattedMessage(String formattedMessage) { 83 | super.setFormattedMessage(capitalize(formattedMessage)); 84 | } 85 | 86 | @Override 87 | public void setStackTrace(String stackTrace) { 88 | super.setStackTrace(capitalize(stackTrace)); 89 | } 90 | 91 | @Override 92 | public void setCallerData(String callerData) { 93 | super.setCallerData(capitalize(callerData)); 94 | } 95 | 96 | @Override 97 | public void setCallerClass(String callerClass) { 98 | super.setCallerClass(capitalize(callerClass)); 99 | } 100 | 101 | @Override 102 | public void setCallerMethod(String callerMethod) { 103 | super.setCallerMethod(capitalize(callerMethod)); 104 | } 105 | 106 | @Override 107 | public void setCallerFile(String callerFile) { 108 | super.setCallerFile(capitalize(callerFile)); 109 | } 110 | 111 | @Override 112 | public void setCallerLine(String callerLine) { 113 | super.setCallerLine(capitalize(callerLine)); 114 | } 115 | 116 | @Override 117 | public void setMdc(String mdc) { 118 | super.setMdc(capitalize(mdc)); 119 | } 120 | 121 | @Override 122 | public void setMarker(String marker) { 123 | super.setMarker(capitalize(marker)); 124 | } 125 | 126 | private String capitalize(String value) { 127 | if (value == null || value.isEmpty() || FieldNames.IGNORE_NAME.equals(value)) { 128 | return value; 129 | } else { 130 | return value.substring(0, 1).toUpperCase() + value.substring(1); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /logback-ext-dynamodb-appender/src/main/java/org/eluder/logback/ext/dynamodb/appender/DynamoDbAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.dynamodb.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-dynamodb-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.spi.ILoggingEvent; 30 | import ch.qos.logback.core.filter.Filter; 31 | import com.amazonaws.regions.RegionUtils; 32 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsyncClient; 33 | import com.amazonaws.services.dynamodbv2.document.Item; 34 | import com.amazonaws.services.dynamodbv2.document.PrimaryKey; 35 | import com.amazonaws.services.dynamodbv2.document.internal.InternalUtils; 36 | import com.amazonaws.services.dynamodbv2.model.AttributeValue; 37 | import com.amazonaws.services.dynamodbv2.model.PutItemRequest; 38 | import com.amazonaws.services.dynamodbv2.model.PutItemResult; 39 | import org.eluder.logback.ext.aws.core.AbstractAwsEncodingStringAppender; 40 | import org.eluder.logback.ext.aws.core.AwsSupport; 41 | import org.eluder.logback.ext.core.AppenderExecutors; 42 | import org.eluder.logback.ext.aws.core.LoggingEventHandler; 43 | import org.eluder.logback.ext.core.StringPayloadConverter; 44 | import org.eluder.logback.ext.jackson.JacksonEncoder; 45 | 46 | import java.util.Map; 47 | import java.util.UUID; 48 | import java.util.concurrent.CountDownLatch; 49 | 50 | import static java.lang.String.format; 51 | 52 | public class DynamoDbAppender extends AbstractAwsEncodingStringAppender { 53 | 54 | private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 55 | private static final String DEFAULT_PRIMARY_KEY = "Id"; 56 | private static final int DEFAULT_MAX_PAYLOAD_SIZE = 384; 57 | 58 | private String region; 59 | private String table; 60 | private String primaryKey = DEFAULT_PRIMARY_KEY; 61 | 62 | private AmazonDynamoDBAsyncClient dynamoDb; 63 | 64 | public DynamoDbAppender() { 65 | super(); 66 | setMaxPayloadSize(DEFAULT_MAX_PAYLOAD_SIZE); 67 | } 68 | 69 | protected DynamoDbAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 70 | super(awsSupport, sdkLoggingFilter); 71 | setMaxPayloadSize(DEFAULT_MAX_PAYLOAD_SIZE); 72 | } 73 | 74 | public void setRegion(String region) { 75 | this.region = region; 76 | } 77 | 78 | public void setTable(String table) { 79 | this.table = table; 80 | } 81 | 82 | public void setPrimaryKey(String primaryKey) { 83 | this.primaryKey = primaryKey; 84 | } 85 | 86 | @Override 87 | public void start() { 88 | if (getEncoder() == null) { 89 | JacksonEncoder encoder = new JacksonEncoder(); 90 | encoder.setFieldNames(new CapitalizingFieldNames()); 91 | encoder.setTimeStampFormat(TIMESTAMP_FORMAT); 92 | setEncoder(encoder); 93 | } 94 | setConverter(new StringPayloadConverter(getCharset(), isBinary())); 95 | super.start(); 96 | } 97 | 98 | @Override 99 | protected void doStart() { 100 | dynamoDb = new AmazonDynamoDBAsyncClient( 101 | getCredentials(), 102 | getClientConfiguration(), 103 | AppenderExecutors.newExecutor(this, getThreadPoolSize()) 104 | ); 105 | dynamoDb.setRegion(RegionUtils.getRegion(region)); 106 | } 107 | 108 | @Override 109 | protected void doStop() { 110 | if (dynamoDb != null) { 111 | AppenderExecutors.shutdown(this, dynamoDb.getExecutorService(), getMaxFlushTime()); 112 | dynamoDb.shutdown(); 113 | dynamoDb = null; 114 | } 115 | } 116 | 117 | @Override 118 | protected void handle(final ILoggingEvent event, final String encoded) throws Exception { 119 | Item item = Item.fromJSON(encoded).withPrimaryKey(createEventId(event)); 120 | Map attributes = InternalUtils.toAttributeValues(item); 121 | PutItemRequest request = new PutItemRequest(table, attributes); 122 | String errorMessage = format("Appender '%s' failed to send logging event '%s' to DynamoDB table '%s'", getName(), event, table); 123 | CountDownLatch latch = new CountDownLatch(isAsyncParent() ? 0 : 1); 124 | dynamoDb.putItemAsync(request, new LoggingEventHandler(this, latch, errorMessage)); 125 | AppenderExecutors.awaitLatch(this, latch, getMaxFlushTime()); 126 | } 127 | 128 | protected PrimaryKey createEventId(ILoggingEvent event) { 129 | return new PrimaryKey(primaryKey, UUID.randomUUID().toString()); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /logback-ext-jackson/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | logback-ext 9 | org.eluder.logback 10 | 1.0-SNAPSHOT 11 | 12 | 13 | logback-ext-jackson 14 | 15 | 16 | 17 | ch.qos.logback 18 | logback-core 19 | 20 | 21 | ch.qos.logback 22 | logback-classic 23 | 24 | 25 | org.eluder.logback 26 | logback-ext-core 27 | 28 | 29 | com.fasterxml.jackson.core 30 | jackson-core 31 | ${jackson.version} 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | ${jackson.version} 37 | 38 | 39 | junit 40 | junit 41 | test 42 | 43 | 44 | org.assertj 45 | assertj-core 46 | test 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /logback-ext-jackson/src/main/java/org/eluder/logback/ext/jackson/JacksonEncoder.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.jackson; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-jackson 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.pattern.ThrowableProxyConverter; 30 | import ch.qos.logback.classic.spi.ILoggingEvent; 31 | import ch.qos.logback.core.Context; 32 | import ch.qos.logback.core.spi.ContextAwareBase; 33 | import com.fasterxml.jackson.core.JsonGenerator; 34 | import com.fasterxml.jackson.databind.ObjectMapper; 35 | import org.eluder.logback.ext.core.CharacterEncoder; 36 | import org.eluder.logback.ext.core.FieldNames; 37 | import org.slf4j.Marker; 38 | 39 | import java.io.IOException; 40 | import java.io.OutputStream; 41 | import java.nio.charset.Charset; 42 | import java.text.SimpleDateFormat; 43 | import java.util.Date; 44 | import java.util.Iterator; 45 | import java.util.Map; 46 | 47 | public class JacksonEncoder extends ContextAwareBase implements CharacterEncoder { 48 | 49 | private final ObjectMapper mapper = new ObjectMapper().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true); 50 | private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); 51 | 52 | private Charset charset = Charset.forName("UTF-8"); 53 | private FieldNames fieldNames = new FieldNames(); 54 | private String timeStampFormat; 55 | private boolean newline; 56 | 57 | private boolean started; 58 | private JsonWriter writer; 59 | 60 | @Override 61 | public final void setCharset(Charset charset) { 62 | this.charset = charset; 63 | } 64 | 65 | @Override 66 | public final Charset getCharset() { 67 | return charset; 68 | } 69 | 70 | public final void setFieldNames(FieldNames fieldNames) { 71 | this.fieldNames = fieldNames; 72 | } 73 | 74 | public final FieldNames getFieldNames() { 75 | return fieldNames; 76 | } 77 | 78 | public final void setTimeStampFormat(String timeStampFormat) { 79 | this.timeStampFormat = timeStampFormat; 80 | } 81 | 82 | public final String getTimeStampFormat() { 83 | return timeStampFormat; 84 | } 85 | 86 | public final void setNewline(boolean newline) { 87 | this.newline = newline; 88 | } 89 | 90 | public final boolean isNewline() { 91 | return newline; 92 | } 93 | 94 | @Override 95 | public void setContext(Context context) { 96 | throwableProxyConverter.setContext(context); 97 | super.setContext(context); 98 | } 99 | 100 | @Override 101 | public boolean isStarted() { 102 | return started; 103 | } 104 | 105 | @Override 106 | public void start() { 107 | throwableProxyConverter.start(); 108 | started = true; 109 | } 110 | 111 | @Override 112 | public void stop() { 113 | started = false; 114 | throwableProxyConverter.stop(); 115 | } 116 | 117 | @Override 118 | public void close() throws IOException { 119 | if (writer != null) { 120 | writer.close(); 121 | writer = null; 122 | } 123 | } 124 | 125 | @Override 126 | public void init(OutputStream os) throws IOException { 127 | writer = new JsonWriter(os, charset, getMapper()); 128 | } 129 | 130 | @Override 131 | public void doEncode(ILoggingEvent event) throws IOException { 132 | JsonWriter.ObjectWriter ow = writer.writeObject(); 133 | writeTimeStamp(ow, event); 134 | writeLogger(ow, event); 135 | writeMessage(ow, event); 136 | writeStackTrace(ow, event); 137 | writeCallerData(ow, event); 138 | writeMarker(ow, event); 139 | writeMdc(ow, event); 140 | JsonWriter w = ow.done(); 141 | if (newline) { 142 | w.newline(); 143 | } 144 | w.flush(); 145 | } 146 | 147 | protected ObjectMapper getMapper() { 148 | return mapper; 149 | } 150 | 151 | protected void writeTimeStamp(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 152 | if (timeStampFormat != null) { 153 | String timeStamp = new SimpleDateFormat(timeStampFormat).format(new Date(event.getTimeStamp())); 154 | writer.writeStringField(fieldNames.getTimeStamp(), timeStamp, isActive(fieldNames.getTimeStamp())); 155 | } else { 156 | writer.writeNumberField(fieldNames.getTimeStamp(), event.getTimeStamp(), isActive(fieldNames.getTimeStamp())); 157 | } 158 | } 159 | 160 | protected void writeLogger(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 161 | writer.writeStringField(fieldNames.getLevel(), event.getLevel().toString(), isActive(fieldNames.getLevel())); 162 | writer.writeNumberField(fieldNames.getLevelValue(), event.getLevel().toInt(), isActive(fieldNames.getLevelValue())); 163 | writer.writeStringField(fieldNames.getThreadName(), event.getThreadName(), isActive(fieldNames.getThreadName())); 164 | writer.writeStringField(fieldNames.getLoggerName(), event.getLoggerName(), isActive(fieldNames.getLoggerName())); 165 | } 166 | 167 | protected void writeMessage(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 168 | writer.writeStringField(fieldNames.getMessage(), event.getMessage(), isActive(fieldNames.getMessage())); 169 | writer.writeStringField(fieldNames.getFormattedMessage(), event.getFormattedMessage(), isActive(fieldNames.getFormattedMessage())); 170 | } 171 | 172 | protected void writeStackTrace(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 173 | String stackTrace = throwableProxyConverter.convert(event); 174 | if (stackTrace != null && !stackTrace.isEmpty()) { 175 | writer.writeStringField(fieldNames.getStackTrace(), stackTrace, isActive(fieldNames.getStackTrace())); 176 | } 177 | } 178 | 179 | protected void writeCallerData(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 180 | if (event.hasCallerData()) { 181 | StackTraceElement callerData = event.getCallerData()[0]; 182 | JsonWriter.ObjectWriter> ow = 183 | writer.writeObject(fieldNames.getCallerData(), isActive(fieldNames.getCallerData())); 184 | ow.writeStringField(fieldNames.getCallerClass(), callerData.getClassName(), isActive(fieldNames.getCallerClass())); 185 | ow.writeStringField(fieldNames.getCallerMethod(), callerData.getMethodName(), isActive(fieldNames.getCallerMethod())); 186 | ow.writeStringField(fieldNames.getCallerFile(), callerData.getFileName(), isActive(fieldNames.getCallerFile())); 187 | ow.writeNumberField(fieldNames.getCallerLine(), callerData.getLineNumber(), isActive(fieldNames.getCallerLine())); 188 | ow.done(); 189 | } 190 | } 191 | 192 | protected void writeMarker(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 193 | Marker marker = event.getMarker(); 194 | if (marker != null) { 195 | JsonWriter.ArrayWriter>> aw = 196 | writer.writeObject(fieldNames.getMarker(), isActive(fieldNames.getMarker())).writeArray(marker.getName(), true); 197 | Iterator markers = marker.iterator(); 198 | while (markers.hasNext()) { 199 | String name = markers.next().getName(); 200 | aw.writeString(name, name != null && !name.isEmpty()); 201 | } 202 | aw.done().done(); 203 | } 204 | } 205 | 206 | protected void writeMdc(JsonWriter.ObjectWriter writer, ILoggingEvent event) throws IOException { 207 | Map mdc = event.getMDCPropertyMap(); 208 | if (mdc != null && !mdc.isEmpty()) { 209 | JsonWriter.ObjectWriter> ow = 210 | writer.writeObject(fieldNames.getMdc(), isActive(fieldNames.getMdc())); 211 | for (Map.Entry property : mdc.entrySet()) { 212 | ow.writeStringField(property.getKey(), property.getValue(), true); 213 | } 214 | ow.done(); 215 | } 216 | } 217 | 218 | private boolean isActive(String name) { 219 | return !FieldNames.IGNORE_NAME.equals(name); 220 | } 221 | 222 | } 223 | -------------------------------------------------------------------------------- /logback-ext-jackson/src/main/java/org/eluder/logback/ext/jackson/JsonWriter.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.jackson; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-jackson 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import com.fasterxml.jackson.core.JsonGenerator; 30 | import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; 31 | import com.fasterxml.jackson.databind.ObjectMapper; 32 | 33 | import java.io.Closeable; 34 | import java.io.IOException; 35 | import java.io.OutputStream; 36 | import java.io.OutputStreamWriter; 37 | import java.nio.charset.Charset; 38 | 39 | public class JsonWriter implements Closeable { 40 | 41 | private final JsonGenerator generator; 42 | 43 | public JsonWriter(OutputStream os, Charset charset, ObjectMapper mapper) throws IOException { 44 | generator = mapper.getFactory() 45 | .createGenerator(new OutputStreamWriter(os, charset)) 46 | .configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false) 47 | .setPrettyPrinter(new MinimalPrettyPrinter("")); 48 | } 49 | 50 | @Override 51 | public void close() throws IOException { 52 | generator.close(); 53 | } 54 | 55 | public JsonWriter flush() throws IOException { 56 | generator.flush(); 57 | return this; 58 | } 59 | 60 | public JsonWriter newline() throws IOException { 61 | generator.writeRaw('\n'); 62 | return this; 63 | } 64 | 65 | public ObjectWriter writeObject() throws IOException { 66 | return new ObjectWriter(this, true); 67 | } 68 | 69 | public ArrayWriter writeArray() throws IOException { 70 | return new ArrayWriter(this, true); 71 | } 72 | 73 | public class ObjectWriter { 74 | private final T parent; 75 | private final boolean enabled; 76 | 77 | ObjectWriter(T parent, boolean parentEnabled) throws IOException { 78 | this.parent = parent; 79 | this.enabled = parentEnabled; 80 | if (enabled) { 81 | generator.writeStartObject(); 82 | } 83 | } 84 | 85 | ObjectWriter(T parent, boolean parentEnabled, String name, boolean active) throws IOException { 86 | this.parent = parent; 87 | this.enabled = parentEnabled && active; 88 | if (enabled) { 89 | generator.writeObjectFieldStart(name); 90 | } 91 | } 92 | 93 | public ObjectWriter writeStringField(String name, String value, boolean active) throws IOException { 94 | if (enabled && active) { 95 | generator.writeStringField(name, value); 96 | } 97 | return this; 98 | } 99 | 100 | public ObjectWriter writeNumberField(String name, int value, boolean active) throws IOException { 101 | if (enabled && active) { 102 | generator.writeNumberField(name, value); 103 | } 104 | return this; 105 | } 106 | 107 | public ObjectWriter writeNumberField(String name, long value, boolean active) throws IOException { 108 | if (enabled && active) { 109 | generator.writeNumberField(name, value); 110 | } 111 | return this; 112 | } 113 | 114 | public ArrayWriter> writeArray(String name, boolean active) throws IOException { 115 | return new ArrayWriter>(this, enabled, name, active); 116 | } 117 | 118 | public ObjectWriter> writeObject(String name, boolean active) throws IOException { 119 | return new ObjectWriter>(this, enabled, name, active); 120 | } 121 | 122 | public T done() throws IOException { 123 | if (enabled) { 124 | generator.writeEndObject(); 125 | } 126 | return parent; 127 | } 128 | } 129 | 130 | public class ArrayWriter { 131 | private final T parent; 132 | private final boolean enabled; 133 | 134 | ArrayWriter(T parent, boolean parentEnabled) throws IOException { 135 | this.parent = parent; 136 | this.enabled = parentEnabled; 137 | if (enabled) { 138 | generator.writeStartArray(); 139 | } 140 | } 141 | 142 | ArrayWriter(T parent, boolean parentEnabled, String name, boolean active) throws IOException { 143 | this.parent = parent; 144 | this.enabled = parentEnabled && active; 145 | if (enabled) { 146 | generator.writeArrayFieldStart(name); 147 | } 148 | } 149 | 150 | public ArrayWriter writeString(String value, boolean active) throws IOException { 151 | if (enabled && active) { 152 | generator.writeString(value); 153 | } 154 | return this; 155 | } 156 | 157 | public ArrayWriter> writeArray() throws IOException { 158 | return new ArrayWriter>(this, enabled); 159 | } 160 | 161 | public ObjectWriter> writeObject() throws IOException { 162 | return new ObjectWriter>(this, enabled); 163 | } 164 | 165 | public T done() throws IOException { 166 | if (enabled) { 167 | generator.writeEndArray(); 168 | } 169 | return parent; 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /logback-ext-jackson/src/test/java/org/eluder/logback/ext/jackson/JacksonEncoderTest.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.jackson; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-jackson 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.Level; 30 | import ch.qos.logback.classic.Logger; 31 | import ch.qos.logback.classic.LoggerContext; 32 | import ch.qos.logback.classic.spi.ILoggingEvent; 33 | import ch.qos.logback.classic.spi.LoggingEvent; 34 | import org.eluder.logback.ext.core.FieldNames; 35 | import org.junit.After; 36 | import org.junit.Before; 37 | import org.junit.Test; 38 | 39 | import java.io.ByteArrayOutputStream; 40 | 41 | import static org.junit.Assert.assertNotNull; 42 | 43 | public class JacksonEncoderTest { 44 | 45 | private final LoggerContext context = new LoggerContext(); 46 | private final Logger logger = context.getLogger(JacksonEncoderTest.class); 47 | 48 | private JacksonEncoder encoder; 49 | 50 | @Before 51 | public void start() { 52 | encoder = new JacksonEncoder(); 53 | encoder.start(); 54 | } 55 | 56 | @After 57 | public void stop() { 58 | encoder.stop(); 59 | encoder = null; 60 | } 61 | 62 | @Test 63 | public void encodeLoggingEvent() throws Exception { 64 | FieldNames fn = new FieldNames(); 65 | fn.setLevel(FieldNames.IGNORE_NAME); 66 | encoder.setFieldNames(fn); 67 | 68 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 69 | encoder.init(stream); 70 | encoder.doEncode(createLoggingEvent("Hellö JSON!")); 71 | String json = new String(stream.toByteArray(), "UTF-8"); 72 | assertNotNull(json); 73 | } 74 | 75 | private ILoggingEvent createLoggingEvent(String message) { 76 | return new LoggingEvent("", logger, Level.DEBUG, message, new IllegalArgumentException("fobar"), null); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /logback-ext-jackson/src/test/java/org/eluder/logback/ext/jackson/JsonWriterTest.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.jackson; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-jackson 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import com.fasterxml.jackson.databind.ObjectMapper; 30 | import org.junit.Test; 31 | 32 | import java.io.ByteArrayOutputStream; 33 | import java.io.IOException; 34 | import java.io.OutputStream; 35 | import java.nio.charset.Charset; 36 | 37 | import static org.assertj.core.api.Assertions.*; 38 | 39 | 40 | public class JsonWriterTest { 41 | 42 | private final ObjectMapper mapper = new ObjectMapper(); 43 | private final Charset charset = Charset.forName("UTF-8"); 44 | 45 | @Test 46 | public void writeObject() throws Exception { 47 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 48 | JsonWriter writer = writer(os); 49 | writer.writeObject() 50 | .writeStringField("hello", "world", true) 51 | .writeNumberField("missing", 1, false) 52 | .done() 53 | .close(); 54 | 55 | assertThat(os.toString(charset.name())).isEqualTo("{\"hello\":\"world\"}"); 56 | } 57 | 58 | @Test 59 | public void writeArray() throws Exception { 60 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 61 | JsonWriter writer = writer(os); 62 | writer.writeArray() 63 | .writeString("hello", true) 64 | .writeString("world", false) 65 | .writeString("again", true) 66 | .done() 67 | .close(); 68 | 69 | assertThat(os.toString(charset.name())).isEqualTo("[\"hello\",\"again\"]"); 70 | } 71 | 72 | @Test 73 | public void writeComplexObject() throws Exception { 74 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 75 | JsonWriter writer = writer(os); 76 | writer.writeObject() 77 | .writeArray("array", true) 78 | .writeString("val1", true) 79 | .writeString("val2", true) 80 | .done() 81 | .writeObject("object", true) 82 | .writeArray("subarray", true) 83 | .writeObject() 84 | .writeNumberField("num", 10, true) 85 | .writeStringField("str", "test", true) 86 | .done() 87 | .writeArray() 88 | .writeString("sub1", true) 89 | .writeString("sub2", true) 90 | .done() 91 | .done() 92 | .writeStringField("theend", "yes", true) 93 | .done() 94 | .done() 95 | .close(); 96 | 97 | assertThat(os.toString(charset.name())).isEqualTo("{\"array\":[\"val1\",\"val2\"],\"object\":{\"subarray\":[{\"num\":10,\"str\":\"test\"},[\"sub1\",\"sub2\"]],\"theend\":\"yes\"}}"); 98 | } 99 | 100 | private JsonWriter writer(OutputStream os) throws IOException { 101 | return new JsonWriter(os, charset, mapper); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /logback-ext-kinesis-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.eluder.logback 8 | logback-ext 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-kinesis-appender 13 | 14 | 15 | 16 | ch.qos.logback 17 | logback-core 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | 23 | 24 | org.eluder.logback 25 | logback-ext-core 26 | 27 | 28 | org.eluder.logback 29 | logback-ext-aws-core 30 | 31 | 32 | org.eluder.logback 33 | logback-ext-jackson 34 | 35 | 36 | com.amazonaws 37 | aws-java-sdk-core 38 | ${aws.version} 39 | 40 | 41 | com.amazonaws 42 | aws-java-sdk-kinesis 43 | ${aws.version} 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /logback-ext-kinesis-appender/src/main/java/org/eluder/logback/ext/kinesis/appender/AsyncKinesisAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.kinesis.appender; 2 | 3 | import ch.qos.logback.classic.AsyncAppender; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | import ch.qos.logback.core.Context; 6 | import ch.qos.logback.core.Layout; 7 | import ch.qos.logback.core.encoder.Encoder; 8 | 9 | import java.nio.charset.Charset; 10 | 11 | public class AsyncKinesisAppender extends AsyncAppender { 12 | 13 | private final KinesisAppender appender; 14 | 15 | public AsyncKinesisAppender() { 16 | this(new KinesisAppender()); 17 | } 18 | 19 | protected AsyncKinesisAppender(KinesisAppender appender) { 20 | appender.setAsyncParent(true); 21 | addAppender(appender); 22 | this.appender = appender; 23 | } 24 | 25 | public void setRegion(String region) { 26 | appender.setRegion(region); 27 | } 28 | 29 | public void setStream(String stream) { 30 | appender.setStream(stream); 31 | } 32 | 33 | public void setAccessKey(String accessKey) { 34 | appender.setAccessKey(accessKey); 35 | } 36 | 37 | public void setSecretKey(String secretKey) { 38 | appender.setSecretKey(secretKey); 39 | } 40 | 41 | public void setMaxPayloadSize(int maxPayloadSize) { 42 | appender.setMaxPayloadSize(maxPayloadSize); 43 | } 44 | 45 | public void setThreadPoolSize(int threadPoolSize) { 46 | appender.setThreadPoolSize(threadPoolSize); 47 | } 48 | 49 | @Override 50 | public final void setMaxFlushTime(int maxFlushTime) { 51 | appender.setMaxFlushTime(maxFlushTime); 52 | // add an extra 100 millis to wait for the internal event queue handling 53 | super.setMaxFlushTime(maxFlushTime + 100); 54 | } 55 | 56 | public void setCharset(Charset charset) { 57 | appender.setCharset(charset); 58 | } 59 | 60 | public void setEncoder(Encoder encoder) { 61 | appender.setEncoder(encoder); 62 | } 63 | 64 | public void setLayout(Layout layout) { 65 | appender.setLayout(layout); 66 | } 67 | 68 | public void setBinary(boolean binary) { 69 | appender.setBinary(binary); 70 | } 71 | 72 | @Override 73 | public void setName(String name) { 74 | appender.setName(name); 75 | super.setName(name); 76 | } 77 | 78 | @Override 79 | public void setContext(Context context) { 80 | appender.setContext(context); 81 | super.setContext(context); 82 | } 83 | 84 | @Override 85 | public void start() { 86 | appender.start(); 87 | super.start(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /logback-ext-kinesis-appender/src/main/java/org/eluder/logback/ext/kinesis/appender/KinesisAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.kinesis.appender; 2 | 3 | import ch.qos.logback.core.spi.DeferredProcessingAware; 4 | import com.amazonaws.regions.RegionUtils; 5 | import com.amazonaws.services.kinesis.AmazonKinesisAsyncClient; 6 | import com.amazonaws.services.kinesis.model.PutRecordRequest; 7 | import com.amazonaws.services.kinesis.model.PutRecordResult; 8 | import com.amazonaws.util.StringUtils; 9 | import org.eluder.logback.ext.aws.core.AbstractAwsEncodingStringAppender; 10 | import org.eluder.logback.ext.aws.core.LoggingEventHandler; 11 | import org.eluder.logback.ext.core.AppenderExecutors; 12 | import org.eluder.logback.ext.core.ByteArrayPayloadConverter; 13 | 14 | import java.nio.ByteBuffer; 15 | import java.util.UUID; 16 | import java.util.concurrent.CountDownLatch; 17 | 18 | import static java.lang.String.format; 19 | 20 | public class KinesisAppender extends AbstractAwsEncodingStringAppender { 21 | 22 | private String region; 23 | private String stream; 24 | 25 | private AmazonKinesisAsyncClient kinesis; 26 | 27 | public final void setRegion(String region) { 28 | this.region = region; 29 | } 30 | 31 | public final void setStream(String stream) { 32 | this.stream = stream; 33 | } 34 | 35 | @Override 36 | public void start() { 37 | if (RegionUtils.getRegion(region) == null) { 38 | addError(format("Region not set or invalid for appender '%s'", getName())); 39 | return; 40 | } 41 | if (StringUtils.isNullOrEmpty(stream)) { 42 | addError(format("Stream not set for appender '%s", getName())); 43 | return; 44 | } 45 | setConverter(new ByteArrayPayloadConverter()); 46 | super.start(); 47 | } 48 | 49 | @Override 50 | protected void doStart() { 51 | kinesis = new AmazonKinesisAsyncClient( 52 | getCredentials(), 53 | getClientConfiguration(), 54 | AppenderExecutors.newExecutor(this, getThreadPoolSize()) 55 | ); 56 | kinesis.setRegion(RegionUtils.getRegion(region)); 57 | } 58 | 59 | @Override 60 | protected void doStop() { 61 | if (kinesis != null) { 62 | AppenderExecutors.shutdown(this, kinesis.getExecutorService(), getMaxFlushTime()); 63 | kinesis.shutdown(); 64 | kinesis = null; 65 | } 66 | } 67 | 68 | @Override 69 | protected void handle(E event, byte[] encoded) throws Exception { 70 | ByteBuffer buffer = ByteBuffer.wrap(encoded); 71 | PutRecordRequest request = new PutRecordRequest() 72 | .withPartitionKey(getPartitionKey(event)) 73 | .withStreamName(stream) 74 | .withData(buffer); 75 | String errorMessage = format("Appender '%s' failed to send logging event '%s' to Kinesis stream '%s'", getName(), event, stream); 76 | CountDownLatch latch = new CountDownLatch(isAsyncParent() ? 0 : 1); 77 | kinesis.putRecordAsync(request, new LoggingEventHandler(this, latch, errorMessage)); 78 | AppenderExecutors.awaitLatch(this, latch, getMaxFlushTime()); 79 | } 80 | 81 | protected String getPartitionKey(E event) { 82 | return UUID.randomUUID().toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | logback-ext 9 | org.eluder.logback 10 | 1.0-SNAPSHOT 11 | 12 | 13 | logback-ext-lmax-appender 14 | 15 | 16 | 17 | ch.qos.logback 18 | logback-core 19 | 20 | 21 | ch.qos.logback 22 | logback-classic 23 | true 24 | 25 | 26 | ch.qos.logback 27 | logback-access 28 | true 29 | 30 | 31 | org.eluder.logback 32 | logback-ext-core 33 | 34 | 35 | com.lmax 36 | disruptor 37 | ${lmax.version} 38 | 39 | 40 | 41 | junit 42 | junit 43 | test 44 | 45 | 46 | org.openjdk.jmh 47 | jmh-core 48 | test 49 | 50 | 51 | org.openjdk.jmh 52 | jmh-generator-annprocess 53 | test 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/main/java/org/eluder/logback/ext/lmax/appender/AccessEventDisruptorAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.access.spi.IAccessEvent; 4 | 5 | public class AccessEventDisruptorAppender extends DelegatingDisruptorAppender { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/main/java/org/eluder/logback/ext/lmax/appender/DelegatingDisruptorAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.core.Appender; 4 | import ch.qos.logback.core.Context; 5 | import ch.qos.logback.core.spi.AppenderAttachable; 6 | import ch.qos.logback.core.spi.AppenderAttachableImpl; 7 | import ch.qos.logback.core.spi.DeferredProcessingAware; 8 | import com.lmax.disruptor.WorkHandler; 9 | 10 | import java.util.Iterator; 11 | 12 | public class DelegatingDisruptorAppender extends DisruptorAppender implements AppenderAttachable { 13 | 14 | private final AppenderAttachableImpl appenders = new AppenderAttachableImpl<>(); 15 | 16 | public DelegatingDisruptorAppender() { 17 | setWorkHandler(new AppendersWorkHandler()); 18 | } 19 | 20 | @Override 21 | public void start() { 22 | lock.lock(); 23 | try { 24 | if (isStarted()) { 25 | return; 26 | } 27 | startDelegateAppenders(); 28 | super.start(); 29 | } finally { 30 | lock.unlock(); 31 | } 32 | } 33 | 34 | @Override 35 | public void stop() { 36 | lock.lock(); 37 | try { 38 | if (!isStarted()) { 39 | return; 40 | } 41 | super.stop(); 42 | stopDelegateAppenders(); 43 | } finally { 44 | lock.unlock(); 45 | } 46 | } 47 | 48 | protected void startDelegateAppenders() { 49 | Iterator> iter = appenders.iteratorForAppenders(); 50 | while (iter.hasNext()) { 51 | Appender appender = iter.next(); 52 | if (!appender.isStarted()) { 53 | appender.start(); 54 | } 55 | } 56 | } 57 | 58 | protected void stopDelegateAppenders() { 59 | Iterator> iter = appenders.iteratorForAppenders(); 60 | while (iter.hasNext()) { 61 | Appender appender = iter.next(); 62 | if (appender.isStarted()) { 63 | appender.stop(); 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public void addAppender(Appender newAppender) { 70 | if (getContext() != null && newAppender.getContext() == null) { 71 | newAppender.setContext(getContext()); 72 | } 73 | appenders.addAppender(newAppender); 74 | } 75 | 76 | @Override 77 | public Iterator> iteratorForAppenders() { 78 | return appenders.iteratorForAppenders(); 79 | } 80 | 81 | @Override 82 | public Appender getAppender(String name) { 83 | return appenders.getAppender(name); 84 | } 85 | 86 | @Override 87 | public boolean isAttached(Appender appender) { 88 | return appenders.isAttached(appender); 89 | } 90 | 91 | @Override 92 | public void detachAndStopAllAppenders() { 93 | appenders.detachAndStopAllAppenders(); 94 | } 95 | 96 | @Override 97 | public boolean detachAppender(Appender appender) { 98 | return appenders.detachAppender(appender); 99 | } 100 | 101 | @Override 102 | public boolean detachAppender(String name) { 103 | return appenders.detachAppender(name); 104 | } 105 | 106 | @Override 107 | public void setContext(Context context) { 108 | Iterator> iter = appenders.iteratorForAppenders(); 109 | while (iter.hasNext()) { 110 | Appender appender = iter.next(); 111 | if (appender.getContext() == null) { 112 | appender.setContext(context); 113 | } 114 | } 115 | super.setContext(context); 116 | } 117 | 118 | private class AppendersWorkHandler implements WorkHandler> { 119 | @Override 120 | public void onEvent(LogEvent event) throws Exception { 121 | appenders.appendLoopOnAppenders(event.event); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/main/java/org/eluder/logback/ext/lmax/appender/DisruptorAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 4 | import ch.qos.logback.core.spi.ContextAware; 5 | import ch.qos.logback.core.spi.DeferredProcessingAware; 6 | import com.lmax.disruptor.EventFactory; 7 | import com.lmax.disruptor.EventTranslatorOneArg; 8 | import com.lmax.disruptor.ExceptionHandler; 9 | import com.lmax.disruptor.LifecycleAware; 10 | import com.lmax.disruptor.RingBuffer; 11 | import com.lmax.disruptor.TimeoutException; 12 | import com.lmax.disruptor.WaitStrategy; 13 | import com.lmax.disruptor.WorkHandler; 14 | import com.lmax.disruptor.dsl.Disruptor; 15 | import com.lmax.disruptor.dsl.ProducerType; 16 | import org.eluder.logback.ext.core.AppenderExecutors; 17 | 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.concurrent.locks.ReentrantLock; 21 | 22 | import static java.lang.String.format; 23 | 24 | public class DisruptorAppender extends UnsynchronizedAppenderBase { 25 | 26 | protected final ReentrantLock lock = new ReentrantLock(true); 27 | 28 | private static final int SLEEP_ON_DRAIN = 50; 29 | private static final int DEFAULT_THREAD_POOL_SIZE = 1; 30 | private static final int DEFAULT_BUFFER_SIZE = 8192; 31 | 32 | private EventFactory> eventFactory = new LogEventFactory<>(); 33 | private EventTranslatorOneArg, E> eventTranslator = new LogEventTranslator<>(); 34 | private ExceptionHandler> exceptionHandler = new LogExceptionHandler<>(this); 35 | private WorkHandler> workHandler; 36 | 37 | private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE; 38 | private int bufferSize = DEFAULT_BUFFER_SIZE; 39 | private int maxFlushTime = AppenderExecutors.DEFAULT_MAX_FLUSH_TIME; 40 | private ProducerType producerType = ProducerType.MULTI; 41 | private WaitStrategy waitStrategy = WaitStrategyFactory.DEFAULT_WAIT_STRATEGY; 42 | 43 | private Disruptor> disruptor; 44 | private ExecutorService executor; 45 | 46 | public final void setEventFactory(EventFactory> eventFactory) { 47 | this.eventFactory = eventFactory; 48 | } 49 | 50 | public final void setEventTranslator(EventTranslatorOneArg, E> eventTranslator) { 51 | this.eventTranslator = eventTranslator; 52 | } 53 | 54 | public final void setExceptionHandler(ExceptionHandler> exceptionHandler) { 55 | this.exceptionHandler = exceptionHandler; 56 | } 57 | 58 | public final void setWorkHandler(WorkHandler> workHandler) { 59 | this.workHandler = workHandler; 60 | } 61 | 62 | public final void setThreadPoolSize(int threadPoolSize) { 63 | this.threadPoolSize = threadPoolSize; 64 | } 65 | 66 | public final void setBufferSize(int bufferSize) { 67 | this.bufferSize = bufferSize; 68 | } 69 | 70 | public final void setMaxFlushTime(int maxFlushTime) { 71 | this.maxFlushTime = maxFlushTime; 72 | } 73 | 74 | public final void setProducerType(ProducerType producerType) { 75 | this.producerType = producerType; 76 | } 77 | 78 | public final void setWaitStrategy(WaitStrategy waitStrategy) { 79 | this.waitStrategy = waitStrategy; 80 | } 81 | 82 | public final void setWaitStrategyType(String waitStrategyType) { 83 | setWaitStrategy(WaitStrategyFactory.createFromType(waitStrategyType)); 84 | } 85 | 86 | @Override 87 | @SuppressWarnings("unchecked") 88 | public void start() { 89 | if (workHandler == null) { 90 | addError(format("Event handler not set for appender '%s'", getName())); 91 | return; 92 | } 93 | lock.lock(); 94 | try { 95 | if (isStarted()) { 96 | return; 97 | } 98 | executor = AppenderExecutors.newExecutor(this, threadPoolSize); 99 | disruptor = new Disruptor<>( 100 | eventFactory, 101 | bufferSize, 102 | executor, 103 | producerType, 104 | waitStrategy 105 | ); 106 | disruptor.handleExceptionsWith(exceptionHandler); 107 | disruptor.handleEventsWithWorkerPool(createWorkers()); 108 | disruptor.start(); 109 | super.start(); 110 | } finally { 111 | lock.unlock(); 112 | } 113 | } 114 | 115 | @Override 116 | public void stop() { 117 | lock.lock(); 118 | try { 119 | if (!isStarted()) { 120 | return; 121 | } 122 | super.stop(); 123 | shutdownDisruptor(); 124 | executor.shutdownNow(); 125 | } finally { 126 | lock.unlock(); 127 | } 128 | } 129 | 130 | @Override 131 | protected void append(E eventObject) { 132 | prepareForDeferredProcessing(eventObject); 133 | disruptor.publishEvent(eventTranslator, eventObject); 134 | } 135 | 136 | protected void prepareForDeferredProcessing(E event) { 137 | event.prepareForDeferredProcessing(); 138 | } 139 | 140 | @SuppressWarnings("unchecked") 141 | private WorkHandler>[] createWorkers() { 142 | WorkHandler> handler = new ClearingWorkHandler<>(workHandler); 143 | WorkHandler>[] workers = new WorkHandler[threadPoolSize]; 144 | for (int i = 0; i < threadPoolSize; i++) { 145 | workers[i] = handler; 146 | } 147 | return workers; 148 | } 149 | 150 | private void shutdownDisruptor() { 151 | // disruptor busy waits while shutting down so this is a workaround 152 | // for not to hog all the CPU on shutdown 153 | long until = System.currentTimeMillis() + maxFlushTime; 154 | while (System.currentTimeMillis() < until && hashBackLog()) { 155 | try { 156 | Thread.sleep(SLEEP_ON_DRAIN); 157 | } catch (InterruptedException ex) { 158 | // noop 159 | } 160 | } 161 | try { 162 | disruptor.shutdown(0, TimeUnit.MILLISECONDS); 163 | } catch (TimeoutException ex) { 164 | addWarn(format("Disruptor did not shut down in %d milliseconds, " + 165 | "logging events might have been discarded", 166 | maxFlushTime)); 167 | } 168 | } 169 | 170 | private boolean hashBackLog() { 171 | RingBuffer> buffer = disruptor.getRingBuffer(); 172 | return !buffer.hasAvailableCapacity(buffer.getBufferSize()); 173 | } 174 | 175 | protected static class LogEvent { 176 | public volatile E event; 177 | } 178 | 179 | protected static class LogEventFactory implements EventFactory> { 180 | @Override 181 | public LogEvent newInstance() { 182 | return new LogEvent<>(); 183 | } 184 | } 185 | 186 | protected static class LogEventTranslator implements EventTranslatorOneArg, E> { 187 | @Override 188 | public void translateTo(LogEvent event, long sequence, E arg0) { 189 | event.event = arg0; 190 | } 191 | } 192 | 193 | protected static class LogExceptionHandler implements ExceptionHandler> { 194 | 195 | private final ContextAware context; 196 | 197 | public LogExceptionHandler(ContextAware context) { 198 | this.context = context; 199 | } 200 | 201 | @Override 202 | public void handleEventException(Throwable ex, long sequence, LogEvent event) { 203 | if (ex instanceof InterruptedException) { 204 | context.addWarn("Disruptor was interrupted while processing event"); 205 | } else { 206 | context.addError("Failed to process event", ex); 207 | } 208 | } 209 | 210 | @Override 211 | public void handleOnStartException(Throwable ex) { 212 | context.addError("Failed to start disruptor", ex); 213 | } 214 | 215 | @Override 216 | public void handleOnShutdownException(Throwable ex) { 217 | context.addError("Failed to shutdown disruptor", ex); 218 | } 219 | } 220 | 221 | /** 222 | * Clears logback event objects from distruptor event context to allow proper garbage collecting. 223 | */ 224 | private static class ClearingWorkHandler implements WorkHandler>, LifecycleAware { 225 | 226 | private final WorkHandler> delegate; 227 | private boolean started = false; 228 | 229 | public ClearingWorkHandler(WorkHandler> delegate) { 230 | this.delegate = delegate; 231 | } 232 | 233 | @Override 234 | public void onEvent(LogEvent event) throws Exception { 235 | try { 236 | delegate.onEvent(event); 237 | } finally { 238 | event.event = null; 239 | } 240 | } 241 | 242 | @Override 243 | public synchronized void onStart() { 244 | if (!started) { 245 | if (delegate instanceof LifecycleAware) { 246 | ((LifecycleAware) delegate).onStart(); 247 | } 248 | started = true; 249 | } 250 | } 251 | 252 | @Override 253 | public synchronized void onShutdown() { 254 | if (started) { 255 | started = false; 256 | if (delegate instanceof LifecycleAware) { 257 | ((LifecycleAware) delegate).onShutdown(); 258 | } 259 | } 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/main/java/org/eluder/logback/ext/lmax/appender/LoggingEventDisruptorAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | 5 | public class LoggingEventDisruptorAppender extends DelegatingDisruptorAppender { 6 | 7 | private boolean includeCallerData = false; 8 | 9 | public final boolean isIncludeCallerData() { 10 | return includeCallerData; 11 | } 12 | 13 | public final void setIncludeCallerData(boolean includeCallerData) { 14 | this.includeCallerData = includeCallerData; 15 | } 16 | 17 | @Override 18 | protected void prepareForDeferredProcessing(ILoggingEvent event) { 19 | super.prepareForDeferredProcessing(event); 20 | if (includeCallerData) { 21 | event.getCallerData(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/main/java/org/eluder/logback/ext/lmax/appender/WaitStrategyFactory.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import com.lmax.disruptor.BlockingWaitStrategy; 4 | import com.lmax.disruptor.BusySpinWaitStrategy; 5 | import com.lmax.disruptor.SleepingWaitStrategy; 6 | import com.lmax.disruptor.WaitStrategy; 7 | import com.lmax.disruptor.YieldingWaitStrategy; 8 | 9 | public class WaitStrategyFactory { 10 | 11 | public static final WaitStrategy DEFAULT_WAIT_STRATEGY = new BlockingWaitStrategy(); 12 | 13 | public static WaitStrategy createFromType(String name) { 14 | if ("BusySpin".equalsIgnoreCase(name)) { 15 | return new BusySpinWaitStrategy(); 16 | } else if ("Blocking".equalsIgnoreCase(name)) { 17 | return new BlockingWaitStrategy(); 18 | } else if ("Yielding".equalsIgnoreCase(name)) { 19 | return new YieldingWaitStrategy(); 20 | } else if ("Sleeping".equalsIgnoreCase(name)) { 21 | return new SleepingWaitStrategy(); 22 | } else { 23 | throw new IllegalArgumentException("Invalid or unsupported wait strategy type '" + name + "'"); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/test/java/org/eluder/logback/ext/lmax/appender/AppenderBenchmark.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.classic.Logger; 4 | import ch.qos.logback.classic.LoggerContext; 5 | import ch.qos.logback.classic.spi.ILoggingEvent; 6 | import ch.qos.logback.classic.spi.LoggingEvent; 7 | import ch.qos.logback.core.Appender; 8 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 9 | import ch.qos.logback.core.spi.AppenderAttachable; 10 | import org.openjdk.jmh.annotations.*; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | 17 | @State(Scope.Benchmark) 18 | public abstract class AppenderBenchmark & AppenderAttachable> { 19 | 20 | private static final int EVENTS = 32000; 21 | private static final LoggerContext CONTEXT = (LoggerContext) LoggerFactory.getILoggerFactory(); 22 | 23 | private static final AtomicInteger idx = new AtomicInteger(0); 24 | private static final CountDownLatch[] latches = new CountDownLatch[99999]; 25 | 26 | 27 | @Param({ "1" }) 28 | public int consumers; 29 | 30 | private EmulatingAppender controller; 31 | private T appender; 32 | 33 | @State(Scope.Thread) 34 | public static class ThreadContext { 35 | LoggingEvent event; 36 | int index; 37 | 38 | @Setup(Level.Trial) 39 | public void setupContext() { 40 | index = idx.getAndIncrement(); 41 | event = new LoggingEvent( 42 | "org.eluder.logback.ext.lmax.appender.AppenderBenchmark", 43 | CONTEXT.getLogger(Logger.ROOT_LOGGER_NAME), ch.qos.logback.classic.Level.INFO, "" + index, null, null 44 | ); 45 | } 46 | 47 | @Setup(Level.Invocation) 48 | public void setupLatch() { 49 | latches[index] = new CountDownLatch(EVENTS); 50 | } 51 | 52 | public void await() throws InterruptedException { 53 | latches[index].await(); 54 | } 55 | } 56 | 57 | @Setup(Level.Trial) 58 | public void setupAppender() { 59 | controller = new EmulatingAppender(CONTEXT); 60 | appender = createAppender(consumers); 61 | appender.setContext(CONTEXT); 62 | appender.addAppender(controller); 63 | 64 | controller.start(); 65 | appender.start(); 66 | } 67 | 68 | @TearDown(Level.Trial) 69 | public void tearDownAppender() { 70 | appender.stop(); 71 | controller.stop(); 72 | } 73 | 74 | protected abstract T createAppender(int consumers); 75 | 76 | @Benchmark 77 | @BenchmarkMode(Mode.Throughput) 78 | @OutputTimeUnit(TimeUnit.SECONDS) 79 | @OperationsPerInvocation(EVENTS) 80 | public void throughput(ThreadContext context) throws Exception { 81 | append(context); 82 | } 83 | 84 | @Benchmark 85 | @BenchmarkMode(Mode.SampleTime) 86 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 87 | @OperationsPerInvocation(EVENTS) 88 | public void latency(ThreadContext context) throws Exception { 89 | append(context); 90 | } 91 | 92 | private void append(ThreadContext context) throws Exception { 93 | for (int i = 0; i < EVENTS; i++) { 94 | appender.doAppend(context.event); 95 | } 96 | context.await(); 97 | } 98 | 99 | public static class EmulatingAppender extends UnsynchronizedAppenderBase { 100 | 101 | public EmulatingAppender(LoggerContext context) { 102 | setContext(context); 103 | setName("emulator"); 104 | } 105 | 106 | @Override 107 | protected void append(ILoggingEvent eventObject) { 108 | int index = Integer.parseInt(eventObject.getMessage()); 109 | latches[index].countDown(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/test/java/org/eluder/logback/ext/lmax/appender/AsyncAppenderBenchmark.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import ch.qos.logback.classic.AsyncAppender; 4 | 5 | public class AsyncAppenderBenchmark extends AppenderBenchmark { 6 | 7 | @Override 8 | protected AsyncAppender createAppender(int consumers) { 9 | AsyncAppender appender = new AsyncAppender(); 10 | appender.setName("async"); 11 | appender.setQueueSize(1048576); 12 | return appender; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/test/java/org/eluder/logback/ext/lmax/appender/DisruptorAppenderBenchmark.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | public class DisruptorAppenderBenchmark extends AppenderBenchmark { 4 | 5 | @Override 6 | protected LoggingEventDisruptorAppender createAppender(int consumers) { 7 | LoggingEventDisruptorAppender appender = new LoggingEventDisruptorAppender(); 8 | appender.setName("disruptor"); 9 | appender.setBufferSize(1048576); 10 | appender.setThreadPoolSize(consumers); 11 | return appender; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /logback-ext-lmax-appender/src/test/java/org/eluder/logback/ext/lmax/appender/LoggingEventDisruptorAppenderPerf.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.lmax.appender; 2 | 3 | import org.junit.Test; 4 | import org.openjdk.jmh.runner.Runner; 5 | import org.openjdk.jmh.runner.options.Options; 6 | import org.openjdk.jmh.runner.options.OptionsBuilder; 7 | import org.openjdk.jmh.runner.options.TimeValue; 8 | 9 | public class LoggingEventDisruptorAppenderPerf { 10 | 11 | @Test 12 | public void benchmarkTest() throws Exception { 13 | Options options = new OptionsBuilder() 14 | .include(AsyncAppenderBenchmark.class.getSimpleName()) 15 | .include(DisruptorAppenderBenchmark.class.getSimpleName()) 16 | .warmupTime(TimeValue.seconds(5)) 17 | .warmupIterations(2) 18 | .measurementTime(TimeValue.seconds(10)) 19 | .measurementIterations(10) 20 | .threads(16) 21 | .forks(1) 22 | .build(); 23 | new Runner(options).run(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /logback-ext-sns-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | logback-ext 8 | org.eluder.logback 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-sns-appender 13 | 14 | 15 | 16 | ch.qos.logback 17 | logback-core 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | 23 | 24 | org.eluder.logback 25 | logback-ext-core 26 | 27 | 28 | org.eluder.logback 29 | logback-ext-aws-core 30 | 31 | 32 | com.amazonaws 33 | aws-java-sdk-core 34 | ${aws.version} 35 | 36 | 37 | com.amazonaws 38 | aws-java-sdk-sns 39 | ${aws.version} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /logback-ext-sns-appender/src/main/java/org/eluder/logback/ext/sns/appender/AsyncSnsAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.sns.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-sns-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.AsyncAppender; 30 | import ch.qos.logback.classic.spi.ILoggingEvent; 31 | import ch.qos.logback.core.Context; 32 | import ch.qos.logback.core.Layout; 33 | import ch.qos.logback.core.encoder.Encoder; 34 | 35 | import java.nio.charset.Charset; 36 | 37 | public class AsyncSnsAppender extends AsyncAppender { 38 | 39 | private final SnsAppender appender; 40 | 41 | public AsyncSnsAppender() { 42 | this(new SnsAppender()); 43 | } 44 | 45 | protected AsyncSnsAppender(SnsAppender appender) { 46 | appender.setAsyncParent(true); 47 | addAppender(appender); 48 | this.appender = appender; 49 | } 50 | 51 | public void setAccessKey(String accessKey) { 52 | appender.setAccessKey(accessKey); 53 | } 54 | 55 | public void setSecretKey(String secretKey) { 56 | appender.setSecretKey(secretKey); 57 | } 58 | 59 | public void setMaxPayloadSize(int maxPayloadSize) { 60 | appender.setMaxPayloadSize(maxPayloadSize); 61 | } 62 | 63 | public void setRegion(String region) { 64 | appender.setRegion(region); 65 | } 66 | 67 | public void setTopic(String topic) { 68 | appender.setTopic(topic); 69 | } 70 | 71 | public void setSubject(String subject) { 72 | appender.setSubject(subject); 73 | } 74 | 75 | public final void setThreadPoolSize(int threadPoolSize) { 76 | appender.setThreadPoolSize(threadPoolSize); 77 | } 78 | 79 | @Override 80 | public final void setMaxFlushTime(int maxFlushTime) { 81 | appender.setMaxFlushTime(maxFlushTime); 82 | // add an extra 100 millis to wait for the internal event queue handling 83 | super.setMaxFlushTime(maxFlushTime + 100); 84 | } 85 | 86 | public void setCharset(Charset charset) { 87 | appender.setCharset(charset); 88 | } 89 | 90 | public void setEncoder(Encoder encoder) { 91 | appender.setEncoder(encoder); 92 | } 93 | 94 | public void setLayout(Layout layout) { 95 | appender.setLayout(layout); 96 | } 97 | 98 | public void setBinary(boolean binary) { 99 | appender.setBinary(binary); 100 | } 101 | 102 | @Override 103 | public void setName(String name) { 104 | appender.setName(name); 105 | super.setName(name); 106 | } 107 | 108 | @Override 109 | public void setContext(Context context) { 110 | appender.setContext(context); 111 | super.setContext(context); 112 | } 113 | 114 | @Override 115 | public void start() { 116 | appender.start(); 117 | super.start(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /logback-ext-sns-appender/src/main/java/org/eluder/logback/ext/sns/appender/SnsAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.sns.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-sns-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.filter.Filter; 30 | import ch.qos.logback.core.spi.DeferredProcessingAware; 31 | import com.amazonaws.regions.RegionUtils; 32 | import com.amazonaws.services.sns.AmazonSNSAsyncClient; 33 | import com.amazonaws.services.sns.model.PublishRequest; 34 | import com.amazonaws.services.sns.model.PublishResult; 35 | import org.eluder.logback.ext.aws.core.AbstractAwsEncodingStringAppender; 36 | import org.eluder.logback.ext.aws.core.AwsSupport; 37 | import org.eluder.logback.ext.aws.core.LoggingEventHandler; 38 | import org.eluder.logback.ext.core.AppenderExecutors; 39 | import org.eluder.logback.ext.core.StringPayloadConverter; 40 | 41 | import java.util.concurrent.CountDownLatch; 42 | import java.util.concurrent.Executors; 43 | 44 | import static java.lang.String.format; 45 | 46 | public class SnsAppender extends AbstractAwsEncodingStringAppender { 47 | 48 | private String region; 49 | private String topic; 50 | private String subject; 51 | 52 | private AmazonSNSAsyncClient sns; 53 | 54 | public SnsAppender() { 55 | super(); 56 | } 57 | 58 | protected SnsAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 59 | super(awsSupport, sdkLoggingFilter); 60 | } 61 | 62 | public void setRegion(String region) { 63 | this.region = region; 64 | } 65 | 66 | public void setTopic(String topic) { 67 | this.topic = topic; 68 | } 69 | 70 | public void setSubject(String subject) { 71 | this.subject = subject; 72 | } 73 | 74 | @Override 75 | public void start() { 76 | setConverter(new StringPayloadConverter(getCharset(), isBinary())); 77 | super.start(); 78 | } 79 | 80 | @Override 81 | protected void doStart() { 82 | sns = new AmazonSNSAsyncClient( 83 | getCredentials(), 84 | getClientConfiguration(), 85 | Executors.newFixedThreadPool(getThreadPoolSize()) 86 | ); 87 | sns.setRegion(RegionUtils.getRegion(region)); 88 | } 89 | 90 | @Override 91 | protected void doStop() { 92 | if (sns != null) { 93 | AppenderExecutors.shutdown(this, sns.getExecutorService(), getMaxFlushTime()); 94 | sns.shutdown(); 95 | sns = null; 96 | } 97 | } 98 | 99 | @Override 100 | protected void handle(final E event, final String encoded) throws Exception { 101 | PublishRequest request = new PublishRequest(topic, encoded, subject); 102 | String errorMessage = format("Appender '%s' failed to send logging event '%s' to SNS topic '%s'", getName(), event, topic); 103 | CountDownLatch latch = new CountDownLatch(isAsyncParent() ? 0 : 1); 104 | sns.publishAsync(request, new LoggingEventHandler(this, latch, errorMessage)); 105 | AppenderExecutors.awaitLatch(this, latch, getMaxFlushTime()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /logback-ext-sqs-appender/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | logback-ext 8 | org.eluder.logback 9 | 1.0-SNAPSHOT 10 | 11 | 12 | logback-ext-sqs-appender 13 | 14 | 15 | 16 | ch.qos.logback 17 | logback-core 18 | 19 | 20 | ch.qos.logback 21 | logback-classic 22 | 23 | 24 | org.eluder.logback 25 | logback-ext-core 26 | 27 | 28 | org.eluder.logback 29 | logback-ext-aws-core 30 | 31 | 32 | com.amazonaws 33 | aws-java-sdk-core 34 | ${aws.version} 35 | 36 | 37 | com.amazonaws 38 | aws-java-sdk-sqs 39 | ${aws.version} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /logback-ext-sqs-appender/src/main/java/org/eluder/logback/ext/sqs/appender/AsyncSqsAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.sqs.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-sqs-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.classic.AsyncAppender; 30 | import ch.qos.logback.classic.spi.ILoggingEvent; 31 | import ch.qos.logback.core.Context; 32 | import ch.qos.logback.core.Layout; 33 | import ch.qos.logback.core.encoder.Encoder; 34 | 35 | import java.nio.charset.Charset; 36 | 37 | public class AsyncSqsAppender extends AsyncAppender { 38 | 39 | private final SqsAppender appender; 40 | 41 | public AsyncSqsAppender() { 42 | this(new SqsAppender()); 43 | } 44 | 45 | protected AsyncSqsAppender(SqsAppender appender) { 46 | appender.setAsyncParent(true); 47 | addAppender(appender); 48 | this.appender = appender; 49 | } 50 | 51 | public void setQueueUrl(String queueUrl) { 52 | appender.setQueueUrl(queueUrl); 53 | } 54 | 55 | public void setAccessKey(String accessKey) { 56 | appender.setAccessKey(accessKey); 57 | } 58 | 59 | public void setSecretKey(String secretKey) { 60 | appender.setSecretKey(secretKey); 61 | } 62 | 63 | public void setMaxPayloadSize(int maxPayloadSize) { 64 | appender.setMaxPayloadSize(maxPayloadSize); 65 | } 66 | 67 | public final void setThreadPoolSize(int threadPoolSize) { 68 | appender.setThreadPoolSize(threadPoolSize); 69 | } 70 | 71 | @Override 72 | public final void setMaxFlushTime(int maxFlushTime) { 73 | appender.setMaxFlushTime(maxFlushTime); 74 | // add an extra 100 millis to wait for the internal event queue handling 75 | super.setMaxFlushTime(maxFlushTime + 100); 76 | } 77 | 78 | public void setCharset(Charset charset) { 79 | appender.setCharset(charset); 80 | } 81 | 82 | public void setEncoder(Encoder encoder) { 83 | appender.setEncoder(encoder); 84 | } 85 | 86 | public void setLayout(Layout layout) { 87 | appender.setLayout(layout); 88 | } 89 | 90 | public void setBinary(boolean binary) { 91 | appender.setBinary(binary); 92 | } 93 | 94 | @Override 95 | public void setName(String name) { 96 | appender.setName(name); 97 | super.setName(name); 98 | } 99 | 100 | @Override 101 | public void setContext(Context context) { 102 | appender.setContext(context); 103 | super.setContext(context); 104 | } 105 | 106 | @Override 107 | public void start() { 108 | appender.start(); 109 | super.start(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /logback-ext-sqs-appender/src/main/java/org/eluder/logback/ext/sqs/appender/SqsAppender.java: -------------------------------------------------------------------------------- 1 | package org.eluder.logback.ext.sqs.appender; 2 | 3 | /* 4 | * #[license] 5 | * logback-ext-sqs-appender 6 | * %% 7 | * Copyright (C) 2014 - 2015 Tapio Rautonen 8 | * %% 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * %[license] 27 | */ 28 | 29 | import ch.qos.logback.core.filter.Filter; 30 | import ch.qos.logback.core.spi.DeferredProcessingAware; 31 | import com.amazonaws.services.sqs.AmazonSQSAsyncClient; 32 | import com.amazonaws.services.sqs.model.SendMessageRequest; 33 | import com.amazonaws.services.sqs.model.SendMessageResult; 34 | import org.eluder.logback.ext.aws.core.AbstractAwsEncodingStringAppender; 35 | import org.eluder.logback.ext.aws.core.AwsSupport; 36 | import org.eluder.logback.ext.aws.core.LoggingEventHandler; 37 | import org.eluder.logback.ext.core.AppenderExecutors; 38 | import org.eluder.logback.ext.core.StringPayloadConverter; 39 | 40 | import java.net.URI; 41 | import java.net.URISyntaxException; 42 | import java.util.concurrent.CountDownLatch; 43 | import java.util.concurrent.Executors; 44 | 45 | import static java.lang.String.format; 46 | 47 | public class SqsAppender extends AbstractAwsEncodingStringAppender { 48 | 49 | private String queueUrl; 50 | 51 | private AmazonSQSAsyncClient sqs; 52 | 53 | public SqsAppender() { 54 | super(); 55 | } 56 | 57 | protected SqsAppender(AwsSupport awsSupport, Filter sdkLoggingFilter) { 58 | super(awsSupport, sdkLoggingFilter); 59 | } 60 | 61 | public final void setQueueUrl(String queueUrl) { 62 | this.queueUrl = queueUrl; 63 | } 64 | 65 | @Override 66 | public void start() { 67 | if (queueUrl == null) { 68 | addError(format("Queue url not set for appender '%s'", getName())); 69 | return; 70 | } 71 | setConverter(new StringPayloadConverter(getCharset(), isBinary())); 72 | super.start(); 73 | } 74 | 75 | @Override 76 | protected void doStart() { 77 | sqs = new AmazonSQSAsyncClient( 78 | getCredentials(), 79 | getClientConfiguration(), 80 | Executors.newFixedThreadPool(getThreadPoolSize()) 81 | ); 82 | sqs.setEndpoint(getEndpoint()); 83 | } 84 | 85 | @Override 86 | protected void doStop() { 87 | if (sqs != null) { 88 | AppenderExecutors.shutdown(this, sqs.getExecutorService(), getMaxFlushTime()); 89 | sqs.shutdown(); 90 | sqs = null; 91 | } 92 | } 93 | 94 | protected String getEndpoint() { 95 | try { 96 | return new URI(queueUrl).getHost(); 97 | } catch (URISyntaxException ex) { 98 | throw new IllegalArgumentException("Malformed queue url", ex); 99 | } 100 | } 101 | 102 | @Override 103 | protected void handle(final E event, final String encoded) throws Exception { 104 | SendMessageRequest request = new SendMessageRequest(queueUrl, encoded); 105 | String errorMessage = format("Appender '%s' failed to send logging event '%s' to SQS queue '%s'", getName(), event, queueUrl); 106 | CountDownLatch latch = new CountDownLatch(isAsyncParent() ? 0 : 1); 107 | sqs.sendMessageAsync(request, new LoggingEventHandler(this, latch, errorMessage)); 108 | AppenderExecutors.awaitLatch(this, latch, getMaxFlushTime()); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | eluder-parent 9 | org.eluder 10 | 8-SNAPSHOT 11 | 12 | 13 | 14 | org.eluder.logback 15 | logback-ext 16 | 1.0-SNAPSHOT 17 | pom 18 | 19 | logback-ext 20 | Extensions for Logback logging library. 21 | https://github.com/trautonen/logback-ext 22 | 2014 23 | 24 | 25 | 26 | Tapio Rautonen 27 | 28 | 29 | 30 | 31 | 32 | The MIT License (MIT) 33 | http://opensource.org/licenses/MIT 34 | repo 35 | 36 | 37 | 38 | 39 | scm:git:git://github.com/trautonen/logback-ext.git 40 | scm:git:git://github.com/trautonen/logback-ext.git 41 | https://github.com/trautonen/logback-ext 42 | 43 | 44 | 45 | 1.7 46 | 18.0 47 | 2.5.4 48 | 1.10.2 49 | 3.3.2 50 | 1.10.5 51 | 52 | 53 | 54 | logback-ext-core 55 | logback-ext-jackson 56 | logback-ext-aws-core 57 | logback-ext-sqs-appender 58 | logback-ext-sns-appender 59 | logback-ext-dynamodb-appender 60 | logback-ext-cloudwatch-appender 61 | logback-ext-kinesis-appender 62 | logback-ext-lmax-appender 63 | 64 | 65 | 66 | 67 | 68 | org.eluder.logback 69 | logback-ext-core 70 | ${project.version} 71 | 72 | 73 | org.eluder.logback 74 | logback-ext-jackson 75 | ${project.version} 76 | 77 | 78 | org.eluder.logback 79 | logback-ext-aws-core 80 | ${project.version} 81 | 82 | 83 | org.eluder.logback 84 | logback-ext-sqs-appender 85 | ${project.version} 86 | 87 | 88 | org.eluder.logback 89 | logback-ext-sns-appender 90 | ${project.version} 91 | 92 | 93 | org.eluder.logback 94 | logback-ext-dynamodb-appender 95 | ${project.version} 96 | 97 | 98 | org.eluder.logback 99 | logback-ext-lmax-appender 100 | ${project.version} 101 | 102 | 103 | org.assertj 104 | assertj-core 105 | 2.1.0 106 | 107 | 108 | org.openjdk.jmh 109 | jmh-core 110 | ${jmh.version} 111 | 112 | 113 | org.openjdk.jmh 114 | jmh-generator-annprocess 115 | ${jmh.version} 116 | 117 | 118 | 119 | 120 | 121 | 122 | benchmark 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-surefire-plugin 129 | ${maven.surefire.version} 130 | 131 | 132 | **/*Perf.java 133 | 134 | 135 | **/*Test.java 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | --------------------------------------------------------------------------------