Joseph Earl

Customizing the logback-access format

observabilityspring-bootlogbacklogback-accesstomcat

If you’re already using Logback and Logback Logstash Encoder with Spring Boot to log to something like ELK or Splunk and want to add HTTP access logs to your application then logback-access is simple to integrate and the output format and destination can be easily configured through XML just like for standard Logback.

To begin add the logback-access dependency to your project:

implementation 'ch.qos.logback:logback-access'

Then configure the embedded server (Tomcat in this case) to integrate with logback-access:

@Configuration
public class LogbackAccessConfiguration implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> {
    @Override
    public void customize(ConfigurableTomcatWebServerFactory factory) {
        LogbackValve logbackValve = new LogbackValve();
        logbackValve.setFilename("logback-access.xml");
        factory.addEngineValves(logbackValve);
    }
}

And finally create a logback-access.xml in src/main/resources:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashAccessEncoder" />
    </appender>

    <appender-ref ref="STDOUT" />
</configuration>

By default the access events do not have a log level, to add one change the encoder configuration and adding a JSON object in customFields that will be merged with JSON output from the encoder:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashAccessEncoder">
            <customFields>{"level": "DEBUG"}</customFields>
        </encoder>
    </appender>

    <appender-ref ref="STDOUT" />
</configuration>

You can also rename fields in the JSON output and add request and response headers to the output.

If you want complete control over the output you can use AccessEventCompositeJsonEncoder instead of LogstashAccessEncoder to specify exactly which fields are included in the JSON and the format of the message.

In the following example we include the timestamp field from the common fields, the requestedUrl, statusCode and elapsedTime fields from the access fields, and in addition a constant level field, a user_agent field with value taken from a request header and a simple custom message format:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.AccessEventCompositeJsonEncoder">
            <providers>
                <timestamp/>
                <requestedUrl/>
                <statusCode/>
                <elapsedTime/>
                <pattern>
                    <pattern>
                        {
                            "level": "DEBUG",
                            "user_agent": "%i{User-Agent}",
                            "message": "%requestURL returned %statusCode in %elapsedTime ms"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <appender-ref ref="STDOUT" />
</configuration>

If you want to ignore certain URLs and not have accesses to them logged, you can do this by adding a filter to your appender configuration, for example to not log accesses to the /actuator path (and sub paths):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator class="ch.qos.logback.access.net.URLEvaluator">
                <URL>/actuator</URL>
            </evaluator>
            <OnMismatch>NEUTRAL</OnMismatch>
            <OnMatch>DENY</OnMatch>
        </filter>
        <encoder class="net.logstash.logback.encoder.LogstashAccessEncoder" />
    </appender>

    <appender-ref ref="STDOUT" />
</configuration>