How to Enable Debug Logging in Spring Boot for Production Issues

Something is broken in production. The app is running, users are reporting an issue, and you have no stack trace, no error message, and no obvious clue in the logs. Your first instinct is to add more logging — but the app is live, and redeploying just to change a log level feels like the wrong move.

The good news: Spring Boot gives you multiple ways to enable detailed debug logging without touching your code, without rebuilding the jar, and in some cases without even restarting the application. This guide covers all of them — from the simple properties-file approach to dynamic log level changes at runtime via Spring Boot Actuator.

Understanding Log Levels in Spring Boot

Spring Boot uses SLF4J as the logging API and Logback as the default implementation. Log levels are hierarchical — setting a level on a package applies to all classes within it:

LevelWhen to UseVerbosity
ERRORApplication failures requiring immediate attentionLowest
WARNUnexpected situations that aren’t failures (yet)Low
INFONormal application lifecycle events (default level)Medium
DEBUGDetailed flow for diagnosing problemsHigh
TRACEEverything — every method call, every SQL bind parameterHighest

By default, Spring Boot logs at INFO level. Most production issues require DEBUG or TRACE on a specific package — not globally, which would flood your logs with thousands of irrelevant lines per second.

Method 1: Set Log Levels in application.properties

The simplest approach. Add these to your application.properties or application.yml:

# Enable DEBUG for your own application code only
logging.level.com.example.demo=DEBUG

# Enable DEBUG for Spring Web (shows request mapping, handler resolution)
logging.level.org.springframework.web=DEBUG

# Enable DEBUG for Hibernate SQL (shows every query)
logging.level.org.hibernate.SQL=DEBUG

# Enable TRACE for Hibernate bind parameters (shows actual values in queries)
logging.level.org.hibernate.orm.jdbc.bind=TRACE

# Enable DEBUG for Spring Security (shows filter chain decisions)
logging.level.org.springframework.security=DEBUG

In application.yml:

logging:
  level:
    com.example.demo: DEBUG
    org.springframework.web: DEBUG
    org.hibernate.SQL: DEBUG
    org.springframework.security: DEBUG

⚠️ Be specific with package names. Setting logging.level.root=DEBUG enables debug on the entire application — including every Spring internal, every Hibernate operation, every HTTP request detail — which can generate millions of log lines per minute in production and seriously degrade performance.

Method 2: Pass Log Levels at Startup (No Code Change)

If you can restart the app (or are doing a rolling deployment), you can pass log levels as command-line arguments without touching any properties files:

java -jar app.jar \
  --logging.level.com.example.demo=DEBUG \
  --logging.level.org.springframework.web=DEBUG

Or as JVM system properties:

java -Dlogging.level.com.example.demo=DEBUG -jar app.jar

This is ideal for Kubernetes or Docker deployments where you control the startup command via environment variables or config maps:

# In a Kubernetes ConfigMap or deployment environment
- name: LOGGING_LEVEL_COM_EXAMPLE_DEMO
  value: "DEBUG"

Spring Boot automatically maps environment variables with underscores and uppercase to the equivalent dot-notation property names.

Method 3: Change Log Levels at Runtime Without Restarting (Actuator)

This is the most powerful option for production — you can change log levels on a running application with a single HTTP call, with no restart required.

Step 1: Add the Actuator Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Step 2: Expose the Loggers Endpoint

application.properties:

management.endpoints.web.exposure.include=loggers,health,info
management.endpoint.loggers.enabled=true

application.yml:

management:
  endpoints:
    web:
      exposure:
        include: loggers,health,info
  endpoint:
    loggers:
      enabled: true

Step 3: Check the Current Log Level for a Package

curl http://localhost:8080/actuator/loggers/com.example.demo

Response:

{
  "configuredLevel": null,
  "effectiveLevel": "INFO"
}

Step 4: Change the Log Level Dynamically

curl -X POST \
  http://localhost:8080/actuator/loggers/com.example.demo \
  -H "Content-Type: application/json" \
  -d '{"configuredLevel": "DEBUG"}'

No response body means it worked. Check your logs — debug output starts immediately. To revert back to INFO:

curl -X POST \
  http://localhost:8080/actuator/loggers/com.example.demo \
  -H "Content-Type: application/json" \
  -d '{"configuredLevel": "INFO"}'

🔒 Important: Secure the Actuator endpoints in production. Never expose /actuator publicly. Restrict access using Spring Security or network-level controls (only accessible from internal IPs or a bastion host):

application.properties:

# Bind actuator to a separate management port (internal only)
management.server.port=8081
management.server.address=127.0.0.1

application.yml:

management:
  server:
    port: 8081
    address: 127.0.0.1

Method 4: Use Spring Profiles for Environment-Specific Log Levels

The cleanest long-term approach is to define log levels per environment using Spring profiles, so you never have to manually change them at all:

Using .properties files:

# application-dev.properties
logging.level.com.example.demo=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=DEBUG
spring.jpa.show-sql=true

# application-prod.properties
logging.level.com.example.demo=INFO
logging.level.org.springframework=WARN
spring.jpa.show-sql=false

Using .yml files:

# application-dev.yml
logging:
  level:
    com.example.demo: DEBUG
    org.springframework.web: DEBUG
    org.hibernate.SQL: DEBUG
spring:
  jpa:
    show-sql: true

# application-prod.yml
logging:
  level:
    com.example.demo: INFO
    org.springframework: WARN
spring:
  jpa:
    show-sql: false

Activate the correct profile at startup:

java -jar app.jar --spring.profiles.active=prod

Writing Effective Log Statements in Your Code

Enabling debug logging is only half the equation — your own log statements need to be useful. Use SLF4J’s parameterized logging (never string concatenation, which evaluates even when the log level is disabled):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public Order processOrder(Long orderId) {
        log.info("Processing order: {}", orderId);

        // DEBUG: useful detail, not needed in normal operation
        log.debug("Fetching order from DB — orderId={}", orderId);

        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> {
                log.error("Order not found — orderId={}", orderId);
                return new OrderNotFoundException(orderId);
            });

        log.debug("Order retrieved — status={}, items={}", order.getStatus(), order.getItems().size());
        return order;
    }
}

With Lombok, you can skip the boilerplate entirely:

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class OrderService {

    public Order processOrder(Long orderId) {
        log.info("Processing order: {}", orderId);
        log.debug("Fetching order — orderId={}", orderId);
        // ...
    }
}

Production Debug Logging — Safety Checklist

  • ✅ Enable DEBUG only on the specific package you’re diagnosing — never root
  • ✅ Use Actuator’s /actuator/loggers endpoint for zero-downtime log level changes
  • ✅ Revert to INFO immediately after diagnosis — don’t leave DEBUG running in production
  • ✅ Never log sensitive data — passwords, tokens, PII, payment details — at any log level
  • ✅ Secure Actuator endpoints — bind to management port, restrict to internal network only
  • ✅ Use parameterized logging (log.debug("id={}", id)) not string concatenation
  • ✅ Use profile-specific properties to keep dev/prod log levels separate by default

Conclusion

Debug logging in Spring Boot is a spectrum — from a simple properties change to dynamic runtime control via Actuator, there’s always an option that fits your deployment situation. For most production issues, the Actuator loggers endpoint is the right tool: no restart, no redeployment, targeted to a specific package, and fully reversible. Combine it with profile-based defaults and properly written log statements in your own code, and you’ll diagnose most production issues in minutes rather than hours.

Leave a Comment

Your email address will not be published. Required fields are marked *