一键导入
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)