Spring: Spring Boot with SLF4J/Logback sending to syslog

The Spring framework provides a proven and well documented model for the development of custom projects and services. The Spring Boot project takes an opinionated view of building production Spring applications, which favors convention over configuration.

In this article we will explore how to configure a Spring Boot project to use the Simple Logging Facade for Java (SLF4J) with a Logback backend to send log events to the console, filesystem, and syslog.

The code and configurations from this article can all be found in my fork of the gs-rest-service project.

Spring Boot without configured logging

For this article, we will work on the simple gs-rest-service from the Spring Guides.  This exposes an endpoint at ‘/greeting’, and returns a JSON string containing an id and ‘Hello, World!’ greeting.

On Ubuntu, retrieving the project, building the package, and then running the service looks like:

[shell]
> sudo apt-get install git openjdk-7-jdk maven -y
> git clone https://github.com/spring-guides/gs-rest-service.git
> cd gs-rest/service/complete
> ./mvnw package
> java -jar target/gs-rest-service-0.1.0.jar
[/shell]

Then in another terminal use curl to call the service:

[shell]
curl http://localhost:8080/greeting
{“id”:1,”content”:”Hello, World!”}
[/shell]

Notice that barring the initial logging output from the embedded Tomcat, there is no logging produced by calling the service.

Spring Boot configured for SLF4J/Logback

Now create the file ‘gs-rest-service/complete/src/main/resources/logback.xml’ which can be downloaded from my github. But below is a snippet:

<configuration debug="true" scan="true" scanPeriod="30 seconds">
...

<appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
  <syslogHost>127.0.0.1</syslogHost>
  <facility>LOCAL0</facility>
  <port>514</port>
  <throwableExcluded>true</throwableExcluded>
  <suffixPattern>gs-rest-service %m thread:%t priority:%p category:%c exception:%exception</suffixPattern> 
</appender>

...

<logger name="hello" level="debug" additivity="false">
  <appender-ref ref="STDOUT" />
  <appender-ref ref="FILEROLLING" />
  <appender-ref ref="SYSLOG" />
</logger>

...

This sets up DEBUG level messages to go to stdout, and package specific messages coming from hello.GreetingController.java to be sent to a rolling file appender in /tmp and also to the local syslog server.

For details on enabling syslog on an Ubuntu server click, read my article here. The syslog pattern is specifying ‘gs-rest-service’ as the programname which can then be routed to its own file under ‘/var/log’.

The @scan and @scanPeriod attributes allow us to modify the logging configuration at runtime.

Then modify ‘src/main/java/hello/GreetingController.java’ so it looks like:
[JAVA]
package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

static final Logger logger = LoggerFactory.getLogger(GreetingController.class);

private static final String template = “Hello, %s!”;
private final AtomicLong counter = new AtomicLong();

@RequestMapping(“/greeting”)
public Greeting greeting(@RequestParam(value=”name”, defaultValue=”World”) String name) {

logger.debug(“debug message slf4j”);
logger.info(“info message slf4j”);
logger.warn(“warn message slf4j”);
logger.error(“error message slf4j”);
if(“forceerror”.equals(name)) {
try {
int i = 1/0;
}catch(Exception exc) {
logger.error(“error message with stack trace slf4j”,
new Exception(“I forced this exception”,exc));
}
}

return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}

} // class

[/JAVA]

Now repackage the service and run it again:

[shell]
> ./mvnw package
> java -jar target/gs-rest-service-0.1.0.jar
[/shell]

Use curl to call the service again, and notice how DEBUG level messages from the framework and embedded Tomcat are going to stdout, but the ‘hello’ package specific messages that are WARN level and above are going to ‘/tmp/rolling-yyyy-mm-dd.x.log’:

19:53:08.579 [http-nio-8080-exec-2] WARN  hello.GreetingController - warn message slf4j
19:53:08.579 [http-nio-8080-exec-2] ERROR hello.GreetingController - error message slf4j

If you have configured a syslog server, or other entity capable of receiving syslog messages (e.g. logstash), then you should also see the application messages going there as well.

Read my article here for full details on configuring a syslog server on Ubuntu.

 

 

REFERENCES

https://github.com/fabianlee/gs-rest-service

https://projects.spring.io/spring-boot/

https://github.com/spring-guides/gs-rest-service

http://docs.spring.io/spring-boot/docs/1.5.1.RELEASE/reference/htmlsingle/#production-ready-logger-configuration

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html

https://logback.qos.ch/manual/appenders.html

http://javabeat.net/spring-boot-logging/

https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html

http://stackoverflow.com/questions/20795373/how-to-map-levels-of-java-util-logging-and-slf4j-logger

https://www.mkyong.com/spring-boot/spring-boot-slf4j-logging-example/

http://stackoverflow.com/questions/33844580/how-do-i-change-log-level-in-runtime-without-restarting-spring-boot-application

http://stackoverflow.com/questions/18102898/how-can-i-change-log-level-of-single-logger-in-runtime