Simple Logging - VittorioDeMarzi/hero-beans GitHub Wiki

Description

This page provides a guide for setting up and using logging in our Kotlin + Spring Boot services deployed on AWS.

1. Why logging?

  • Debugging: understand what happened and why.
  • Operations: investigate incidents in production.
  • Analytics: measure flows and performance (with structured logs).

2. Log levels (rules)

  • TRACE โ€“ ultraโ€‘detailed steps (usually off).

  • DEBUG โ€“ developer details (enable locally / during incidents).

  • INFO โ€“ key business events (default in prod). Examples: order created, payment sent, user authenticated.

  • WARN โ€“ something unexpected, but system continues. Example: fallback used, slow external call.

  • ERROR โ€“ failures that require attention; always log with exception.

Do not log: passwords, tokens, secrets.

Log Levels (from lowest to highest):

TRACE < DEBUG < INFO < WARN < ERROR

๐Ÿ‘‰ The higher the level, the fewer messages will appear in the logs.

๐Ÿ”น Dev (local development)

  • root = INFO โ†’ shows all INFO, WARN, ERROR from Spring, Hibernate, libraries.

  • techcourse.herobeans = INFO. ๐Ÿ‘‰ Result: verbose logs, good for debugging, we can see requests and responses.

๐Ÿ”น Prod (production)

  • root = WARN โ†’ external libraries log only WARN and ERROR.

  • techcourse.herobeans = INFO โ†’ our services still log business INFO (e.g., โ€œCheckout startedโ€, โ€œOrder createdโ€).

  • org.hibernate = ERROR โ†’ Hibernate logs only errors, no SQL noise. ๐Ÿ‘‰ Result: clean logs โ€” only your info messages plus important warnings/errors.

๐Ÿ”น Test (unit/integration tests)

  • root = WARN

  • techcourse.herobeans = INFO ๐Ÿ‘‰ Result: tests are not spammed by framework logs, but INFO is still visible โ€” useful for debugging tests.

3. Dependencies

Spring Boot already includes SLF4J + Logback for logging. We additionally use Kotlin Logging for more concise and Kotlin-friendly syntax.

dependencies {
    implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
}

5. Using Kotlin Logging

import mu.KotlinLogging
private val log = KotlinLogging.logger {}


class OrderService {
fun placeOrder(id: Long) {
log.info { "Placing order id=$id" }
try { /* ... */ }
catch (ex: Exception) {log.error(ex) { "Failed order id=$id" }}
}
}

GlobalExceptionHandler

Responsibilities

  • Centralize all error logging and error responses.
  • Log one error per request (avoid duplicates), with correlation id and request path.

CorrelationIdFilter

Adds or generates an X-Correlation-Id for each request. It stores the ID in MDC so it appears in all logs, and also returns it in the response header, making it easy to trace a single request across logs.

6. Where we added logs

Controllers & Services

  • Added INFO logs for key business actions (e.g., product created, order placed).

  • Rule: one log entry per main action โ†’ helps track normal flow.

  • Example:

log.info { "User ${user.id} placed order ${order.id}" }

GlobalExceptionHandler

  • Logs all handled exceptions at ERROR level (with stacktrace).

  • Includes request path + correlationId for traceability.

  • Example log:

2025-08-20 18:12:41.630 ERROR [0d74ac79-ffae-4b7a-bea0-1a65fb6f5f3b] t.h.exception.GlobalExceptionHandler - Exception at POST /api/address: Max 5 addresses allowed

7.Log Files Separation

We configured two rolling log files via logback-spring.xml:

  • application.log โ†’ contains INFO, WARN (normal application flow).

  • general.errors.log โ†’ contains only ERROR (critical issues).

This helps operations teams quickly find errors without being distracted by normal logs.

8. Logback Configuration (logback-spring.xml)

This file defines how logging works in our service. It controls:

  • where logs are stored

  • what log levels go into which file

  • how logs are rotated (archived)

Log directory

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

By default logs are written into a logs folder (relative to the project). On server (e.g., AWS EC2) we set an absolute path, like:

<property name="LOG_DIR" value="/home/ubuntu/app/logs" />

Log pattern

<property name="PATTERN"
          value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{correlationId}] %logger{36} - %msg%n"/>
  • Date/time โ†’ 2025-08-20 14:12:45.123

  • Level โ†’ INFO, ERROR, etc.

  • CorrelationId โ†’ unique per request (from CorrelationIdFilter).

  • Logger name โ†’ class name (shortened to 36 chars).

  • Message โ†’ actual log text.

Application log (application.log)

<appender name="APP" ...>
  • Collects INFO and WARN logs (normal application flow).

  • Explicitly excludes ERROR, so errors wonโ€™t appear twice.

  • Rolling policy:

    • Rotate daily and when file reaches 10MB.

    • Keep 14 days of history.

๐Ÿ‘‰ Used for business events and operational monitoring.

Error log (general.errors.log)

<appender name="ERR" ...>
  • Collects only ERROR logs.

  • Rolling policy:

    • Rotate daily and when file reaches 10MB.

    • Keep 30 days of history.

๐Ÿ‘‰ Used for production troubleshooting (fast access to all failures without noise).

Loggers

<logger name="techcourse.herobeans" level="INFO" additivity="false">
    <appender-ref ref="APP"/>
    <appender-ref ref="ERR"/>
</logger>
  • Our code (techcourse.herobeans package)

    • Default level = INFO.

    • Logs go to both application.log and general.errors.log.

    • additivity="false" prevents duplication in parent loggers.

<root level="WARN">
    <appender-ref ref="APP"/>
    <appender-ref ref="ERR"/>
</root>
  • All other libraries/frameworks (Spring, Hibernate, etc.).

    • Default level = WARN.

    • Means: we donโ€™t log their INFO spam, but still see warnings & errors.\

Summary

  • application.log โ†’ INFO + WARN, main system events, short retention (14 days).

  • general.errors.log โ†’ ERROR only, long retention (30 days).

  • Logs are rotated automatically โ†’ no risk of filling disk.

Useful Links

โš ๏ธ **GitHub.com Fallback** โš ๏ธ