DEV Community

Cover image for Spring Boot Logging convention - JMLogFlow
Joao Marques
Joao Marques

Posted on

Spring Boot Logging convention - JMLogFlow

JMLogFlow is a lightweight, pragmatic logging convention designed to improve maintainability of Spring Boot applications.
It’s centered around placing meaningful log messages in strategic locations throughout the application to trace execution and data flow, without overwhelming the log files.

1 - Controller Logging

Log once at the start of every controller method.

What to log:

  • Http method

  • Endpoint path

  • Path variables

  • Query parameters

  • Important Custom headers (Except the ones with sensitive information)

❌ Do not log the entire request body or object, and do not log any authorization information.

log.info("PUT /users/{userId} called with userId={} and userRequestDTO={} with X-ServiceNumber={} with Authorization={}", userId, userRequestDTO, serviceNumber, token);

✅ Log only the necessary

log.info("POST /users/{} called with X-ServiceNumber={}, userId, serviceNumber);

2 - Service Layer Logging

Log at the start and end of every public service method.

log.info("Starting UserService.getUserById with userId={}", userId);
// ... business logic ...
log.info("Finished UserService.getUserById for userId={}", userId);

This creates a clear log bracket that makes tracing the flow between layers easy.

3 - Repository Layer Logging

When data is retrieved from the database:

  • Log the number of rows returned.
  • If it’s a count query, log the result count.
  • If the query is suposed to return one item, like find by id, log only 1 important information.

log.info("Fetched {} users from the database for active={}", users.size(), active);

log.info("Total number of active users in the system: {}", userCount);

log.info("Retrieved user with id: {} from database", userId);

4 - External Service Logging

When receiving data from an external system, from a rest api or soap, or even a 3rd party sdk.

  • If it returns a list, log how many rows were returned.

  • If it returns a single object, log only key information (e.g., an ID or status, etc)

log.info("External billing service returned {} invoices for accountId={}", invoices.size(), accountId);

log.info("Payment gateway responded with transactionId={} and status={}", transaction.getId(), transaction.getStatus());

5 - Business Decision Logging

Every business decision branch should be logged.

What is a business decision branch?

A business decision branch is any point in the logic where the behavior of the application diverges based on rules, conditions, or domain-specific decisions. These aren’t just technical if/else branches, they are meaningful choices based on business rules.

Examples:

  • If a user is eligible for a discount:

log.info("User {} is eligible for a {}% discount based on subscription plan", userId, discountAmount);

  • If the system decides to skip billing:

log.info("Billing skipped for account {} because the billing period is not yet complete", accountId);

Logging these moments creates an audit trail of why the application behaved in a certain way.

6 - General Rules

  • Every log should explain in plain English what’s happening.

  • Do not log sensitive information (passwords, tokens, etc.).

  • JMLogFlow is strict to log.info, you can use your team convention for log.debug, log.warn and log.error

Example Summary

// 2 - Service Layer Logging
log.info("Starting createInvoice for accountId={}", accountId);

// 3 - Repository Layer Logging: only 1 important information
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new NotFoundException("Account not found with id " + accountId));
log.info("Account {} retrieved from database", accountId);

// 5 - Business Decision Logging: check if account is in trial
if (account.isTrial()) {
log.info("Account {} is in trial mode, skipping invoice generation", accountId);
return;
}

// 3 - Repository Layer Logging: Fetch billable items
List<Item> billableItems = repository.findBillableItems(accountId);
log.info("Found {} billable items for accountId={}", billableItems.size(), accountId);

// 4 - External Service Logging: Send to external billing service
ExternalInvoiceResponseDTO invoice = invoiceService.sendToBillingSystem(billableItems);
log.info("Invoice created with id={} and status={}", invoice.getId(), invoice.getStatus());

// 2 - Service Layer Logging: End of service
log.info("Finished createInvoice for accountId={}", accountId);

By placing logs at meaningful points of the flow and writing them in clear, understandable English, developers can quickly find issues issues, understand behavior, and maintain confidence in prod environment.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.