Skip to content

Commit 17e9f29

Browse files
authored
Add support for additional fields for log4j, JUL, and JBoss Log Manager (#116)
1 parent 9257b26 commit 17e9f29

File tree

21 files changed

+343
-78
lines changed

21 files changed

+343
-78
lines changed

docs/tab-widgets/ecs-encoder.asciidoc

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,20 @@ Note that this requires a slightly more complex <<setup-stack-trace-as-array, Fi
7373
|`false`
7474
|If `true`, adds the {ecs-ref}/ecs-log.html[`log.origin.file.name`],
7575
{ecs-ref}/ecs-log.html[`log.origin.file.line`] and {ecs-ref}/ecs-log.html[`log.origin.function`] fields.
76-
Note that you also have to set `includeLocation="true"` on your loggers and appenders if you are using the async ones.
76+
Note that you also have to set `<includeCallerData>true</includeCallerData>` on your appenders if you are using the async ones.
7777
|===
7878

7979
To include any custom field in the output, use following syntax:
8080

8181
[source,xml]
8282
----
8383
<additionalField>
84-
<key>foo</key>
85-
<value>bar</value>
84+
<key>key1</key>
85+
<value>value1</value>
8686
</additionalField>
8787
<additionalField>
88-
<key>baz</key>
89-
<value>qux</value>
88+
<key>key2</key>
89+
<value>value2</value>
9090
</additionalField>
9191
----
9292

@@ -156,8 +156,8 @@ To include any custom field in the output, use following syntax:
156156
[source,xml]
157157
----
158158
<EcsLayout>
159-
<KeyValuePair key="additionalField1" value="constant value"/>
160-
<KeyValuePair key="additionalField2" value="$${ctx:key}"/>
159+
<KeyValuePair key="key1" value="constant value"/>
160+
<KeyValuePair key="key2" value="$${ctx:key}"/>
161161
</EcsLayout>
162162
----
163163

@@ -178,15 +178,15 @@ For example:
178178
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
179179
<appender name="LogToConsole" class="org.apache.log4j.ConsoleAppender">
180180
<param name="Target" value="System.out"/>
181-
<layout class="co.elastic.logging.log4j.EcsLayout">
182-
<param name="serviceName" value="my-app"/>
183-
</layout>
181+
<layout class="co.elastic.logging.log4j.EcsLayout">
182+
<param name="serviceName" value="my-app"/>
183+
</layout>
184184
</appender>
185185
<appender name="LogToFile" class="org.apache.log4j.RollingFileAppender">
186186
<param name="File" value="logs/app.log"/>
187-
<layout class="co.elastic.logging.log4j.EcsLayout">
188-
<param name="serviceName" value="my-app"/>
189-
</layout>
187+
<layout class="co.elastic.logging.log4j.EcsLayout">
188+
<param name="serviceName" value="my-app"/>
189+
</layout>
190190
</appender>
191191
<root>
192192
<priority value="INFO"/>
@@ -195,6 +195,48 @@ For example:
195195
</root>
196196
</log4j:configuration>
197197
----
198+
199+
200+
**Layout Parameters**
201+
202+
|===
203+
|Parameter name |Type |Default |Description
204+
205+
|`serviceName`
206+
|String
207+
|
208+
|Sets the `service.name` field so you can filter your logs by a particular service
209+
210+
|`eventDataset`
211+
|String
212+
|`${serviceName}.log`
213+
|Sets the `event.dataset` field used by the machine learning job of the Logs app to look for anomalies in the log rate.
214+
215+
|`stackTraceAsArray`
216+
|boolean
217+
|`false`
218+
|Serializes the {ecs-ref}/ecs-error.html[`error.stack_trace`] as a JSON array where each element is in a new line to improve readability.
219+
Note that this requires a slightly more complex <<setup-stack-trace-as-array, Filebeat configuration>>.
220+
221+
|`includeOrigin`
222+
|boolean
223+
|`false`
224+
|If `true`, adds the {ecs-ref}/ecs-log.html[`log.origin.file.name`],
225+
{ecs-ref}/ecs-log.html[`log.origin.file.line`] and {ecs-ref}/ecs-log.html[`log.origin.function`] fields.
226+
Note that you also have to set `<param name="LocationInfo" value="true"/>Info` if you are using `AsyncAppender`.
227+
|===
228+
229+
To include any custom field in the output, use following syntax:
230+
231+
[source,xml]
232+
----
233+
<layout class="co.elastic.logging.log4j.EcsLayout">
234+
<param name="additionalField" value="key1=value1"/>
235+
<param name="additionalField" value="key2=value2"/>
236+
</layout>
237+
----
238+
239+
Custom fields are included in the order they are declared.
198240
// end::log4j[]
199241

200242
// tag::jul[]
@@ -235,6 +277,13 @@ co.elastic.logging.jul.EcsFormatter.serviceName=my-app
235277
|If `true`, adds the {ecs-ref}/ecs-log.html[`log.origin.file.name`],
236278
{ecs-ref}/ecs-log.html[`log.origin.file.line`] and {ecs-ref}/ecs-log.html[`log.origin.function`] fields.
237279
Note that JUL does not stores line number and `log.origin.file.line` will have '1' value.
280+
281+
|`additionalFields`
282+
|String
283+
|
284+
|Adds additional static fields to all log events.
285+
The fields are specified as comma-separated key-value pairs.
286+
Example: `co.elastic.logging.jul.EcsFormatter.additionalFields=key1=value1,key2=value2`.
238287
|===
239288
// end::jul[]
240289

@@ -281,5 +330,12 @@ $WILDFLY_HOME/bin/jboss-cli.sh -c '/subsystem=logging/custom-formatter=ECS:add(m
281330
|`false`
282331
|If `true`, adds the {ecs-ref}/ecs-log.html[`log.origin.file.name`],
283332
{ecs-ref}/ecs-log.html[`log.origin.file.line`] and {ecs-ref}/ecs-log.html[`log.origin.function`] fields.
333+
334+
|`additionalFields`
335+
|String
336+
|
337+
|Adds additional static fields to all log events.
338+
The fields are specified as comma-separated key-value pairs.
339+
Example: `additionalFields=key1=value1,key2=value2`.
284340
|===
285341
// end::jboss[]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*-
2+
* #%L
3+
* Java ECS logging
4+
* %%
5+
* Copyright (C) 2019 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.logging;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
public class AdditionalField {
31+
private String key;
32+
private String value = "";
33+
34+
public AdditionalField() {
35+
}
36+
37+
public AdditionalField(String key, String value) {
38+
this.key = key;
39+
this.value = value;
40+
}
41+
42+
public static List<AdditionalField> parse(String additionalFields) {
43+
String[] split = additionalFields.split(",");
44+
ArrayList<AdditionalField> result = new ArrayList<AdditionalField>(split.length);
45+
for (String additionalField : split) {
46+
AdditionalField field = of(additionalField);
47+
result.add(field);
48+
}
49+
return result;
50+
}
51+
52+
public static AdditionalField of(String additionalField) {
53+
String[] keyValue = additionalField.split("=");
54+
if (keyValue.length != 2) {
55+
throw new IllegalArgumentException("Could not parse " + additionalField);
56+
}
57+
return new AdditionalField(keyValue[0].trim(), keyValue[1].trim());
58+
}
59+
60+
public String getKey() {
61+
return key;
62+
}
63+
64+
public void setKey(String key) {
65+
this.key = key;
66+
}
67+
68+
public String getValue() {
69+
return value;
70+
}
71+
72+
public void setValue(String value) {
73+
this.value = value;
74+
}
75+
}

ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import java.io.PrintWriter;
2828
import java.io.Writer;
29+
import java.util.List;
2930
import java.util.Map;
3031
import java.util.regex.Pattern;
3132

@@ -282,6 +283,21 @@ public static String computeEventDataset(String eventDataset, String serviceName
282283
return eventDataset;
283284
}
284285

286+
public static void serializeAdditionalFields(StringBuilder builder, List<AdditionalField> additionalFields) {
287+
if (!additionalFields.isEmpty()) {
288+
for (int i = 0, size = additionalFields.size(); i < size; i++) {
289+
AdditionalField additionalField = additionalFields.get(i);
290+
if (additionalField.getKey() != null) {
291+
builder.append('\"');
292+
JsonUtils.quoteAsString(additionalField.getKey(), builder);
293+
builder.append("\":\"");
294+
JsonUtils.quoteAsString(additionalField.getValue(), builder);
295+
builder.append("\",");
296+
}
297+
}
298+
}
299+
}
300+
285301
private static class StringBuilderWriter extends Writer {
286302

287303
private final StringBuilder buffer;

ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ void testMetadata() throws Exception {
6767
validateLog(getAndValidateLastLogLine());
6868
}
6969

70+
@Test
71+
final void testAdditionalFields() throws Exception {
72+
debug("test");
73+
assertThat(getAndValidateLastLogLine().get("key1").textValue()).isEqualTo("value1");
74+
assertThat(getAndValidateLastLogLine().get("key2").textValue()).isEqualTo("value2");
75+
validateLog(getAndValidateLastLogLine());
76+
}
77+
7078
@Test
7179
void testSimpleLog() throws Exception {
7280
debug("test");

jboss-logmanager-ecs-formatter/src/main/java/co/elastic/logging/jboss/logmanager/EcsFormatter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,20 @@
2525
package co.elastic.logging.jboss.logmanager;
2626

2727
import co.elastic.logging.EcsJsonSerializer;
28+
import co.elastic.logging.AdditionalField;
2829
import org.jboss.logmanager.ExtFormatter;
2930
import org.jboss.logmanager.ExtLogRecord;
3031
import org.jboss.logmanager.LogManager;
3132

33+
import java.util.ArrayList;
34+
import java.util.Collections;
35+
import java.util.List;
36+
3237
public class EcsFormatter extends ExtFormatter {
3338

3439
private String serviceName;
3540
private String eventDataset;
41+
private List<AdditionalField> additionalFields = Collections.emptyList();
3642
private boolean includeOrigin;
3743
private boolean stackTraceAsArray;
3844

@@ -55,6 +61,7 @@ public String format(ExtLogRecord record) {
5561
EcsJsonSerializer.serializeEventDataset(builder, eventDataset);
5662
EcsJsonSerializer.serializeThreadName(builder, record.getThreadName());
5763
EcsJsonSerializer.serializeLoggerName(builder, record.getLoggerName());
64+
EcsJsonSerializer.serializeAdditionalFields(builder, additionalFields);
5865
EcsJsonSerializer.serializeMDC(builder, record.getMdcCopy());
5966
String ndc = record.getNdc();
6067
if (ndc != null && !ndc.isEmpty()) {
@@ -92,6 +99,10 @@ public void setEventDataset(String eventDataset) {
9299
this.eventDataset = eventDataset;
93100
}
94101

102+
public void setAdditionalFields(String additionalFields) {
103+
this.additionalFields = AdditionalField.parse(additionalFields);
104+
}
105+
95106
private String getProperty(final String name, final String defaultValue) {
96107
String value = LogManager.getLogManager().getProperty(name);
97108
if (value == null) {

jboss-logmanager-ecs-formatter/src/test/java/co/elastic/logging/jboss/logmanager/JBossLogManagerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ void setUp() {
9191
formatter.setStackTraceAsArray(true);
9292
formatter.setServiceName("test");
9393
formatter.setEventDataset("testdataset.log");
94+
formatter.setAdditionalFields("key1=value1,key2=value2");
9495

9596
logger.setLevel(Level.ALL);
9697
logger.addHandler(new StreamHandler(byteArrayOutputStream, formatter) {

jul-ecs-formatter/src/main/java/co/elastic/logging/jul/EcsFormatter.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
*/
2525
package co.elastic.logging.jul;
2626

27+
import java.util.Collections;
28+
import java.util.List;
2729
import java.util.logging.Formatter;
2830
import java.util.logging.LogManager;
2931
import java.util.logging.LogRecord;
3032

33+
import co.elastic.logging.AdditionalField;
3134
import co.elastic.logging.EcsJsonSerializer;
3235

3336
public class EcsFormatter extends Formatter {
@@ -39,6 +42,7 @@ public class EcsFormatter extends Formatter {
3942
private String serviceName;
4043
private boolean includeOrigin;
4144
private String eventDataset;
45+
private List<AdditionalField> additionalFields = Collections.emptyList();
4246

4347
/**
4448
* Default constructor. Will read configuration from LogManager properties.
@@ -59,6 +63,7 @@ public String format(final LogRecord record) {
5963
EcsJsonSerializer.serializeLogLevel(builder, record.getLevel().getName());
6064
EcsJsonSerializer.serializeFormattedMessage(builder, super.formatMessage(record));
6165
EcsJsonSerializer.serializeEcsVersion(builder);
66+
EcsJsonSerializer.serializeAdditionalFields(builder, additionalFields);
6267
EcsJsonSerializer.serializeMDC(builder, mdcSupplier.getMDC());
6368
EcsJsonSerializer.serializeServiceName(builder, serviceName);
6469
EcsJsonSerializer.serializeEventDataset(builder, eventDataset);
@@ -95,6 +100,10 @@ public void setEventDataset(String eventDataset) {
95100
this.eventDataset = eventDataset;
96101
}
97102

103+
public void setAdditionalFields(String additionalFields) {
104+
this.additionalFields = AdditionalField.parse(additionalFields);
105+
}
106+
98107
private String getProperty(final String name, final String defaultValue) {
99108
String value = LogManager.getLogManager().getProperty(name);
100109
if (value == null) {
@@ -104,7 +113,7 @@ private String getProperty(final String name, final String defaultValue) {
104113
}
105114
return value;
106115
}
107-
116+
108117
private String buildFileName(String className) {
109118
String result = UNKNOWN_FILE;
110119
if (className != null) {
@@ -119,5 +128,4 @@ private String buildFileName(String className) {
119128
}
120129
return result;
121130
}
122-
123131
}

jul-ecs-formatter/src/test/java/co/elastic/logging/jul/EcsFormatterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class EcsFormatterTest {
3939

4040
private final EcsFormatter formatter = new EcsFormatter();
4141

42-
private final LogRecord record = new LogRecord(Level.INFO, "Example Meesage");
42+
private final LogRecord record = new LogRecord(Level.INFO, "Example Message");
4343
private final ObjectMapper objectMapper = new ObjectMapper();
4444

4545
@BeforeEach

jul-ecs-formatter/src/test/java/co/elastic/logging/jul/JulLoggingTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ void setUp() {
116116
formatter.setStackTraceAsArray(true);
117117
formatter.setServiceName("test");
118118
formatter.setEventDataset("testdataset.log");
119+
formatter.setAdditionalFields("key1=value1,key2=value2");
119120

120121
Handler handler = new InMemoryStreamHandler(out, formatter);
121122
handler.setLevel(Level.ALL);

0 commit comments

Comments
 (0)