From 78060458c11a5659e84cb969e3f18a1bdad16d2f Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Thu, 9 Jan 2025 17:34:38 +0200 Subject: [PATCH 1/2] Add marker information to ECS structured logging See gh-43768 Signed-off-by: Dmytro Nosan --- ...ticCommonSchemaStructuredLogFormatter.java | 24 +++++++++++++- ...ticCommonSchemaStructuredLogFormatter.java | 27 +++++++++++++++- ...mmonSchemaStructuredLogFormatterTests.java | 30 +++++++++++++++++- ...mmonSchemaStructuredLogFormatterTests.java | 31 ++++++++++++++++++- 4 files changed, 108 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java index bac717700d0..324ac47911f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java @@ -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 getMarkers(Marker marker) { + Set result = new TreeSet<>(); + addMarkers(result, marker); + return result; + } + + private static void addMarkers(Set result, Marker marker) { + result.add(marker.getName()); + if (marker.hasParents()) { + for (Marker parent : marker.getParents()) { + addMarkers(result, parent); + } + } + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java index ef8511d831b..4f80adbff55 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java @@ -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 getMarkers(List markers) { + Set result = new TreeSet<>(); + addMarkers(result, markers.iterator()); + return result; + } + + private static void addMarkers(Set result, Iterator iterator) { + while (iterator.hasNext()) { + Marker marker = iterator.next(); + result.add(marker.getName()); + if (marker.hasReferences()) { + addMarkers(result, marker.iterator()); + } + } } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java index 0ffb0a88e1e..3e9eb3cd081 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -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,29 @@ 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 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"))); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java index 6c2c355cccb..5ccb73e5ac1 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -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,30 @@ 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 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"))); + } + } From e47ba06e7ea59e948f67bd260ab2ba2232cc235c Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Mon, 13 Jan 2025 10:13:17 +0100 Subject: [PATCH 2/2] Polish "Add marker information to ECS structured logging" See gh-43768 --- .../ElasticCommonSchemaStructuredLogFormatterTests.java | 4 ---- .../ElasticCommonSchemaStructuredLogFormatterTests.java | 4 ---- 2 files changed, 8 deletions(-) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java index 3e9eb3cd081..2b3c2090c49 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -106,18 +106,14 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL @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 deserialized = deserialize(json); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java index 5ccb73e5ac1..f9eb4d7653c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -99,18 +99,14 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL 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 deserialized = deserialize(json);