PrepZone Logo
PrepZone

Structured Logs and Metrics

Logback configuration, MDC correlation and Micrometer metrics export.

Read these first

Why this matters

  • Grepping unstructured log files across twelve pods during an outage is slow; JSON logs with a correlation ID let you filter one checkout journey in seconds.
  • Micrometer meters are vendor-neutral — the same Timer feeds Prometheus, Datadog, or CloudWatch depending on which registry you add at deploy time.
  • Business metrics like orders.placed and inventory.reservations.failed belong alongside JVM metrics; they surface revenue-impacting problems before users complain.
Logs
Logback + MDCStructured JSON
Metrics
MicrometerCounters, timers
Traces
OpenTelemetryTrace propagation
Logs, metrics and traces — the three pillars wired through Actuator and Micrometer.

Logging and metrics toolkit

  • MDC (Mapped Diagnostic Context) — thread-local map of key-value pairs appended to every log line in that thread.
  • Logback logback-spring.xml — configure JSON appenders, log levels per package, and profile-specific overrides.
  • MeterRegistry — central registry for counters, timers, gauges, and distribution summaries.
  • @Timed / @Counted — declarative instrumentation via Micrometer's aspect (requires @EnableAspectJAutoProxy).
  • Observation API — unified abstraction for metrics, tracing, and logging context in Spring Boot 3.x.

Correlation IDs with MDC

Java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorrelationIdFilter extends OncePerRequestFilter {

    public static final String HEADER = "X-Correlation-Id";
    public static final String MDC_KEY = "correlationId";

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String correlationId = Optional.ofNullable(request.getHeader(HEADER))
                .filter(id -> !id.isBlank())
                .orElse(UUID.randomUUID().toString());
        MDC.put(MDC_KEY, correlationId);
        response.setHeader(HEADER, correlationId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove(MDC_KEY);
        }
    }
}
Java
<!-- logback-spring.xml -->
<configuration>
    <springProfile name="prod">
        <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
        </appender>
        <root level="INFO">
            <appender-ref ref="JSON"/>
        </root>
    </springProfile>
</configuration>

Custom business metrics

Java
@Service
public class OrderService {

    private final Counter ordersPlaced;
    private final Timer checkoutTimer;
    private final OrderRepository orderRepository;

    public OrderService(MeterRegistry registry, OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
        this.ordersPlaced = registry.counter("bookstore.orders.placed");
        this.checkoutTimer = registry.timer("bookstore.checkout.duration");
    }

    public OrderResponse placeOrder(PlaceOrderRequest request) {
        return checkoutTimer.record(() -> {
            Order order = orderRepository.save(buildOrder(request));
            ordersPlaced.increment();
            log.info("Order {} placed for customer {}", order.getId(), request.customerId());
            return OrderResponse.from(order);
        });
    }
}
Java
@Configuration
public class MetricsConfig {

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

@Service
public class BookService {

    @Timed(value = "bookstore.catalog.lookup", description = "ISBN lookup duration")
    public BookDetail getByIsbn(String isbn) {
        return bookRepository.findByIsbn(isbn)
                .map(BookDetail::from)
                .orElseThrow(() -> new BookNotFoundException(isbn));
    }
}
Java
management:
  metrics:
    tags:
      application: bookstore-api
      environment: ${spring.profiles.active}
    distribution:
      percentiles-histogram:
        bookstore.checkout.duration: true

Quick recall

Everything you need if you only revisit this box.

  • Put a correlation ID in MDC at the filter layer; include it in every log line via Logback pattern or JSON encoder.
  • Use MeterRegistry.counter() and registry.timer() for business metrics alongside auto-configured JVM meters.
  • @Timed on service methods gives free latency histograms when TimedAspect is registered.
  • Tag meters with application and environment so dashboards filter correctly in multi-tenant setups.
  • Propagate MDC across async boundaries — it does not travel automatically.

Test yourself

Answer these before moving on — recall is what makes it stick.