sleuth valve for tomcat

inject header on first request for springboot avoid proxy/firewall instrumentation

At idealo we do a lot of microservices with spring-boot. To trace request we use spring-cloud-sleuth as opentracing implementation. I’m missing the injection of the trace header on the first request. You still need to deliver those with your request. So here follows my solution…

concept

Let me show my approach. It is aimed to be easy to implement and easy to comprehend.

I follow these simple steps:

  1. add a custom tomcat valve named SleuthValve
  2. configure tomcat with LogbackValve and SleuthValve in AccessLogAutoConfiguration
  3. configure logback-access.xml to log traces

This really nice integrates with logback-redis.


SleuthValve.java:

@RequiredArgsConstructor // lombok
class SleuthValve extends ValveBase {
    private final Tracer tracer;

    @Override
    public void invoke(Request request, Response response) 
        throws IOException, ServletException {

        enrichWithSleuthHeaderWhenMissing(tracer, request);

        Valve next = getNext();
        if (null == next) {
            // no next valve
            return;
        }
        next.invoke(request, response);
    }

    private static void enrichWithSleuthHeaderWhenMissing(Tracer tracer, 
                                                          Request request) {
        String header = request.getHeader(Span.TRACE_ID_NAME);
        if (null == header) {

            org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
            MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();

            Span span = tracer.createSpan("SleuthValve");

            addHeader(mimeHeaders, Span.TRACE_ID_NAME, span.traceIdString());
            addHeader(mimeHeaders, Span.SPAN_ID_NAME, span.traceIdString());
        }
    }

    private static void addHeader(MimeHeaders mimeHeaders, 
                                  String traceIdName, 
                                  String value) {
        MessageBytes messageBytes = mimeHeaders.addValue(traceIdName);
        messageBytes.setString(value);
    }
}

See SleuthValveTest.java for the tests.

AccessLogAutoConfiguration.java for spring-boot:

@Slf4j
@Configuration
public class AccessLogAutoConfiguration {
    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(SleuthValve sleuthValve, 
                                                                  LogbackValve logbackValve) {
        return container -> {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                ((TomcatEmbeddedServletContainerFactory) container)
                        .addContextCustomizers((TomcatContextCustomizer) context -> {
                            context.getPipeline().addValve(sleuthValve);
                            context.getPipeline().addValve(logbackValve);
                        });
            } else {
                log.warn("no access-log auto-configuration for container: {}", container);
            }
        };
    }

    @Bean
    public SleuthValve sleuthValve(Tracer tracer) {
        return new SleuthValve(tracer);
    }

    @Bean
    public LogbackValve logbackValve() {
        LogbackValve logbackValve = new LogbackValve();
        logbackValve.putProperty("HOSTNAME", new ContextUtil(null).safelyGetLocalHostName());
        logbackValve.setFilename("logback-access.xml");
        logbackValve.setQuiet(true);
        return logbackValve;
    }
}

logback-access.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="FILE_ACCESS" class="ch.qos.logback.core.FileAppender">
        <append>false</append>
        <file>target/logs/access.log</file>
        <encoder>
            <pattern>ACCESS ${HOSTNAME} %i{X-B3-TraceId}</pattern>
        </encoder>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>ACCESS ${HOSTNAME} %i{X-B3-TraceId}</pattern>
        </encoder>
    </appender>

    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE_ACCESS"/>

</configuration>

Hint:

  • all code is available via gist
  • code uses lombok annotation