원클릭으로
logging-patterns
Structured logging with SLF4J, MDC context propagation, and observability patterns for Spring Boot
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Structured logging with SLF4J, MDC context propagation, and observability patterns for Spring Boot
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when implementing Spring Boot 3.x applications. Provides hands-on implementation patterns for web, data, security, testing, and cloud-native development.
Review REST API contracts for HTTP semantics, versioning, backward compatibility, and response consistency. Use when user asks "review API", "check endpoints", "REST review", or before releasing API changes.
Use when designing Spring Boot 3.x application architecture, making technology decisions, or planning project structure. Provides architectural patterns and references for project setup, JPA, security, testing, and reactive programming.
Use when designing, reviewing, or modifying REST API endpoints to enforce proper HTTP verbs, status codes, versioning, and API contract best practices.
Use when reviewing or writing code to enforce DRY, KISS, and YAGNI principles with Java-specific guidance.
Use when implementing or reviewing code that benefits from established design patterns like Builder, Factory, Strategy, Observer, Decorator, or Adapter.
| name | logging-patterns |
| description | Structured logging with SLF4J, MDC context propagation, and observability patterns for Spring Boot |
<!-- logback-spring.xml -->
<configuration>
<springProfile name="!production">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{correlationId}] %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="production">
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>correlationId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<includeMdcKeyName>requestPath</includeMdcKeyName>
<customFields>{"service":"my-service","environment":"production"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="JSON" />
</root>
</springProfile>
<!-- Reduce noise from frameworks -->
<logger name="org.springframework" level="WARN" />
<logger name="org.hibernate.SQL" level="WARN" />
<logger name="org.hibernate.type.descriptor.sql" level="WARN" />
<logger name="com.zaxxer.hikari" level="WARN" />
</configuration>
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorrelationIdFilter extends OncePerRequestFilter {
private static final String CORRELATION_HEADER = "X-Correlation-ID";
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
var correlationId = request.getHeader(CORRELATION_HEADER);
if (correlationId == null || correlationId.isBlank()) {
correlationId = UUID.randomUUID().toString();
}
MDC.put("correlationId", correlationId);
MDC.put("requestMethod", request.getMethod());
MDC.put("requestPath", request.getRequestURI());
response.setHeader(CORRELATION_HEADER, correlationId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
@Component
public class UserContextFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
var auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
MDC.put("userId", auth.getName());
}
try {
chain.doFilter(request, response);
} finally {
MDC.remove("userId");
}
}
}
@Service
@Slf4j
public class OrderService {
public OrderDto createOrder(CreateOrderRequest request) {
// DEBUG: detailed information for diagnosing problems
log.debug("Creating order with {} items for customer={}",
request.items().size(), request.customerId());
var order = processOrder(request);
// INFO: significant business events
log.info("Order created: orderId={} customerId={} totalAmount={}",
order.getId(), order.getCustomerId(), order.getTotalAmount());
return orderMapper.toDto(order);
}
public void processPayment(String orderId) {
try {
paymentGateway.charge(orderId);
} catch (PaymentDeclinedException e) {
// WARN: recoverable situation that needs attention
log.warn("Payment declined for orderId={}: {}", orderId, e.getMessage());
throw e;
} catch (PaymentGatewayException e) {
// ERROR: unexpected failure requiring investigation
log.error("Payment gateway failure for orderId={}", orderId, e);
throw new ServiceUnavailableException("Payment processing failed", e);
}
}
}
@Component
@Slf4j
public class RequestLoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
request.setAttribute("startTime", System.nanoTime());
log.info("Incoming request: method={} path={} query={}",
request.getMethod(), request.getRequestURI(), request.getQueryString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
var startTime = (Long) request.getAttribute("startTime");
var duration = Duration.ofNanos(System.nanoTime() - startTime);
log.info("Request completed: method={} path={} status={} duration={}ms",
request.getMethod(), request.getRequestURI(),
response.getStatus(), duration.toMillis());
}
}
// Never log these:
// - Passwords, tokens, API keys
// - Credit card numbers, SSNs
// - Personal health information
// - Full request bodies with sensitive fields
// BAD
log.info("User authenticated: email={} password={}", email, password);
log.info("Payment: card={}", cardNumber);
// GOOD
log.info("User authenticated: email={}", maskEmail(email));
log.info("Payment: card=****{}", lastFour(cardNumber));
public static String maskEmail(String email) {
int atIndex = email.indexOf('@');
if (atIndex <= 2) return "***" + email.substring(atIndex);
return email.substring(0, 2) + "***" + email.substring(atIndex);
}
// MDC context is thread-local -- propagate to async threads
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
var executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
}
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
var contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
if (contextMap != null) MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
{} syntax)