This is an automated email from the ASF dual-hosted git repository.
ramanathan1504 pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/2.x by this push:
new b6ba7d0af6 Add native tracing fields to LogEvent to eliminate async
MDC overhead (#4171)
b6ba7d0af6 is described below
commit b6ba7d0af60783e98fbe52aee4f9ea3e70deed25
Author: Ramanathan <[email protected]>
AuthorDate: Mon Jul 6 23:58:28 2026 +0530
Add native tracing fields to LogEvent to eliminate async MDC overhead
(#4171)
* Add tracing fields to RingBufferLogEvent and related classes
* Add W3C trace context support with pattern converters for trace ID, span
ID, and trace flags
* Enhance TraceContextProviderService for exception safety and simplify
trace ID retrieval
* Add tests for tracing fields serialization and pattern converters
* Refactor tracing metadata documentation and improve code comments for
clarity
* Add native W3C tracing fields to LogEvent and introduce
TraceContextProvider SPI
* Add benchmark for ContextDataProvider tracing approach
* Enhance TraceContextProviderService to handle SecurityManager
restrictions gracefully
---
.../logging/log4j/spi/TraceContextProvider.java | 52 +++++
.../org/apache/logging/log4j/spi/package-info.java | 2 +-
.../log4j/core/async/RingBufferLogEventTest.java | 45 ++++
.../logging/log4j/core/impl/Log4jLogEventTest.java | 165 +++++++++++++++
.../log4j/core/impl/MutableLogEventTest.java | 29 +++
.../log4j/core/impl/TestTraceContextProvider.java | 53 +++++
.../core/impl/TraceContextIntegrationTest.java | 233 +++++++++++++++++++++
...g.apache.logging.log4j.spi.TraceContextProvider | 1 +
.../org/apache/logging/log4j/core/LogEvent.java | 41 ++++
.../log4j/core/async/RingBufferLogEvent.java | 71 ++++++-
.../core/async/RingBufferLogEventTranslator.java | 51 ++++-
.../logging/log4j/core/async/package-info.java | 2 +-
.../logging/log4j/core/impl/Log4jLogEvent.java | 150 +++++++++++--
.../logging/log4j/core/impl/MutableLogEvent.java | 45 +++-
.../logging/log4j/core/impl/package-info.java | 2 +-
.../apache/logging/log4j/core/package-info.java | 2 +-
.../log4j/core/pattern/SpanIdPatternConverter.java | 43 ++++
.../core/pattern/TraceFlagsPatternConverter.java | 43 ++++
.../core/pattern/TraceIdPatternConverter.java | 43 ++++
.../logging/log4j/core/pattern/package-info.java | 2 +-
.../core/util/TraceContextProviderService.java | 131 ++++++++++++
.../logging/log4j/core/util/package-info.java | 2 +-
.../log4j/perf/jmh/AsyncTraceContextBenchmark.java | 181 ++++++++++++++++
.../.2.x.x/1976_add_native_tracing_fields.xml | 13 ++
24 files changed, 1376 insertions(+), 26 deletions(-)
diff --git
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/TraceContextProvider.java
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/TraceContextProvider.java
new file mode 100644
index 0000000000..de2ffbeb8b
--- /dev/null
+++
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/TraceContextProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.spi;
+
+/**
+ * Service Provider Interface (SPI) for retrieving distributed tracing
metadata (such as W3C Trace Context)
+ * from the active execution context.
+ * <p>
+ * Implementing this SPI allows tracing frameworks (e.g., OpenTelemetry,
Micrometer, Zipkin) to pass native
+ * trace identifiers directly to Log4j events. This completely bypasses the
{@link org.apache.logging.log4j.ThreadContext}
+ * map, eliminating map-cloning and garbage collection overhead during
asynchronous logging.
+ * </p>
+ * <p>
+ * Log4j locates implementations of this interface using the standard Java
{@link java.util.ServiceLoader} mechanism.
+ * To register a custom provider, create a plain-text file named
+ * {@code org.apache.logging.log4j.spi.TraceContextProvider} in the {@code
META-INF/services/} directory
+ * containing the fully qualified class name of the implementation.
+ * </p>
+ *
+ * @since 2.27.0
+ */
+public interface TraceContextProvider {
+
+ /**
+ * Returns the standard trace ID from the active context, or {@code null}.
+ */
+ String getTraceId();
+
+ /**
+ * Returns the standard span ID from the active context, or {@code null}.
+ */
+ String getSpanId();
+
+ /**
+ * Returns the standard trace flags from the active context, or {@code
null}.
+ */
+ String getTraceFlags();
+}
diff --git
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
index 3b6b5c2585..0956aad003 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/package-info.java
@@ -19,7 +19,7 @@
* API classes.
*/
@Export
-@Version("2.25.0")
+@Version("2.26.0")
package org.apache.logging.log4j.spi;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
index 5377040565..20b2a7ec44 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
@@ -16,10 +16,12 @@
*/
package org.apache.logging.log4j.core.async;
+import static org.apache.logging.log4j.core.util.ClockFactory.getClock;
import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -31,6 +33,7 @@ import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.ThreadContext.ContextStack;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.ContextDataFactory;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.time.internal.FixedPreciseClock;
import org.apache.logging.log4j.core.util.Clock;
@@ -468,4 +471,46 @@ class RingBufferLogEventTest {
// Verify interaction exhaustion
verifyNoMoreInteractions(asyncLogger, message, throwable, contextData,
contextStack);
}
+
+ @Test
+ void testRingBufferLogEventTracingFieldsAndClear() {
+ final RingBufferLogEvent event = new RingBufferLogEvent();
+
+ // Check initial state
+ assertThat(event.getTraceId()).isNull();
+ assertThat(event.getSpanId()).isNull();
+ assertThat(event.getTraceFlags()).isNull();
+
+ // Initialize with trace fields inside 18-parameter setValues
+ event.setValues(
+ null,
+ "TestLogger",
+ null,
+ "FQCN",
+ Level.INFO,
+ new SimpleMessage("msg"),
+ null,
+ ContextDataFactory.createContextData(),
+ ThreadContext.EMPTY_STACK,
+ 123L,
+ "thread-name",
+ 5,
+ null,
+ getClock(),
+ new DummyNanoClock(),
+ "trace-id-ringbuffer",
+ "span-id-ringbuffer",
+ "01");
+
+ // Assert values are retrievable
+ assertThat(event.getTraceId()).isEqualTo("trace-id-ringbuffer");
+ assertThat(event.getSpanId()).isEqualTo("span-id-ringbuffer");
+ assertThat(event.getTraceFlags()).isEqualTo("01");
+
+ // Verify clearing wipes tracing state to avoid leakage on slot reuse
+ event.clear();
+ assertNull(event.getTraceId());
+ assertNull(event.getSpanId());
+ assertNull(event.getTraceFlags());
+ }
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
index bc90e594d2..082519142f 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.impl;
import static org.apache.logging.log4j.test.junit.SerialUtil.deserialize;
import static org.apache.logging.log4j.test.junit.SerialUtil.serialize;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -29,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Field;
+import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
@@ -36,6 +38,8 @@ import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.ThreadContext.ContextStack;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.convert.Base64Converter;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.ClockFactory;
import org.apache.logging.log4j.core.util.ClockFactoryTest;
@@ -45,6 +49,7 @@ import org.apache.logging.log4j.message.ObjectMessage;
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.message.ReusableObjectMessage;
import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.SortedArrayStringMap;
import org.apache.logging.log4j.util.StringMap;
import org.apache.logging.log4j.util.Strings;
@@ -541,4 +546,164 @@ public class Log4jLogEventTest {
// Throws an NPE in 2.6.2
assertNotNull(new Log4jLogEvent().toString());
}
+
+ @Test
+ public void testCustomLegacyLogEventDefaultBehavior() {
+ // Create an anonymous/stub class implementing LogEvent without
implementing getTraceId/getSpanId/getTraceFlags
+ final LogEvent legacyEvent = new LogEvent() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public LogEvent toImmutable() {
+ return this;
+ }
+
+ @Override
+ @Deprecated
+ public Map<String, String> getContextMap() {
+ return java.util.Collections.emptyMap();
+ }
+
+ @Override
+ public ReadOnlyStringMap getContextData() {
+ return ContextDataFactory.emptyFrozenContextData();
+ }
+
+ @Override
+ public ThreadContext.ContextStack getContextStack() {
+ return ThreadContext.EMPTY_STACK;
+ }
+
+ @Override
+ public String getLoggerFqcn() {
+ return null;
+ }
+
+ @Override
+ public Level getLevel() {
+ return Level.INFO;
+ }
+
+ @Override
+ public String getLoggerName() {
+ return "LegacyLogger";
+ }
+
+ @Override
+ public Marker getMarker() {
+ return null;
+ }
+
+ @Override
+ public Message getMessage() {
+ return new SimpleMessage("Legacy msg");
+ }
+
+ @Override
+ public long getTimeMillis() {
+ return 0;
+ }
+
+ @Override
+ public Instant getInstant() {
+ return new MutableInstant();
+ }
+
+ @Override
+ public StackTraceElement getSource() {
+ return null;
+ }
+
+ @Override
+ public String getThreadName() {
+ return "main";
+ }
+
+ @Override
+ public long getThreadId() {
+ return 1;
+ }
+
+ @Override
+ public int getThreadPriority() {
+ return 5;
+ }
+
+ @Override
+ public Throwable getThrown() {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public ThrowableProxy getThrownProxy() {
+ return null;
+ }
+
+ @Override
+ public boolean isEndOfBatch() {
+ return false;
+ }
+
+ @Override
+ public boolean isIncludeLocation() {
+ return false;
+ }
+
+ @Override
+ public void setEndOfBatch(boolean endOfBatch) {}
+
+ @Override
+ public void setIncludeLocation(boolean locationRequired) {}
+
+ @Override
+ public long getNanoTime() {
+ return 0;
+ }
+ };
+
+ assertNull(legacyEvent.getTraceId());
+ assertNull(legacyEvent.getSpanId());
+ assertNull(legacyEvent.getTraceFlags());
+ }
+
+ @Test
+ void testTracingFieldsInBuilderAndCopy() {
+ final Log4jLogEvent originalEvent = Log4jLogEvent.newBuilder()
+ .setLoggerName("TestLogger")
+ .setLevel(Level.DEBUG)
+ .setMessage(new
org.apache.logging.log4j.message.SimpleMessage("Test message"))
+ .setTraceId("4bf92f3577b34da6a3ce929d0e0e4736")
+ .setSpanId("00f067aa0ba902b7")
+ .setTraceFlags("01")
+ .build();
+
+
assertThat(originalEvent.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(originalEvent.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(originalEvent.getTraceFlags()).isEqualTo("01");
+
+ final Log4jLogEvent copiedEvent = new
Log4jLogEvent.Builder(originalEvent).build();
+
assertThat(copiedEvent.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(copiedEvent.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(copiedEvent.getTraceFlags()).isEqualTo("01");
+ }
+
+ @Test
+ void testTracingFieldsSerialization() throws Exception {
+ final Log4jLogEvent originalEvent = Log4jLogEvent.newBuilder()
+ .setLoggerName("SerializationLogger")
+ .setMessage(new
org.apache.logging.log4j.message.SimpleMessage("dummy message"))
+ .setTraceId("trace-serialize-123")
+ .setSpanId("span-serialize-456")
+ .setTraceFlags("01")
+ .build();
+
+ final byte[] serializedBytes = serialize(originalEvent);
+
+ final Log4jLogEvent deserializedEvent = deserialize(serializedBytes);
+
+
assertThat(deserializedEvent.getTraceId()).isEqualTo("trace-serialize-123");
+
assertThat(deserializedEvent.getSpanId()).isEqualTo("span-serialize-456");
+ assertThat(deserializedEvent.getTraceFlags()).isEqualTo("01");
+ }
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
index 092c2133e2..459c84fbaa 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
@@ -366,4 +366,33 @@ class MutableLogEventTest {
final Log4jLogEvent immutable = mutable.toImmutable();
assertThat(immutable.getSource()).isEqualTo(source);
}
+
+ @Test
+ void testMutableLogEventTracingFieldsPropagationAndClear() {
+ final Log4jLogEvent sourceEvent = Log4jLogEvent.newBuilder()
+ .setLoggerName("SourceLogger")
+ .setTraceId("trace-xyz-123")
+ .setSpanId("span-abc-456")
+ .setTraceFlags("01")
+ .build();
+
+ final MutableLogEvent mutableEvent = new MutableLogEvent();
+
+ // Initially empty
+ assertThat(mutableEvent.getTraceId()).isNull();
+ assertThat(mutableEvent.getSpanId()).isNull();
+ assertThat(mutableEvent.getTraceFlags()).isNull();
+
+ // Verify propagation via initFrom
+ mutableEvent.initFrom(sourceEvent);
+ assertThat(mutableEvent.getTraceId()).isEqualTo("trace-xyz-123");
+ assertThat(mutableEvent.getSpanId()).isEqualTo("span-abc-456");
+ assertThat(mutableEvent.getTraceFlags()).isEqualTo("01");
+
+ // Verify clearing removes tracking state to prevent leakage on pool
reuse
+ mutableEvent.clear();
+ assertThat(mutableEvent.getTraceId()).isNull();
+ assertThat(mutableEvent.getSpanId()).isNull();
+ assertThat(mutableEvent.getTraceFlags()).isNull();
+ }
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TestTraceContextProvider.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TestTraceContextProvider.java
new file mode 100644
index 0000000000..94a0d009ed
--- /dev/null
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TestTraceContextProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import org.apache.logging.log4j.spi.TraceContextProvider;
+
+public class TestTraceContextProvider implements TraceContextProvider {
+
+ private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
+ private static final ThreadLocal<String> SPAN_ID = new ThreadLocal<>();
+ private static final ThreadLocal<String> TRACE_FLAGS = new ThreadLocal<>();
+
+ public static void setContext(final String traceId, final String spanId,
final String traceFlags) {
+ TRACE_ID.set(traceId);
+ SPAN_ID.set(spanId);
+ TRACE_FLAGS.set(traceFlags);
+ }
+
+ public static void clearContext() {
+ TRACE_ID.remove();
+ SPAN_ID.remove();
+ TRACE_FLAGS.remove();
+ }
+
+ @Override
+ public String getTraceId() {
+ return TRACE_ID.get();
+ }
+
+ @Override
+ public String getSpanId() {
+ return SPAN_ID.get();
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return TRACE_FLAGS.get();
+ }
+}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TraceContextIntegrationTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TraceContextIntegrationTest.java
new file mode 100644
index 0000000000..0a61571cb1
--- /dev/null
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/TraceContextIntegrationTest.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.async.RingBufferLogEvent;
+import org.apache.logging.log4j.core.async.RingBufferLogEventTranslator;
+import org.apache.logging.log4j.core.pattern.SpanIdPatternConverter;
+import org.apache.logging.log4j.core.pattern.TraceFlagsPatternConverter;
+import org.apache.logging.log4j.core.pattern.TraceIdPatternConverter;
+import org.apache.logging.log4j.core.util.ClockFactory;
+import org.apache.logging.log4j.core.util.DummyNanoClock;
+import org.apache.logging.log4j.core.util.TraceContextProviderService;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.spi.TraceContextProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TraceContextIntegrationTest {
+
+ private final TestTraceContextProvider testProvider = new
TestTraceContextProvider();
+
+ @BeforeEach
+ void setUp() {
+ TraceContextProviderService.setActiveProvider(testProvider);
+
TestTraceContextProvider.setContext("4bf92f3577b34da6a3ce929d0e0e4736",
"00f067aa0ba902b7", "01");
+ ThreadContext.clearMap();
+ }
+
+ @AfterEach
+ void tearDown() {
+ TraceContextProviderService.setActiveProvider(null);
+ TestTraceContextProvider.clearContext();
+ ThreadContext.clearMap();
+ }
+
+ @Test
+ public void testLog4jLogEventNativeCaptureWithEmptyThreadContext() {
+ assertThat(ThreadContext.getContext()).isEmpty();
+
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("TestLogger")
+ .setLevel(Level.INFO)
+ .setMessage(new SimpleMessage("Standard event testing"))
+ .build();
+
+
assertThat(event.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(event.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(event.getTraceFlags()).isEqualTo("01");
+ assertThat(event.getContextData().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testMutableLogEventNativeCapture() {
+ final MutableLogEvent event = new MutableLogEvent();
+ event.setMessage(new SimpleMessage("Mutable event testing"));
+ event.initTime(ClockFactory.getClock(), new DummyNanoClock());
+
+
assertThat(event.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(event.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(event.getTraceFlags()).isEqualTo("01");
+ assertThat(event.getContextData().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testRingBufferLogEventTranslatorNativeCapture() {
+ final RingBufferLogEvent event = new RingBufferLogEvent();
+ final RingBufferLogEventTranslator translator = new
RingBufferLogEventTranslator();
+
+ translator.setBasicValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ new SimpleMessage("Async event testing"),
+ null,
+ ThreadContext.getImmutableStack(),
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock());
+
+ translator.translateTo(event, 0);
+
+
assertThat(event.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(event.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(event.getTraceFlags()).isEqualTo("01");
+ assertThat(event.getContextData().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testLog4jLogEventDirectConstructorNativeCapture() {
+ final LogEvent event = new Log4jLogEvent(
+ "TestLogger", null, "FQCN", Level.INFO, new
SimpleMessage("Direct constructor testing"), null, null);
+
+
assertThat(event.getTraceId()).isEqualTo("4bf92f3577b34da6a3ce929d0e0e4736");
+ assertThat(event.getSpanId()).isEqualTo("00f067aa0ba902b7");
+ assertThat(event.getTraceFlags()).isEqualTo("01");
+ assertThat(event.getContextData().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testProviderExceptionSafety() {
+ TraceContextProviderService.setActiveProvider(new
TraceContextProvider() {
+ @Override
+ public String getTraceId() {
+ throw new RuntimeException("Simulated provider failure");
+ }
+
+ @Override
+ public String getSpanId() {
+ return null;
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return null;
+ }
+ });
+
+ assertThatCode(() -> {
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("BuggyLogger")
+ .setLevel(Level.ERROR)
+ .setMessage(new SimpleMessage("Exception safety
test"))
+ .build();
+
+ assertThat(event.getTraceId()).isEmpty();
+ })
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testTracePatternConverters() {
+ // Setup an event with trace data
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setTraceId("test-trace-123")
+ .setSpanId("test-span-456")
+ .setTraceFlags("01")
+ .build();
+
+ // Test Trace ID Converter
+ final StringBuilder traceSb = new StringBuilder();
+
org.apache.logging.log4j.core.pattern.TraceIdPatternConverter.newInstance(null)
+ .format(event, traceSb);
+ assertThat(traceSb.toString()).isEqualTo("test-trace-123");
+
+ // Test Span ID Converter
+ final StringBuilder spanSb = new StringBuilder();
+
org.apache.logging.log4j.core.pattern.SpanIdPatternConverter.newInstance(null)
+ .format(event, spanSb);
+ assertThat(spanSb.toString()).isEqualTo("test-span-456");
+
+ // Test Trace Flags Converter
+ final StringBuilder flagsSb = new StringBuilder();
+
org.apache.logging.log4j.core.pattern.TraceFlagsPatternConverter.newInstance(null)
+ .format(event, flagsSb);
+ assertThat(flagsSb.toString()).isEqualTo("01");
+ }
+
+ @Test
+ public void testTracePatternConvertersWithNullValues() {
+ TestTraceContextProvider.clearContext();
+
+ final LogEvent event = Log4jLogEvent.newBuilder().build();
+ final StringBuilder sb = new StringBuilder();
+
+ TraceIdPatternConverter.newInstance(null).format(event, sb);
+ SpanIdPatternConverter.newInstance(null).format(event, sb);
+ TraceFlagsPatternConverter.newInstance(null).format(event, sb);
+
+ assertThat(sb.toString()).isEmpty();
+ }
+
+ @Test
+ public void testSecurityManagerFallback() {
+ final SecurityManager originalSm = System.getSecurityManager();
+ try {
+ // . Install a mock SecurityManager that blocks access to our
global key
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public void checkPropertyAccess(String key) {
+ if ("log4j2.activeTraceContextProvider".equals(key)) {
+ throw new SecurityException("Simulated enterprise
security exception");
+ }
+ }
+
+ @Override
+ public void checkPermission(java.security.Permission perm) {
+ // Allow all other permissions so the test runner doesn't
crash
+ }
+ });
+
+ // Assert that setting the provider does not crash the application
+ assertThatCode(() ->
TraceContextProviderService.setActiveProvider(testProvider))
+ .doesNotThrowAnyException();
+
+ // Assert that getting the provider safely falls back to the local
variable
+
assertThat(TraceContextProviderService.getActiveProvider()).isEqualTo(testProvider);
+
+ } catch (UnsupportedOperationException e) {
+ // Java 17+ blocks setting SecurityManager by default without
specific JVM flags.
+ // If the JVM blocks the test, we gracefully skip it.
+ System.out.println("Skipping SecurityManager test because JVM
restricts it.");
+ } finally {
+ // Restore the original security manager
+ try {
+ System.setSecurityManager(originalSm);
+ } catch (UnsupportedOperationException ignored) {
+ }
+ }
+ }
+}
diff --git
a/log4j-core-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.TraceContextProvider
b/log4j-core-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.TraceContextProvider
new file mode 100644
index 0000000000..0e5fd8db11
--- /dev/null
+++
b/log4j-core-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.TraceContextProvider
@@ -0,0 +1 @@
+org.apache.logging.log4j.core.impl.TestTraceContextProvider
\ No newline at end of file
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
index 63daa9d3bb..f4cc136ff3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
@@ -241,4 +241,45 @@ public interface LogEvent extends Serializable {
* @since Log4J 2.4
*/
long getNanoTime();
+ /**
+ * Returns the distributed tracing Trace ID from the active context (e.g.,
W3C Trace Context).
+ * <p>
+ * A Trace ID is typically a 32-character lowercase hexadecimal string
representing the
+ * entire trace forest. The default implementation returns {@code null}.
+ * </p>
+ *
+ * @return the Trace ID, or {@code null} if no tracing context is active
or supported.
+ * @since 2.27.0
+ */
+ default String getTraceId() {
+ return null;
+ }
+
+ /**
+ * Returns the distributed tracing Span ID from the active context (e.g.,
W3C Trace Context).
+ * <p>
+ * A Span ID is typically a 16-character lowercase hexadecimal string
representing a
+ * single operation within a trace. The default implementation returns
{@code null}.
+ * </p>
+ *
+ * @return the Span ID, or {@code null} if no tracing context is active or
supported.
+ * @since 2.27.0
+ */
+ default String getSpanId() {
+ return null;
+ }
+
+ /**
+ * Returns the distributed tracing Trace Flags from the active context
(e.g., W3C Trace Context).
+ * <p>
+ * Trace Flags are typically a 2-character lowercase hexadecimal string
representing
+ * tracing options (such as whether the trace is sampled). The default
implementation returns {@code null}.
+ * </p>
+ *
+ * @return the Trace Flags, or {@code null} if no tracing context is
active or supported.
+ * @since 2.27.0
+ */
+ default String getTraceFlags() {
+ return null;
+ }
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
index 2afb007387..b64a32e118 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
@@ -91,6 +91,9 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
private String fqcn;
private StackTraceElement location;
private ContextStack contextStack;
+ private String traceId;
+ private String spanId;
+ private String traceFlags;
private transient AsyncLogger asyncLogger;
@@ -109,7 +112,10 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
final int threadPriority,
final StackTraceElement aLocation,
final Clock clock,
- final NanoClock nanoClock) {
+ final NanoClock nanoClock,
+ final String traceId,
+ final String spanId,
+ final String traceFlags) {
this.threadPriority = threadPriority;
this.threadId = threadId;
this.level = aLevel;
@@ -125,9 +131,50 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
this.contextData = mutableContextData;
this.contextStack = aContextStack;
this.asyncLogger = anAsyncLogger;
+ this.traceId = traceId;
+ this.spanId = spanId;
+ this.traceFlags = traceFlags;
this.populated = true;
}
+ public void setValues(
+ final AsyncLogger anAsyncLogger,
+ final String aLoggerName,
+ final Marker aMarker,
+ final String theFqcn,
+ final Level aLevel,
+ final Message msg,
+ final Throwable aThrowable,
+ final StringMap mutableContextData,
+ final ContextStack aContextStack,
+ final long threadId,
+ final String threadName,
+ final int threadPriority,
+ final StackTraceElement aLocation,
+ final Clock clock,
+ final NanoClock nanoClock) {
+ setValues(
+ anAsyncLogger,
+ aLoggerName,
+ aMarker,
+ theFqcn,
+ aLevel,
+ msg,
+ aThrowable,
+ mutableContextData,
+ aContextStack,
+ threadId,
+ threadName,
+ threadPriority,
+ aLocation,
+ clock,
+ nanoClock,
+ null, // traceId
+ null, // spanId
+ null // traceFlags
+ );
+ }
+
private void initTime(final Clock clock) {
if (message instanceof TimestampMessage) {
instant.initFromEpochMilli(((TimestampMessage)
message).getTimestamp(), 0);
@@ -399,6 +446,21 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
return nanoTime;
}
+ @Override
+ public String getTraceId() {
+ return traceId;
+ }
+
+ @Override
+ public String getSpanId() {
+ return spanId;
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return traceFlags;
+ }
+
/**
* Release references held by ring buffer to allow objects to be
garbage-collected.
*/
@@ -415,6 +477,9 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
this.location = null;
this.contextStack = null;
this.asyncLogger = null;
+ this.traceId = null;
+ this.spanId = null;
+ this.traceFlags = null;
}
private void clearMessage() {
@@ -499,6 +564,8 @@ public class RingBufferLogEvent implements LogEvent,
ReusableMessage, CharSequen
.setThreadPriority(threadPriority) //
.setThrown(getThrown()) // may deserialize from thrownProxy
.setInstant(instant) //
- ;
+ .setTraceId(traceId)
+ .setSpanId(spanId)
+ .setTraceFlags(traceFlags);
}
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
index f6fee9fcf5..e7bffd9084 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.NanoClock;
+import org.apache.logging.log4j.core.util.TraceContextProviderService;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.StringMap;
@@ -51,6 +52,9 @@ public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferL
private StackTraceElement location;
private Clock clock;
private NanoClock nanoClock;
+ private String traceId;
+ private String spanId;
+ private String traceFlags;
// Due to the usage pattern of this class, these are effectively final
private long threadId = Thread.currentThread().getId();
@@ -79,7 +83,10 @@ public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferL
threadPriority,
location,
clock,
- nanoClock);
+ nanoClock,
+ traceId,
+ spanId,
+ traceFlags);
} finally {
clear(); // clear the translator
}
@@ -100,7 +107,10 @@ public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferL
null, // contextStack
null, // location
null, // clock
- null // nanoClock
+ null, // nanoClock
+ null, // traceId
+ null, // spanId
+ null // traceFlags
);
}
@@ -115,7 +125,10 @@ public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferL
final ContextStack aContextStack,
final StackTraceElement aLocation,
final Clock aClock,
- final NanoClock aNanoClock) {
+ final NanoClock aNanoClock,
+ final String traceId,
+ final String spanId,
+ final String traceFlags) {
this.asyncLogger = anAsyncLogger;
this.loggerName = aLoggerName;
this.marker = aMarker;
@@ -127,8 +140,40 @@ public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferL
this.location = aLocation;
this.clock = aClock;
this.nanoClock = aNanoClock;
+ this.traceId = traceId;
+ this.spanId = spanId;
+ this.traceFlags = traceFlags;
}
+ public void setBasicValues(
+ final AsyncLogger anAsyncLogger,
+ final String aLoggerName,
+ final Marker aMarker,
+ final String theFqcn,
+ final Level aLevel,
+ final Message msg,
+ final Throwable aThrowable,
+ final ContextStack aContextStack,
+ final StackTraceElement aLocation,
+ final Clock aClock,
+ final NanoClock aNanoClock) {
+
+ setBasicValues(
+ anAsyncLogger,
+ aLoggerName,
+ aMarker,
+ theFqcn,
+ aLevel,
+ msg,
+ aThrowable,
+ aContextStack,
+ aLocation,
+ aClock,
+ aNanoClock,
+ TraceContextProviderService.getTraceId(),
+ TraceContextProviderService.getSpanId(),
+ TraceContextProviderService.getTraceFlags());
+ }
/**
* @deprecated since 2.25.0. {@link RingBufferLogEventTranslator}
instances should only be used on the thread that
* created it.
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
index 24e9b7643f..e8e8a9189d 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java
@@ -18,7 +18,7 @@
* Provides Asynchronous Logger classes and interfaces for low-latency logging.
*/
@Export
-@Version("2.26.0")
+@Version("2.27.0")
package org.apache.logging.log4j.core.async;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
index 471b1cd5be..d2693e69d1 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
@@ -38,6 +38,7 @@ import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.ClockFactory;
import org.apache.logging.log4j.core.util.DummyNanoClock;
import org.apache.logging.log4j.core.util.NanoClock;
+import org.apache.logging.log4j.core.util.TraceContextProviderService;
import org.apache.logging.log4j.message.LoggerNameAwareMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ReusableMessage;
@@ -94,6 +95,10 @@ public class Log4jLogEvent implements LogEvent {
// 5. Deprecated fields, only used for serialization
private ThrowableProxy thrownProxy;
+ // 6. Tracing fields
+ private final String traceId;
+ private final String spanId;
+ private final String traceFlags;
/** LogEvent Builder helper class. */
public static class Builder implements
org.apache.logging.log4j.core.util.Builder<LogEvent> {
@@ -123,9 +128,26 @@ public class Log4jLogEvent implements LogEvent {
private StringMap contextData;
private ThreadContext.ContextStack contextStack;
+ private String traceId = Strings.EMPTY;
+ private String spanId = Strings.EMPTY;
+ private String traceFlags = Strings.EMPTY;
+
public Builder() {
this.contextData = createContextData((List<Property>) null);
this.contextStack = ThreadContext.getImmutableStack();
+
+ final String tId = TraceContextProviderService.getTraceId();
+ if (tId != null) {
+ this.traceId = tId;
+ }
+ final String sId = TraceContextProviderService.getSpanId();
+ if (sId != null) {
+ this.spanId = sId;
+ }
+ final String flags = TraceContextProviderService.getTraceFlags();
+ if (flags != null) {
+ this.traceFlags = flags;
+ }
}
/**
@@ -159,6 +181,10 @@ public class Log4jLogEvent implements LogEvent {
// but since we are copying the event, we want to call it.
this.source = other.getSource();
+ this.traceId = other.getTraceId();
+ this.spanId = other.getSpanId();
+ this.traceFlags = other.getTraceFlags();
+
Message message = other.getMessage();
this.message = message instanceof ReusableMessage
? ((ReusableMessage) message).memento()
@@ -175,6 +201,21 @@ public class Log4jLogEvent implements LogEvent {
this.contextStack = other.getContextStack();
}
+ public Builder setTraceId(String traceId) {
+ this.traceId = traceId;
+ return this;
+ }
+
+ public Builder setSpanId(String spanId) {
+ this.spanId = spanId;
+ return this;
+ }
+
+ public Builder setTraceFlags(String traceFlags) {
+ this.traceFlags = traceFlags;
+ return this;
+ }
+
public Builder setLevel(final Level level) {
this.level = level;
return this;
@@ -303,7 +344,10 @@ public class Log4jLogEvent implements LogEvent {
source,
instant.getEpochMillisecond(),
instant.getNanoOfMillisecond(),
- nanoTime);
+ nanoTime,
+ traceId,
+ spanId,
+ traceFlags);
result.setIncludeLocation(includeLocation);
result.setEndOfBatch(endOfBatch);
return result;
@@ -339,7 +383,10 @@ public class Log4jLogEvent implements LogEvent {
0,
null,
CLOCK,
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ Strings.EMPTY,
+ Strings.EMPTY,
+ Strings.EMPTY);
}
/**
@@ -363,7 +410,10 @@ public class Log4jLogEvent implements LogEvent {
null,
timestamp,
0,
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ Strings.EMPTY,
+ Strings.EMPTY,
+ Strings.EMPTY);
}
/**
@@ -420,7 +470,10 @@ public class Log4jLogEvent implements LogEvent {
0, // thread priority
null, // StackTraceElement source
CLOCK, //
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ TraceContextProviderService.getTraceId(),
+ TraceContextProviderService.getSpanId(),
+ TraceContextProviderService.getTraceFlags());
}
/**
@@ -457,7 +510,10 @@ public class Log4jLogEvent implements LogEvent {
0, // thread priority
source, // StackTraceElement source
CLOCK, //
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ TraceContextProviderService.getTraceId(),
+ TraceContextProviderService.getSpanId(),
+ TraceContextProviderService.getTraceFlags());
}
/**
@@ -503,7 +559,10 @@ public class Log4jLogEvent implements LogEvent {
location,
timestampMillis,
0,
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ Strings.EMPTY,
+ Strings.EMPTY,
+ Strings.EMPTY);
}
/**
@@ -552,7 +611,10 @@ public class Log4jLogEvent implements LogEvent {
location,
timestamp,
0,
- nanoClock.nanoTime());
+ nanoClock.nanoTime(),
+ Strings.EMPTY,
+ Strings.EMPTY,
+ Strings.EMPTY);
return result;
}
@@ -590,7 +652,10 @@ public class Log4jLogEvent implements LogEvent {
final StackTraceElement source,
final long timestampMillis,
final int nanoOfMillisecond,
- final long nanoTime) {
+ final long nanoTime,
+ final String traceId,
+ final String spanId,
+ final String traceFlags) {
this(
loggerName,
marker,
@@ -604,7 +669,10 @@ public class Log4jLogEvent implements LogEvent {
threadName,
threadPriority,
source,
- nanoTime);
+ nanoTime,
+ traceId,
+ spanId,
+ traceFlags);
final long millis =
message instanceof TimestampMessage ? ((TimestampMessage)
message).getTimestamp() : timestampMillis;
instant.initFromEpochMilli(millis, nanoOfMillisecond);
@@ -624,7 +692,10 @@ public class Log4jLogEvent implements LogEvent {
final int threadPriority,
final StackTraceElement source,
final Clock clock,
- final long nanoTime) {
+ final long nanoTime,
+ final String traceId,
+ final String spanId,
+ final String traceFlags) {
this(
loggerName,
marker,
@@ -638,7 +709,10 @@ public class Log4jLogEvent implements LogEvent {
threadName,
threadPriority,
source,
- nanoTime);
+ nanoTime,
+ traceId,
+ spanId,
+ traceFlags);
if (message instanceof TimestampMessage) {
instant.initFromEpochMilli(((TimestampMessage)
message).getTimestamp(), 0);
} else {
@@ -659,7 +733,10 @@ public class Log4jLogEvent implements LogEvent {
final String threadName,
final int threadPriority,
final StackTraceElement source,
- final long nanoTime) {
+ final long nanoTime,
+ final String traceId,
+ final String spanId,
+ final String traceFlags) {
this.loggerName = loggerName;
this.marker = marker;
this.loggerFqcn = loggerFQCN;
@@ -676,6 +753,9 @@ public class Log4jLogEvent implements LogEvent {
((LoggerNameAwareMessage) message).setLoggerName(loggerName);
}
this.nanoTime = nanoTime;
+ this.traceId = traceId != null ? traceId : Strings.EMPTY;
+ this.spanId = spanId != null ? spanId : Strings.EMPTY;
+ this.traceFlags = traceFlags != null ? traceFlags : Strings.EMPTY;
}
private static StringMap createContextData(final Map<String, String>
contextMap) {
@@ -985,7 +1065,10 @@ public class Log4jLogEvent implements LogEvent {
proxy.source,
proxy.timeMillis,
proxy.nanoOfMillisecond,
- proxy.nanoTime);
+ proxy.nanoTime,
+ proxy.traceId,
+ proxy.spanId,
+ proxy.traceFlags);
result.setEndOfBatch(proxy.isEndOfBatch);
result.setIncludeLocation(proxy.isLocationRequired);
return result;
@@ -1093,6 +1176,15 @@ public class Log4jLogEvent implements LogEvent {
if (thrown != null ? !thrown.equals(that.thrown) : that.thrown !=
null) {
return false;
}
+ if (traceId != null ? !traceId.equals(that.traceId) : that.traceId !=
null) {
+ return false;
+ }
+ if (spanId != null ? !spanId.equals(that.spanId) : that.spanId !=
null) {
+ return false;
+ }
+ if (traceFlags != null ? !traceFlags.equals(that.traceFlags) :
that.traceFlags != null) {
+ return false;
+ }
return true;
}
@@ -1116,10 +1208,27 @@ public class Log4jLogEvent implements LogEvent {
result = 31 * result + (source != null ? source.hashCode() : 0);
result = 31 * result + (includeLocation ? 1 : 0);
result = 31 * result + (endOfBatch ? 1 : 0);
+ result = 31 * result + (traceId != null ? traceId.hashCode() : 0);
+ result = 31 * result + (spanId != null ? spanId.hashCode() : 0);
+ result = 31 * result + (traceFlags != null ? traceFlags.hashCode() :
0);
// Check:ON: MagicNumber
return result;
}
+ @Override
+ public String getTraceId() {
+ return traceId;
+ }
+
+ @Override
+ public String getSpanId() {
+ return spanId;
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return traceFlags;
+ }
/**
* Proxy pattern used to serialize the LogEvent.
*/
@@ -1160,6 +1269,10 @@ public class Log4jLogEvent implements LogEvent {
/** @since 2.4 */
private final transient long nanoTime;
+ private final String traceId;
+ private final String spanId;
+ private final String traceFlags;
+
public LogEventProxy(final Log4jLogEvent event, final boolean
includeLocation) {
this.loggerFQCN = event.loggerFqcn;
this.marker = event.marker;
@@ -1180,6 +1293,9 @@ public class Log4jLogEvent implements LogEvent {
this.isLocationRequired = includeLocation;
this.isEndOfBatch = event.endOfBatch;
this.nanoTime = event.nanoTime;
+ this.traceId = event.getTraceId();
+ this.spanId = event.getSpanId();
+ this.traceFlags = event.getTraceFlags();
}
public LogEventProxy(final LogEvent event, final boolean
includeLocation) {
@@ -1210,6 +1326,9 @@ public class Log4jLogEvent implements LogEvent {
this.isLocationRequired = includeLocation;
this.isEndOfBatch = event.isEndOfBatch();
this.nanoTime = event.getNanoTime();
+ this.traceId = event.getTraceId();
+ this.spanId = event.getSpanId();
+ this.traceFlags = event.getTraceFlags();
}
private static Message memento(final ReusableMessage message) {
@@ -1257,7 +1376,10 @@ public class Log4jLogEvent implements LogEvent {
source,
timeMillis,
nanoOfMillisecond,
- nanoTime);
+ nanoTime,
+ traceId,
+ spanId,
+ traceFlags);
result.setEndOfBatch(isEndOfBatch);
result.setIncludeLocation(isLocationRequired);
result.thrownProxy = thrownProxy;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 9a99eae624..6733d48a31 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -30,6 +30,7 @@ import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.util.Clock;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.NanoClock;
+import org.apache.logging.log4j.core.util.TraceContextProviderService;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterConsumer;
import org.apache.logging.log4j.message.ParameterVisitable;
@@ -71,6 +72,9 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
StackTraceElement source;
private ThreadContext.ContextStack contextStack;
transient boolean reserved = false;
+ private String traceId;
+ private String spanId;
+ private String traceFlags;
public MutableLogEvent() {
// messageText and the parameter array are lazily initialized
@@ -133,6 +137,10 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
this.endOfBatch = event.isEndOfBatch();
this.includeLocation = event.isIncludeLocation();
this.nanoTime = event.getNanoTime();
+
+ this.traceId = event.getTraceId();
+ this.spanId = event.getSpanId();
+ this.traceFlags = event.getTraceFlags();
setMessage(event.getMessage());
}
@@ -157,6 +165,9 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
}
contextStack = null;
+ traceId = null;
+ spanId = null;
+ traceFlags = null;
// ThreadName should not be cleared: this field is set in the
ReusableLogEventFactory
// where this instance is kept in a ThreadLocal, so it usually does
not change.
// threadName = null; // no need to clear threadName
@@ -359,6 +370,9 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
instant.initFrom(clock);
}
nanoTime = nanoClock.nanoTime();
+ this.traceId = TraceContextProviderService.getTraceId();
+ this.spanId = TraceContextProviderService.getSpanId();
+ this.traceFlags = TraceContextProviderService.getTraceFlags();
}
@Override
@@ -485,6 +499,33 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
this.nanoTime = nanoTime;
}
+ @Override
+ public String getTraceId() {
+ return traceId;
+ }
+
+ public void setTraceId(final String traceId) {
+ this.traceId = traceId;
+ }
+
+ @Override
+ public String getSpanId() {
+ return spanId;
+ }
+
+ public void setSpanId(final String spanId) {
+ this.spanId = spanId;
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return traceFlags;
+ }
+
+ public void setTraceFlags(final String traceFlags) {
+ this.traceFlags = traceFlags;
+ }
+
/**
* Creates a LogEventProxy that can be serialized.
* @return a LogEventProxy.
@@ -534,6 +575,8 @@ public class MutableLogEvent implements LogEvent,
ReusableMessage, ParameterVisi
.setThreadPriority(threadPriority) //
.setThrown(getThrown()) // may deserialize from thrownProxy
.setInstant(instant) //
- ;
+ .setTraceId(traceId)
+ .setSpanId(spanId)
+ .setTraceFlags(traceFlags);
}
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/package-info.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/package-info.java
index 666e8325ed..2d4bdcd199 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/package-info.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/package-info.java
@@ -18,7 +18,7 @@
* Log4j 2 private implementation classes.
*/
@Export
-@Version("2.24.1")
+@Version("2.25.0")
package org.apache.logging.log4j.core.impl;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
index c09868a666..45bc926c52 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java
@@ -18,7 +18,7 @@
* Implementation of Log4j 2.
*/
@Export
-@Version("2.25.3")
+@Version("2.26.0")
package org.apache.logging.log4j.core;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SpanIdPatternConverter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SpanIdPatternConverter.java
new file mode 100644
index 0000000000..94544ca945
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SpanIdPatternConverter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.pattern;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+
+@Plugin(name = "SpanIdPatternConverter", category = PatternConverter.CATEGORY)
+@ConverterKeys({"spanId"})
+public final class SpanIdPatternConverter extends LogEventPatternConverter {
+
+ private static final SpanIdPatternConverter INSTANCE = new
SpanIdPatternConverter();
+
+ private SpanIdPatternConverter() {
+ super("SpanId", "spanId");
+ }
+
+ public static SpanIdPatternConverter newInstance(final String[] options) {
+ return INSTANCE;
+ }
+
+ @Override
+ public void format(final LogEvent event, final StringBuilder toAppendTo) {
+ final String spanId = event.getSpanId();
+ if (spanId != null) {
+ toAppendTo.append(spanId);
+ }
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceFlagsPatternConverter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceFlagsPatternConverter.java
new file mode 100644
index 0000000000..a27502abcf
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceFlagsPatternConverter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.pattern;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+
+@Plugin(name = "TraceFlagsPatternConverter", category =
PatternConverter.CATEGORY)
+@ConverterKeys({"traceFlags"})
+public final class TraceFlagsPatternConverter extends LogEventPatternConverter
{
+
+ private static final TraceFlagsPatternConverter INSTANCE = new
TraceFlagsPatternConverter();
+
+ private TraceFlagsPatternConverter() {
+ super("TraceFlags", "traceFlags");
+ }
+
+ public static TraceFlagsPatternConverter newInstance(final String[]
options) {
+ return INSTANCE;
+ }
+
+ @Override
+ public void format(final LogEvent event, final StringBuilder toAppendTo) {
+ final String traceFlags = event.getTraceFlags();
+ if (traceFlags != null) {
+ toAppendTo.append(traceFlags);
+ }
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceIdPatternConverter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceIdPatternConverter.java
new file mode 100644
index 0000000000..ab1ed09acc
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TraceIdPatternConverter.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.pattern;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+
+@Plugin(name = "TraceIdPatternConverter", category = PatternConverter.CATEGORY)
+@ConverterKeys({"traceId"})
+public final class TraceIdPatternConverter extends LogEventPatternConverter {
+
+ private static final TraceIdPatternConverter INSTANCE = new
TraceIdPatternConverter();
+
+ private TraceIdPatternConverter() {
+ super("TraceId", "traceId");
+ }
+
+ public static TraceIdPatternConverter newInstance(final String[] options) {
+ return INSTANCE;
+ }
+
+ @Override
+ public void format(final LogEvent event, final StringBuilder toAppendTo) {
+ final String traceId = event.getTraceId();
+ if (traceId != null) {
+ toAppendTo.append(traceId);
+ }
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java
index df5bc576a2..9a21c95c79 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java
@@ -18,7 +18,7 @@
* Provides classes implementing format specifiers in conversion patterns.
*/
@Export
-@Version("2.26.0")
+@Version("2.27.0")
package org.apache.logging.log4j.core.pattern;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/TraceContextProviderService.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/TraceContextProviderService.java
new file mode 100644
index 0000000000..ec6de9b137
--- /dev/null
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/TraceContextProviderService.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.util;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+import org.apache.logging.log4j.spi.TraceContextProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Service registry designed to discover, cache, and safely query the active
{@link TraceContextProvider}.
+ */
+public final class TraceContextProviderService {
+
+ private static final String GLOBAL_KEY =
"log4j2.activeTraceContextProvider";
+ private static volatile TraceContextProvider ACTIVE_PROVIDER;
+
+ static {
+ TraceContextProvider found = null;
+ try {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl == null) {
+ cl = TraceContextProviderService.class.getClassLoader();
+ }
+
+ ServiceLoader<TraceContextProvider> loader =
ServiceLoader.load(TraceContextProvider.class, cl);
+ Iterator<TraceContextProvider> iterator = loader.iterator();
+
+ if (!iterator.hasNext() && cl !=
TraceContextProviderService.class.getClassLoader()) {
+ loader = ServiceLoader.load(
+ TraceContextProvider.class,
TraceContextProviderService.class.getClassLoader());
+ iterator = loader.iterator();
+ }
+
+ if (iterator.hasNext()) {
+ found = iterator.next();
+ StatusLogger.getLogger()
+ .info("Using TraceContextProvider: {}",
found.getClass().getName());
+ }
+ } catch (final Throwable t) {
+ StatusLogger.getLogger().warn("Error loading TraceContextProvider
service", t);
+ }
+ ACTIVE_PROVIDER = found != null ? found :
NoOpTraceContextProvider.INSTANCE;
+ }
+
+ public static TraceContextProvider getActiveProvider() {
+ try {
+ final Object globalProvider =
System.getProperties().get(GLOBAL_KEY);
+ if (globalProvider instanceof TraceContextProvider) {
+ return (TraceContextProvider) globalProvider;
+ }
+ } catch (final SecurityException ignored) {
+ // Gracefully ignore SecurityManager restrictions in strict
environments
+ }
+ return ACTIVE_PROVIDER;
+ }
+
+ public static void setActiveProvider(final TraceContextProvider provider) {
+ try {
+ if (provider == null) {
+ System.getProperties().remove(GLOBAL_KEY);
+ ACTIVE_PROVIDER = NoOpTraceContextProvider.INSTANCE;
+ } else {
+ System.getProperties().put(GLOBAL_KEY, provider);
+ ACTIVE_PROVIDER = provider;
+ }
+ } catch (final SecurityException ignored) {
+ // Fallback for strict environments
+ ACTIVE_PROVIDER = provider != null ? provider :
NoOpTraceContextProvider.INSTANCE;
+ }
+ }
+
+ public static String getTraceId() {
+ try {
+ return getActiveProvider().getTraceId();
+ } catch (final Throwable t) {
+ return null;
+ }
+ }
+
+ public static String getSpanId() {
+ try {
+ return getActiveProvider().getSpanId();
+ } catch (final Throwable t) {
+ return null;
+ }
+ }
+
+ public static String getTraceFlags() {
+ try {
+ return getActiveProvider().getTraceFlags();
+ } catch (final Throwable t) {
+ return null;
+ }
+ }
+
+ private static final class NoOpTraceContextProvider implements
TraceContextProvider {
+ static final NoOpTraceContextProvider INSTANCE = new
NoOpTraceContextProvider();
+
+ @Override
+ public String getTraceId() {
+ return null;
+ }
+
+ @Override
+ public String getSpanId() {
+ return null;
+ }
+
+ @Override
+ public String getTraceFlags() {
+ return null;
+ }
+ }
+
+ private TraceContextProviderService() {}
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java
index e4ac85f5eb..b9efa51956 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/package-info.java
@@ -18,7 +18,7 @@
* Log4j 2 helper classes.
*/
@Export
-@Version("2.25.3")
+@Version("2.26.0")
package org.apache.logging.log4j.core.util;
import org.osgi.annotation.bundle.Export;
diff --git
a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncTraceContextBenchmark.java
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncTraceContextBenchmark.java
new file mode 100644
index 0000000000..53c284a7e0
--- /dev/null
+++
b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncTraceContextBenchmark.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.perf.jmh;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.async.RingBufferLogEvent;
+import org.apache.logging.log4j.core.async.RingBufferLogEventTranslator;
+import org.apache.logging.log4j.core.impl.ContextDataFactory;
+import org.apache.logging.log4j.core.util.ClockFactory;
+import org.apache.logging.log4j.core.util.DummyNanoClock;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Fork(1)
+@State(Scope.Benchmark)
+public class AsyncTraceContextBenchmark {
+
+ private SimpleMessage message;
+
+ @Setup
+ public void setup() {
+ message = new SimpleMessage("Test performance message");
+ }
+
+ // Reusable thread-local state to match Disruptor events and translators.
+
+ @State(Scope.Thread)
+ public static class ThreadState {
+ final RingBufferLogEvent event = new RingBufferLogEvent();
+ final RingBufferLogEventTranslator translator = new
RingBufferLogEventTranslator();
+ }
+
+ @Benchmark
+ public void baseline(final ThreadState state) {
+ state.translator.setBasicValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ message,
+ null,
+ null,
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock());
+ state.translator.translateTo(state.event, 0);
+ }
+
+ @Benchmark
+ public void threadContextTracing(final ThreadState state) {
+ ThreadContext.put("traceId", "4bf92f3577b34da6a3ce929d0e0e4736");
+ ThreadContext.put("spanId", "00f067aa0ba902b7");
+ ThreadContext.put("traceFlags", "01");
+
+ try {
+ state.translator.setBasicValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ message,
+ null,
+ ThreadContext.getImmutableStack(),
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock());
+ state.translator.translateTo(state.event, 0);
+ } finally {
+ ThreadContext.clearMap();
+ }
+ }
+
+ @Benchmark
+ public void nativeTracing(final ThreadState state) {
+ state.translator.setBasicValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ message,
+ null,
+ null,
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock(),
+ "4bf92f3577b34da6a3ce929d0e0e4736",
+ "00f067aa0ba902b7",
+ "01");
+ state.translator.translateTo(state.event, 0);
+ }
+
+ public static void main(final String[] args) throws Exception {
+ new Runner(new OptionsBuilder()
+
.include(AsyncTraceContextBenchmark.class.getSimpleName())
+ .build())
+ .run();
+ }
+ /**
+ * Simulates ContextDataProvider approach.
+ * It avoids MDC, but forces the creation of multiple StringMaps for every
log event.
+ */
+ @Benchmark
+ public void contextDataProviderTracing(final ThreadState state) {
+ // OTel allocates a brand new map for the trace context (Allocation 1)
+ final org.apache.logging.log4j.util.StringMap providerMap =
ContextDataFactory.createContextData();
+ providerMap.putValue("traceId", "4bf92f3577b34da6a3ce929d0e0e4736");
+ providerMap.putValue("spanId", "00f067aa0ba902b7");
+ providerMap.putValue("traceFlags", "01");
+
+ // Log4j's internal ContextDataInjector sees the RingBuffer map is
frozen.
+ // To avoid crashing, it allocates a NEW map to safely merge the data
(Allocation 2)
+ final org.apache.logging.log4j.util.StringMap newMergedMap =
ContextDataFactory.createContextData();
+ newMergedMap.putAll(providerMap); // Merges the OTel data
+
+ // Execute translator setup (keeps CPU comparison fair)
+ state.translator.setBasicValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ message,
+ null,
+ null,
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock());
+
+ // Simulate translateTo() injecting the new map directly into the
RingBuffer slot
+ state.event.setValues(
+ null,
+ "Logger",
+ null,
+ "FQCN",
+ Level.INFO,
+ message,
+ null,
+ newMergedMap, // Log4j injects the newly allocated map here
+ ThreadContext.getImmutableStack(),
+ 1L,
+ "main",
+ 5,
+ null,
+ ClockFactory.getClock(),
+ new DummyNanoClock());
+ }
+}
diff --git a/src/changelog/.2.x.x/1976_add_native_tracing_fields.xml
b/src/changelog/.2.x.x/1976_add_native_tracing_fields.xml
new file mode 100644
index 0000000000..7894ba4c0c
--- /dev/null
+++ b/src/changelog/.2.x.x/1976_add_native_tracing_fields.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns="https://logging.apache.org/xml/ns"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ https://logging.apache.org/xml/ns
+ https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="added">
+ <issue id="1976"
link="https://github.com/apache/logging-log4j2/issues/1976"/>
+ <issue id="4171"
link="https://github.com/apache/logging-log4j2/pull/4171"/>
+ <description format="asciidoc">
+ Add native W3C tracing fields (`traceId`, `spanId`, `traceFlags`) to
`LogEvent` and introduce the `TraceContextProvider` SPI for garbage-free
distributed tracing context propagation.
+ </description>
+</entry>
\ No newline at end of file