Summary

As of Spring Boot v3.4, structured logging is now built-in into Spring Boot.

The following pull requests adds a Graylog Extended Log Format (GELF) formatter to the structured logging of Spring Boot.

Features

  • Add gelf structured logging formatter with Logback
  • Add gelf structured logging formatter with Log4j2
  • Describe GELF Formatter in the logging documentation
  • Add logging.structured.gelf.service.* properties to additional-spring-configuration-metadata.json

Examples

Log messages

Example INFO log in GELF format:

{"version":"1.1","short_message":"Hello structured logging!","timestamp":1.725547337613E9,"level":6,"_level_name":"INFO","_process_pid":16597,"_process_thread_name":"main","host":"spring-boot-gelf","_log_logger":"com.slissner.springbootgelf.ExampleLogger","_testkey_testmessage":"test","_userId":"1"}

Example ERROR log with stack trace in GELF format:

{"version":"1.1","short_message":"Test exception","timestamp":1.725547337719E9,"level":3,"_level_name":"ERROR","_process_pid":16597,"_process_thread_name":"main","host":"spring-boot-gelf","_log_logger":"com.slissner.springbootgelf.ExampleLogger","full_message":"Test exception\n\njava.lang.RuntimeException: Boom\n\tat com.slissner.springbootgelf.ExampleLogger.run(ExampleLogger.java:23) ~[main\/:?]\n\tat org.springframework.boot.SpringApplication.lambda$callRunner$5(SpringApplication.java:792) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:776) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) [?:?]\n\tat java.base\/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) [?:?]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) [?:?]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) [?:?]\n\tat java.base\/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) [?:?]\n\tat org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1365) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat com.slissner.springbootgelf.SpringBootGelfApplication.main(SpringBootGelfApplication.java:10) [main\/:?]\n","_error_type":"java.lang.RuntimeException","_error_stack_trace":"java.lang.RuntimeException: Boom\n\tat com.slissner.springbootgelf.ExampleLogger.run(ExampleLogger.java:23) ~[main\/:?]\n\tat org.springframework.boot.SpringApplication.lambda$callRunner$5(SpringApplication.java:792) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88) ~[spring-core-6.2.0-M7.jar:6.2.0-M7]\n\tat org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:776) ~[spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) [?:?]\n\tat java.base\/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) [?:?]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) [?:?]\n\tat java.base\/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) [?:?]\n\tat java.base\/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) [?:?]\n\tat java.base\/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) [?:?]\n\tat org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1365) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) [spring-boot-3.4.0-SNAPSHOT.jar:3.4.0-SNAPSHOT]\n\tat com.slissner.springbootgelf.SpringBootGelfApplication.main(SpringBootGelfApplication.java:10) [main\/:?]\n","_error_message":"Boom"}

Screenshots

Search Dashboard

Search Dashboard

INFO log:

INFO log

ERROR log:

ERROR log

Local Testing

In order to test this pull request locally, please have a look at the following demo applications:

  • A demo application for Logback
  • Another demo application for Log4j2

Only the demo application for Logback is set up to actually send logs to a local Graylog instance. It uses an UDP log appender from the https://github.com/osiegmar/logback-gelf library.

Please see the above mentioned repository for further explanation on how to set up quickly a local instance of Graylog.

See also

Comment From: pivotal-cla

@slissner Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

Comment From: pivotal-cla

@slissner Thank you for signing the Contributor License Agreement!

Comment From: mhalbritter

Thank you very much and congratulations on your first contribution :tada:! I've polished a bit in https://github.com/spring-projects/spring-boot/commit/b5e7302031224ae67e6dac8ecd4dfea2b116fb07, for example fixing the double timestamp formatting.

Comment From: slissner

Thank you very much @mhalbritter for finalizing this!! I wanted to work on your issues this Friday, but you were faster :) Awesome to see GELF integrated with Spring Boot.