Skip to content

Commit 2802b73

Browse files
Implemented ECS formatter for JBoss Logmanager (#75)
1 parent 2f5bbb8 commit 2802b73

File tree

7 files changed

+436
-0
lines changed

7 files changed

+436
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# ECS formatter for JBoss Log Manager
2+
3+
Formatter for JBoss Log Manager which produce ECS-compatible records. May be useful for applications which use JBoss Log Manager as primary logging framework (e.g. WildFly).
4+
5+
## Step 1: add dependency
6+
7+
Latest version: [![Maven Central](https://img.shields.io/maven-central/v/co.elastic.logging/jboss-logmanager-ecs-formatter.svg)](https://search.maven.org/search?q=g:co.elastic.logging%20AND%20a:jboss-logmanager-ecs-formatter)
8+
9+
Add a dependency to your application
10+
```xml
11+
<dependency>
12+
<groupId>co.elastic.logging</groupId>
13+
<artifactId>jboss-logmanager-ecs-formatter</artifactId>
14+
<version>${ecs-logging-java.version}</version>
15+
</dependency>
16+
```
17+
If you are not using a dependency management tool, like maven, you have to add both, `jboss-logmanager-ecs-formatter` and `ecs-logging-core` jars manually to the classpath.
18+
19+
## Step 2: use the `EcsFormatter`
20+
21+
Specify `co.elastic.logging.jboss.logmanager.EcsFormatter` as `formatter` for the required log handler.
22+
23+
## Example (WildFly)
24+
25+
Create a jboss-logmanager-ecs-formatter` module
26+
27+
```shell script
28+
$WILDFLY_HOME/bin/jboss-cli.sh -c 'module add --name=co.elastic.logging.jboss-logmanager-ecs-formatter --resources=jboss-logmanager-ecs-formatter-${ecs-logging-java.version}.jar:/tmp/ecs-logging-core-${ecs-logging-java.version}.jar --dependencies=org.jboss.logmanager'
29+
```
30+
31+
Add the formatter to a handler in the logging subsystem
32+
33+
```shell script
34+
$WILDFLY_HOME/bin/jboss-cli.sh -c '/subsystem=logging/custom-formatter=ECS:add(module=co.elastic.logging.jboss-logmanager-ecs-formatter, class=co.elastic.logging.jboss.logmanager.EcsFormatter, properties={serviceName=my-app}),\
35+
/subsystem=logging/console-handler=CONSOLE:write-attribute(name=named-formatter,value=ECS)'
36+
```
37+
38+
## Layout Parameters
39+
40+
|Parameter name |Type |Default|Description|
41+
|-----------------|-------|-------|-----------|
42+
|serviceName |String | |Sets the `service.name` field so you can filter your logs by a particular service |
43+
|eventDataset |String |`${serviceName}.log`|Sets the `event.dataset` field used by the machine learning job of the Logs app to look for anomalies in the log rate. |
44+
|stackTraceAsArray|boolean|`false`|Serializes the [`error.stack_trace`](https://www.elastic.co/guide/en/ecs/current/ecs-error.html) as a JSON array where each element is in a new line to improve readability. Note that this requires a slightly more complex [Filebeat configuration](../README.md#when-stacktraceasarray-is-enabled).|
45+
|includeOrigin |boolean|`false`|If `true`, adds the [`log.origin.file.name`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html), [`log.origin.file.line`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html) and [`log.origin.function`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html) fields. |
46+
47+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<parent>
4+
<artifactId>ecs-logging-java-parent</artifactId>
5+
<groupId>co.elastic.logging</groupId>
6+
<version>0.4.1-SNAPSHOT</version>
7+
</parent>
8+
<modelVersion>4.0.0</modelVersion>
9+
10+
<properties>
11+
<parent.base.dir>${project.basedir}/..</parent.base.dir>
12+
</properties>
13+
14+
<artifactId>jboss-logmanager-ecs-formatter</artifactId>
15+
<name>${project.groupId}:${project.artifactId}</name>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.jboss.logmanager</groupId>
20+
<artifactId>jboss-logmanager</artifactId>
21+
<version>2.1.15.Final</version>
22+
<scope>provided</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>co.elastic.logging</groupId>
26+
<artifactId>ecs-logging-core</artifactId>
27+
<version>${project.version}</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>${project.groupId}</groupId>
31+
<artifactId>ecs-logging-core</artifactId>
32+
<version>${project.version}</version>
33+
<type>test-jar</type>
34+
<scope>test</scope>
35+
</dependency>
36+
</dependencies>
37+
</project>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.jboss.logmanager;
26+
27+
import co.elastic.logging.EcsJsonSerializer;
28+
import org.jboss.logmanager.ExtFormatter;
29+
import org.jboss.logmanager.ExtLogRecord;
30+
import org.jboss.logmanager.LogManager;
31+
32+
public class EcsFormatter extends ExtFormatter {
33+
34+
private String serviceName;
35+
private String eventDataset;
36+
private boolean includeOrigin;
37+
private boolean stackTraceAsArray;
38+
39+
public EcsFormatter() {
40+
serviceName = getProperty("co.elastic.logging.jboss.logmanager.EcsFormatter.serviceName", null);
41+
eventDataset = getProperty("co.elastic.logging.jboss.logmanager.EcsFormatter.eventDataset", null);
42+
eventDataset = EcsJsonSerializer.computeEventDataset(eventDataset, serviceName);
43+
includeOrigin = Boolean.getBoolean(getProperty("co.elastic.logging.jboss.logmanager.EcsFormatter.includeOrigin", "false"));
44+
stackTraceAsArray = Boolean.getBoolean(getProperty("co.elastic.logging.jboss.logmanager.EcsFormatter.stackTraceAsArray", "false"));
45+
}
46+
47+
@Override
48+
public String format(ExtLogRecord record) {
49+
StringBuilder builder = new StringBuilder();
50+
EcsJsonSerializer.serializeObjectStart(builder, record.getMillis());
51+
EcsJsonSerializer.serializeLogLevel(builder, record.getLevel().getName());
52+
EcsJsonSerializer.serializeFormattedMessage(builder, record.getFormattedMessage());
53+
EcsJsonSerializer.serializeServiceName(builder, serviceName);
54+
EcsJsonSerializer.serializeEventDataset(builder, eventDataset);
55+
EcsJsonSerializer.serializeThreadName(builder, record.getThreadName());
56+
EcsJsonSerializer.serializeLoggerName(builder, record.getLoggerName());
57+
EcsJsonSerializer.serializeMDC(builder, record.getMdcCopy());
58+
String ndc = record.getNdc();
59+
if (ndc != null && !ndc.isEmpty()) {
60+
EcsJsonSerializer.serializeTagStart(builder);
61+
for (String tag : ndc.split("\\.")) {
62+
EcsJsonSerializer.serializeSingleTag(builder, tag);
63+
}
64+
EcsJsonSerializer.serializeTagEnd(builder);
65+
}
66+
if (includeOrigin && record.getSourceFileName() != null && record.getSourceMethodName() != null) {
67+
EcsJsonSerializer.serializeOrigin(builder, record.getSourceFileName(), record.getSourceMethodName(), record.getSourceLineNumber());
68+
}
69+
Throwable throwable = record.getThrown();
70+
if (throwable != null) {
71+
EcsJsonSerializer.serializeException(builder, throwable, stackTraceAsArray);
72+
}
73+
EcsJsonSerializer.serializeObjectEnd(builder);
74+
return builder.toString();
75+
}
76+
77+
public void setIncludeOrigin(final boolean includeOrigin) {
78+
this.includeOrigin = includeOrigin;
79+
}
80+
81+
public void setServiceName(final String serviceName) {
82+
this.serviceName = serviceName;
83+
eventDataset = EcsJsonSerializer.computeEventDataset(eventDataset, serviceName);
84+
}
85+
86+
public void setStackTraceAsArray(final boolean stackTraceAsArray) {
87+
this.stackTraceAsArray = stackTraceAsArray;
88+
}
89+
90+
public void setEventDataset(String eventDataset) {
91+
this.eventDataset = eventDataset;
92+
}
93+
94+
private String getProperty(final String name, final String defaultValue) {
95+
String value = LogManager.getLogManager().getProperty(name);
96+
if (value == null) {
97+
value = defaultValue;
98+
} else {
99+
value = value.trim();
100+
}
101+
return value;
102+
}
103+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.jboss.logmanager;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
import java.io.PrintWriter;
30+
import java.time.Instant;
31+
import java.util.Map;
32+
33+
import org.jboss.logmanager.ExtLogRecord;
34+
import org.jboss.logmanager.Level;
35+
import org.junit.jupiter.api.BeforeEach;
36+
import org.junit.jupiter.api.Test;
37+
38+
class EcsFormatterTest {
39+
40+
private final EcsFormatter formatter = new EcsFormatter();
41+
42+
private final ExtLogRecord record = new ExtLogRecord(Level.INFO, "Example Message", "ExampleLoggerClass");
43+
44+
@BeforeEach
45+
void setUp() {
46+
record.setInstant(Instant.ofEpochMilli(5));
47+
record.setLoggerName("ExampleLogger");
48+
record.setSourceFileName("ExampleSourceFile.java");
49+
record.setSourceClassName("ExampleSourceClass");
50+
record.setSourceMethodName("exampleSourceMethod");
51+
record.setSourceLineNumber(10);
52+
record.setThreadName("ExampleThread");
53+
}
54+
55+
@Test
56+
void testSingleNDCInformationLogging() {
57+
record.setNdc("exampleTag");
58+
59+
assertThat(formatter.format(record)).isEqualTo("{" +
60+
"\"@timestamp\":\"1970-01-01T00:00:00.005Z\", " +
61+
"\"log.level\": \"INFO\", " +
62+
"\"message\":\"Example Message\", " +
63+
"\"process.thread.name\":\"ExampleThread\"," +
64+
"\"log.logger\":\"ExampleLogger\"," +
65+
"\"tags\":[\"exampleTag\"]" +
66+
"}\n");
67+
}
68+
69+
@Test
70+
void testMultipleNDCInformationLogging() {
71+
record.setNdc("exampleTag1.exampleTag2");
72+
73+
assertThat(formatter.format(record)).isEqualTo("{" +
74+
"\"@timestamp\":\"1970-01-01T00:00:00.005Z\", " +
75+
"\"log.level\": \"INFO\", " +
76+
"\"message\":\"Example Message\", " +
77+
"\"process.thread.name\":\"ExampleThread\"," +
78+
"\"log.logger\":\"ExampleLogger\"," +
79+
"\"tags\":[\"exampleTag1\",\"exampleTag2\"]" +
80+
"}\n");
81+
}
82+
83+
@Test
84+
void testExceptionLogging() {
85+
record.setThrown(new RuntimeException("Example Exception Message") {
86+
@Override
87+
public void printStackTrace(PrintWriter pw) {
88+
pw.println("co.elastic.logging.jboss.logmanager.EcsFormatterTest$1: Example Exception Message");
89+
pw.println("\tat co.elastic.logging.jboss.logmanager.EcsFormatterTest.testExceptionLogging(EcsFormatterTest.java:125)");
90+
}
91+
});
92+
93+
assertThat(formatter.format(record)).isEqualTo("{" +
94+
"\"@timestamp\":\"1970-01-01T00:00:00.005Z\", " +
95+
"\"log.level\": \"INFO\", " +
96+
"\"message\":\"Example Message\", " +
97+
"\"process.thread.name\":\"ExampleThread\"," +
98+
"\"log.logger\":\"ExampleLogger\"," +
99+
"\"error.type\":\"co.elastic.logging.jboss.logmanager.EcsFormatterTest$1\"," +
100+
"\"error.message\":\"Example Exception Message\"," +
101+
"\"error.stack_trace\":\"co.elastic.logging.jboss.logmanager.EcsFormatterTest$1: Example Exception Message\\n\\tat co.elastic.logging.jboss.logmanager.EcsFormatterTest.testExceptionLogging(EcsFormatterTest.java:125)\\n\"" +
102+
"}\n");
103+
}
104+
}
105+

0 commit comments

Comments
 (0)