Categories
Code

Spring Boot http access logging in three steps

Sometimes it can be handy to have a Spring Boot app log http requests to its embedded web server. Here is a simple way to achieve this, with minimal dependencies. The examples apply only when using default Apache Tomcat embedded web server and Logback as logging implementation.

1. Add dependency logback-access to your project:

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-access</artifactId>
</dependency>

2. Add Logback access log configuration file to root of classpath:
src/main/resources/logback-access.xml

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <appender name="access_stdout" 
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>common</pattern>
        </encoder>
    </appender>

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

3. Add a @Configuration class which integrates Logback into Tomcat (Kotlin in this example):

import ch.qos.logback.access.tomcat.LogbackValve
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.nio.file.Files

@Configuration
open class AccessLogConfiguration {

    @Bean
    fun addLogbackAccessValve() = TomcatContextCustomizer { context ->

        javaClass.getResourceAsStream("/logback-access.xml").use {
            Files.createDirectories((context.catalinaBase.toPath()
                .resolve(LogbackValve.DEFAULT_CONFIG_FILE)).parent)

            Files.copy(it, context.catalinaBase.toPath()
                .resolve(LogbackValve.DEFAULT_CONFIG_FILE))
        }

        LogbackValve().let {
            it.isQuiet = true
            context.pipeline.addValve(it)
        }
    }

}

Here we copy the Logback access configuration file into Tomcat embedded web server runtime dir. This circumvents an issue with LogbackValve being initialized in a different class loader context by Tomcat, where it is not able to resolve its configuration file in the Spring Boot app main classpath. If it’s not working, set isQuiet to false to debug.

Now your Spring Boot app logs http requests through the console appender in a typical format:

127.0.0.1 - - [20/feb./2021:22:16:54 +0100] "GET / HTTP/1.1" 200 332

Getting Logstash-compatible JSON-output

In case you use Kibana and need structured log formatting, you can easily achieve this now. First ensure you have the dependency
net.logstash.logback:logstash-logback-encoder added to your build. Then simply use a slightly different
logback-access.xml configuration file:

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

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

Now your access log messages will be structured like this:

{
  "@timestamp": "2021-02-21T17:38:30.813+01:00",
  "@version": "1",
  "message": "127.0.0.1 - - [2021-02-21T17:38:30.813+01:00] \"GET / HTTP/1.1\" 200 332",
  "method": "GET",
  "protocol": "HTTP/1.1",
  "status_code": 200,
  "requested_url": "GET / HTTP/1.1",
  "requested_uri": "/",
  "remote_host": "127.0.0.1",
  "content_length": 332,
  "elapsed_time": 47
}