DEV Community

Cover image for Java Spring Boot Logging: Log Levels, Logback, JSON Logs & Production Best Practices
Ayush Shrivastava
Ayush Shrivastava

Posted on

Java Spring Boot Logging: Log Levels, Logback, JSON Logs & Production Best Practices

Production-Grade Logging in Spring Boot: A Complete Guide to Logging Levels, Files, JSON Logs, Correlation IDs, and Best Practices

Logging is one of the most important parts of a production backend system.

When everything works, you may not think much about logs.

But when production starts returning 500 errors at 2 AM, a customer reports that an API is failing, or a payment request behaves unexpectedly, logs become one of your most important debugging tools.

Poor logging makes production debugging painful.

Good logging helps you answer:

  • What happened?
  • When did it happen?
  • Which user triggered it?
  • Which request caused it?
  • Which service handled it?
  • How long did it take?
  • What failed?
  • Why did it fail?
  • What should we investigate next?

In this article, we will build a production-grade logging strategy for a Java Spring Boot application.


1. What Does Production-Grade Logging Mean?

Production-grade logging is not simply:

log.info("User created");
Enter fullscreen mode Exit fullscreen mode

Production logging should be:

  • Structured
  • Searchable
  • Consistent
  • Secure
  • Configurable
  • Environment-aware
  • Correlated across requests
  • Useful during debugging
  • Suitable for monitoring and alerting

A good logging architecture might look like this:

Spring Boot Application
        |
        v
     Logback
        |
        +---- application.log
        |
        +---- error.log
        |
        +---- audit.log
        |
        +---- access.log
        |
        v
Log Aggregation
        |
        +---- ELK
        +---- Grafana Loki
        +---- CloudWatch
        +---- Datadog
        +---- Splunk
Enter fullscreen mode Exit fullscreen mode

The goal is not to log everything.

The goal is to log the right information at the right level.


2. Understanding Log Levels

Spring Boot uses SLF4J as the logging abstraction and commonly uses Logback as the underlying logging implementation.

The most common log levels are:

TRACE
DEBUG
INFO
WARN
ERROR
Enter fullscreen mode Exit fullscreen mode

The order represents increasing severity.


TRACE

TRACE is the most detailed logging level.

Example:

log.trace("Entering calculateInvoice() with customerId={}", customerId);
Enter fullscreen mode Exit fullscreen mode

Use TRACE for very detailed diagnostic information.

Usually:

Production: OFF
Development: Sometimes ON
Debugging: Useful
Enter fullscreen mode Exit fullscreen mode

Avoid keeping TRACE enabled globally in production because it can generate huge amounts of logs.


3. DEBUG

DEBUG is useful for developers.

Example:

log.debug("Fetching customer with customerId={}", customerId);
Enter fullscreen mode Exit fullscreen mode

Another example:

log.debug("Payment request received for orderId={}", orderId);
Enter fullscreen mode Exit fullscreen mode

DEBUG logs are useful when troubleshooting a specific feature.

A common production strategy is:

INFO  -> Default
DEBUG -> Temporarily enabled when troubleshooting
Enter fullscreen mode Exit fullscreen mode

4. INFO

INFO should contain important application events.

For example:

log.info("User successfully created. userId={}", userId);
Enter fullscreen mode Exit fullscreen mode

Or:

log.info("Order successfully created. orderId={}, customerId={}",
        orderId,
        customerId);
Enter fullscreen mode Exit fullscreen mode

Good INFO logs might include:

Application started
User registered
Order created
Payment completed
File uploaded
Scheduled job completed
External integration connected
Enter fullscreen mode Exit fullscreen mode

But don't log every line of your application at INFO.


5. WARN

WARN indicates something unexpected or potentially problematic.

Example:

log.warn("Login attempt failed. email={}", email);
Enter fullscreen mode Exit fullscreen mode

Another example:

log.warn("Payment provider response time is high. durationMs={}",
        durationMs);
Enter fullscreen mode Exit fullscreen mode

WARN means:

"The application is still functioning, but someone should pay attention."

Examples:

  • Retry occurred
  • External API is slow
  • Deprecated API was called
  • Configuration is missing but has a fallback
  • Login failed repeatedly
  • Database connection pool is close to its limit

6. ERROR

ERROR represents a failure that needs investigation.

Example:

log.error("Failed to create order. orderId={}", orderId, exception);
Enter fullscreen mode Exit fullscreen mode

Notice that the exception is passed separately:

log.error("Failed to create order", exception);
Enter fullscreen mode Exit fullscreen mode

Instead of:

log.error("Failed to create order " + exception.getMessage());
Enter fullscreen mode Exit fullscreen mode

The first approach preserves the stack trace.


7. Never Log Sensitive Information

One of the most important production logging rules is:

Logs should never become a source of sensitive data leakage.

Never log:

Passwords
Access tokens
Refresh tokens
API keys
Credit card numbers
CVV
Session IDs
Private keys
Authorization headers
Enter fullscreen mode Exit fullscreen mode

Bad:

log.info("Login request: username={}, password={}",
        username,
        password);
Enter fullscreen mode Exit fullscreen mode

Good:

log.info("Login attempt received. username={}", username);
Enter fullscreen mode Exit fullscreen mode

Even better, depending on your privacy requirements, avoid logging email addresses or other personal identifiers unless there is a clear operational reason.


8. Use Parameterized Logging

Avoid string concatenation.

Don't do this:

log.info("User created: " + userId);
Enter fullscreen mode Exit fullscreen mode

Prefer:

log.info("User created. userId={}", userId);
Enter fullscreen mode Exit fullscreen mode

For multiple values:

log.info(
    "Order created. orderId={}, customerId={}, amount={}",
    orderId,
    customerId,
    amount
);
Enter fullscreen mode Exit fullscreen mode

Parameterized logging is cleaner and avoids unnecessary string construction.


9. Create a Centralized Logging Configuration

Spring Boot makes it easy to configure Logback.

You can create:

src/main/resources/logback-spring.xml
Enter fullscreen mode Exit fullscreen mode

A basic configuration:

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <property name="LOG_DIR" value="./logs"/>

    <appender name="CONSOLE"
              class="ch.qos.logback.core.ConsoleAppender">

        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                [%thread]
                %-5level
                %logger{36}
                -
                %msg%n
            </pattern>
        </encoder>

    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>

</configuration>
Enter fullscreen mode Exit fullscreen mode

Now the application produces readable logs such as:

2026-08-08 10:30:25.123 [http-nio-8080-exec-1] INFO
c.example.user.UserService -
User created. userId=123
Enter fullscreen mode Exit fullscreen mode

10. Different Log Files for Different Levels

For a production application, you may want separate files.

For example:

logs/
├── application.log
├── error.log
├── audit.log
└── access.log
Enter fullscreen mode Exit fullscreen mode

This makes troubleshooting easier.

For example:

application.log
Enter fullscreen mode Exit fullscreen mode

contains general application events.

error.log
Enter fullscreen mode Exit fullscreen mode

contains ERROR events.

audit.log
Enter fullscreen mode Exit fullscreen mode

contains important security/business events.

access.log
Enter fullscreen mode Exit fullscreen mode

contains HTTP request information.


11. Creating an ERROR Log File

Example:

<appender name="ERROR_FILE"
          class="ch.qos.logback.core.rolling.RollingFileAppender">

    <file>${LOG_DIR}/error.log</file>

    <rollingPolicy
        class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

        <fileNamePattern>
            ${LOG_DIR}/archive/error.%d{yyyy-MM-dd}.%i.log.gz
        </fileNamePattern>

        <maxFileSize>100MB</maxFileSize>
        <maxHistory>30</maxHistory>
        <totalSizeCap>5GB</totalSizeCap>

    </rollingPolicy>

    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ERROR</level>
    </filter>

    <encoder>
        <pattern>
            %d{yyyy-MM-dd HH:mm:ss.SSS}
            [%thread]
            %-5level
            %logger{36}
            -
            %msg%n
        </pattern>
    </encoder>

</appender>
Enter fullscreen mode Exit fullscreen mode

Now ERROR logs can be stored separately.


12. Why Log Rotation Matters

Imagine your application generates:

application.log
Enter fullscreen mode Exit fullscreen mode

and you never rotate it.

After several months:

application.log = 150 GB
Enter fullscreen mode Exit fullscreen mode

Your server's disk eventually becomes full.

This can cause much bigger problems.

For example:

Application
    |
    v
Disk full
    |
    +---- Logging fails
    +---- Database operations may fail
    +---- Temporary files cannot be created
    +---- Application becomes unstable
Enter fullscreen mode Exit fullscreen mode

That's why production applications need:

  • Maximum file size
  • Maximum history
  • Total storage limit
  • Compression
  • Time-based rotation

Example:

<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
Enter fullscreen mode Exit fullscreen mode

13. Application Logs vs Audit Logs

Not every important event is an application error.

Consider:

Admin disabled user 123
Enter fullscreen mode Exit fullscreen mode

This isn't an ERROR.

It is an audit event.

Create a separate audit logger.

private static final Logger auditLogger =
        LoggerFactory.getLogger("AUDIT");
Enter fullscreen mode Exit fullscreen mode

Then:

auditLogger.info(
    "User disabled. adminId={}, targetUserId={}",
    adminId,
    targetUserId
);
Enter fullscreen mode Exit fullscreen mode

This gives you a separate audit stream.


14. Audit Logging Is Extremely Important

For systems involving multiple users, administrators, payments, permissions, or sensitive operations, audit logging becomes extremely valuable.

Examples:

USER_CREATED
USER_DISABLED
USER_ENABLED
PASSWORD_CHANGED
ROLE_CHANGED
LOGIN_SUCCESS
LOGIN_FAILED
API_KEY_CREATED
API_KEY_REVOKED
DATA_EXPORTED
PAYMENT_COMPLETED
Enter fullscreen mode Exit fullscreen mode

Instead of:

log.info("Something happened");
Enter fullscreen mode Exit fullscreen mode

Use structured information:

auditLogger.info(
    "AUDIT event=ROLE_CHANGED actorId={} targetUserId={} oldRole={} newRole={}",
    actorId,
    targetUserId,
    oldRole,
    newRole
);
Enter fullscreen mode Exit fullscreen mode

Now the event can easily be searched.


15. Logging HTTP Requests

For backend systems, request logging is extremely useful.

You want to know:

HTTP Method
URL
Status Code
Execution Time
Request ID
User ID
Enter fullscreen mode Exit fullscreen mode

Example:

GET /api/users/123
status=200
duration=45ms
requestId=9f7a2
Enter fullscreen mode Exit fullscreen mode

A servlet filter is one approach.

@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

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

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain)
            throws ServletException, IOException {

        long start = System.currentTimeMillis();

        try {
            filterChain.doFilter(request, response);
        } finally {

            long duration =
                    System.currentTimeMillis() - start;

            log.info(
                "HTTP request method={} uri={} status={} durationMs={}",
                request.getMethod(),
                request.getRequestURI(),
                response.getStatus(),
                duration
            );
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This gives you a basic access log.


16. Correlation IDs

This is one of the most useful concepts in distributed systems.

Imagine a request:

Frontend
   |
   v
API Gateway
   |
   v
User Service
   |
   v
Payment Service
   |
   v
Notification Service
Enter fullscreen mode Exit fullscreen mode

One request could generate dozens of logs.

How do you identify which logs belong to the same request?

Use a:

Correlation ID
Enter fullscreen mode Exit fullscreen mode

Example:

requestId=7f83ab29
Enter fullscreen mode Exit fullscreen mode

Then every service logs:

requestId=7f83ab29
Enter fullscreen mode Exit fullscreen mode

Now you can search the entire system using that ID.


17. Implementing Correlation ID with MDC

SLF4J provides MDC:

MDC.put("requestId", requestId);
Enter fullscreen mode Exit fullscreen mode

Example:

@Component
public class CorrelationIdFilter extends OncePerRequestFilter {

    private static final String REQUEST_ID = "requestId";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain)
            throws ServletException, IOException {

        String requestId = request.getHeader(REQUEST_ID);

        if (requestId == null || requestId.isBlank()) {
            requestId = UUID.randomUUID().toString();
        }

        MDC.put(REQUEST_ID, requestId);

        response.setHeader(REQUEST_ID, requestId);

        try {
            filterChain.doFilter(request, response);
        } finally {
            MDC.remove(REQUEST_ID);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now add it to Logback:

<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS}
    [%thread]
    %-5level
    [%X{requestId}]
    %logger{36}
    -
    %msg%n
</pattern>
Enter fullscreen mode Exit fullscreen mode

The resulting log becomes:

2026-08-08 12:20:10.120
[http-nio-8080-exec-2]
INFO
[7f83ab29]
UserService -
Fetching user userId=123
Enter fullscreen mode Exit fullscreen mode

This is much easier to debug.


18. Structured Logging

Traditional logs look like:

User created successfully userId=123
Enter fullscreen mode Exit fullscreen mode

Structured logs can look like JSON:

{
  "timestamp": "2026-08-08T12:20:10.120Z",
  "level": "INFO",
  "service": "user-service",
  "requestId": "7f83ab29",
  "userId": "123",
  "event": "USER_CREATED"
}
Enter fullscreen mode Exit fullscreen mode

This is much easier for log aggregation systems to process.

For production environments, structured JSON logging is often preferable.


19. Why JSON Logs Are Better for Production

Suppose you use:

ELK
Grafana Loki
Datadog
AWS CloudWatch
Splunk
Enter fullscreen mode Exit fullscreen mode

You can query structured fields.

For example:

level = ERROR
service = payment-service
environment = production
Enter fullscreen mode Exit fullscreen mode

Or:

requestId = 7f83ab29
Enter fullscreen mode Exit fullscreen mode

Or:

durationMs > 1000
Enter fullscreen mode Exit fullscreen mode

This becomes much more powerful than searching plain text.


20. Logging Exceptions Correctly

Bad:

try {
    paymentService.process(payment);
} catch (Exception e) {
    log.error("Payment failed: " + e.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

This loses the stack trace.

Better:

try {
    paymentService.process(payment);
} catch (Exception e) {
    log.error(
        "Payment processing failed. paymentId={}",
        paymentId,
        e
    );
}
Enter fullscreen mode Exit fullscreen mode

Now you get:

ERROR Payment processing failed. paymentId=123

java.lang.IllegalStateException: Payment provider timeout
    at PaymentService.process(...)
    at PaymentController.create(...)
Enter fullscreen mode Exit fullscreen mode

The stack trace is extremely important for debugging.


21. Don't Log the Same Exception Multiple Times

A common mistake is:

Repository
   ↓
Service
   ↓
Controller
Enter fullscreen mode Exit fullscreen mode

Every layer catches and logs the same exception.

You might get:

ERROR Database failure
ERROR Service failure
ERROR Controller failure
Enter fullscreen mode Exit fullscreen mode

Three logs for one problem.

Prefer centralized exception handling when possible.

For Spring Boot:

@RestControllerAdvice
public class GlobalExceptionHandler {

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

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleException(
            Exception exception) {

        log.error(
            "Unhandled application exception",
            exception
        );

        return ResponseEntity
                .internalServerError()
                .body("Something went wrong");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now unexpected exceptions can be logged centrally.


22. Logging Business Events

Not every useful log is technical.

Business events can be extremely valuable.

Example:

log.info(
    "Order completed. orderId={}, customerId={}, amount={}, currency={}",
    orderId,
    customerId,
    amount,
    currency
);
Enter fullscreen mode Exit fullscreen mode

This can help answer questions such as:

How many orders were completed?
Which payment failed?
How long does checkout take?
Which customer experienced the problem?
Enter fullscreen mode Exit fullscreen mode

Logging should help both developers and operations teams.


23. Logging External API Calls

Suppose your application calls:

Stripe
Salesforce
OpenAI
AWS
Google Maps
Email provider
SMS provider
Enter fullscreen mode Exit fullscreen mode

You should log useful metadata.

Example:

long start = System.currentTimeMillis();

try {

    PaymentResponse response =
            paymentClient.createPayment(request);

    long duration =
            System.currentTimeMillis() - start;

    log.info(
        "Payment provider call completed. provider={} status={} durationMs={}",
        "stripe",
        response.status(),
        duration
    );

} catch (Exception e) {

    long duration =
            System.currentTimeMillis() - start;

    log.error(
        "Payment provider call failed. provider={} durationMs={}",
        "stripe",
        duration,
        e
    );
}
Enter fullscreen mode Exit fullscreen mode

But never log:

Authorization header
API key
Access token
Full card details
Sensitive request payload
Enter fullscreen mode Exit fullscreen mode

24. Logging Database Operations

Don't log every SQL query in production unless you have a specific reason.

For example, enabling:

spring.jpa.show-sql=true
Enter fullscreen mode Exit fullscreen mode

in production can create huge amounts of output.

For development:

spring.jpa.show-sql=true
Enter fullscreen mode Exit fullscreen mode

may be useful.

For production:

spring.jpa.show-sql=false
Enter fullscreen mode Exit fullscreen mode

Instead, monitor slow queries through proper database monitoring and profiling tools.


25. Different Logging Configuration Per Environment

Your logging configuration should change based on the environment.

Development:

DEBUG
Readable console logs
More diagnostic information
Enter fullscreen mode Exit fullscreen mode

Production:

INFO
JSON logs
Error tracking
Structured fields
Log rotation
Centralized log collection
Enter fullscreen mode Exit fullscreen mode

Example:

spring:
  profiles:
    active: dev
Enter fullscreen mode Exit fullscreen mode

You can maintain:

application-dev.yml
application-prod.yml
Enter fullscreen mode Exit fullscreen mode

And configure logging accordingly.


26. Production Logging Architecture

A practical production architecture might look like:

                  Spring Boot
                      |
                      v
                   Logback
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   Application     Error        Audit
      Logs          Logs         Logs
        |             |             |
        +-------------+-------------+
                      |
                      v
                Log Collector
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   CloudWatch       Loki          ELK
                      |
                      v
                  Dashboard
                      |
                      v
                   Alerts
Enter fullscreen mode Exit fullscreen mode

This is much better than simply SSHing into a server and running:

tail -f application.log
Enter fullscreen mode Exit fullscreen mode

every time something breaks.


27. Docker and Kubernetes Logging

If your application runs inside Docker or Kubernetes, writing logs only to local files may not be the best strategy.

A common approach is:

Application
     |
     v
stdout / stderr
     |
     v
Docker / Kubernetes
     |
     v
Log Collector
     |
     v
Centralized Logging Platform
Enter fullscreen mode Exit fullscreen mode

For example:

Spring Boot
    ↓
stdout
    ↓
Docker
    ↓
Fluent Bit
    ↓
Elasticsearch
    ↓
Kibana
Enter fullscreen mode Exit fullscreen mode

This allows logs to remain available even when containers are recreated.


28. Logging in Kubernetes

In Kubernetes, pods are disposable.

That means this:

Pod A
  |
  +--- application.log
Enter fullscreen mode Exit fullscreen mode

is not necessarily a reliable long-term logging strategy.

Instead:

Pod
 |
 v
stdout
 |
 v
Container Runtime
 |
 v
Log Collector
 |
 v
Centralized Storage
Enter fullscreen mode Exit fullscreen mode

This is generally more suitable for cloud-native applications.


29. Log Levels Should Be Intentional

A useful rule:

TRACE → Extremely detailed diagnostics

DEBUG → Developer troubleshooting

INFO  → Important application events

WARN  → Unexpected but recoverable situation

ERROR → Failure requiring investigation
Enter fullscreen mode Exit fullscreen mode

Don't do this:

log.error("User logged in successfully");
Enter fullscreen mode Exit fullscreen mode

Use:

log.info("User login successful. userId={}", userId);
Enter fullscreen mode Exit fullscreen mode

Log levels should communicate severity.


30. Don't Log Everything

More logs do not automatically mean better observability.

Bad:

log.info("Starting method");
log.info("Entering service");
log.info("Repository called");
log.info("Repository returned");
log.info("Service completed");
log.info("Controller completed");
Enter fullscreen mode Exit fullscreen mode

This creates noise.

Instead:

log.info(
    "User profile updated. userId={} durationMs={}",
    userId,
    durationMs
);
Enter fullscreen mode Exit fullscreen mode

Log meaningful events.


31. Logging Performance

Logging can affect application performance.

Especially dangerous:

log.debug(
    "Huge object: {}",
    objectWithThousandsOfFields
);
Enter fullscreen mode Exit fullscreen mode

When DEBUG isn't enabled, parameterized logging helps avoid unnecessary string concatenation, but object serialization or expensive argument computation can still cost time.

Avoid:

log.debug("Response: {}", expensiveMethod());
Enter fullscreen mode Exit fullscreen mode

if the computation itself is expensive.

You can guard expensive operations:

if (log.isDebugEnabled()) {
    log.debug("Detailed response: {}", expensiveMethod());
}
Enter fullscreen mode Exit fullscreen mode

Use this only when the computation is genuinely expensive.


32. Async Logging

High-throughput applications can benefit from asynchronous logging.

Instead of:

Application
    |
    v
Write log
    |
    v
Disk
Enter fullscreen mode Exit fullscreen mode

you can use:

Application
    |
    v
Async Queue
    |
    v
Logger
    |
    v
Disk / Collector
Enter fullscreen mode Exit fullscreen mode

This reduces the amount of time application threads spend waiting on logging operations.

However, asynchronous logging should be configured carefully to avoid losing logs during abrupt shutdowns and to prevent queue overflow.


33. Log Retention

Production logs should have a retention policy.

For example:

Application logs → 30 days
Audit logs       → 90 days
Security logs    → 180 days
Enter fullscreen mode Exit fullscreen mode

The actual retention period should be based on:

  • Compliance
  • Security requirements
  • Business requirements
  • Storage cost
  • Incident investigation needs

Don't keep everything forever.


34. A Practical Logback Configuration

A simplified production configuration could look like:

<configuration>

    <property name="LOG_DIR" value="./logs"/>

    <appender name="CONSOLE"
              class="ch.qos.logback.core.ConsoleAppender">

        <encoder>
            <pattern>
                %d{yyyy-MM-dd'T'HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            </pattern>
        </encoder>

    </appender>

    <appender name="APPLICATION_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">

        <file>${LOG_DIR}/application.log</file>

        <rollingPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

            <fileNamePattern>
                ${LOG_DIR}/archive/application.%d{yyyy-MM-dd}.%i.log.gz
            </fileNamePattern>

            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>

        </rollingPolicy>

        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            </pattern>
        </encoder>

    </appender>

    <appender name="ERROR_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">

        <file>${LOG_DIR}/error.log</file>

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>

        <rollingPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

            <fileNamePattern>
                ${LOG_DIR}/archive/error.%d{yyyy-MM-dd}.%i.log.gz
            </fileNamePattern>

            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>2GB</totalSizeCap>

        </rollingPolicy>

        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS}
                %-5level
                [%X{requestId}]
                %logger{36}
                -
                %msg%n
            </pattern>
        </encoder>

    </appender>

    <root level="INFO">

        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="APPLICATION_FILE"/>
        <appender-ref ref="ERROR_FILE"/>

    </root>

</configuration>
Enter fullscreen mode Exit fullscreen mode

This gives you:

logs/
├── application.log
├── error.log
└── archive/
    ├── application.2026-08-08.0.log.gz
    ├── error.2026-08-08.0.log.gz
    └── ...
Enter fullscreen mode Exit fullscreen mode

35. Real-World Example

Imagine a customer reports:

"My payment failed."

Without structured logging, you might search:

payment failed
Enter fullscreen mode Exit fullscreen mode

and find thousands of results.

With production-grade logging, you can search:

requestId=7f83ab29
Enter fullscreen mode Exit fullscreen mode

Then you might see:

INFO  requestId=7f83ab29
Payment request received. orderId=ORD-123

INFO  requestId=7f83ab29
Calling payment provider. provider=stripe

WARN  requestId=7f83ab29
Payment provider response slow. durationMs=4200

ERROR requestId=7f83ab29
Payment provider call failed. orderId=ORD-123
Enter fullscreen mode Exit fullscreen mode

Now you know exactly what happened.

That's the real value of production logging.


36. Logging Best Practices Checklist

Before deploying a Spring Boot application, check:

Log levels

[ ] TRACE is disabled in production
[ ] DEBUG is used intentionally
[ ] INFO contains meaningful events
[ ] WARN represents recoverable problems
[ ] ERROR represents actual failures
Enter fullscreen mode Exit fullscreen mode

Security

[ ] Passwords are never logged
[ ] Tokens are never logged
[ ] API keys are never logged
[ ] Sensitive headers are never logged
[ ] Sensitive payloads are not logged
Enter fullscreen mode Exit fullscreen mode

Reliability

[ ] Log rotation is configured
[ ] Maximum file size is configured
[ ] Retention policy exists
[ ] Disk usage is monitored
Enter fullscreen mode Exit fullscreen mode

Observability

[ ] Request ID exists
[ ] Correlation ID exists
[ ] Important business events are logged
[ ] External API failures are logged
[ ] Slow operations can be identified
Enter fullscreen mode Exit fullscreen mode

Production

[ ] Structured logging is available
[ ] Logs can be centralized
[ ] Alerts can be created
[ ] Logs are searchable
[ ] Audit events are separated when required
Enter fullscreen mode Exit fullscreen mode

37. What I Consider a Good Production Logging Strategy

For a modern Spring Boot backend, my preferred baseline would be:

Spring Boot
     |
     +--- SLF4J
     |
     +--- Logback
     |
     +--- Structured JSON
     |
     +--- Request ID / Correlation ID
     |
     +--- INFO as default
     |
     +--- DEBUG for troubleshooting
     |
     +--- ERROR for failures
     |
     +--- Audit logging for important actions
     |
     +--- Log rotation / retention
     |
     +--- Centralized log aggregation
     |
     +--- Monitoring + Alerting
Enter fullscreen mode Exit fullscreen mode

For cloud deployments, I would generally prefer sending structured logs to a centralized platform rather than depending exclusively on local log files.


38. Final Thoughts

Logging is not something you add at the end of development.

It is part of backend architecture.

A production-ready application should make it easy to understand:

What happened?
When?
Where?
Who triggered it?
Which request?
Which service?
How long?
What failed?
Why?
Enter fullscreen mode Exit fullscreen mode

The goal isn't to create millions of log lines.

The goal is to create useful signals.

A good production logging system gives developers confidence when everything is working and, more importantly, gives them the information they need when something goes wrong.

If your application is running in production, ask yourself:

"If this API fails at 3 AM, can I understand exactly what happened from the logs?"

If the answer is no, your logging strategy probably needs another iteration.

Top comments (0)