How to enable Tomcat's access logs with Spring Boot and write them encoded as JSON to stdout.
With Spring Boot, access logs could be enabled with server.tomcat.accesslog.enabled, but this writes the access logs to a temporary file.
In a containerized or cloud-native environments, logs are collected from stdout and forwarded to a log collector, such as Elasticsearch.
This demo shows how to configure Spring Boot 2 to write access logs to console and encode them as JSON.
{"@timestamp":"2020-05-14T22:12:33.780+02:00","@version":"1","message":"This is a normal log statement: bar","logger_name":"de.jochenchrist.springboot.accesslogs.FooController","thread_name":"http-nio-8080-exec-1","level":"INFO","level_value":20000,"foo":"bar"}
{"@timestamp":"2020-05-14T22:12:33.836+02:00","@version":"1","@type":"access","client-host":"0:0:0:0:0:0:0:1","remote-user":"-","request-message":"GET","request-url":"GET / HTTP/1.1","request-uri":"/","status-code":200,"bytes-sent":3,"elapsed-time":164,"message":"GET / HTTP/1.1 200"}Add to your pom.xml:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.2</version>
</dependency>Add a WebServerFactoryCustomizer, that adds the LogbackValve to the WebServerFactory.
The default filename for the logback configuration is logback-access.xml, which can be changed here.
@Configuration
public class AccessLogsConfiguration {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> accessLogsCustomizer() {
return factory -> {
var logbackValve = new LogbackValve();
logbackValve.setFilename("logback-access.xml");
logbackValve.setAsyncSupported(true);
factory.addContextValves(logbackValve);
};
}
}Put the logback configuration file in src/main/resources (probably next to your existing logback.xml for the Spring application). Configure it to use the AccessEventCompositeJsonEncoder (provided by Logstash Logback Encoder) with your desired JSON structure:
<configuration debug="false">
<appender name="accessJsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.AccessEventCompositeJsonEncoder">
<providers>
<timestamp />
<pattern>
<pattern>
{
"@version" : "1",
"@type" : "access",
"client-host" : "%clientHost",
"remote-user" : "%user",
"request-message" : "%requestMethod",
"request-url" : "%requestURL",
"request-uri" : "%requestURI",
"status-code" : "#asLong{%statusCode}" ,
"bytes-sent" : "#asLong{%bytesSent}",
"elapsed-time" : "#asLong{%elapsedTime}",
"message" : "%requestURL %statusCode"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<appender-ref ref="accessJsonConsoleAppender" />
</configuration>Thanks to my colleagues Martin Eigenbrodt for the initial concept and Timo Loist for pointing me to this solution.