├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── internetitem
│ └── logback
│ └── elasticsearch
│ ├── AbstractElasticsearchAppender.java
│ ├── AbstractElasticsearchPublisher.java
│ ├── AccessElasticsearchPublisher.java
│ ├── ClassicElasticsearchPublisher.java
│ ├── ElasticsearchAccessAppender.java
│ ├── ElasticsearchAppender.java
│ ├── ElasticsearchOutputAggregator.java
│ ├── PropertySerializer.java
│ ├── config
│ ├── AWSAuthentication.java
│ ├── Authentication.java
│ ├── BasicAuthentication.java
│ ├── ElasticsearchProperties.java
│ ├── HttpRequestHeader.java
│ ├── HttpRequestHeaders.java
│ ├── Property.java
│ └── Settings.java
│ ├── util
│ ├── AbstractPropertyAndEncoder.java
│ ├── AccessPropertyAndEncoder.java
│ ├── Base64.java
│ ├── ClassicPropertyAndEncoder.java
│ └── ErrorReporter.java
│ └── writer
│ ├── ElasticsearchWriter.java
│ ├── LoggerWriter.java
│ ├── SafeWriter.java
│ └── StdErrWriter.java
└── test
└── java
└── com
└── internetitem
└── logback
└── elasticsearch
├── ElasticsearchAppenderTest.java
└── PropertySerializerTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /.project
3 | /.classpath
4 | /.settings/
5 | /target/
6 | /bin/
7 |
8 | *.iml
9 | *.jar
10 | *.class
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | LICENSE
2 | -------
3 |
4 | logback-elasticsearch-appender
5 | Copyright (C) 2015, Adam Batkin. All rights reserved.
6 |
7 | This program and the accompanying materials are dual-licensed under
8 | either the terms of the Eclipse Public License v1.0 as published by
9 | the Eclipse Foundation
10 |
11 | or (per the licensee's choosing)
12 |
13 | under the terms of the GNU Lesser General Public License version 2.1
14 | as published by the Free Software Foundation.
15 |
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Logback Elasticsearch Appender
2 | ===============================
3 |
4 | [](https://travis-ci.org/internetitem/logback-elasticsearch-appender)
5 |
6 | Send log events directly from Logback to Elasticsearch. Logs are delivered asynchronously (i.e. not on the main thread) so will not block execution of the program. Note that the queue backlog can be bounded and messages *can* be lost if Elasticsearch is down and either the backlog queue is full or the producer program is trying to exit (it will retry up to a configured number of attempts, but will not block shutdown of the program beyond that). For long-lived programs, this should not be a problem, as messages should be delivered eventually.
7 |
8 | This software is dual-licensed under the EPL 1.0 and LGPL 2.1, which is identical to the [Logback License](http://logback.qos.ch/license.html) itself.
9 |
10 | Usage
11 | =====
12 | Include slf4j and logback as usual (depending on this library will *not* automatically pull them in).
13 |
14 | In your `pom.xml` (or equivalent), add:
15 |
16 |
17 | com.internetitem
18 | logback-elasticsearch-appender
19 | 1.6
20 |
21 |
22 | In your `logback.xml`:
23 |
24 |
25 | http://yourserver/_bulk
26 | logs-%date{yyyy-MM-dd}
27 | tester
28 | es-logger
29 | es-error-logger
30 | 30000
31 | false
32 | false
33 | false
34 | 104857600
35 | 3
36 | 30000
37 | 250
38 | false
39 | false
40 | 100
41 |
42 |
43 |
44 | host
45 | ${HOSTNAME}
46 | false
47 |
48 |
49 | severity
50 | %level
51 |
52 |
53 | thread
54 | %thread
55 |
56 |
57 | stacktrace
58 | %ex
59 |
60 |
61 | logger
62 | %logger
63 |
64 |
65 |
66 |
67 | Content-Type
68 | application/json
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | %msg
87 |
88 |
89 |
90 |
91 |
92 |
93 | Configuration Reference
94 | =======================
95 |
96 | * `url` (required): The URL to your Elasticsearch bulk API endpoint
97 | * `index` (required): Name if the index to publish to (populated using PatternLayout just like individual properties - see below)
98 | * `type` (optional): Elasticsearch `_type` field for records. Although this library does not require `type` to be populated, Elasticsearch may, unless the configured URL includes the type (i.e. `{index}/{type}/_bulk` as opposed to `/_bulk` and `/{index}/_bulk`). See the Elasticsearch [Bulk API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html) documentation for more information
99 | * `sleepTime` (optional, default 250): Time (in ms) to sleep between attempts at delivering a message
100 | * `maxRetries` (optional, default 3): Number of times to attempt retrying a message on failure. Note that subsequent log messages reset the retry count to 0. This value is important if your program is about to exit (i.e. it is not producing any more log lines) but is unable to deliver some messages to ES
101 | * `connectTimeout` (optional, default 30000): Elasticsearch connect timeout (in ms)
102 | * `readTimeout` (optional, default 30000): Elasticsearch read timeout (in ms)
103 | * `includeCallerData` (optional, default false): If set to `true`, save the caller data (identical to the [AsyncAppender's includeCallerData](http://logback.qos.ch/manual/appenders.html#asyncIncludeCallerData))
104 | * `errorsToStderr` (optional, default false): If set to `true`, any errors in communicating with Elasticsearch will also be dumped to stderr (normally they are only reported to the internal Logback Status system, in order to prevent a feedback loop)
105 | * `logsToStderr` (optional, default false): If set to `true`, dump the raw Elasticsearch messages to stderr
106 | * `maxQueueSize` (optional, default 104,857,600 = 200MB): Maximum size (in characters) of the send buffer. After this point, *logs will be dropped*. This should only happen if Elasticsearch is down, but this is a self-protection mechanism to ensure that the logging system doesn't cause the main process to run out of memory. Note that this maximum is approximate; once the maximum is hit, no new logs will be accepted until it shrinks, but any logs already accepted to be processed will still be added to the buffer
107 | * `loggerName` (optional): If set, raw ES-formatted log data will be sent to this logger
108 | * `errorLoggerName` (optional): If set, any internal errors or problems will be logged to this logger
109 | * `rawJsonMessage` (optional, default false): If set to `true`, the log message is interpreted as pre-formatted raw JSON message.
110 | * `includeMdc` (optional, default false): If set to `true`, then all [MDC](http://www.slf4j.org/api/org/slf4j/MDC.html) values will be mapped to properties on the JSON payload.
111 | * `maxMessageSize` (optional, default -1): If set to a number greater than 0, truncate messages larger than this length, then append "`..`" to denote that the message was truncated
112 | * `authentication` (optional): Add the ability to send authentication headers (see below)
113 |
114 | The fields `@timestamp` and `message` are always sent and can not currently be configured. Additional fields can be sent by adding `` elements to the `` set.
115 |
116 | * `name` (required): Key to be used in the log event
117 | * `value` (required): Text string to be sent. Internally, the value is populated using a Logback PatternLayout, so all [Conversion Words](http://logback.qos.ch/manual/layouts.html#conversionWord) can be used (in addition to the standard static variable interpolations like `${HOSTNAME}`).
118 | * `allowEmpty` (optional, default `false`): Normally, if the `value` results in a `null` or empty string, the field will not be sent. If `allowEmpty` is set to `true` then the field will be sent regardless
119 | * `type` (optional, default `String`): type of the field on the resulting JSON message. Possible values are: `String`, `int`, `float` and `boolean`.
120 |
121 | Groovy Configuration
122 | ====================
123 |
124 | If you configure logback using `logback.groovy`, this can be configured as follows:
125 |
126 | import com.internetitem.logback.elasticsearch.ElasticsearchAppender
127 |
128 | appender("ELASTIC", ElasticsearchAppender){
129 | url = 'http://yourserver/_bulk'
130 | index = 'logs-%date{yyyy-MM-dd}'
131 | type = 'log'
132 | rawJsonMessage = true
133 | errorsToStderr = true
134 | authentication = new BasicAuthentication()
135 | def configHeaders = new HttpRequestHeaders()
136 | configHeaders.addHeader(new HttpRequestHeader(name: 'Content-Type', value: 'text/plain'))
137 | headers = configHeaders
138 | }
139 |
140 | root(INFO, ["ELASTIC"])
141 |
142 | Authentication
143 | ==============
144 |
145 | Authentication is a pluggable mechanism. You must specify the authentication class on the XML element itself. The currently supported classes are:
146 |
147 | * `com.internetitem.logback.elasticsearch.config.BasicAuthentication` - Username and password are taken from the URL (i.e. `http://username:password@yourserver/_bulk`)
148 | * `com.internetitem.logback.elasticsearch.config.AWSAuthentication` - Authenticate using the AWS SDK, for use with the [Amazon Elasticsearch Service](https://aws.amazon.com/elasticsearch-service/) (note that you will also need to include `com.amazonaws:aws-java-sdk-core` as a dependency)
149 |
150 | Logback Access
151 | ==============
152 |
153 | Included is also an Elasticsearch appender for Logback Access. The configuration is almost identical, with the following two differences:
154 |
155 | * The Appender class name is `com.internetitem.logback.elasticsearch.ElasticsearchAccessAppender`
156 | * The `value` for each `property` uses the [Logback Access conversion words](http://logback.qos.ch/manual/layouts.html#logback-access).
157 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.internetitem
6 | logback-elasticsearch-appender
7 | 1.7-SNAPSHOT
8 | jar
9 |
10 | Logback Elasticsearch Appender
11 | Send log events directly from Logback to Elasticsearch
12 | https://github.com/internetitem/logback-elasticsearch-appender
13 |
14 |
15 |
16 | The MIT License (MIT)
17 | http://opensource.org/licenses/MIT
18 | repo
19 |
20 |
21 |
22 |
23 | Internet Item
24 | https://internetitem.com/
25 |
26 |
27 |
28 | scm:git:https://github.com/internetitem/logback-elasticsearch-appender.git
29 | scm:git:https://github.com/internetitem/logback-elasticsearch-appender.git
30 | https://github.com/internetitem/logback-elasticsearch-appender
31 | HEAD
32 |
33 |
34 |
35 |
36 | Adam Batkin
37 | adam@batkin.net
38 |
39 |
40 |
41 |
42 | UTF-8
43 | UTF-8
44 |
45 |
46 |
47 |
48 | ch.qos.logback
49 | logback-classic
50 | 1.1.7
51 | provided
52 |
53 |
54 | ch.qos.logback
55 | logback-access
56 | 1.1.7
57 | provided
58 |
59 |
60 | org.slf4j
61 | slf4j-api
62 | 1.7.21
63 | provided
64 |
65 |
66 | com.fasterxml.jackson.core
67 | jackson-core
68 | 2.8.0
69 |
70 |
71 | com.amazonaws
72 | aws-java-sdk-core
73 | 1.11.31
74 | provided
75 |
76 |
77 | junit
78 | junit
79 | 4.12
80 | test
81 |
82 |
83 | org.mockito
84 | mockito-all
85 | 1.10.19
86 | test
87 |
88 |
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-release-plugin
95 | 2.5.3
96 |
97 | v@{project.version}
98 | false
99 | release
100 |
101 |
102 |
103 | org.apache.maven.plugins
104 | maven-compiler-plugin
105 | 3.5.1
106 |
107 | 1.7
108 | 1.7
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | release
117 |
118 |
119 |
120 | org.apache.maven.plugins
121 | maven-source-plugin
122 | 3.0.1
123 |
124 |
125 | attach-sources
126 |
127 | jar-no-fork
128 |
129 |
130 |
131 |
132 |
133 | org.apache.maven.plugins
134 | maven-javadoc-plugin
135 | 2.10.4
136 |
137 |
138 | attach-javadocs
139 |
140 | jar
141 |
142 |
143 |
144 |
145 |
146 | org.apache.maven.plugins
147 | maven-gpg-plugin
148 | 1.6
149 |
150 |
151 | sign-artifacts
152 | verify
153 |
154 | sign
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | ossrh
167 | https://oss.sonatype.org/content/repositories/snapshots
168 |
169 |
170 | ossrh
171 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/AbstractElasticsearchAppender.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import java.io.IOException;
4 | import java.net.MalformedURLException;
5 | import java.net.URL;
6 |
7 | import ch.qos.logback.core.UnsynchronizedAppenderBase;
8 | import com.internetitem.logback.elasticsearch.config.Authentication;
9 | import com.internetitem.logback.elasticsearch.config.ElasticsearchProperties;
10 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeaders;
11 | import com.internetitem.logback.elasticsearch.config.Settings;
12 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
13 |
14 | public abstract class AbstractElasticsearchAppender extends UnsynchronizedAppenderBase {
15 |
16 | protected Settings settings;
17 | protected ElasticsearchProperties elasticsearchProperties;
18 | protected AbstractElasticsearchPublisher publisher;
19 | protected ErrorReporter errorReporter;
20 | protected HttpRequestHeaders headers;
21 |
22 | public AbstractElasticsearchAppender() {
23 | this.settings = new Settings();
24 | this.headers = new HttpRequestHeaders();
25 | }
26 |
27 | public AbstractElasticsearchAppender(Settings settings) {
28 | this.settings = settings;
29 | }
30 |
31 | @Override
32 | public void start() {
33 | super.start();
34 | this.errorReporter = getErrorReporter();
35 | try {
36 | this.publisher = buildElasticsearchPublisher();
37 | } catch (IOException e) {
38 | throw new RuntimeException(e);
39 | }
40 | }
41 |
42 | protected void publishEvent(T eventObject) {
43 | publisher.addEvent(eventObject);
44 | }
45 |
46 | //VisibleForTesting
47 | protected ErrorReporter getErrorReporter() {
48 | return new ErrorReporter(settings, getContext());
49 | }
50 |
51 | //VisibleForTesting
52 | protected abstract AbstractElasticsearchPublisher buildElasticsearchPublisher() throws IOException;
53 |
54 | @Override
55 | public void stop() {
56 | super.stop();
57 | }
58 |
59 | @Override
60 | protected void append(T eventObject) {
61 | appendInternal(eventObject);
62 | }
63 |
64 | protected abstract void appendInternal(T eventObject);
65 |
66 | public void setProperties(ElasticsearchProperties elasticsearchProperties) {
67 | this.elasticsearchProperties = elasticsearchProperties;
68 | }
69 |
70 | public void setSleepTime(int sleepTime) {
71 | settings.setSleepTime(sleepTime);
72 | }
73 |
74 | public void setMaxRetries(int maxRetries) {
75 | settings.setMaxRetries(maxRetries);
76 | }
77 |
78 | public void setConnectTimeout(int connectTimeout) {
79 | settings.setConnectTimeout(connectTimeout);
80 | }
81 |
82 | public void setReadTimeout(int readTimeout) {
83 | settings.setReadTimeout(readTimeout);
84 | }
85 |
86 | public void setIncludeCallerData(boolean includeCallerData) {
87 | settings.setIncludeCallerData(includeCallerData);
88 | }
89 |
90 | public void setErrorsToStderr(boolean errorsToStderr) {
91 | settings.setErrorsToStderr(errorsToStderr);
92 | }
93 |
94 | public void setLogsToStderr(boolean logsToStderr) {
95 | settings.setLogsToStderr(logsToStderr);
96 | }
97 |
98 | public void setMaxQueueSize(int maxQueueSize) {
99 | settings.setMaxQueueSize(maxQueueSize);
100 | }
101 |
102 | public void setIndex(String index) {
103 | settings.setIndex(index);
104 | }
105 |
106 | public void setType(String type) {
107 | settings.setType(type);
108 | }
109 |
110 | public void setUrl(String url) throws MalformedURLException {
111 | settings.setUrl(new URL(url));
112 | }
113 |
114 | public void setLoggerName(String logger) {
115 | settings.setLoggerName(logger);
116 | }
117 |
118 | public void setErrorLoggerName(String logger) {
119 | settings.setErrorLoggerName(logger);
120 | }
121 |
122 | public void setHeaders(HttpRequestHeaders httpRequestHeaders) {
123 | this.headers = httpRequestHeaders;
124 | }
125 |
126 | public void setRawJsonMessage(boolean rawJsonMessage) {
127 | settings.setRawJsonMessage(rawJsonMessage);
128 | }
129 |
130 | public void setIncludeMdc(boolean includeMdc) {
131 | settings.setIncludeMdc(includeMdc);
132 | }
133 |
134 | public void setAuthentication(Authentication auth) {
135 | settings.setAuthentication(auth);
136 | }
137 |
138 | public void setMaxMessageSize(int maxMessageSize) {
139 | settings.setMaxMessageSize(maxMessageSize);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/AbstractElasticsearchPublisher.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import ch.qos.logback.core.Context;
4 | import com.fasterxml.jackson.core.JsonFactory;
5 | import com.fasterxml.jackson.core.JsonGenerator;
6 | import com.internetitem.logback.elasticsearch.config.ElasticsearchProperties;
7 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeaders;
8 | import com.internetitem.logback.elasticsearch.config.Property;
9 | import com.internetitem.logback.elasticsearch.config.Settings;
10 | import com.internetitem.logback.elasticsearch.util.AbstractPropertyAndEncoder;
11 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
12 | import com.internetitem.logback.elasticsearch.writer.ElasticsearchWriter;
13 | import com.internetitem.logback.elasticsearch.writer.LoggerWriter;
14 | import com.internetitem.logback.elasticsearch.writer.StdErrWriter;
15 |
16 | import java.io.IOException;
17 | import java.text.DateFormat;
18 | import java.text.SimpleDateFormat;
19 | import java.util.ArrayList;
20 | import java.util.Date;
21 | import java.util.List;
22 | import java.util.concurrent.atomic.AtomicInteger;
23 |
24 | public abstract class AbstractElasticsearchPublisher implements Runnable {
25 |
26 | private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(1);
27 | private static final ThreadLocal DATE_FORMAT = new ThreadLocal () {
28 | @Override
29 | protected DateFormat initialValue() {
30 | return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
31 | }
32 | };
33 |
34 | public static final String THREAD_NAME_PREFIX = "es-writer-";
35 |
36 |
37 | private volatile List events;
38 | private ElasticsearchOutputAggregator outputAggregator;
39 | private List> propertyList;
40 |
41 | private AbstractPropertyAndEncoder indexPattern;
42 | private JsonFactory jf;
43 | private JsonGenerator jsonGenerator;
44 |
45 | private ErrorReporter errorReporter;
46 | protected Settings settings;
47 |
48 | private final Object lock;
49 |
50 | private volatile boolean working;
51 |
52 | private final PropertySerializer propertySerializer;
53 |
54 | public AbstractElasticsearchPublisher(Context context, ErrorReporter errorReporter, Settings settings, ElasticsearchProperties properties, HttpRequestHeaders headers) throws IOException {
55 | this.errorReporter = errorReporter;
56 | this.events = new ArrayList();
57 | this.lock = new Object();
58 | this.settings = settings;
59 |
60 | this.outputAggregator = configureOutputAggregator(settings, errorReporter, headers);
61 |
62 | this.jf = new JsonFactory();
63 | this.jf.setRootValueSeparator(null);
64 | this.jsonGenerator = jf.createGenerator(outputAggregator);
65 |
66 | this.indexPattern = buildPropertyAndEncoder(context, new Property("", settings.getIndex(), false));
67 | this.propertyList = generatePropertyList(context, properties);
68 |
69 | this.propertySerializer = new PropertySerializer();
70 | }
71 |
72 | private static ElasticsearchOutputAggregator configureOutputAggregator(Settings settings, ErrorReporter errorReporter, HttpRequestHeaders httpRequestHeaders) {
73 | ElasticsearchOutputAggregator spigot = new ElasticsearchOutputAggregator(settings, errorReporter);
74 |
75 | if (settings.isLogsToStderr()) {
76 | spigot.addWriter(new StdErrWriter());
77 | }
78 |
79 | if (settings.getLoggerName() != null) {
80 | spigot.addWriter(new LoggerWriter(settings.getLoggerName()));
81 | }
82 |
83 | if (settings.getUrl() != null) {
84 | spigot.addWriter(new ElasticsearchWriter(errorReporter, settings, httpRequestHeaders));
85 | }
86 |
87 | return spigot;
88 | }
89 |
90 | private List> generatePropertyList(Context context, ElasticsearchProperties properties) {
91 | List> list = new ArrayList>();
92 | if (properties != null) {
93 | for (Property property : properties.getProperties()) {
94 | list.add(buildPropertyAndEncoder(context, property));
95 | }
96 | }
97 | return list;
98 | }
99 |
100 | protected abstract AbstractPropertyAndEncoder buildPropertyAndEncoder(Context context, Property property);
101 |
102 | public void addEvent(T event) {
103 | if (!outputAggregator.hasOutputs()) {
104 | return;
105 | }
106 |
107 | synchronized (lock) {
108 | events.add(event);
109 | if (!working) {
110 | working = true;
111 | Thread thread = new Thread(this, THREAD_NAME_PREFIX + THREAD_COUNTER.getAndIncrement());
112 | thread.start();
113 | }
114 | }
115 | }
116 |
117 | public void run() {
118 | int currentTry = 1;
119 | int maxRetries = settings.getMaxRetries();
120 | while (true) {
121 | try {
122 | Thread.sleep(settings.getSleepTime());
123 |
124 | List eventsCopy = null;
125 | synchronized (lock) {
126 | if (!events.isEmpty()) {
127 | eventsCopy = events;
128 | events = new ArrayList();
129 | currentTry = 1;
130 | }
131 |
132 | if (eventsCopy == null) {
133 | if (!outputAggregator.hasPendingData()) {
134 | // all done
135 | working = false;
136 | return;
137 | } else {
138 | // Nothing new, must be a retry
139 | if (currentTry > maxRetries) {
140 | // Oh well, better luck next time
141 | working = false;
142 | return;
143 | }
144 | }
145 | }
146 | }
147 |
148 | if (eventsCopy != null) {
149 | serializeEvents(jsonGenerator, eventsCopy, propertyList);
150 | }
151 |
152 | if (!outputAggregator.sendData()) {
153 | currentTry++;
154 | }
155 | } catch (Exception e) {
156 | errorReporter.logError("Internal error handling log data: " + e.getMessage(), e);
157 | currentTry++;
158 | }
159 | }
160 | }
161 |
162 |
163 | private void serializeEvents(JsonGenerator gen, List eventsCopy, List> propertyList) throws IOException {
164 | for (T event : eventsCopy) {
165 | serializeIndexString(gen, event);
166 | gen.writeRaw('\n');
167 | serializeEvent(gen, event, propertyList);
168 | gen.writeRaw('\n');
169 | }
170 | gen.flush();
171 | }
172 |
173 | private void serializeIndexString(JsonGenerator gen, T event) throws IOException {
174 | gen.writeStartObject();
175 | gen.writeObjectFieldStart("index");
176 | gen.writeObjectField("_index", indexPattern.encode(event));
177 | String type = settings.getType();
178 | if (type != null) {
179 | gen.writeObjectField("_type", type);
180 | }
181 | gen.writeEndObject();
182 | gen.writeEndObject();
183 | }
184 |
185 | private void serializeEvent(JsonGenerator gen, T event, List> propertyList) throws IOException {
186 | gen.writeStartObject();
187 |
188 | serializeCommonFields(gen, event);
189 |
190 | for (AbstractPropertyAndEncoder pae : propertyList) {
191 | propertySerializer.serializeProperty(gen, event, pae);
192 | }
193 |
194 | gen.writeEndObject();
195 | }
196 |
197 | protected abstract void serializeCommonFields(JsonGenerator gen, T event) throws IOException;
198 |
199 | protected static String getTimestamp(long timestamp) {
200 | return DATE_FORMAT.get().format(new Date(timestamp));
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/AccessElasticsearchPublisher.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import java.io.IOException;
4 |
5 | import ch.qos.logback.access.spi.IAccessEvent;
6 | import ch.qos.logback.core.Context;
7 | import com.fasterxml.jackson.core.JsonGenerator;
8 | import com.internetitem.logback.elasticsearch.config.ElasticsearchProperties;
9 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeaders;
10 | import com.internetitem.logback.elasticsearch.config.Property;
11 | import com.internetitem.logback.elasticsearch.config.Settings;
12 | import com.internetitem.logback.elasticsearch.util.AbstractPropertyAndEncoder;
13 | import com.internetitem.logback.elasticsearch.util.AccessPropertyAndEncoder;
14 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
15 |
16 | public class AccessElasticsearchPublisher extends AbstractElasticsearchPublisher {
17 |
18 | public AccessElasticsearchPublisher(Context context, ErrorReporter errorReporter, Settings settings, ElasticsearchProperties properties, HttpRequestHeaders httpRequestHeaders) throws IOException {
19 | super(context, errorReporter, settings, properties, httpRequestHeaders);
20 | }
21 |
22 | @Override
23 | protected AbstractPropertyAndEncoder buildPropertyAndEncoder(Context context, Property property) {
24 | return new AccessPropertyAndEncoder(property, context);
25 | }
26 |
27 | @Override
28 | protected void serializeCommonFields(JsonGenerator gen, IAccessEvent event) throws IOException {
29 | gen.writeObjectField("@timestamp", getTimestamp(event.getTimeStamp()));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/ClassicElasticsearchPublisher.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import java.io.IOException;
4 | import java.util.Map;
5 |
6 | import ch.qos.logback.classic.spi.ILoggingEvent;
7 | import ch.qos.logback.core.Context;
8 | import com.fasterxml.jackson.core.JsonGenerator;
9 | import com.internetitem.logback.elasticsearch.config.ElasticsearchProperties;
10 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeaders;
11 | import com.internetitem.logback.elasticsearch.config.Property;
12 | import com.internetitem.logback.elasticsearch.config.Settings;
13 | import com.internetitem.logback.elasticsearch.util.AbstractPropertyAndEncoder;
14 | import com.internetitem.logback.elasticsearch.util.ClassicPropertyAndEncoder;
15 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
16 |
17 | public class ClassicElasticsearchPublisher extends AbstractElasticsearchPublisher {
18 |
19 | public ClassicElasticsearchPublisher(Context context, ErrorReporter errorReporter, Settings settings, ElasticsearchProperties properties, HttpRequestHeaders headers) throws IOException {
20 | super(context, errorReporter, settings, properties, headers);
21 | }
22 |
23 | @Override
24 | protected AbstractPropertyAndEncoder buildPropertyAndEncoder(Context context, Property property) {
25 | return new ClassicPropertyAndEncoder(property, context);
26 | }
27 |
28 | @Override
29 | protected void serializeCommonFields(JsonGenerator gen, ILoggingEvent event) throws IOException {
30 | gen.writeObjectField("@timestamp", getTimestamp(event.getTimeStamp()));
31 |
32 | if (settings.isRawJsonMessage()) {
33 | gen.writeFieldName("message");
34 | gen.writeRawValue(event.getFormattedMessage());
35 | } else {
36 | String formattedMessage = event.getFormattedMessage();
37 | if (settings.getMaxMessageSize() > 0 && formattedMessage.length() > settings.getMaxMessageSize()) {
38 | formattedMessage = formattedMessage.substring(0, settings.getMaxMessageSize()) + "..";
39 | }
40 | gen.writeObjectField("message", formattedMessage);
41 | }
42 |
43 | if(settings.isIncludeMdc()) {
44 | for (Map.Entry entry : event.getMDCPropertyMap().entrySet()) {
45 | gen.writeObjectField(entry.getKey(), entry.getValue());
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/ElasticsearchAccessAppender.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import java.io.IOException;
4 |
5 | import ch.qos.logback.access.spi.IAccessEvent;
6 | import com.internetitem.logback.elasticsearch.config.Settings;
7 |
8 | public class ElasticsearchAccessAppender extends AbstractElasticsearchAppender {
9 |
10 | public ElasticsearchAccessAppender() {
11 | }
12 |
13 | public ElasticsearchAccessAppender(Settings settings) {
14 | super(settings);
15 | }
16 |
17 | @Override
18 | protected void appendInternal(IAccessEvent eventObject) {
19 | eventObject.prepareForDeferredProcessing();
20 | publishEvent(eventObject);
21 | }
22 |
23 | protected AccessElasticsearchPublisher buildElasticsearchPublisher() throws IOException {
24 | return new AccessElasticsearchPublisher(getContext(), errorReporter, settings, elasticsearchProperties, headers);
25 | }
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/ElasticsearchAppender.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import java.io.IOException;
4 |
5 | import ch.qos.logback.classic.spi.ILoggingEvent;
6 | import com.internetitem.logback.elasticsearch.config.Settings;
7 |
8 | public class ElasticsearchAppender extends AbstractElasticsearchAppender {
9 |
10 | public ElasticsearchAppender() {
11 | }
12 |
13 | public ElasticsearchAppender(Settings settings) {
14 | super(settings);
15 | }
16 |
17 | @Override
18 | protected void appendInternal(ILoggingEvent eventObject) {
19 |
20 | String targetLogger = eventObject.getLoggerName();
21 |
22 | String loggerName = settings.getLoggerName();
23 | if (loggerName != null && loggerName.equals(targetLogger)) {
24 | return;
25 | }
26 |
27 | String errorLoggerName = settings.getErrorLoggerName();
28 | if (errorLoggerName != null && errorLoggerName.equals(targetLogger)) {
29 | return;
30 | }
31 |
32 | eventObject.prepareForDeferredProcessing();
33 | if (settings.isIncludeCallerData()) {
34 | eventObject.getCallerData();
35 | }
36 |
37 | publishEvent(eventObject);
38 | }
39 |
40 | protected ClassicElasticsearchPublisher buildElasticsearchPublisher() throws IOException {
41 | return new ClassicElasticsearchPublisher(getContext(), errorReporter, settings, elasticsearchProperties, headers);
42 | }
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/ElasticsearchOutputAggregator.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import com.internetitem.logback.elasticsearch.config.Settings;
4 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
5 | import com.internetitem.logback.elasticsearch.writer.SafeWriter;
6 |
7 | import java.io.IOException;
8 | import java.io.Writer;
9 | import java.util.ArrayList;
10 | import java.util.Date;
11 | import java.util.List;
12 |
13 | public class ElasticsearchOutputAggregator extends Writer {
14 |
15 | private Settings settings;
16 | private ErrorReporter errorReporter;
17 | private List writers;
18 |
19 | public ElasticsearchOutputAggregator(Settings settings, ErrorReporter errorReporter) {
20 | this.writers = new ArrayList();
21 | this.settings = settings;
22 | this.errorReporter = errorReporter;
23 | }
24 |
25 | public void addWriter(SafeWriter writer) {
26 | writers.add(writer);
27 | }
28 |
29 | @Override
30 | public void write(char[] cbuf, int off, int len) throws IOException {
31 | for (SafeWriter writer : writers) {
32 | writer.write(cbuf, off, len);
33 | }
34 | }
35 |
36 | public boolean hasPendingData() {
37 | for (SafeWriter writer : writers) {
38 | if (writer.hasPendingData()) {
39 | return true;
40 | }
41 | }
42 | return false;
43 | }
44 |
45 | public boolean hasOutputs() {
46 | return !writers.isEmpty();
47 | }
48 |
49 | public boolean sendData() {
50 | boolean success = true;
51 | for (SafeWriter writer : writers) {
52 | try {
53 | writer.sendData();
54 | } catch (IOException e) {
55 | success = false;
56 | errorReporter.logWarning("Failed to send events to Elasticsearch: " + e.getMessage());
57 | if (settings.isErrorsToStderr()) {
58 | System.err.println("[" + new Date().toString() + "] Failed to send events to Elasticsearch: " + e.getMessage());
59 | }
60 |
61 | }
62 | }
63 | return success;
64 | }
65 |
66 | @Override
67 | public void flush() throws IOException {
68 | // No-op
69 | }
70 |
71 | @Override
72 | public void close() throws IOException {
73 | // No-op
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/PropertySerializer.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator;
4 | import com.internetitem.logback.elasticsearch.util.AbstractPropertyAndEncoder;
5 |
6 | import java.io.IOException;
7 |
8 | class PropertySerializer {
9 | void serializeProperty(JsonGenerator jsonGenerator, T event, AbstractPropertyAndEncoder propertyAndEncoder) throws IOException {
10 | String value = propertyAndEncoder.encode(event);
11 | if (propertyAndEncoder.allowEmpty() || (value != null && !value.isEmpty())) {
12 | switch (propertyAndEncoder.getType()) {
13 | case INT:
14 | serializeIntField(jsonGenerator, propertyAndEncoder, value);
15 | break;
16 | case FLOAT:
17 | serializeFloatField(jsonGenerator, propertyAndEncoder, value);
18 | break;
19 | case BOOLEAN:
20 | serializeBooleanField(jsonGenerator, propertyAndEncoder, value);
21 | break;
22 | default:
23 | serializeStringField(jsonGenerator, propertyAndEncoder, value);
24 | }
25 | }
26 | }
27 |
28 | private void serializeStringField(JsonGenerator jsonGenerator, AbstractPropertyAndEncoder propertyAndEncoder, String value) throws IOException {
29 | jsonGenerator.writeObjectField(propertyAndEncoder.getName(), value);
30 | }
31 |
32 | private void serializeIntField(JsonGenerator jsonGenerator, AbstractPropertyAndEncoder propertyAndEncoder, String value) throws IOException {
33 | try {
34 | jsonGenerator.writeNumberField(propertyAndEncoder.getName(), Integer.valueOf(value));
35 | } catch (NumberFormatException e) {
36 | serializeStringField(jsonGenerator, propertyAndEncoder, value);
37 | }
38 | }
39 |
40 | private void serializeFloatField(JsonGenerator jsonGenerator, AbstractPropertyAndEncoder propertyAndEncoder, String value) throws IOException {
41 | try {
42 | jsonGenerator.writeNumberField(propertyAndEncoder.getName(), Float.valueOf(value));
43 | } catch (NumberFormatException e) {
44 | serializeStringField(jsonGenerator, propertyAndEncoder, value);
45 | }
46 | }
47 |
48 | private void serializeBooleanField(JsonGenerator jsonGenerator, AbstractPropertyAndEncoder propertyAndEncoder, String value) throws IOException {
49 | if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
50 | jsonGenerator.writeBooleanField(propertyAndEncoder.getName(), Boolean.valueOf(value));
51 | } else {
52 | serializeStringField(jsonGenerator, propertyAndEncoder, value);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/AWSAuthentication.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import java.io.InputStream;
4 | import java.io.UnsupportedEncodingException;
5 | import java.net.HttpURLConnection;
6 | import java.net.URI;
7 | import java.net.URISyntaxException;
8 | import java.net.URL;
9 | import java.util.Collections;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | import com.amazonaws.ReadLimitInfo;
15 | import com.amazonaws.SignableRequest;
16 | import com.amazonaws.auth.AWS4Signer;
17 | import com.amazonaws.auth.AWSCredentials;
18 | import com.amazonaws.auth.AWSCredentialsProvider;
19 | import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
20 | import com.amazonaws.http.HttpMethodName;
21 | import com.amazonaws.regions.DefaultAwsRegionProviderChain;
22 | import com.amazonaws.util.StringInputStream;
23 |
24 | /**
25 | * This class implements Amazon AWS v4 Signature signing for ElasticSearch.
26 | *
27 | * @author blagerweij
28 | */
29 | public class AWSAuthentication implements Authentication {
30 |
31 | private final AWS4Signer signer;
32 | private final AWSCredentials credentials;
33 |
34 | public AWSAuthentication() {
35 | signer = new AWS4Signer(false);
36 | signer.setServiceName("es");
37 | signer.setRegionName(new DefaultAwsRegionProviderChain().getRegion());
38 | AWSCredentialsProvider credsProvider = new DefaultAWSCredentialsProviderChain();
39 | credentials = credsProvider.getCredentials();
40 | }
41 |
42 | @Override
43 | public void addAuth(HttpURLConnection urlConnection, String body) {
44 |
45 | signer.sign(new URLConnectionSignableRequest(urlConnection, body), credentials);
46 | }
47 |
48 | /**
49 | * Wrapper for signing a HttpURLConnection
50 | */
51 | private static class URLConnectionSignableRequest implements SignableRequest {
52 |
53 | private final HttpURLConnection urlConnection;
54 | private final String body;
55 | private final Map headers = new HashMap<>();
56 |
57 | public URLConnectionSignableRequest(HttpURLConnection urlConnection, String body) {
58 | this.urlConnection = urlConnection;
59 | this.body = body;
60 | addHeader("User-Agent","ElasticSearchWriter/1.0");
61 | addHeader("Accept","*/*");
62 | addHeader("Content-Type","application/json");
63 | addHeader("Content-Length",String.valueOf(body.length()));
64 | }
65 |
66 | @Override
67 | public void addHeader(String name, String value) {
68 | this.urlConnection.addRequestProperty(name, value);
69 | headers.put(name,value);
70 | }
71 |
72 | @Override
73 | public Map getHeaders() {
74 | return headers;
75 | }
76 |
77 | @Override
78 | public String getResourcePath() {
79 | return urlConnection.getURL().getPath();
80 | }
81 |
82 | @Override
83 | public void addParameter(String name, String value) {
84 |
85 | }
86 |
87 | @Override
88 | public Map> getParameters() {
89 | return Collections.emptyMap();
90 | }
91 |
92 | @Override
93 | public URI getEndpoint() {
94 | try {
95 | URL u = urlConnection.getURL();
96 | return new URI(u.getProtocol(),null, u.getHost(), u.getPort(), null, null, null);
97 | } catch (URISyntaxException e) {
98 | throw new RuntimeException(e);
99 | }
100 | }
101 |
102 | @Override
103 | public HttpMethodName getHttpMethod() {
104 | return HttpMethodName.fromValue(urlConnection.getRequestMethod());
105 | }
106 |
107 | @Override
108 | public int getTimeOffset() {
109 | return 0;
110 | }
111 |
112 | @Override
113 | public InputStream getContent() {
114 | try {
115 | return new StringInputStream(body);
116 | } catch (UnsupportedEncodingException e) {
117 | throw new RuntimeException(e);
118 | }
119 |
120 | }
121 |
122 | @Override
123 | public void setContent(InputStream content) {
124 | }
125 |
126 | @Override
127 | public InputStream getContentUnwrapped() {
128 | return getContent();
129 | }
130 |
131 | @Override
132 | public ReadLimitInfo getReadLimitInfo() {
133 | return null;
134 | }
135 |
136 | @Override
137 | public Object getOriginalRequestObject() {
138 | return null;
139 | }
140 | }
141 | }
142 |
143 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/Authentication.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import java.net.HttpURLConnection;
4 |
5 | public interface Authentication {
6 | /**
7 | * Modify the given urlConnection for whatever authentication scheme is used.
8 | *
9 | * @param urlConnection the connection to the server
10 | * @param body the message being sent
11 | */
12 | void addAuth(HttpURLConnection urlConnection, String body);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/BasicAuthentication.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import com.internetitem.logback.elasticsearch.util.Base64;
4 |
5 | import java.net.HttpURLConnection;
6 |
7 | public class BasicAuthentication implements Authentication {
8 | public void addAuth(HttpURLConnection urlConnection, String body) {
9 | String userInfo = urlConnection.getURL().getUserInfo();
10 | if (userInfo != null) {
11 | String basicAuth = "Basic " + Base64.encode(userInfo.getBytes());
12 | urlConnection.setRequestProperty("Authorization", basicAuth);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/ElasticsearchProperties.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class ElasticsearchProperties {
7 |
8 | private List properties;
9 |
10 | public ElasticsearchProperties() {
11 | this.properties = new ArrayList();
12 | }
13 |
14 | public List getProperties() {
15 | return properties;
16 | }
17 |
18 | public void addProperty(Property property) {
19 | properties.add(property);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/HttpRequestHeader.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | /**
4 | * A key value pair for the http header.
5 | */
6 | public class HttpRequestHeader {
7 |
8 | private String name;
9 | private String value;
10 |
11 | public String getName() {
12 | return name;
13 | }
14 |
15 | public void setName(String name) {
16 | this.name = name;
17 | }
18 |
19 | public String getValue() {
20 | return value;
21 | }
22 |
23 | public void setValue(String value) {
24 | this.value = value;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/HttpRequestHeaders.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 |
6 | /**
7 | * A container for the headers which will be sent to elasticsearch.
8 | */
9 | public class HttpRequestHeaders {
10 |
11 | private List headers = new LinkedList();
12 |
13 | public List getHeaders() {
14 | return headers;
15 | }
16 |
17 | public void addHeader(HttpRequestHeader header) {
18 | this.headers.add(header);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/Property.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | public class Property {
4 | private String name;
5 | private String value;
6 | private boolean allowEmpty;
7 | private Type type = Type.STRING;
8 |
9 | public enum Type {
10 | STRING, INT, FLOAT, BOOLEAN
11 | }
12 |
13 | public Property() {
14 | }
15 |
16 | public Property(String name, String value, boolean allowEmpty) {
17 | this.name = name;
18 | this.value = value;
19 | this.allowEmpty = allowEmpty;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 |
30 | public String getValue() {
31 | return value;
32 | }
33 |
34 | public void setValue(String value) {
35 | this.value = value;
36 | }
37 |
38 | public boolean isAllowEmpty() {
39 | return allowEmpty;
40 | }
41 |
42 | public void setAllowEmpty(boolean allowEmpty) {
43 | this.allowEmpty = allowEmpty;
44 | }
45 |
46 | public Type getType() {
47 | return type;
48 | }
49 |
50 | public void setType(String type) {
51 | try {
52 | this.type = Enum.valueOf(Type.class, type.toUpperCase());
53 | } catch (IllegalArgumentException e) {
54 | this.type = Type.STRING;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/config/Settings.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.config;
2 |
3 | import java.net.URL;
4 |
5 | public class Settings {
6 |
7 | private String index;
8 | private String type;
9 | private URL url;
10 |
11 | private String loggerName;
12 | private String errorLoggerName;
13 |
14 | private int sleepTime = 250;
15 | private int maxRetries = 3;
16 | private int connectTimeout = 30000;
17 | private int readTimeout = 30000;
18 | private boolean logsToStderr;
19 | private boolean errorsToStderr;
20 | private boolean includeCallerData;
21 | private boolean includeMdc;
22 | private boolean rawJsonMessage;
23 | private int maxQueueSize = 100 * 1024 * 1024;
24 | private Authentication authentication;
25 | private int maxMessageSize = -1;
26 |
27 | public String getIndex() {
28 | return index;
29 | }
30 |
31 | public void setIndex(String index) {
32 | this.index = index;
33 | }
34 |
35 | public String getType() {
36 | return type;
37 | }
38 |
39 | public void setType(String type) {
40 | this.type = type;
41 | }
42 |
43 | public int getSleepTime() {
44 | return sleepTime;
45 | }
46 |
47 | public void setSleepTime(int sleepTime) {
48 | if (sleepTime < 100) {
49 | sleepTime = 100;
50 | }
51 | this.sleepTime = sleepTime;
52 | }
53 |
54 | public int getMaxRetries() {
55 | return maxRetries;
56 | }
57 |
58 | public void setMaxRetries(int maxRetries) {
59 | this.maxRetries = maxRetries;
60 | }
61 |
62 | public int getConnectTimeout() {
63 | return connectTimeout;
64 | }
65 |
66 | public void setConnectTimeout(int connectTimeout) {
67 | this.connectTimeout = connectTimeout;
68 | }
69 |
70 | public int getReadTimeout() {
71 | return readTimeout;
72 | }
73 |
74 | public void setReadTimeout(int readTimeout) {
75 | this.readTimeout = readTimeout;
76 | }
77 |
78 | public boolean isLogsToStderr() {
79 | return logsToStderr;
80 | }
81 |
82 | public void setLogsToStderr(boolean logsToStderr) {
83 | this.logsToStderr = logsToStderr;
84 | }
85 |
86 | public boolean isErrorsToStderr() {
87 | return errorsToStderr;
88 | }
89 |
90 | public void setErrorsToStderr(boolean errorsToStderr) {
91 | this.errorsToStderr = errorsToStderr;
92 | }
93 |
94 | public boolean isIncludeCallerData() {
95 | return includeCallerData;
96 | }
97 |
98 | public void setIncludeCallerData(boolean includeCallerData) {
99 | this.includeCallerData = includeCallerData;
100 | }
101 |
102 | public int getMaxQueueSize() {
103 | return maxQueueSize;
104 | }
105 |
106 | public void setMaxQueueSize(int maxQueueSize) {
107 | this.maxQueueSize = maxQueueSize;
108 | }
109 |
110 | public String getLoggerName() {
111 | return loggerName;
112 | }
113 |
114 | public void setLoggerName(String loggerName) {
115 | this.loggerName = loggerName;
116 | }
117 |
118 | public URL getUrl() {
119 | return url;
120 | }
121 |
122 | public void setUrl(URL url) {
123 | this.url = url;
124 | }
125 |
126 | public String getErrorLoggerName() {
127 | return errorLoggerName;
128 | }
129 |
130 | public void setErrorLoggerName(String errorLoggerName) {
131 | this.errorLoggerName = errorLoggerName;
132 | }
133 |
134 | public boolean isRawJsonMessage() {
135 | return rawJsonMessage;
136 | }
137 |
138 | public void setRawJsonMessage(boolean rawJsonMessage) {
139 | this.rawJsonMessage = rawJsonMessage;
140 | }
141 |
142 | public Authentication getAuthentication() {
143 | return authentication;
144 | }
145 |
146 | public void setAuthentication(Authentication authentication) {
147 | this.authentication = authentication;
148 | }
149 |
150 | public boolean isIncludeMdc() {
151 | return includeMdc;
152 | }
153 |
154 | public void setIncludeMdc(boolean includeMdc) {
155 | this.includeMdc = includeMdc;
156 | }
157 |
158 | public int getMaxMessageSize() {
159 | return maxMessageSize;
160 | }
161 |
162 | public void setMaxMessageSize(int maxMessageSize) {
163 | this.maxMessageSize = maxMessageSize;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/util/AbstractPropertyAndEncoder.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.util;
2 |
3 | import ch.qos.logback.core.Context;
4 | import ch.qos.logback.core.pattern.PatternLayoutBase;
5 | import com.internetitem.logback.elasticsearch.config.Property;
6 |
7 | public abstract class AbstractPropertyAndEncoder {
8 | private Property property;
9 | private PatternLayoutBase layout;
10 |
11 | public AbstractPropertyAndEncoder(Property property, Context context) {
12 | this.property = property;
13 |
14 | this.layout = getLayout();
15 | this.layout.setContext(context);
16 | this.layout.setPattern(property.getValue());
17 | this.layout.setPostCompileProcessor(null);
18 | this.layout.start();
19 | }
20 |
21 | protected abstract PatternLayoutBase getLayout();
22 |
23 | public String encode(T event) {
24 | return layout.doLayout(event);
25 | }
26 |
27 | public String getName() {
28 | return property.getName();
29 | }
30 |
31 | public boolean allowEmpty() {
32 | return property.isAllowEmpty();
33 | }
34 |
35 | public Property.Type getType() {
36 | return property.getType();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/util/AccessPropertyAndEncoder.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.util;
2 |
3 | import ch.qos.logback.access.spi.IAccessEvent;
4 | import ch.qos.logback.access.PatternLayout;
5 | import ch.qos.logback.core.Context;
6 | import ch.qos.logback.core.pattern.PatternLayoutBase;
7 | import com.internetitem.logback.elasticsearch.config.Property;
8 |
9 | public class AccessPropertyAndEncoder extends AbstractPropertyAndEncoder {
10 |
11 | public AccessPropertyAndEncoder(Property property, Context context) {
12 | super(property, context);
13 | }
14 |
15 | @Override
16 | protected PatternLayoutBase getLayout() {
17 | return new PatternLayout();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/util/Base64.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.util;
2 |
3 | import java.io.ByteArrayOutputStream;
4 |
5 | public class Base64
6 | {
7 | public static String encode(byte[] data)
8 | {
9 | char[] tbl = {
10 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
11 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
12 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
13 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
14 |
15 | StringBuilder buffer = new StringBuilder();
16 | int pad = 0;
17 | for (int i = 0; i < data.length; i += 3) {
18 |
19 | int b = ((data[i] & 0xFF) << 16) & 0xFFFFFF;
20 | if (i + 1 < data.length) {
21 | b |= (data[i+1] & 0xFF) << 8;
22 | } else {
23 | pad++;
24 | }
25 | if (i + 2 < data.length) {
26 | b |= (data[i+2] & 0xFF);
27 | } else {
28 | pad++;
29 | }
30 |
31 | for (int j = 0; j < 4 - pad; j++) {
32 | int c = (b & 0xFC0000) >> 18;
33 | buffer.append(tbl[c]);
34 | b <<= 6;
35 | }
36 | }
37 | for (int j = 0; j < pad; j++) {
38 | buffer.append("=");
39 | }
40 |
41 | return buffer.toString();
42 | }
43 |
44 | public static byte[] decode(String data)
45 | {
46 | int[] tbl = {
47 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
48 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
49 | -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
50 | 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2,
51 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
52 | 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30,
53 | 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
54 | 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
55 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
56 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
57 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
58 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
59 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
60 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
61 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
62 | byte[] bytes = data.getBytes();
63 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
64 | for (int i = 0; i < bytes.length; ) {
65 | int b = 0;
66 | if (tbl[bytes[i]] != -1) {
67 | b = (tbl[bytes[i]] & 0xFF) << 18;
68 | }
69 | // skip unknown characters
70 | else {
71 | i++;
72 | continue;
73 | }
74 |
75 | int num = 0;
76 | if (i + 1 < bytes.length && tbl[bytes[i+1]] != -1) {
77 | b = b | ((tbl[bytes[i+1]] & 0xFF) << 12);
78 | num++;
79 | }
80 | if (i + 2 < bytes.length && tbl[bytes[i+2]] != -1) {
81 | b = b | ((tbl[bytes[i+2]] & 0xFF) << 6);
82 | num++;
83 | }
84 | if (i + 3 < bytes.length && tbl[bytes[i+3]] != -1) {
85 | b = b | (tbl[bytes[i+3]] & 0xFF);
86 | num++;
87 | }
88 |
89 | while (num > 0) {
90 | int c = (b & 0xFF0000) >> 16;
91 | buffer.write((char)c);
92 | b <<= 8;
93 | num--;
94 | }
95 | i += 4;
96 | }
97 | return buffer.toByteArray();
98 | }
99 | }
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/util/ClassicPropertyAndEncoder.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.util;
2 |
3 | import ch.qos.logback.classic.PatternLayout;
4 | import ch.qos.logback.classic.spi.ILoggingEvent;
5 | import ch.qos.logback.core.Context;
6 | import ch.qos.logback.core.pattern.PatternLayoutBase;
7 | import com.internetitem.logback.elasticsearch.config.Property;
8 |
9 | public class ClassicPropertyAndEncoder extends AbstractPropertyAndEncoder {
10 |
11 | public ClassicPropertyAndEncoder(Property property, Context context) {
12 | super(property, context);
13 | }
14 |
15 | @Override
16 | protected PatternLayoutBase getLayout() {
17 | return new PatternLayout();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/util/ErrorReporter.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.util;
2 |
3 | import ch.qos.logback.core.Context;
4 | import ch.qos.logback.core.spi.ContextAwareBase;
5 | import com.internetitem.logback.elasticsearch.config.Settings;
6 | import org.slf4j.LoggerFactory;
7 |
8 | public class ErrorReporter extends ContextAwareBase {
9 |
10 | private Settings settings;
11 |
12 | public ErrorReporter(Settings settings, Context context) {
13 | setContext(context);
14 | this.settings = settings;
15 | }
16 |
17 | public void logError(String message, Throwable e) {
18 | String loggerName = settings.getErrorLoggerName();
19 | if (loggerName != null) {
20 | LoggerFactory.getLogger(loggerName).error(message, e);
21 | }
22 | addError(message, e);
23 | }
24 |
25 | public void logWarning(String message) {
26 | String loggerName = settings.getErrorLoggerName();
27 | if (loggerName != null) {
28 | LoggerFactory.getLogger(loggerName).warn(message);
29 | }
30 | addWarn(message);
31 | }
32 |
33 | public void logInfo(String message) {
34 | String loggerName = settings.getErrorLoggerName();
35 | if (loggerName != null) {
36 | LoggerFactory.getLogger(loggerName).info(message);
37 | }
38 | addInfo(message);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/writer/ElasticsearchWriter.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.writer;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.InputStreamReader;
6 | import java.io.OutputStreamWriter;
7 | import java.io.Writer;
8 | import java.net.HttpURLConnection;
9 | import java.util.Collection;
10 | import java.util.Collections;
11 |
12 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeader;
13 | import com.internetitem.logback.elasticsearch.config.HttpRequestHeaders;
14 | import com.internetitem.logback.elasticsearch.config.Settings;
15 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
16 |
17 | public class ElasticsearchWriter implements SafeWriter {
18 |
19 | private StringBuilder sendBuffer;
20 |
21 | private ErrorReporter errorReporter;
22 | private Settings settings;
23 | private Collection headerList;
24 |
25 | private boolean bufferExceeded;
26 |
27 | public ElasticsearchWriter(ErrorReporter errorReporter, Settings settings, HttpRequestHeaders headers) {
28 | this.errorReporter = errorReporter;
29 | this.settings = settings;
30 | this.headerList = headers != null && headers.getHeaders() != null
31 | ? headers.getHeaders()
32 | : Collections.emptyList();
33 |
34 | this.sendBuffer = new StringBuilder();
35 | }
36 |
37 | public void write(char[] cbuf, int off, int len) {
38 | if (bufferExceeded) {
39 | return;
40 | }
41 |
42 | sendBuffer.append(cbuf, off, len);
43 |
44 | if (sendBuffer.length() >= settings.getMaxQueueSize()) {
45 | errorReporter.logWarning("Send queue maximum size exceeded - log messages will be lost until the buffer is cleared");
46 | bufferExceeded = true;
47 | }
48 | }
49 |
50 | public void sendData() throws IOException {
51 | if (sendBuffer.length() <= 0) {
52 | return;
53 | }
54 |
55 | HttpURLConnection urlConnection = (HttpURLConnection)(settings.getUrl().openConnection());
56 | try {
57 | urlConnection.setDoInput(true);
58 | urlConnection.setDoOutput(true);
59 | urlConnection.setReadTimeout(settings.getReadTimeout());
60 | urlConnection.setConnectTimeout(settings.getConnectTimeout());
61 | urlConnection.setRequestMethod("POST");
62 |
63 | String body = sendBuffer.toString();
64 |
65 | if (!headerList.isEmpty()) {
66 | for(HttpRequestHeader header: headerList) {
67 | urlConnection.setRequestProperty(header.getName(), header.getValue());
68 | }
69 | }
70 |
71 | if (settings.getAuthentication() != null) {
72 | settings.getAuthentication().addAuth(urlConnection, body);
73 | }
74 |
75 | Writer writer = new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8");
76 | writer.write(body);
77 | writer.flush();
78 | writer.close();
79 |
80 | int rc = urlConnection.getResponseCode();
81 | if (rc != 200) {
82 | String data = slurpErrors(urlConnection);
83 | throw new IOException("Got response code [" + rc + "] from server with data " + data);
84 | }
85 | } finally {
86 | urlConnection.disconnect();
87 | }
88 |
89 | sendBuffer.setLength(0);
90 | if (bufferExceeded) {
91 | errorReporter.logInfo("Send queue cleared - log messages will no longer be lost");
92 | bufferExceeded = false;
93 | }
94 | }
95 |
96 | public boolean hasPendingData() {
97 | return sendBuffer.length() != 0;
98 | }
99 |
100 | private static String slurpErrors(HttpURLConnection urlConnection) {
101 | try {
102 | InputStream stream = urlConnection.getErrorStream();
103 | if (stream == null) {
104 | return "";
105 | }
106 |
107 | StringBuilder builder = new StringBuilder();
108 | InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
109 | char[] buf = new char[2048];
110 | int numRead;
111 | while ((numRead = reader.read(buf)) > 0) {
112 | builder.append(buf, 0, numRead);
113 | }
114 | return builder.toString();
115 | } catch (Exception e) {
116 | return "";
117 | }
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/writer/LoggerWriter.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.writer;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | public class LoggerWriter implements SafeWriter {
7 |
8 | private String loggerName;
9 |
10 | private Logger logger;
11 |
12 | public LoggerWriter(String loggerName) {
13 | this.loggerName = loggerName;
14 | }
15 |
16 | public void write(char[] cbuf, int off, int len) {
17 | if (logger == null) {
18 | logger = LoggerFactory.getLogger(loggerName);
19 | }
20 | logger.info(new String(cbuf, 0, len));
21 | }
22 |
23 | public void sendData() {
24 | // No-op
25 | }
26 |
27 | public boolean hasPendingData() {
28 | return false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/writer/SafeWriter.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.writer;
2 |
3 | import java.io.IOException;
4 |
5 | public interface SafeWriter {
6 |
7 | void write(char[] cbuf, int off, int len);
8 |
9 | void sendData() throws IOException;
10 |
11 | boolean hasPendingData();
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/internetitem/logback/elasticsearch/writer/StdErrWriter.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch.writer;
2 |
3 | public class StdErrWriter implements SafeWriter {
4 |
5 | public void write(char[] cbuf, int off, int len) {
6 | System.err.println(new String(cbuf, 0, len));
7 | }
8 |
9 | public void sendData() {
10 | // No-op
11 | }
12 |
13 | public boolean hasPendingData() {
14 | return false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/com/internetitem/logback/elasticsearch/ElasticsearchAppenderTest.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 |
4 | import ch.qos.logback.classic.spi.ILoggingEvent;
5 | import ch.qos.logback.core.Context;
6 | import com.internetitem.logback.elasticsearch.config.ElasticsearchProperties;
7 | import com.internetitem.logback.elasticsearch.config.Settings;
8 | import com.internetitem.logback.elasticsearch.util.ErrorReporter;
9 | import org.hamcrest.core.IsInstanceOf;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.runners.MockitoJUnitRunner;
15 |
16 | import java.io.IOException;
17 | import java.net.MalformedURLException;
18 | import java.net.URL;
19 |
20 | import static org.hamcrest.core.Is.is;
21 | import static org.junit.Assert.assertThat;
22 | import static org.mockito.BDDMockito.given;
23 | import static org.mockito.Mockito.*;
24 |
25 | @RunWith(MockitoJUnitRunner.class)
26 | public class ElasticsearchAppenderTest {
27 |
28 |
29 | @Mock
30 | private ClassicElasticsearchPublisher elasticsearchPublisher;
31 | @Mock
32 | private ErrorReporter errorReporter;
33 | @Mock
34 | private Settings settings;
35 | @Mock
36 | private ElasticsearchProperties elasticsearchProperties;
37 | @Mock
38 | private Context mockedContext;
39 |
40 | private boolean publisherSet = false;
41 | private boolean errorReporterSet = false;
42 | private AbstractElasticsearchAppender appender;
43 |
44 | @Before
45 | public void setUp() {
46 |
47 | appender = new ElasticsearchAppender() {
48 | @Override
49 | protected ClassicElasticsearchPublisher buildElasticsearchPublisher() throws IOException {
50 | publisherSet = true;
51 | return elasticsearchPublisher;
52 | }
53 |
54 | @Override
55 | protected ErrorReporter getErrorReporter() {
56 | errorReporterSet = true;
57 | return errorReporter;
58 | }
59 | };
60 | }
61 |
62 | @Test
63 | public void should_set_the_collaborators_when_started() {
64 | appender.start();
65 |
66 |
67 | assertThat(publisherSet, is(true));
68 | assertThat(errorReporterSet, is(true));
69 | }
70 |
71 | @Test
72 | public void should_throw_error_when_publisher_setup_fails_during_startup() {
73 | ElasticsearchAppender appender = new ElasticsearchAppender() {
74 | @Override
75 | protected ClassicElasticsearchPublisher buildElasticsearchPublisher() throws IOException {
76 | throw new IOException("Failed to start Publisher");
77 | }
78 | };
79 |
80 | try {
81 | appender.start();
82 | } catch (Exception e) {
83 | assertThat(e, IsInstanceOf.instanceOf(RuntimeException.class));
84 | assertThat(e.getMessage(), is("java.io.IOException: Failed to start Publisher"));
85 | }
86 |
87 |
88 | }
89 |
90 | @Test
91 | public void should_not_publish_events_when_logger_set() {
92 | String loggerName = "elastic-debug-log";
93 | ILoggingEvent eventToLog = mock(ILoggingEvent.class);
94 | given(eventToLog.getLoggerName()).willReturn(loggerName);
95 |
96 |
97 | appender.setLoggerName(loggerName);
98 | appender.start();
99 |
100 |
101 | appender.append(eventToLog);
102 |
103 | verifyZeroInteractions(elasticsearchPublisher);
104 | }
105 |
106 |
107 | @Test
108 | public void should_not_publish_events_when_errorlogger_set() {
109 | String errorLoggerName = "elastic-error-log";
110 | ILoggingEvent eventToLog = mock(ILoggingEvent.class);
111 | given(eventToLog.getLoggerName()).willReturn(errorLoggerName);
112 |
113 |
114 | appender.setErrorLoggerName(errorLoggerName);
115 | appender.start();
116 |
117 |
118 | appender.append(eventToLog);
119 |
120 | verifyZeroInteractions(elasticsearchPublisher);
121 | }
122 |
123 |
124 | @Test
125 | public void should_publish_events_when_loggername_is_null() {
126 | ILoggingEvent eventToPublish = mock(ILoggingEvent.class);
127 | given(eventToPublish.getLoggerName()).willReturn(null);
128 | String errorLoggerName = "es-error";
129 |
130 | appender.setErrorLoggerName(errorLoggerName);
131 | appender.start();
132 |
133 |
134 | appender.append(eventToPublish);
135 |
136 | verify(elasticsearchPublisher, times(1)).addEvent(eventToPublish);
137 | }
138 |
139 |
140 | @Test
141 | public void should_publish_events_when_loggername_is_different_from_the_elasticsearch_loggers() {
142 | ILoggingEvent eventToPublish = mock(ILoggingEvent.class);
143 | String differentLoggerName = "different-logger";
144 | String errorLoggerName = "es-errors";
145 | given(eventToPublish.getLoggerName()).willReturn(differentLoggerName);
146 |
147 |
148 | appender.setErrorLoggerName(errorLoggerName);
149 | appender.start();
150 |
151 |
152 | appender.append(eventToPublish);
153 |
154 | verify(elasticsearchPublisher, times(1)).addEvent(eventToPublish);
155 | }
156 |
157 | @Test
158 | public void should_create_error_reporter_with_same_context() {
159 | ElasticsearchAppender appender = new ElasticsearchAppender(){
160 | @Override
161 | public Context getContext() {
162 | return mockedContext;
163 | }
164 | };
165 |
166 | ErrorReporter errorReporter = appender.getErrorReporter();
167 |
168 | assertThat(errorReporter.getContext(), is(mockedContext));
169 | }
170 |
171 |
172 | @Test
173 | public void should_delegate_setters_to_settings() throws MalformedURLException {
174 | ElasticsearchAppender appender = new ElasticsearchAppender(settings);
175 | boolean includeCallerData = false;
176 | boolean errorsToStderr = false;
177 | boolean rawJsonMessage = false;
178 | boolean includeMdc = true;
179 | String index = "app-logs";
180 | String type = "appenderType";
181 | int maxQueueSize = 10;
182 | String logger = "es-logger";
183 | String url = "http://myelasticsearch.mycompany.com";
184 | String errorLogger = "es-error-logger";
185 | int maxRetries = 10000;
186 | int aSleepTime = 10000;
187 | int readTimeout = 10000;
188 | int connectTimeout = 5000;
189 |
190 | appender.setIncludeCallerData(includeCallerData);
191 | appender.setSleepTime(aSleepTime);
192 | appender.setReadTimeout(readTimeout);
193 | appender.setErrorsToStderr(errorsToStderr);
194 | appender.setLogsToStderr(errorsToStderr);
195 | appender.setMaxQueueSize(maxQueueSize);
196 | appender.setIndex(index);
197 | appender.setType(type);
198 | appender.setUrl(url);
199 | appender.setLoggerName(logger);
200 | appender.setErrorLoggerName(errorLogger);
201 | appender.setMaxRetries(maxRetries);
202 | appender.setConnectTimeout(connectTimeout);
203 | appender.setRawJsonMessage(rawJsonMessage);
204 | appender.setIncludeMdc(includeMdc);
205 |
206 | verify(settings, times(1)).setReadTimeout(readTimeout);
207 | verify(settings, times(1)).setSleepTime(aSleepTime);
208 | verify(settings, times(1)).setIncludeCallerData(includeCallerData);
209 | verify(settings, times(1)).setErrorsToStderr(errorsToStderr);
210 | verify(settings, times(1)).setLogsToStderr(errorsToStderr);
211 | verify(settings, times(1)).setMaxQueueSize(maxQueueSize);
212 | verify(settings, times(1)).setIndex(index);
213 | verify(settings, times(1)).setType(type);
214 | verify(settings, times(1)).setUrl(new URL(url));
215 | verify(settings, times(1)).setLoggerName(logger);
216 | verify(settings, times(1)).setErrorLoggerName(errorLogger);
217 | verify(settings, times(1)).setMaxRetries(maxRetries);
218 | verify(settings, times(1)).setConnectTimeout(connectTimeout);
219 | verify(settings, times(1)).setRawJsonMessage(rawJsonMessage);
220 | verify(settings, times(1)).setIncludeMdc(includeMdc);
221 | }
222 |
223 |
224 | }
--------------------------------------------------------------------------------
/src/test/java/com/internetitem/logback/elasticsearch/PropertySerializerTest.java:
--------------------------------------------------------------------------------
1 | package com.internetitem.logback.elasticsearch;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import ch.qos.logback.core.Context;
5 | import com.fasterxml.jackson.core.JsonGenerator;
6 | import com.internetitem.logback.elasticsearch.config.Property;
7 | import com.internetitem.logback.elasticsearch.util.ClassicPropertyAndEncoder;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.runners.MockitoJUnitRunner;
12 |
13 | import static org.hamcrest.CoreMatchers.is;
14 | import static org.junit.Assert.assertThat;
15 | import static org.mockito.Mockito.verify;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class PropertySerializerTest {
19 | @Mock
20 | private Context context;
21 |
22 | @Mock
23 | private JsonGenerator jsonGenerator;
24 |
25 | @Mock
26 | private ILoggingEvent loggingEvent;
27 |
28 | private PropertySerializer propertySerializer = new PropertySerializer<>();
29 |
30 | @Test
31 | public void should_default_to_string_type() throws Exception {
32 | // given
33 | Property property = new Property();
34 | property.setValue("propertyValue");
35 |
36 | // when
37 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
38 |
39 | // then
40 | assertThat(property.getType(), is(Property.Type.STRING));
41 | verify(jsonGenerator).writeObject("propertyValue");
42 | }
43 |
44 | @Test
45 | public void should_serialize_int_as_number() throws Exception {
46 | // given
47 | Property property = new Property();
48 | property.setValue("123");
49 | property.setType("int");
50 |
51 | // when
52 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
53 |
54 | // then
55 | verify(jsonGenerator).writeNumber(123);
56 | }
57 |
58 | @Test
59 | public void should_serialize_object_when_invalid_int() throws Exception {
60 | // given
61 | Property property = new Property();
62 | property.setValue("A123Z");
63 | property.setType("int");
64 |
65 | // when
66 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
67 |
68 | // then
69 | verify(jsonGenerator).writeObject("A123Z");
70 | }
71 |
72 | @Test
73 | public void should_serialize_float_as_number() throws Exception {
74 | // given
75 | Property property = new Property();
76 | property.setValue("12.30");
77 | property.setType("float");
78 |
79 | // when
80 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
81 |
82 | // then
83 | verify(jsonGenerator).writeNumber(12.30f);
84 | }
85 |
86 | @Test
87 | public void should_serialize_object_when_invalid_float() throws Exception {
88 | // given
89 | Property property = new Property();
90 | property.setValue("A12.30Z");
91 | property.setType("float");
92 |
93 | // when
94 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
95 |
96 | // then
97 | verify(jsonGenerator).writeObject("A12.30Z");
98 | }
99 |
100 | @Test
101 | public void should_serialize_true_as_boolean() throws Exception {
102 | // given
103 | Property property = new Property();
104 | property.setValue("true");
105 | property.setType("boolean");
106 |
107 | // when
108 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
109 |
110 | // then
111 | verify(jsonGenerator).writeBoolean(true);
112 | }
113 |
114 | @Test
115 | public void should_serialize_object_when_invalid_boolean() throws Exception {
116 | // given
117 | Property property = new Property();
118 | property.setValue("AtrueZ");
119 | property.setType("boolean");
120 |
121 | // when
122 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
123 |
124 | // then
125 | verify(jsonGenerator).writeObject("AtrueZ");
126 | }
127 |
128 | @Test
129 | public void should_serialize_object_when_invalid_type() throws Exception {
130 | // given
131 | Property property = new Property();
132 | property.setValue("value");
133 | property.setType("invalidType");
134 |
135 | // when
136 | propertySerializer.serializeProperty(jsonGenerator, loggingEvent, new ClassicPropertyAndEncoder(property, context));
137 |
138 | // then
139 | verify(jsonGenerator).writeObject("value");
140 | }
141 | }
--------------------------------------------------------------------------------