Skip to content

Logging Strategy

Guidelines for consistent, useful logging across the Smartsapp backend.


Stack

  • Framework: SLF4J + Logback (Spring Boot default)
  • Structured output: logstash-logback-encoder for JSON in non-dev profiles
  • Config file: backend/src/main/resources/logback-spring.xml

Output Formats

Profile Format Use case
default, dev Human-readable console with colours Local development
All others JSON (Logstash format) Deployed environments — parseable by Loki, ELK, Datadog, etc.

MDC Context (Automatic)

Every authenticated request automatically includes these fields in log output via backend/src/main/java/com/smartsapp/system/config/JwtAuthFilter.java:

MDC Key Source Example
requestId RequestIdFilter — client-sent X-Request-Id header, or auto-generated UUID a1b2c3d4-e5f6-7890-abcd-ef1234567890
userId JWT user_id claim 5237
schoolId JWT school_id claim 6
userName JWT user_name claim [email protected]
traceId OpenTelemetry (auto) 64f2b4a8e3d1...
spanId OpenTelemetry (auto) a1b2c3d4...

These appear in every log line — no manual action needed. In JSON mode, they are top-level fields.

Note: requestId and traceId serve different purposes. requestId is a client-facing correlation ID (echoed in X-Request-Id response header and in error bodies). traceId is the OTel distributed trace ID (echoed in X-Trace-Id response header, propagated across Kafka).


Where to Log

Always log

Location Level What to log
Auth rejection (JwtAuthFilter) WARN Missing/malformed token + HTTP method + path
Business rule violations (service layer) WARN Rule that blocked the action + entity context
Entity creation/deletion (service layer) INFO Entity type + ID. Not the full payload.
Kafka publish success (event producers) INFO Topic + event type + entity ID
Kafka publish failure (event producers) ERROR Topic + exception
Unexpected exceptions (GlobalExceptionHandler) ERROR Full stack trace
Expected errors (404, 400) (GlobalExceptionHandler) WARN Error message, no stack trace
Startup/seed events (DataSeeder) INFO What was created

Don't log

  • Every repository/DB call — use spring.jpa.show-sql or OTel JDBC tracing instead
  • Request/response bodies — security risk (tokens, PII). Use DEBUG level at most.
  • Happy-path controller methods — the OTel trace already captures these
  • Read operations (finders/getters) — too frequent. Rely on metrics.

When to use each level

Level Meaning Examples
ERROR Something broke that shouldn't have. Needs investigation. Unhandled exception, Kafka connection lost, DB timeout
WARN Expected failure or degraded operation. 401 auth rejection, business rule violation, 404 not found
INFO Noteworthy state change. Entity created/deleted, seed data loaded, app started
DEBUG Useful for local debugging. Never leave on in production. Request payloads, SQL queries, cache hits/misses

How to Log

Use SLF4J directly — no Lombok @Slf4j:

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

// Good — structured parameters
log.info("School created: id={}", school.getId());
log.warn("Insufficient balance: studentId={}, required={}, available={}", studentId, required, available);
log.error("Failed to publish event to {}", topic, exception);

// Bad — string concatenation
log.info("School created: " + school.getId());  // Don't do this

Rules

  1. Use parameterised messages ({} placeholders), never string concatenation
  2. Pass exceptions as the last argument — SLF4J handles stack trace printing
  3. Don't log sensitive data — no tokens, passwords, full request bodies
  4. Don't log and rethrow — either log it or let it propagate to GlobalExceptionHandler, not both
  5. Keep messages grep-friendly — use consistent prefixes like "School created:", "Auth rejected:"