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

Reply via email to