Best login practices in Java & Kotlin
Good logging practices in Java, Kotlin
Good Logging Practices in Java/Kotlin (Spring Boot + slf4j / kotlin-logger)
Goal: Logs should help us understand, debug, and operate the system in production without overwhelming us or leaking sensitive data.
1. Why Logging Matters
- Operational visibility – understand what the app is doing in production.
- Debugging – reconstruct what happened before a bug or incident.
- Auditing & compliance – track important business events.
- Metrics source – logs can feed dashboards and alerts. [Source]
Bad logging is almost worse than no logging:
- Too noisy → real issues get buried.
- Inconsistent → hard to search or aggregate.
- Leaky → secrets and personal data in logs.
2. Basics: Log levels and when to use them
Common Log levels
TRACE: - Used for very fine-grained, internal details. Rarely enabled in prod.DEBUG– Diagnostics (debugging) for developers.INFO– High level application events (starting, stopping, key business actions). Basically identify current state of the application.WARN– Something unusual, but the system can still continue and no immediate investigation is needed.ERROR– Something failed or data is lost. Investigation is necessary.FATAL(if used) – Application cannot continue and is not recoverable.
Good Level Usage
-
TRACE/DEBUG
- Inputs/outputs of complex algorithms (with redaction).
- Branch decisions in tricky or big flows.
- Only when it adds value for troubleshooting.
-
INFO
- App start/stop, version, environment.
- Key business events: document generated, payment was successfully.
- Integration checkpoints: “Calling Payment API”, “Payment API responded”.
-
WARN
- Temporary issues, fallback scenarios: cache miss causing slow DB lookup, retryable errors i.e network connection dropped.
- Deprecated endpoints used.
- Unexpected input that was handled.
-
ERROR
- Exceptions that bubble up and cause failures.
- Permanent failures: unable to process messages or database constraint violations.
- Integration failures that will likely need intervention.
3. General Logging Guidelines
3.1 Log With Context
Always include enough context to be useful:
- Request ID / correlation ID
- User ID
- Key business identifiers (orderId, jobId, etc.)
- Service name and environment
- Action (created, started, finished, …)
3. 2 Use Parameterized Logging
Avoid string concatenation and let the logger handle it. Instead of
log.debug("User $userId placed order $orderId")
use
log.debug("User {} placed order {}", userId, orderId)
Second one has much better performance when log level is disabled. [Source]
3.3 One Log Entry per Event
-
Avoid logging the same error in multiple layers.
-
Decide who owns logging for an error:
- Often the boundary (controller, listener, scheduler) logs the error.
- Inner layers may log WARN/DEBUG only if they handle or transform the error.
-
Log Structure and Consistency
- Use consistently structured messages: action result: key=value, key2=value2.
- Define conventions:
- Always include operation, status and identifiers.
- Use consistent wording across services.
log.info("order created successfully orderId={} userId={} total={}", orderId, userId, orderTotal);
3.4 Log the Stacktrace Once
- Logging then rethrowing and logging again at a higher layer.
- Logging only e.getMessage() without the stacktrace.
3.5 Performance
- Avoid heavy computations in log statements.
- Use parameterized logs so unnecessary formatting is skipped if the level is disabled.
- Be cautious with logging in tight loops or very hot paths.
- Consider sampling logs in very high-traffic paths (e.g. log 1 in N requests at DEBUG).
Control log level during runtime
In Spring Boot, you can dynamically change the log level at runtime by sending an HTTP request to an endpoint /actuator/loggers/{loggerName} with a JSON payload. Let’s say, to change the log level of the com.microsoft.azure.servicebus package to INFO, you can send the following request
curl -X POST "http://<host>:<port>/actuator/loggers/com.microsoft.azure.servicebus" \
-H "Content-Type: application/json" \
-d '{"configuredLevel":"INFO"}'
Do employ canonical log lines per request
Single, comprehensive log entry that is created at the end of each request to your service. Should include request’s input parameters, caller’s identity and authentication method, number of database queries made, timing information, rate limit count, and any other data you see fit to add.
Kotlin Logger
- More fluent syntax like kotlin itself
- No need to define the class (copy-pasting mistakes) https://github.com/oshai/kotlin-logging
Do & Don’t Summary
9.1 Do
- Use proper levels (INFO for business events, WARN/ERROR for issues).
- Add contextual info (requestId, userId, business IDs).
- Log exceptions with stacktraces at the right layer.
- Use SLF4J with parameterized logs.
- Protect sensitive data.
- Keep log format consistent across services.
9.2 Don’t
- Spam logs with redundant messages.
- Log secrets or personal data.
- Log the same error multiple times at different layers.
- Swallow exceptions silently.
- Use string concatenation or expensive operations in log calls.