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…


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.

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

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

        enrichWithSleuthHeaderWhenMissing(tracer, request);

        Valve next = getNext();
        if (null == next) {
            // no next valve
        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);

See for the tests. for spring-boot:

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

    public SleuthValve sleuthValve(Tracer tracer) {
        return new SleuthValve(tracer);

    public LogbackValve logbackValve() {
        LogbackValve logbackValve = new LogbackValve();
        logbackValve.putProperty("HOSTNAME", new ContextUtil(null).safelyGetLocalHostName());
        return logbackValve;


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

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

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

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



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