From 2f8bf9971af1fa67d09997f42a1b158dc8c3cbc4 Mon Sep 17 00:00:00 2001 From: Arsh Date: Wed, 30 Sep 2026 11:56:10 +0530 Subject: [PATCH 1/2] Add Log4j2 logging support --- README.md | 2 +- .../opentracing-spring-cloud-core/pom.xml | 10 +++ .../log/Log4j2LoggingAutoConfiguration.java | 50 +++++++++++++++ .../cloud/log/Log4j2SpanLogsAppender.java | 61 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + .../cloud/log/Log4j2SpanLogsAppenderTest.java | 61 +++++++++++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2LoggingAutoConfiguration.java create mode 100644 instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppender.java create mode 100644 instrument-starters/opentracing-spring-cloud-core/src/test/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppenderTest.java diff --git a/README.md b/README.md index 59c0031f..14f2d70f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ It contains auto-configurations that instrument and trace the following Spring B - Reactor - RxJava - Redis -- Standard logging - logs are added to active span +- Standard logging - Logback and Log4j2 logs are added to the active span - Spring Messaging - trace messages being sent through [Messaging Channels](https://docs.spring.io/spring-integration/reference/html/#messaging-channels-section) - RabbitMQ diff --git a/instrument-starters/opentracing-spring-cloud-core/pom.xml b/instrument-starters/opentracing-spring-cloud-core/pom.xml index 45f333bf..b8257c82 100644 --- a/instrument-starters/opentracing-spring-cloud-core/pom.xml +++ b/instrument-starters/opentracing-spring-cloud-core/pom.xml @@ -47,6 +47,16 @@ spring-boot-configuration-processor true + + org.apache.logging.log4j + log4j-api + true + + + org.apache.logging.log4j + log4j-core + true + org.springframework.boot spring-boot-starter-aop diff --git a/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2LoggingAutoConfiguration.java b/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2LoggingAutoConfiguration.java new file mode 100644 index 00000000..78340cac --- /dev/null +++ b/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2LoggingAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright © 2017-2026 The OpenTracing Authors + * + * Licensed 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 io.opentracing.contrib.spring.cloud.log; + +import io.opentracing.Tracer; +import io.opentracing.contrib.spring.tracer.configuration.TracerAutoConfiguration; +import javax.annotation.PostConstruct; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(TracerAutoConfiguration.class) +@ConditionalOnClass(name = "org.apache.logging.log4j.core.LoggerContext") +@ConditionalOnProperty(name = "opentracing.spring.cloud.log.enabled", havingValue = "true", matchIfMissing = true) +public class Log4j2LoggingAutoConfiguration { + + @Autowired + private Tracer tracer; + + @PostConstruct + public void postConstruct() { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + LoggerConfig rootLogger = context.getConfiguration().getRootLogger(); + Log4j2SpanLogsAppender appender = new Log4j2SpanLogsAppender(tracer); + appender.start(); + rootLogger.addAppender(appender, null, null); + context.updateLoggers(); + } +} diff --git a/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppender.java b/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppender.java new file mode 100644 index 00000000..ce16b4bb --- /dev/null +++ b/instrument-starters/opentracing-spring-cloud-core/src/main/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppender.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2017-2026 The OpenTracing Authors + * + * Licensed 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 io.opentracing.contrib.spring.cloud.log; + +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.tag.Tags; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; + +public class Log4j2SpanLogsAppender extends AbstractAppender { + + private final Tracer tracer; + + public Log4j2SpanLogsAppender(Tracer tracer) { + super(Log4j2SpanLogsAppender.class.getSimpleName(), null, null, true, Property.EMPTY_ARRAY); + this.tracer = tracer; + } + + @Override + public void append(LogEvent event) { + Span span = tracer.activeSpan(); + if (span == null) { + return; + } + + Map logs = new HashMap<>(6); + logs.put("logger", event.getLoggerName()); + logs.put("level", event.getLevel().toString()); + logs.put("thread", event.getThreadName()); + logs.put("message", event.getMessage().getFormattedMessage()); + + if (Level.ERROR.equals(event.getLevel())) { + Tags.ERROR.set(span, Boolean.TRUE); + logs.put("event", Tags.ERROR.getKey()); + } + + if (event.getThrown() != null) { + logs.put("error.object", event.getThrown()); + } + span.log(TimeUnit.MICROSECONDS.convert(event.getTimeMillis(), TimeUnit.MILLISECONDS), logs); + } +} diff --git a/instrument-starters/opentracing-spring-cloud-core/src/main/resources/META-INF/spring.factories b/instrument-starters/opentracing-spring-cloud-core/src/main/resources/META-INF/spring.factories index 40123753..b13779b1 100644 --- a/instrument-starters/opentracing-spring-cloud-core/src/main/resources/META-INF/spring.factories +++ b/instrument-starters/opentracing-spring-cloud-core/src/main/resources/META-INF/spring.factories @@ -3,6 +3,7 @@ io.opentracing.contrib.spring.cloud.async.CustomAsyncConfigurerAutoConfiguration io.opentracing.contrib.spring.cloud.async.DefaultAsyncAutoConfiguration,\ io.opentracing.contrib.spring.cloud.scheduled.ScheduledAutoConfiguration,\ io.opentracing.contrib.spring.cloud.log.LoggingAutoConfiguration,\ +io.opentracing.contrib.spring.cloud.log.Log4j2LoggingAutoConfiguration,\ io.opentracing.contrib.spring.cloud.traced.TracedAutoConfiguration # Environment Post Processor diff --git a/instrument-starters/opentracing-spring-cloud-core/src/test/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppenderTest.java b/instrument-starters/opentracing-spring-cloud-core/src/test/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppenderTest.java new file mode 100644 index 00000000..b168f564 --- /dev/null +++ b/instrument-starters/opentracing-spring-cloud-core/src/test/java/io/opentracing/contrib/spring/cloud/log/Log4j2SpanLogsAppenderTest.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2017-2026 The OpenTracing Authors + * + * Licensed 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 io.opentracing.contrib.spring.cloud.log; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import io.opentracing.tag.Tags; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.Test; + +public class Log4j2SpanLogsAppenderTest { + + @Test + public void appendsLogEventToActiveSpan() { + MockTracer tracer = new MockTracer(); + MockSpan span = tracer.buildSpan("request").start(); + Log4j2SpanLogsAppender appender = new Log4j2SpanLogsAppender(tracer); + appender.start(); + + try (io.opentracing.Scope ignored = tracer.scopeManager().activate(span)) { + appender.append(Log4jLogEvent.newBuilder() + .setLoggerName("example.Controller") + .setLevel(Level.ERROR) + .setThreadName("request-thread") + .setMessage(new SimpleMessage("request failed")) + .setTimeMillis(1000L) + .setThrown(new IllegalStateException("failure")) + .build()); + } + + assertEquals(1, span.logEntries().size()); + MockSpan.LogEntry entry = span.logEntries().get(0); + assertEquals("example.Controller", entry.fields().get("logger")); + assertEquals("ERROR", entry.fields().get("level")); + assertEquals("request-thread", entry.fields().get("thread")); + assertEquals("request failed", entry.fields().get("message")); + assertEquals(1000000L, entry.timestampMicros()); + assertSame(IllegalStateException.class, ((Throwable) entry.fields().get("error.object")).getClass()); + assertEquals(Tags.ERROR.getKey(), entry.fields().get("event")); + assertEquals(Boolean.TRUE, span.tags().get(Tags.ERROR.getKey())); + span.finish(); + } +} From cc1d7b32e76aae2fb9d6302a2047ce47867e449f Mon Sep 17 00:00:00 2001 From: Arsh Date: Wed, 30 Sep 2026 12:35:42 +0530 Subject: [PATCH 2/2] Fix Feign instrumentation for final OkHttp clients --- .../feign/FeignContextBeanPostProcessor.java | 5 +- .../feign/FeignTracingAutoConfiguration.java | 31 ------------ .../FeignContextBeanPostProcessorTest.java | 47 +++++++++++++++++++ .../feign/FeignManualOkhttpClientTest.java | 22 +++++++++ .../spring/cloud/feign/FeignManualTest.java | 4 ++ 5 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessorTest.java create mode 100644 instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualOkhttpClientTest.java diff --git a/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessor.java b/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessor.java index 756d115e..13dabaf9 100644 --- a/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessor.java +++ b/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessor.java @@ -19,7 +19,6 @@ import io.opentracing.Tracer; import java.util.List; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.cloud.openfeign.FeignContext; @@ -30,10 +29,12 @@ public class FeignContextBeanPostProcessor implements BeanPostProcessor { private Tracer tracer; private List spanDecorators; + private TracedFeignBeanFactory tracedFeignBeanFactory; FeignContextBeanPostProcessor(Tracer tracer, List spanDecorators) { this.tracer = tracer; this.spanDecorators = spanDecorators; + this.tracedFeignBeanFactory = new TracedFeignBeanFactory(tracer, spanDecorators); } @Override @@ -46,6 +47,6 @@ public Object postProcessBeforeInitialization(Object bean, String name) throws B @Override public Object postProcessAfterInitialization(Object bean, String name) throws BeansException { - return bean; + return tracedFeignBeanFactory.from(bean); } } diff --git a/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignTracingAutoConfiguration.java b/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignTracingAutoConfiguration.java index 73d41b34..fe0a4594 100644 --- a/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignTracingAutoConfiguration.java +++ b/instrument-starters/opentracing-spring-cloud-feign-starter/src/main/java/io/opentracing/contrib/spring/cloud/feign/FeignTracingAutoConfiguration.java @@ -16,17 +16,11 @@ package io.opentracing.contrib.spring.cloud.feign; import feign.Client; -import feign.Request; import feign.opentracing.FeignSpanDecorator; -import feign.opentracing.TracingClient; import feign.opentracing.hystrix.TracingConcurrencyStrategy; import io.opentracing.Tracer; import io.opentracing.contrib.spring.tracer.configuration.TracerAutoConfiguration; import java.util.List; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -74,29 +68,4 @@ public HystrixFeign(Tracer tracer) { TracingConcurrencyStrategy.register(tracer); } } - - @Bean - public TracingAspect tracingAspect() { - return new TracingAspect(); - } - - /** - * Trace feign clients created manually - */ - @Aspect - class TracingAspect { - - @Around("execution (* feign.Client.*(..)) && !within(is(FinalType))") - public Object feignClientWasCalled(final ProceedingJoinPoint pjp) throws Throwable { - Object bean = pjp.getTarget(); - if (!(bean instanceof TracingClient)) { - Object[] args = pjp.getArgs(); - return new TracingClientBuilder((Client) bean, tracer) - .withFeignSpanDecorators(spanDecorators) - .build() - .execute((Request) args[0], (Request.Options) args[1]); - } - return pjp.proceed(); - } - } } diff --git a/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessorTest.java b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessorTest.java new file mode 100644 index 00000000..b50f506e --- /dev/null +++ b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignContextBeanPostProcessorTest.java @@ -0,0 +1,47 @@ +/* + * Copyright © 2017-2026 The OpenTracing Authors + * + * Licensed 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 io.opentracing.contrib.spring.cloud.feign; + +import static org.junit.Assert.assertTrue; + +import feign.Client; +import feign.Request; +import feign.Response; +import feign.opentracing.TracingClient; +import io.opentracing.mock.MockTracer; +import java.io.IOException; +import java.util.Collections; +import org.junit.Test; + +public class FeignContextBeanPostProcessorTest { + + @Test + public void wrapsFinalFeignClientWithoutSubclassProxy() throws IOException { + FeignContextBeanPostProcessor postProcessor = new FeignContextBeanPostProcessor( + new MockTracer(), Collections.emptyList()); + + Object client = postProcessor.postProcessAfterInitialization(new FinalClient(), "client"); + + assertTrue(client instanceof TracingClient); + } + + private static final class FinalClient implements Client { + @Override + public Response execute(Request request, Request.Options options) { + return null; + } + } +} diff --git a/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualOkhttpClientTest.java b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualOkhttpClientTest.java new file mode 100644 index 00000000..f35f711f --- /dev/null +++ b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualOkhttpClientTest.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2017-2026 The OpenTracing Authors + * + * Licensed 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 io.opentracing.contrib.spring.cloud.feign; + +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = "feign.okhttp.enabled=true") +public class FeignManualOkhttpClientTest extends FeignManualTest { +} diff --git a/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualTest.java b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualTest.java index e1ed406f..61f5ae3d 100644 --- a/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualTest.java +++ b/instrument-starters/opentracing-spring-cloud-feign-starter/src/test/java/io/opentracing/contrib/spring/cloud/feign/FeignManualTest.java @@ -20,6 +20,7 @@ import feign.Client; import feign.Feign; import feign.RequestLine; +import feign.opentracing.TracingClient; import io.opentracing.contrib.spring.cloud.feign.FeignManualTest.ManualFeignConfiguration; import io.opentracing.contrib.spring.cloud.feign.FeignTest.FeignRibbonLocalConfiguration; import io.opentracing.mock.MockTracer; @@ -51,6 +52,7 @@ static class ManualFeignConfiguration { @Autowired public ManualFeignConfiguration(Client client) { + manualClient = client; feignInterface = Feign.builder().client(client) .target(FeignInterface.class, "http://localService"); } @@ -66,12 +68,14 @@ interface FeignInterface { } protected static FeignInterface feignInterface; + protected static Client manualClient; @Autowired protected MockTracer mockTracer; @Test public void testTracedRequestDefinedUrl() throws InterruptedException { + org.junit.Assert.assertTrue(manualClient instanceof TracingClient); feignInterface.hello(); verify(mockTracer); }