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.
답글 남기기