Structured Logging
Giriş
Logging, bir uygulamanın en temel gözlemleme aracıdır. Metrikler size "ne kadar?" sorusunu cevaplayabilirken, loglar "tam olarak ne oldu?" sorusuna yanıt verir. Ancak tek sunucudaki düz metin logları production ortamının karmaşıklığıyla baş edemez — dağıtık sistemlerde yüzlerce container'dan akan milyonlarca log satırı arasında bir sorunu bulmak, saman yığınında iğne aramaktır. Structured logging (yapılandırılmış loglama), log mesajlarını aranabilir, filtrelenebilir ve analiz edilebilir bir formata dönüştürerek bu problemi çözer.
SLF4J API
SLF4J (Simple Logging Facade for Java), Java'daki logging implementasyonları (Logback, Log4j2 vb.) için bir soyutlama katmanıdır. Spring Boot varsayılan olarak SLF4J + Logback kullanır.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
// Logger, sınıf bazında oluşturulur
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
// Lombok ile daha kısa:
// @Slf4j — otomatik 'log' field'ı ekler
public Order createOrder(OrderRequest request) {
log.info("Creating order for customer: {}", request.getCustomerId());
try {
Order order = processOrder(request);
log.info("Order created successfully. orderId={}, amount={}",
order.getId(), order.getTotalAmount());
return order;
} catch (Exception e) {
log.error("Failed to create order for customer: {}",
request.getCustomerId(), e);
throw e;
}
}
}Placeholder kullanımı: {} placeholder'ları ile parametreler lazy evaluation ile değerlendirilir. String concatenation ("msg: " + value) kullanmayın — log seviyesi kapalıysa bile concatenation yapılır ve performans kaybına neden olur.
Log Seviyeleri
SLF4J beş log seviyesi tanımlar (en düşükten en yükseğe):
| Seviye | Kullanım Alanı |
|---|---|
TRACE | En detaylı seviye. Method giriş/çıkışları, değişken değerleri. Production'da genellikle kapalı. |
DEBUG | Geliştirme sırasında faydalı detaylar. SQL sorguları, cache hit/miss bilgileri. |
INFO | Normal çalışma olayları. Uygulama başlatma, önemli iş akışı adımları. |
WARN | Potansiyel problem. Deprecated API kullanımı, retry denemeleri, yavaş sorgular. |
ERROR | Hata durumları. Exception'lar, başarısız işlemler, bağlantı kopmaları. |
# application.properties — log seviyesi yapılandırması
logging.level.root=INFO
logging.level.com.example.myapp=DEBUG
logging.level.org.springframework.web=WARN
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACELogback Yapılandırması
Spring Boot, Logback'i varsayılan logging framework'ü olarak kullanır. Basit yapılandırma application.properties ile yapılabilir; ancak gelişmiş senaryolar için logback-spring.xml dosyası kullanılır:
<!-- src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Spring Boot varsayılan yapılandırmasını dahil et -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- Konsol çıktısı — geliştirme ortamı için -->
<springProfile name="!production">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- JSON çıktısı — production ortamı için -->
<springProfile name="production">
<appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>requestId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<customFields>{"service":"order-service","environment":"production"}</customFields>
</encoder>
</appender>
<!-- Dosyaya yazma — rotasyon ile -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/myapp/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/myapp/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="JSON_CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>JSON formatında log çıktısı için logstash-logback-encoder bağımlılığı gerekir:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>MDC — Mapped Diagnostic Context
MDC, bir request'in tüm log satırlarına otomatik olarak bağlam bilgisi (context) eklemenizi sağlar. Bir HTTP isteğinin request ID'sini, kullanıcı ID'sini veya correlation ID'sini tüm log satırlarında görmek istiyorsanız MDC kullanırsınız.
@Component
public class RequestLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Benzersiz request ID oluştur veya header'dan al
String requestId = Optional
.ofNullable(httpRequest.getHeader("X-Request-Id"))
.orElse(UUID.randomUUID().toString().substring(0, 8));
String userId = httpRequest.getHeader("X-User-Id");
try {
// MDC'ye değerleri koy
MDC.put("requestId", requestId);
MDC.put("userId", userId != null ? userId : "anonymous");
MDC.put("clientIp", httpRequest.getRemoteAddr());
// Response header'a da ekle (debugging için)
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("X-Request-Id", requestId);
chain.doFilter(request, response);
} finally {
// İstek bittiğinde MDC'yi temizle (thread pool reuse nedeniyle zorunlu!)
MDC.clear();
}
}
}MDC değerleri otomatik olarak her log satırına eklenir (Logback pattern veya JSON encoder'da yapılandırılmış ise):
// Düz metin formatında:
2024-01-15 10:30:45.123 [http-nio-8080-exec-1] INFO OrderService - Creating order [requestId=a1b2c3d4, userId=user42]
// JSON formatında:
{
"@timestamp": "2024-01-15T10:30:45.123Z",
"level": "INFO",
"logger_name": "com.example.OrderService",
"message": "Creating order",
"requestId": "a1b2c3d4",
"userId": "user42",
"clientIp": "192.168.1.100",
"service": "order-service"
}Log Correlation — Dağıtık İzleme
Microservice mimarisinde bir kullanıcı isteği birden fazla servis üzerinden geçer. Tüm servisler arasında aynı correlation ID (veya trace ID) kullanarak logları ilişkilendirmek kritik önem taşır:
// Service A → Service B çağrısı
@Service
public class OrderService {
private final RestClient inventoryClient;
public void createOrder(OrderRequest request) {
String traceId = MDC.get("traceId");
log.info("Creating order, checking inventory...");
// Trace ID'yi downstream servise propagate et
inventoryClient.post()
.uri("/api/inventory/reserve")
.header("X-Trace-Id", traceId)
.body(request.getItems())
.retrieve()
.toBodilessEntity();
}
}Spring Cloud Sleuth veya Micrometer Tracing, bu propagation'ı otomatik yapar. Her log satırında [traceId, spanId] bilgisi görünür.
Best Practices
Hassas verileri loglamayın: Şifreler, kredi kartı numaraları, kişisel bilgiler asla loglara yazılmamalı
Yapılandırılmış format kullanın: Production'da JSON formatı tercih edin; ELK/Loki gibi araçlarla aranabilir olur
MDC ile bağlam ekleyin: Her log satırında requestId, userId gibi bilgiler bulunmalı
Doğru log seviyesini kullanın: Her şeyi INFO yapmayın; DEBUG geliştirme, INFO normal akış, WARN potansiyel sorun, ERROR gerçek hata
Exception'ları doğru loglayın:
log.error("msg", exception)— stack trace otomatik eklenirPerformansa dikkat edin: Yoğun döngülerde log yazmaktan kaçının,
log.isDebugEnabled()ile koruyun
AI Asistan
Sorularını yanıtlamaya hazır