Skip to content

Commit 302756b

Browse files
committed
Merge pull request #43768 from nosan
* pr/43768: Polish "Add marker information to ECS structured logging" Add marker information to ECS structured logging Closes gh-43768
2 parents a49719d + e47ba06 commit 302756b

File tree

4 files changed

+100
-4
lines changed

4 files changed

+100
-4
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,11 @@
1717
package org.springframework.boot.logging.log4j2;
1818

1919
import java.util.Objects;
20+
import java.util.Set;
21+
import java.util.TreeSet;
2022

2123
import org.apache.logging.log4j.Level;
24+
import org.apache.logging.log4j.Marker;
2225
import org.apache.logging.log4j.core.LogEvent;
2326
import org.apache.logging.log4j.core.impl.ThrowableProxy;
2427
import org.apache.logging.log4j.core.time.Instant;
@@ -66,11 +69,30 @@ private static void jsonMembers(Environment environment, JsonWriter.Members<LogE
6669
thrownProxyMembers.add("error.message", ThrowableProxy::getMessage);
6770
thrownProxyMembers.add("error.stack_trace", ThrowableProxy::getExtendedStackTraceAsString);
6871
});
72+
members.add("tags", LogEvent::getMarker)
73+
.whenNotNull()
74+
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)
75+
.whenNotEmpty();
6976
members.add("ecs.version", "8.11");
7077
}
7178

7279
private static java.time.Instant asTimestamp(Instant instant) {
7380
return java.time.Instant.ofEpochMilli(instant.getEpochMillisecond()).plusNanos(instant.getNanoOfMillisecond());
7481
}
7582

83+
private static Set<String> getMarkers(Marker marker) {
84+
Set<String> result = new TreeSet<>();
85+
addMarkers(result, marker);
86+
return result;
87+
}
88+
89+
private static void addMarkers(Set<String> result, Marker marker) {
90+
result.add(marker.getName());
91+
if (marker.hasParents()) {
92+
for (Marker parent : marker.getParents()) {
93+
addMarkers(result, parent);
94+
}
95+
}
96+
}
97+
7698
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,16 @@
1616

1717
package org.springframework.boot.logging.logback;
1818

19+
import java.util.Iterator;
20+
import java.util.List;
1921
import java.util.Objects;
22+
import java.util.Set;
23+
import java.util.TreeSet;
2024

2125
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
2226
import ch.qos.logback.classic.spi.ILoggingEvent;
2327
import ch.qos.logback.classic.spi.IThrowableProxy;
28+
import org.slf4j.Marker;
2429
import org.slf4j.event.KeyValuePair;
2530

2631
import org.springframework.boot.json.JsonWriter;
@@ -69,6 +74,26 @@ private static void jsonMembers(Environment environment, ThrowableProxyConverter
6974
throwableMembers.add("error.stack_trace", throwableProxyConverter::convert);
7075
});
7176
members.add("ecs.version", "8.11");
77+
members.add("tags", ILoggingEvent::getMarkerList)
78+
.whenNotNull()
79+
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)
80+
.whenNotEmpty();
81+
}
82+
83+
private static Set<String> getMarkers(List<Marker> markers) {
84+
Set<String> result = new TreeSet<>();
85+
addMarkers(result, markers.iterator());
86+
return result;
87+
}
88+
89+
private static void addMarkers(Set<String> result, Iterator<Marker> iterator) {
90+
while (iterator.hasNext()) {
91+
Marker marker = iterator.next();
92+
result.add(marker.getName());
93+
if (marker.hasReferences()) {
94+
addMarkers(result, marker.iterator());
95+
}
96+
}
7297
}
7398

7499
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,11 @@
1616

1717
package org.springframework.boot.logging.log4j2;
1818

19+
import java.util.List;
1920
import java.util.Map;
2021

22+
import org.apache.logging.log4j.Marker;
23+
import org.apache.logging.log4j.MarkerManager;
2124
import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
2225
import org.apache.logging.log4j.core.impl.MutableLogEvent;
2326
import org.apache.logging.log4j.message.MapMessage;
@@ -100,4 +103,25 @@ void shouldFormatStructuredMessage() {
100103
"org.example.Test", "message", expectedMessage, "ecs.version", "8.11"));
101104
}
102105

106+
@Test
107+
void shouldFormatMarkersAsTags() {
108+
MutableLogEvent event = createEvent();
109+
Marker parent = MarkerManager.getMarker("parent");
110+
parent.addParents(MarkerManager.getMarker("grandparent"));
111+
Marker parent1 = MarkerManager.getMarker("parent1");
112+
parent1.addParents(MarkerManager.getMarker("grandparent1"));
113+
Marker grandchild = MarkerManager.getMarker("grandchild");
114+
grandchild.addParents(parent);
115+
grandchild.addParents(parent1);
116+
event.setMarker(grandchild);
117+
String json = this.formatter.format(event);
118+
assertThat(json).endsWith("\n");
119+
Map<String, Object> deserialized = deserialize(json);
120+
assertThat(deserialized).containsExactlyInAnyOrderEntriesOf(map("@timestamp", "2024-07-02T08:49:53Z",
121+
"log.level", "INFO", "process.pid", 1, "process.thread.name", "main", "service.name", "name",
122+
"service.version", "1.0.0", "service.environment", "test", "service.node.name", "node-1", "log.logger",
123+
"org.example.Test", "message", "message", "ecs.version", "8.11", "tags",
124+
List.of("grandchild", "grandparent", "grandparent1", "parent", "parent1")));
125+
}
126+
103127
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,12 +17,15 @@
1717
package org.springframework.boot.logging.logback;
1818

1919
import java.util.Collections;
20+
import java.util.List;
2021
import java.util.Map;
2122

2223
import ch.qos.logback.classic.spi.LoggingEvent;
2324
import ch.qos.logback.classic.spi.ThrowableProxy;
2425
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Test;
27+
import org.slf4j.Marker;
28+
import org.slf4j.MarkerFactory;
2629

2730
import org.springframework.mock.env.MockEnvironment;
2831

@@ -92,4 +95,26 @@ void shouldFormatException() {
9295
.replace("\r", "\\r"));
9396
}
9497

98+
@Test
99+
void shouldFormatMarkersAsTags() {
100+
LoggingEvent event = createEvent();
101+
event.setMDCPropertyMap(Collections.emptyMap());
102+
Marker parent = MarkerFactory.getDetachedMarker("parent");
103+
parent.add(MarkerFactory.getDetachedMarker("child"));
104+
Marker parent1 = MarkerFactory.getDetachedMarker("parent1");
105+
parent1.add(MarkerFactory.getDetachedMarker("child1"));
106+
Marker grandparent = MarkerFactory.getMarker("grandparent");
107+
grandparent.add(parent);
108+
grandparent.add(parent1);
109+
event.addMarker(grandparent);
110+
String json = this.formatter.format(event);
111+
assertThat(json).endsWith("\n");
112+
Map<String, Object> deserialized = deserialize(json);
113+
assertThat(deserialized).containsExactlyInAnyOrderEntriesOf(
114+
map("@timestamp", "2024-07-02T08:49:53Z", "log.level", "INFO", "process.pid", 1, "process.thread.name",
115+
"main", "service.name", "name", "service.version", "1.0.0", "service.environment", "test",
116+
"service.node.name", "node-1", "log.logger", "org.example.Test", "message", "message",
117+
"ecs.version", "8.11", "tags", List.of("child", "child1", "grandparent", "parent", "parent1")));
118+
}
119+
95120
}

0 commit comments

Comments
 (0)