logging-patterns

Logging Patterns Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "logging-patterns" with this command: npx skills add decebals/claude-code-java/decebals-claude-code-java-logging-patterns

Logging Patterns Skill

Effective logging for Java applications with focus on structured, AI-parsable formats.

When to Use

  • User says "add logging" / "improve logs" / "debug this"

  • Analyzing application flow from logs

  • Setting up structured logging (JSON)

  • Request tracing with correlation IDs

  • AI/Claude Code needs to analyze application behavior

AI-Friendly Logging

Key insight: JSON logs are better for AI analysis - faster parsing, fewer tokens, direct field access.

Why JSON for AI/Claude Code?

Text format - AI must "interpret" the string

2026-01-29 10:15:30 INFO OrderService - Order 12345 created for user-789, total: 99.99

JSON format - AI extracts fields directly

{"timestamp":"2026-01-29T10:15:30Z","level":"INFO","orderId":12345,"userId":"user-789","total":99.99}

Aspect Text JSON

Parsing Regex/interpretation Direct field access

Token usage Higher (repeated patterns) Lower (structured)

Error extraction Parse stack trace text exception field

Filtering grep patterns jq queries

Recommended Setup for AI-Assisted Development

application.yml - JSON by default

logging: structured: format: console: logstash # Spring Boot 3.4+

When YOU need to read logs manually:

Option 1: Use jq

tail -f app.log | jq .

Option 2: Switch profile temporarily

java -jar app.jar --spring.profiles.active=human-logs

Log Format Optimized for AI Analysis

{ "timestamp": "2026-01-29T10:15:30.123Z", "level": "INFO", "logger": "com.example.OrderService", "message": "Order created", "requestId": "req-abc123", "traceId": "trace-xyz", "orderId": 12345, "userId": "user-789", "duration_ms": 45, "step": "payment_completed" }

Key fields for AI debugging:

  • requestId

  • group all logs from same request

  • step

  • track progress through flow

  • duration_ms

  • identify slow operations

  • level

  • quick filter for errors

Reading Logs with AI/Claude Code

When asking AI to analyze logs:

Get recent errors

cat app.log | jq 'select(.level == "ERROR")' | tail -20

Follow specific request

cat app.log | jq 'select(.requestId == "req-abc123")'

Find slow operations

cat app.log | jq 'select(.duration_ms > 1000)'

AI can then:

  • Parse JSON directly (no guessing)

  • Follow request flow via requestId

  • Identify exactly where errors occurred

  • Measure timing between steps

Quick Setup (Spring Boot 3.4+)

Native Structured Logging

Spring Boot 3.4+ has built-in support - no extra dependencies!

application.yml

logging: structured: format: console: logstash # or "ecs" for Elastic Common Schema

Supported formats: logstash, ecs, gelf

Profile-Based Switching

application.yml (default - JSON for AI/prod)

spring: profiles: default: json-logs


spring: config: activate: on-profile: json-logs logging: structured: format: console: logstash


spring: config: activate: on-profile: human-logs

No structured format = human-readable default

logging: pattern: console: "%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"

Usage:

Default: JSON (for AI, CI/CD, production)

./mvnw spring-boot:run

Human-readable when needed

./mvnw spring-boot:run -Dspring.profiles.active=human-logs

Setup for Spring Boot < 3.4

Logstash Logback Encoder

pom.xml:

<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>7.4</version> </dependency>

logback-spring.xml:

<?xml version="1.0" encoding="UTF-8"?> <configuration>

&#x3C;!-- JSON (default) -->
&#x3C;springProfile name="!human-logs">
    &#x3C;appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
        &#x3C;encoder class="net.logstash.logback.encoder.LogstashEncoder">
            &#x3C;includeMdcKeyName>requestId&#x3C;/includeMdcKeyName>
            &#x3C;includeMdcKeyName>userId&#x3C;/includeMdcKeyName>
        &#x3C;/encoder>
    &#x3C;/appender>
    &#x3C;root level="INFO">
        &#x3C;appender-ref ref="JSON"/>
    &#x3C;/root>
&#x3C;/springProfile>

&#x3C;!-- Human-readable (optional) -->
&#x3C;springProfile name="human-logs">
    &#x3C;appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        &#x3C;encoder>
            &#x3C;pattern>%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n&#x3C;/pattern>
        &#x3C;/encoder>
    &#x3C;/appender>
    &#x3C;root level="INFO">
        &#x3C;appender-ref ref="CONSOLE"/>
    &#x3C;/root>
&#x3C;/springProfile>

</configuration>

Adding Custom Fields (Logstash Encoder)

import static net.logstash.logback.argument.StructuredArguments.kv;

// Fields appear as separate JSON keys log.info("Order created", kv("orderId", order.getId()), kv("userId", user.getId()), kv("total", order.getTotal()), kv("step", "order_created") );

// Output: // {"message":"Order created","orderId":123,"userId":"u-456","total":99.99,"step":"order_created"}

SLF4J Basics

Logger Declaration

import org.slf4j.Logger; import org.slf4j.LoggerFactory;

@Service public class OrderService { private static final Logger log = LoggerFactory.getLogger(OrderService.class); }

// Or with Lombok @Slf4j @Service public class OrderService { // use log directly }

Parameterized Logging

// ✅ GOOD: Evaluated only if level enabled log.debug("Processing order {} for user {}", orderId, userId);

// ❌ BAD: Always concatenates log.debug("Processing order " + orderId + " for user " + userId);

// ✅ For expensive operations if (log.isDebugEnabled()) { log.debug("Full order details: {}", order.toJson()); }

Log Levels

Level When Example

ERROR Failures needing attention Unhandled exception, service down

WARN Unexpected but handled Retry succeeded, deprecated API used

INFO Business events Order created, payment processed

DEBUG Technical details Method params, SQL queries

TRACE Very detailed Loop iterations (rarely used)

log.error("Payment failed", kv("orderId", id), kv("reason", reason), exception); log.warn("Retry succeeded", kv("attempt", 3), kv("orderId", id)); log.info("Order shipped", kv("orderId", id), kv("trackingNumber", tracking)); log.debug("Fetching from DB", kv("query", "findById"), kv("id", id));

MDC (Mapped Diagnostic Context)

MDC adds context to every log entry in a request - essential for tracing.

Request ID Filter

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

@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain chain) throws ServletException, IOException {
    try {
        String requestId = Optional.ofNullable(request.getHeader("X-Request-ID"))
            .filter(s -> !s.isBlank())
            .orElse(UUID.randomUUID().toString().substring(0, 8));

        MDC.put("requestId", requestId);
        response.setHeader("X-Request-ID", requestId);

        chain.doFilter(request, response);
    } finally {
        MDC.clear();
    }
}

}

Add User Context

// After authentication MDC.put("userId", authentication.getName());

// All subsequent logs include userId automatically log.info("User action performed"); // {"userId":"john123","message":"User action performed"}

MDC in Async Operations

// MDC doesn't auto-propagate to new threads!

// ✅ Copy MDC context Map<String, String> context = MDC.getCopyOfContextMap();

CompletableFuture.runAsync(() -> { try { if (context != null) MDC.setContextMap(context); log.info("Async task running"); // Has requestId, userId } finally { MDC.clear(); } });

What to Log

Business Events (INFO)

// Include key identifiers and state log.info("Order created", kv("orderId", id), kv("userId", userId), kv("total", total), kv("itemCount", items.size()), kv("step", "order_created"));

log.info("Payment processed", kv("orderId", id), kv("amount", amount), kv("method", "card"), kv("step", "payment_completed"));

External Calls (with timing)

long start = System.currentTimeMillis(); try { Result result = externalService.call(params); log.info("External call succeeded", kv("service", "PaymentGateway"), kv("operation", "charge"), kv("duration_ms", System.currentTimeMillis() - start)); return result; } catch (Exception e) { log.error("External call failed", kv("service", "PaymentGateway"), kv("operation", "charge"), kv("duration_ms", System.currentTimeMillis() - start), e); throw e; }

Flow Steps (for AI tracing)

public Order processOrder(CreateOrderRequest request) { log.info("Processing started", kv("step", "start"), kv("requestData", request.summary()));

Order order = createOrder(request);
log.info("Order created", kv("step", "order_created"), kv("orderId", order.getId()));

validateInventory(order);
log.info("Inventory validated", kv("step", "inventory_ok"), kv("orderId", order.getId()));

processPayment(order);
log.info("Payment processed", kv("step", "payment_done"), kv("orderId", order.getId()));

log.info("Processing completed", kv("step", "complete"), kv("orderId", order.getId()));
return order;

}

What NOT to Log

// ❌ NEVER log sensitive data log.info("Login", kv("password", password)); // Passwords log.info("Payment", kv("cardNumber", card)); // Full card numbers log.info("Request", kv("token", jwtToken)); // Tokens log.info("User", kv("ssn", socialSecurity)); // PII

// ✅ Safe alternatives log.info("Login attempted", kv("userId", userId)); log.info("Payment", kv("cardLast4", last4)); log.info("Token validated", kv("subject", sub), kv("exp", expiry));

Exception Logging

Log Once at Boundary

// ❌ BAD: Logs same exception multiple times void methodA() { try { methodB(); } catch (Exception e) { log.error("Error", e); throw e; } // Log #1 } void methodB() { try { methodC(); } catch (Exception e) { log.error("Error", e); throw e; } // Log #2 }

// ✅ GOOD: Log at service boundary only @RestControllerAdvice public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity&#x3C;?> handle(Exception e, HttpServletRequest request) {
    log.error("Request failed",
        kv("path", request.getRequestURI()),
        kv("method", request.getMethod()),
        kv("errorType", e.getClass().getSimpleName()),
        e);  // Full stack trace
    return ResponseEntity.status(500).body(errorResponse);
}

}

Include Context

// ❌ Useless log.error("Error occurred", e);

// ✅ Useful for debugging log.error("Order processing failed", kv("orderId", orderId), kv("step", "payment"), kv("userId", userId), kv("attemptNumber", attempt), e);

Quick Reference

// === Setup === private static final Logger log = LoggerFactory.getLogger(MyClass.class);

// === Logging with structured fields === import static net.logstash.logback.argument.StructuredArguments.kv;

log.info("Event", kv("key1", value1), kv("key2", value2)); log.error("Failed", kv("context", ctx), exception);

// === MDC === MDC.put("requestId", requestId); MDC.put("userId", userId); // ... all logs now include these MDC.clear(); // cleanup

// === Levels === log.error() // Failures log.warn() // Handled issues log.info() // Business events log.debug() // Technical details

Analyzing Logs (AI/Human)

Pretty print JSON logs

tail -f app.log | jq .

Filter errors

cat app.log | jq 'select(.level == "ERROR")'

Follow request flow

cat app.log | jq 'select(.requestId == "abc123")'

Find slow operations (>1s)

cat app.log | jq 'select(.duration_ms > 1000)'

Get timeline of steps

cat app.log | jq 'select(.requestId == "abc123") | {time: .timestamp, step: .step, message: .message}'

Related Skills

  • spring-boot-patterns

  • Spring Boot configuration

  • jpa-patterns

  • Database logging (SQL queries)

  • Future: observability-patterns

  • Metrics, tracing, full observability

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

java-code-review

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

clean-code

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

spring-boot-patterns

No summary provided by upstream source.

Repository SourceNeeds Review