Skip to content
CampusEduX

Configuration · Lesson 26 of 95

Logging in Spring Boot

Learn logging in Spring Boot with SLF4J and Logback: log levels, package settings, custom patterns and log files, using a food delivery example.

8 min read

A delivery rider phones the kitchen and says, "Order 42 is picked up." Later he says, "Customer not at home." If nobody writes these calls in a notebook, the shop cannot find out what went wrong when a customer complains. A log is that notebook. It records what your application did, when it did it, and how serious it was.

In this guide on logging in Spring Boot you will learn how to write log messages, what the levels mean, how to change what is shown, and how to keep a copy in a file.

What is logging in Spring Boot?

Spring Boot logs to the console out of the box, using the SLF4J interface with Logback behind it. The spring-boot-starter already includes both, so you add nothing. You ask for a logger, call a method such as info, and the message appears with a time, a level and the class name.

Think of the levels as the colour of a sticky note in the notebook. A white note says "all normal". A yellow note says "watch this". A red note says "something broke". Later you can choose to read only the red and yellow notes.

Why is it used?

  • Finding bugs after they happen. The customer complaint arrives tomorrow. The log shows what happened today.
  • Better than System.out.println. A log message has a level, a time and a source class, and you can switch it off without editing code.
  • Control from settings. You can show detailed messages on your laptop and only warnings on the live server.
  • Files for history. The same messages can go to a file that rotates as it grows.

How it works

Your class calls the SLF4J interface. Logback receives the call, checks the level against your settings, and writes the message to each place it is told to use.

text
log.info("Order accepted") | v +--------------------+ | SLF4J (interface) | +--------------------+ | v +--------------------+ | Logback (engine) | | level ok? | +--------------------+ | | v v console file

If the level of the message is below the level you allowed for that package, Logback throws the message away and it costs almost nothing. Otherwise it formats the message with your pattern and sends it to the console and the file.

Log Levels

From the least serious to the most serious, the levels are:

LevelMeaningExample
TRACETiny detailsEvery loop step
DEBUGDetails for developersStock check for an order
INFONormal eventsOrder accepted
WARNSomething odd, but continuingLow stock
ERRORSomething failedPayment declined

When you set a level, you see that level and everything more serious. The default is INFO, so DEBUG and TRACE messages stay hidden until you ask for them.

Real-Life Example

QuickBite is a food delivery kitchen. When an order arrives, the kitchen checks the stock, warns when the stock is low, accepts the order, and later records if the card payment was declined. Each event has a natural level. The developers want every detail while testing on a laptop, but on the live server they want only warnings and errors, so the log stays small and easy to read.

Code Example

Let's build the QuickBite order service. It logs at four levels and writes a copy to a file. To keep every line short on a phone, we set our own console pattern.

File: pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.1</version> <relativePath/> </parent> <groupId>com.quickbite</groupId> <artifactId>orders</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>21</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

File: application.properties in src/main/resources

properties
logging.pattern.console=%-5level %logger{0}: %msg%n logging.level.root=WARN logging.level.com.quickbite=DEBUG logging.file.name=quickbite.log

File: OrdersApplication.java in package com.quickbite.orders

java
package com.quickbite.orders; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class OrdersApplication { public static void main(String[] args) { SpringApplication.run(OrdersApplication.class, args); } }

File: Kitchen.java in package com.quickbite.orders

java
package com.quickbite.orders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class Kitchen { private static final Logger log = LoggerFactory.getLogger(Kitchen.class); public String accept(int orderId, int stock) { log.debug("Stock check for {}", orderId); if (stock < 3) { log.warn("Low stock: {} left", stock); } log.info("Order {} accepted", orderId); return "Order " + orderId + " accepted"; } public String pay(int orderId) { try { throw new IllegalStateException("Card declined"); } catch (IllegalStateException ex) { log.error("Card declined for {}", orderId, ex); return "Payment failed for order " + orderId; } } }

File: OrderController.java in package com.quickbite.orders

java
package com.quickbite.orders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { private final Kitchen kitchen; public OrderController(Kitchen kitchen) { this.kitchen = kitchen; } @GetMapping("/place") public String place() { return kitchen.accept(42, 2); } @GetMapping("/fail") public String fail() { return kitchen.pay(7); } }

Run it, then call both addresses:

bash
./mvnw spring-boot:run curl http://localhost:8080/place curl http://localhost:8080/fail

Output:

text
DEBUG Kitchen: Stock check for 42 WARN Kitchen: Low stock: 2 left INFO Kitchen: Order 42 accepted ERROR Kitchen: Card declined for 7

Below the last line the console also prints the full stack trace of the exception, which we left out here. A file named quickbite.log appeared in the folder where we started the app, holding the same messages with a full timestamp and thread name.

Code Explained

  • The getLogger call with Kitchen.class creates a logger named after the class. It is private static final, created once.
  • {} is a placeholder. SLF4J fills it with the argument, and it only builds the text if the level is enabled.
  • In log.error, passing the exception as the last argument prints its stack trace. Without it you would lose the reason for the failure.
  • logging.level.root=WARN hides all the noise from Spring itself. The level set for com.quickbite to DEBUG shows every detail from our own package.
  • The %-5level %logger{0}: %msg%n pattern prints the level, the short class name and the message. The default pattern also adds the time, process id and thread, which is more useful on a server but too wide for this page.
  • logging.file.name adds a file next to the console output.

We also started the jar with the level for our package changed to WARN on the command line. Only the WARN and ERROR lines remained. The Java code was the same. Only the setting changed.

Log Groups and File Rolling

You can set the level of one class or one whole package, for example a logging.level entry for a payment package set to TRACE. Spring Boot also has ready groups such as web and sql, which switch on many related loggers at once. For files, Logback rolls the log by size and date, so a file does not grow forever. You can tune this with the rollingpolicy settings under logging.logback.

Choosing What to Log

Good logs answer a question later. Log the start and end of important actions, such as an order accepted or a payment result, and include the id, so you can search for it. Do not log inside a tight loop that runs a million times. One line per business event is enough. Write messages a tired teammate can understand at night, such as "Card declined for 7", not "error 5".

Also keep the levels honest. A missing optional setting is not an ERROR, and a crash is not an INFO. If everything is ERROR, nobody trusts the log.

Common Mistakes

  • Using `System.out.println`. It has no level, no time and no switch. Use a logger.
  • Building strings by hand. log.debug("Stock " + stock) builds the text even when DEBUG is off. Use {} placeholders.
  • Logging an exception as text. log.error(ex.getMessage()) loses the stack trace. Pass the exception itself.
  • Leaving DEBUG on in production. Logs become huge and slow, and may show private data.

Interview Questions

Which logging library does Spring Boot use by default?

Ans:SLF4J as the interface and Logback as the engine.

What is the default log level?

Ans:INFO. So DEBUG and TRACE messages are not shown unless you lower the level.

How do you send logs to a file?

Ans:Set logging.file.name or logging.file.path in the configuration file.

Key Points to Remember

  • Use an SLF4J logger, not System.out.println.
  • Levels from low to high are TRACE, DEBUG, INFO, WARN and ERROR.
  • Set levels per package with logging.level entries.
  • Use {} placeholders and pass exceptions as the last argument.
  • Add logging.file.name to keep a file copy.
  • Never log secrets.

Frequently Asked Questions

Why is logging in Spring Boot better than System.out.println?

A logger adds time, level and class name, can be filtered by level, and can write to files. System.out.println can do none of these.

How do I change the log level without changing code?

Set logging.level for a package in application.properties, or pass it on the command line, as we did with the WARN run.

Does Spring Boot use Log4j?

Not by default. It uses Logback. You can switch to Log4j 2 by excluding the logging starter and adding the Log4j 2 starter.

Where is the log file created?

In the folder where you start the app, unless you give a full path in logging.file.name.

Practice Problems

Try each problem on your own first. Both use the same pom.xml as the QuickBite code above; only change the groupId and artifactId.

Easy: Clinic Token Log

City Care Clinic wants every new token written to the log at INFO level. Build GET /token that hands out token 15 and logs Token 15 issued. Use a short console pattern so each line stays narrow.

Show answer
The service logs through SLF4J. The pattern prints the level, the short class name and the message.

File: application.properties in src/main/resources

properties
logging.pattern.console=%-5level %logger{0}: %msg%n logging.level.root=WARN logging.level.com.citycare=INFO

File: TokenApplication.java in package com.citycare.token

java
package com.citycare.token; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TokenApplication { public static void main(String[] args) { SpringApplication.run(TokenApplication.class, args); } }

File: TokenDesk.java in package com.citycare.token

java
package com.citycare.token; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TokenDesk { private static final Logger log = LoggerFactory.getLogger(TokenDesk.class); @GetMapping("/token") public String token() { int token = 15; log.info("Token {} issued", token); return "Token " + token; } }

After curl http://localhost:8080/token, the console shows this line, among the startup lines that the main class also logs:

text
INFO TokenDesk: Token 15 issued

Medium: Library Fines with Levels

Lakeview Library logs at three levels. When a book is returned on time, log INFO. When it is 1 to 7 days late, log WARN. When it is more than 7 days late, log ERROR. Build GET /return/{days} that returns the fine of 2 rupees a day and logs at the right level. Show the log for 0, 3 and 10 days, then show it again with the package level raised to ERROR.

Show answer
One if chain picks the level. Raising the package level to ERROR hides the INFO and WARN lines without any code change.

File: application.properties in src/main/resources

properties
logging.pattern.console=%-5level %logger{0}: %msg%n logging.level.root=WARN logging.level.com.lakeview=INFO

File: FinesApplication.java in package com.lakeview.fines

java
package com.lakeview.fines; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class FinesApplication { public static void main(String[] args) { SpringApplication.run(FinesApplication.class, args); } }

File: FineDesk.java in package com.lakeview.fines

java
package com.lakeview.fines; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class FineDesk { private static final Logger log = LoggerFactory.getLogger(FineDesk.class); @GetMapping("/return/{days}") public String bookReturn(@PathVariable int days) { int fine = days * 2; if (days == 0) { log.info("Returned on time"); } else if (days <= 7) { log.warn("Late by {} days", days); } else { log.error("Very late: {} days", days); } return "Fine: Rs " + fine; } }

Calling /return/0, /return/3 and /return/10 writes:

text
INFO FineDesk: Returned on time WARN FineDesk: Late by 3 days ERROR FineDesk: Very late: 10 days
bash
java -jar target/fines-0.0.1-SNAPSHOT.jar --logging.level.com.lakeview=ERROR

With that argument, the same three calls write:

text
ERROR FineDesk: Very late: 10 days

Mock Test

  • Logging in Spring Boot - Quick Test

    5 questions to check what you learned in Logging in Spring Boot.

    5 questions · 5 min · Easy
    Start Mock Test