Spring: Logback access logs with custom JSON format and (a workaround to read) MDC
In case you need to send the Spring access log to ELK or Datadog, you’ll probably use the LogstashAccessEncoder . Below the configuration to load it into Spring.
@Slf4j
@Configuration
public class Web {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> accessLogsCustomizer() {
return factory -> {
var logbackValve = new LogbackValve();
logbackValve.setFilename("logback-access.xml");
logbackValve.setAsyncSupported(true);
factory.addContextValves(logbackValve);
};
}
}
<!-- logback-access.xml -->
<configuration>
<appender name="ACCESS_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashAccessEncoder">
... config ...
</encoder>
</appender>
<appender-ref ref="ACCESS_STDOUT"/>
</configuration>
If more customization is what you need, then you can use the AccessEventCompositeJsonEncoder, where you specify the JSON template under <pattern> using the conversion specifiers for the logback layout.
<configuration>
<appender name="ACCESS_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.AccessEventCompositeJsonEncoder">
<providers>
<timestamp />
<pattern>
<pattern>
{
"yourField": {
"yourNestedField": "%reqAttribute{abc}"
},
"http": {
"path" : "%requestURI"
}
"@version" : "2023-01-03",
"@type" : "access"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<appender-ref ref="ACCESS_STDOUT"/>
</configuration>
Note that MDC is not unfortunately supported in the current version, but one way to solve is creating a filter that sets the data as a request attribute in addition to MDC. See the example below the adds the “abc” request attribute that is read by the config above.
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MDCXRequestIdLoggingFilter implements Filter {
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
if (request instanceof HttpServletRequest httpServletRequest
&& response instanceof HttpServletResponse httpServletResponse) {
var abcValue = "SET ME";
MDC.put("abc", abcValue);
httpServletRequest.setAttribute("abc", abcValue);
}
filterChain.doFilter(request, response);
}
}
If the code sets “first value” and then “second value”, that will be the output in the access logs
{
"yourField": {
"yourNestedField": "first value"
},
"http": {
"path" : "/"
},
"@version" : "2023-01-03",
"@type" : "access"
}
{
"yourField": {
"yourNestedField": "second value"
},
"http": {
"path" : "/"
},
"@version" : "2023-01-03",
"@type" : "access"
}
Clap if useful, follow me for more