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
This commit is contained in:
Moritz Halbritter
2025-01-13 10:35:17 +01:00
4 changed files with 100 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,11 @@
package org.springframework.boot.logging.log4j2;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.time.Instant;
@@ -66,6 +69,10 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
thrownProxyMembers.add("error.message", ThrowableProxy::getMessage);
thrownProxyMembers.add("error.stack_trace", ThrowableProxy::getExtendedStackTraceAsString);
});
members.add("tags", LogEvent::getMarker)
.whenNotNull()
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)
.whenNotEmpty();
members.add("ecs.version", "8.11");
}
@@ -73,4 +80,19 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
return java.time.Instant.ofEpochMilli(instant.getEpochMillisecond()).plusNanos(instant.getNanoOfMillisecond());
}
private static Set<String> getMarkers(Marker marker) {
Set<String> result = new TreeSet<>();
addMarkers(result, marker);
return result;
}
private static void addMarkers(Set<String> result, Marker marker) {
result.add(marker.getName());
if (marker.hasParents()) {
for (Marker parent : marker.getParents()) {
addMarkers(result, parent);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,16 @@
package org.springframework.boot.logging.logback;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;
import org.springframework.boot.json.JsonWriter;
@@ -69,6 +74,26 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
throwableMembers.add("error.stack_trace", throwableProxyConverter::convert);
});
members.add("ecs.version", "8.11");
members.add("tags", ILoggingEvent::getMarkerList)
.whenNotNull()
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)
.whenNotEmpty();
}
private static Set<String> getMarkers(List<Marker> markers) {
Set<String> result = new TreeSet<>();
addMarkers(result, markers.iterator());
return result;
}
private static void addMarkers(Set<String> result, Iterator<Marker> iterator) {
while (iterator.hasNext()) {
Marker marker = iterator.next();
result.add(marker.getName());
if (marker.hasReferences()) {
addMarkers(result, marker.iterator());
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,11 @@
package org.springframework.boot.logging.log4j2;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
import org.apache.logging.log4j.core.impl.MutableLogEvent;
import org.apache.logging.log4j.message.MapMessage;
@@ -100,4 +103,25 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
"org.example.Test", "message", expectedMessage, "ecs.version", "8.11"));
}
@Test
void shouldFormatMarkersAsTags() {
MutableLogEvent event = createEvent();
Marker parent = MarkerManager.getMarker("parent");
parent.addParents(MarkerManager.getMarker("grandparent"));
Marker parent1 = MarkerManager.getMarker("parent1");
parent1.addParents(MarkerManager.getMarker("grandparent1"));
Marker grandchild = MarkerManager.getMarker("grandchild");
grandchild.addParents(parent);
grandchild.addParents(parent1);
event.setMarker(grandchild);
String json = this.formatter.format(event);
assertThat(json).endsWith("\n");
Map<String, Object> deserialized = deserialize(json);
assertThat(deserialized).containsExactlyInAnyOrderEntriesOf(map("@timestamp", "2024-07-02T08:49:53Z",
"log.level", "INFO", "process.pid", 1, "process.thread.name", "main", "service.name", "name",
"service.version", "1.0.0", "service.environment", "test", "service.node.name", "node-1", "log.logger",
"org.example.Test", "message", "message", "ecs.version", "8.11", "tags",
List.of("grandchild", "grandparent", "grandparent1", "parent", "parent1")));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,15 @@
package org.springframework.boot.logging.logback;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.mock.env.MockEnvironment;
@@ -92,4 +95,26 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
.replace("\r", "\\r"));
}
@Test
void shouldFormatMarkersAsTags() {
LoggingEvent event = createEvent();
event.setMDCPropertyMap(Collections.emptyMap());
Marker parent = MarkerFactory.getDetachedMarker("parent");
parent.add(MarkerFactory.getDetachedMarker("child"));
Marker parent1 = MarkerFactory.getDetachedMarker("parent1");
parent1.add(MarkerFactory.getDetachedMarker("child1"));
Marker grandparent = MarkerFactory.getMarker("grandparent");
grandparent.add(parent);
grandparent.add(parent1);
event.addMarker(grandparent);
String json = this.formatter.format(event);
assertThat(json).endsWith("\n");
Map<String, Object> deserialized = deserialize(json);
assertThat(deserialized).containsExactlyInAnyOrderEntriesOf(
map("@timestamp", "2024-07-02T08:49:53Z", "log.level", "INFO", "process.pid", 1, "process.thread.name",
"main", "service.name", "name", "service.version", "1.0.0", "service.environment", "test",
"service.node.name", "node-1", "log.logger", "org.example.Test", "message", "message",
"ecs.version", "8.11", "tags", List.of("child", "child1", "grandparent", "parent", "parent1")));
}
}