Skip to content

Commit 74e11f0

Browse files
authored
Add includeOrigin flag (#31)
depends on elastic/ecs#563 closes #3
1 parent 33ae6d4 commit 74e11f0

File tree

14 files changed

+106
-22
lines changed

14 files changed

+106
-22
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Example:
2020
{"@timestamp":"2019-08-06T12:09:12.375Z", "log.level": "INFO", "message":"Tomcat started on port(s): 8080 (http) with context path ''", "service.name":"spring-petclinic","process.thread.name":"restartedMain","log.logger":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer"}
2121
{"@timestamp":"2019-08-06T12:09:12.379Z", "log.level": "INFO", "message":"Started PetClinicApplication in 7.095 seconds (JVM running for 9.082)", "service.name":"spring-petclinic","process.thread.name":"restartedMain","log.logger":"org.springframework.samples.petclinic.PetClinicApplication"}
2222
{"@timestamp":"2019-08-06T14:08:40.199Z", "log.level":"DEBUG", "message":"init find form", "service.name":"spring-petclinic","process.thread.name":"http-nio-8080-exec-8","log.logger":"org.springframework.samples.petclinic.owner.OwnerController","transaction.id":"28b7fb8d5aba51f1","trace.id":"2869b25b5469590610fea49ac04af7da"}
23-
{"@timestamp":"2019-09-17T13:16:48.038Z", "log.level":"ERROR", "message":"Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Expected: controller used to showcase what happens when an exception is thrown] with root cause", "process.thread.name":"http-nio-8080-exec-1","log.logger":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]","log.origin":{"file":"DirectJDKLog.java","function":"log","line":175},"error.code":"java.lang.RuntimeException","error.message":"Expected: controller used to showcase what happens when an exception is thrown","error.stack_trace":[
23+
{"@timestamp":"2019-09-17T13:16:48.038Z", "log.level":"ERROR", "message":"Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Expected: controller used to showcase what happens when an exception is thrown] with root cause", "process.thread.name":"http-nio-8080-exec-1","log.logger":"org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]","log.origin":{"file.name":"DirectJDKLog.java","function":"log","file.line":175},"error.code":"java.lang.RuntimeException","error.message":"Expected: controller used to showcase what happens when an exception is thrown","error.stack_trace":[
2424
"java.lang.RuntimeException: Expected: controller used to showcase what happens when an exception is thrown",
2525
"\tat org.springframework.samples.petclinic.system.CrashController.triggerException(CrashController.java:33)",
2626
"\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
@@ -81,6 +81,9 @@ We recommend using this library to log into a JSON log file and let Filebeat sen
8181
|[`@timestamp`](https://www.elastic.co/guide/en/ecs/current/ecs-base.html) | [`LogEvent#getTimeMillis()`](https://logging.apache.org/log4j/log4j-2.3/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getTimeMillis()) |
8282
|[`log.level`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html) | [`LogEvent#getLevel()`](https://logging.apache.org/log4j/log4j-2.3/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getLevel()) |
8383
|[`log.logger`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html)|[`LogEvent#getLoggerName()`](https://logging.apache.org/log4j/log4j-2.3/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getLoggerName())|
84+
|[`log.origin.file.name`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html)|[StackTraceElement#getFileName()](https://docs.oracle.com/javase/6/docs/api/java/lang/StackTraceElement.html#getFileName())|
85+
|[`log.origin.file.line`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html)|[`[StackTraceElement#getLineNumber()]()`](https://docs.oracle.com/javase/6/docs/api/java/lang/StackTraceElement.html#getLineNumber())|
86+
|[`log.origin.function`](https://www.elastic.co/guide/en/ecs/current/ecs-log.html)|[`[StackTraceElement#getMethodName()]()`](https://docs.oracle.com/javase/6/docs/api/java/lang/StackTraceElement.html#getMethodName())|
8487
|[`message`](https://www.elastic.co/guide/en/ecs/current/ecs-base.html)|[`LogEvent#getMessage()`](https://logging.apache.org/log4j/log4j-2.3/log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getMessage())|
8588
|[`error.code`](https://www.elastic.co/guide/en/ecs/current/ecs-error.html)|[`Throwable#getClass()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#getClass())|
8689
|[`error.message`](https://www.elastic.co/guide/en/ecs/current/ecs-error.html)|[`Throwable#getStackTrace()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#getMessage())|

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,25 @@ public static void serializeTagEnd(StringBuilder builder) {
119119
builder.append("],");
120120
}
121121

122+
public static void serializeOrigin(StringBuilder builder, StackTraceElement stackTraceElement) {
123+
if (stackTraceElement != null) {
124+
serializeOrigin(builder, stackTraceElement.getFileName(), stackTraceElement.getMethodName(), stackTraceElement.getLineNumber());
125+
}
126+
}
127+
128+
public static void serializeOrigin(StringBuilder builder, String fileName, String methodName, int lineNumber) {
129+
builder.append("\"log.origin\":{");
130+
builder.append("\"file.name\":\"");
131+
JsonUtils.quoteAsString(fileName, builder);
132+
builder.append("\",");
133+
builder.append("\"function\":\"");
134+
JsonUtils.quoteAsString(methodName, builder);
135+
builder.append("\",");
136+
builder.append("\"file.line\":");
137+
builder.append(lineNumber);
138+
builder.append("},");
139+
}
140+
122141
public static void serializeLabels(StringBuilder builder, Map<String, ?> labels, Set<String> topLevelLabels) {
123142
if (!labels.isEmpty()) {
124143
for (Map.Entry<String, ?> entry : labels.entrySet()) {

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
@@ -93,6 +93,14 @@ void testLogException() throws Exception {
9393
assertThat(stackTrace).contains("at co.elastic.logging.AbstractEcsLoggingTest.testLogException");
9494
}
9595

96+
@Test
97+
void testLogOrigin() throws Exception {
98+
debug("test");
99+
assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
100+
assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
101+
assertThat(getLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
102+
}
103+
96104
public abstract void putMdc(String key, String value);
97105

98106
public boolean putNdc(String message) {

log4j-ecs-layout/src/main/java/co/elastic/logging/log4j/EcsLayout.java

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

2727
import co.elastic.logging.EcsJsonSerializer;
2828
import org.apache.log4j.Layout;
29+
import org.apache.log4j.spi.LocationInfo;
2930
import org.apache.log4j.spi.LoggingEvent;
3031
import org.apache.log4j.spi.ThrowableInformation;
3132

@@ -37,6 +38,7 @@ public class EcsLayout extends Layout {
3738
private boolean stackTraceAsArray = false;
3839
private String serviceName;
3940
private Set<String> topLevelLabels = new HashSet<String>(EcsJsonSerializer.DEFAULT_TOP_LEVEL_LABELS);
41+
private boolean includeOrigin;
4042

4143
@Override
4244
public String format(LoggingEvent event) {
@@ -49,6 +51,12 @@ public String format(LoggingEvent event) {
4951
EcsJsonSerializer.serializeLoggerName(builder, event.getLoggerName());
5052
EcsJsonSerializer.serializeLabels(builder, event.getProperties(), topLevelLabels);
5153
EcsJsonSerializer.serializeTag(builder, event.getNDC());
54+
if (includeOrigin) {
55+
LocationInfo locationInformation = event.getLocationInformation();
56+
if (locationInformation != null) {
57+
EcsJsonSerializer.serializeOrigin(builder, locationInformation.getFileName(), locationInformation.getMethodName(), getLineNumber(locationInformation));
58+
}
59+
}
5260
ThrowableInformation throwableInformation = event.getThrowableInformation();
5361
if (throwableInformation != null) {
5462
EcsJsonSerializer.serializeException(builder, throwableInformation.getThrowable(), stackTraceAsArray);
@@ -57,6 +65,19 @@ public String format(LoggingEvent event) {
5765
return builder.toString();
5866
}
5967

68+
private static int getLineNumber(LocationInfo locationInformation) {
69+
int lineNumber = -1;
70+
String lineNumberString = locationInformation.getLineNumber();
71+
if (!LocationInfo.NA.equals(lineNumberString)) {
72+
try {
73+
lineNumber = Integer.parseInt(lineNumberString);
74+
} catch (NumberFormatException e) {
75+
// ignore
76+
}
77+
}
78+
return lineNumber;
79+
}
80+
6081
@Override
6182
public boolean ignoresThrowable() {
6283
return false;
@@ -71,6 +92,10 @@ public void setServiceName(String serviceName) {
7192
this.serviceName = serviceName;
7293
}
7394

95+
public void setIncludeOrigin(boolean includeOrigin) {
96+
this.includeOrigin = includeOrigin;
97+
}
98+
7499
public void setStackTraceAsArray(boolean stackTraceAsArray) {
75100
this.stackTraceAsArray = stackTraceAsArray;
76101
}

log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/ListAppender.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131
import java.util.List;
3232

3333
class ListAppender extends AppenderSkeleton {
34-
private List<LoggingEvent> logEvents = new ArrayList<>();
34+
private List<String> logEvents = new ArrayList<>();
3535

3636
@Override
3737
protected void append(LoggingEvent event) {
38-
logEvents.add(event);
38+
logEvents.add(layout.format(event));
3939
}
4040

4141
@Override
@@ -45,10 +45,10 @@ public void close() {
4545

4646
@Override
4747
public boolean requiresLayout() {
48-
return false;
48+
return true;
4949
}
5050

51-
public List<LoggingEvent> getLogEvents() {
51+
public List<String> getLogEvents() {
5252
return logEvents;
5353
}
5454
}

log4j-ecs-layout/src/test/java/co/elastic/logging/log4j/Log4jEcsLayoutTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,11 @@
3131
import org.apache.log4j.MDC;
3232
import org.apache.log4j.NDC;
3333
import org.junit.jupiter.api.AfterEach;
34-
import org.junit.jupiter.api.Assertions;
3534
import org.junit.jupiter.api.Assumptions;
3635
import org.junit.jupiter.api.BeforeEach;
3736

3837
import java.io.IOException;
3938

40-
import static org.assertj.core.api.Assertions.assertThat;
41-
4239
class Log4jEcsLayoutTest extends AbstractEcsLoggingTest {
4340

4441
private Logger logger;
@@ -54,6 +51,8 @@ void setUp() {
5451
ecsLayout = new EcsLayout();
5552
ecsLayout.setServiceName("test");
5653
ecsLayout.setStackTraceAsArray(true);
54+
ecsLayout.setIncludeOrigin(true);
55+
appender.setLayout(ecsLayout);
5756
}
5857

5958
@BeforeEach
@@ -87,7 +86,7 @@ public void error(String message, Throwable t) {
8786

8887
@Override
8988
public JsonNode getLastLogLine() throws IOException {
90-
return objectMapper.readTree(ecsLayout.format(appender.getLogEvents().get(0)));
89+
return objectMapper.readTree(appender.getLogEvents().get(0));
9190
}
9291

9392
}

log4j2-ecs-layout/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Instead of the usual `<PatternLayout/>`, use `<EcsLayout serviceName="my-app"/>`
4747
|serviceName |String | |Sets the `service.name` field so you can filter your logs by a particular service |
4848
|includeMarkers |boolean|`false`|Log [Markers](https://logging.apache.org/log4j/2.0/manual/markers.html) as `tags` |
4949
|stackTraceAsArray|boolean|`false`|Serializes the `error.stack_trace` 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).|
50+
|includeOrigin |boolean|`false`|If `true`, adds the `log.origin.file`, `log.origin.function` and `log.origin.line` fields. Note that you also have to set `includeLocation="true"` on your loggers and appenders if you are using the async ones. |
5051

5152
To include any custom field in the output, use following syntax:
5253

log4j2-ecs-layout/src/main/java/co/elastic/logging/log4j2/EcsLayout.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,17 @@ public void accept(final String key, final Object value, final StringBuilder str
8383
private final PatternFormatter[][] fieldValuePatternFormatter;
8484
private final Set<String> topLevelLabels;
8585
private final boolean stackTraceAsArray;
86-
private String serviceName;
87-
private boolean includeMarkers;
86+
private final String serviceName;
87+
private final boolean includeMarkers;
88+
private final boolean includeOrigin;
8889
private final ConcurrentMap<Class<? extends MultiformatMessage>, Boolean> supportsJson = new ConcurrentHashMap<Class<? extends MultiformatMessage>, Boolean>();
8990

90-
private EcsLayout(Configuration config, String serviceName, boolean includeMarkers, KeyValuePair[] additionalFields, Collection<String> topLevelLabels, boolean stackTraceAsArray) {
91+
private EcsLayout(Configuration config, String serviceName, boolean includeMarkers, KeyValuePair[] additionalFields, Collection<String> topLevelLabels, boolean includeOrigin, boolean stackTraceAsArray) {
9192
super(config, UTF_8, null, null);
9293
this.serviceName = serviceName;
9394
this.includeMarkers = includeMarkers;
9495
this.topLevelLabels = new HashSet<String>(topLevelLabels);
96+
this.includeOrigin = includeOrigin;
9597
this.stackTraceAsArray = stackTraceAsArray;
9698
this.topLevelLabels.add("trace.id");
9799
this.topLevelLabels.add("transaction.id");
@@ -138,6 +140,9 @@ private StringBuilder toText(LogEvent event, StringBuilder builder, boolean gcFr
138140
EcsJsonSerializer.serializeLoggerName(builder, event.getLoggerName());
139141
serializeLabels(event, builder);
140142
serializeTags(event, builder);
143+
if (includeOrigin) {
144+
EcsJsonSerializer.serializeOrigin(builder, event.getSource());
145+
}
141146
EcsJsonSerializer.serializeException(builder, event.getThrown(), stackTraceAsArray);
142147
EcsJsonSerializer.serializeObjectEnd(builder);
143148
return builder;
@@ -310,6 +315,8 @@ public static class Builder extends AbstractStringLayout.Builder<EcsLayout.Build
310315
private KeyValuePair[] additionalFields;
311316
@PluginElement("TopLevelLabels")
312317
private String[] topLevelLabels;
318+
@PluginBuilderAttribute("includeOrigin")
319+
private boolean includeOrigin;
313320

314321
Builder() {
315322
super();
@@ -328,6 +335,10 @@ public boolean isIncludeMarkers() {
328335
return includeMarkers;
329336
}
330337

338+
public boolean isIncludeOrigin() {
339+
return includeOrigin;
340+
}
341+
331342
public String[] getTopLevelLabels() {
332343
return topLevelLabels;
333344
}
@@ -357,14 +368,19 @@ public EcsLayout.Builder setIncludeMarkers(final boolean includeMarkers) {
357368
return asBuilder();
358369
}
359370

371+
public EcsLayout.Builder setIncludeOrigin(final boolean includeOrigin) {
372+
this.includeOrigin = includeOrigin;
373+
return asBuilder();
374+
}
375+
360376
public EcsLayout.Builder setStackTraceAsArray(boolean stackTraceAsArray) {
361377
this.stackTraceAsArray = stackTraceAsArray;
362378
return asBuilder();
363379
}
364380

365381
@Override
366382
public EcsLayout build() {
367-
return new EcsLayout(getConfiguration(), serviceName, includeMarkers, additionalFields, topLevelLabels == null ? Collections.<String>emptyList() : Arrays.<String>asList(topLevelLabels), stackTraceAsArray);
383+
return new EcsLayout(getConfiguration(), serviceName, includeMarkers, additionalFields, topLevelLabels == null ? Collections.<String>emptyList() : Arrays.<String>asList(topLevelLabels), includeOrigin, stackTraceAsArray);
368384
}
369385

370386
public boolean isStackTraceAsArray() {

log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/Log4j2EcsLayoutTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ void setUp() {
6868
.setConfiguration(ctx.getConfiguration())
6969
.setServiceName("test")
7070
.setIncludeMarkers(true)
71+
.setIncludeOrigin(true)
7172
.setStackTraceAsArray(true)
7273
.setAdditionalFields(new KeyValuePair[]{
7374
new KeyValuePair("cluster.uuid", "9fe9134b-20b0-465e-acf9-8cc09ac9053b"),

log4j2-ecs-layout/src/test/resources/log4j2-test.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</Properties>
66
<Appenders>
77
<List name="TestAppender">
8-
<EcsLayout serviceName="test" includeMarkers="true" stackTraceAsArray="true">
8+
<EcsLayout serviceName="test" includeMarkers="true" includeOrigin="true" stackTraceAsArray="true">
99
<KeyValuePair key="cluster.uuid" value="9fe9134b-20b0-465e-acf9-8cc09ac9053b"/>
1010
<KeyValuePair key="node.id" value="${node.id}"/>
1111
<KeyValuePair key="empty" value="${empty}"/>

0 commit comments

Comments
 (0)