Logging — Logback default, structured logging, log level dynamic
Boot pre-configure Logback với pattern, level, file rotation. Bài này bóc cấu hình Logback Boot dùng, log level hierarchy, structured logging JSON (Boot 3.4 GA), MDC cho correlation ID, dynamic log level qua Actuator, đổi sang Log4j2, và pattern log production cho ELK/Loki.
Mọi app Boot in log từ giây đầu start. Bạn không config gì — log đã có pattern đẹp, level INFO, color console. Đó là vì Boot pre-configure Logback sẵn — 1 trong 5 trụ cột production-ready.
Bài này bóc cơ chế: Boot dùng Logback ra sao, pattern log decode thế nào, structured logging JSON (Boot 3.4 GA) cho ELK/Loki, MDC cho correlation ID, dynamic log level qua Actuator runtime (không restart), và pattern thực tế cho production.
1. Stack logging Boot — 4 layer
Boot không tự code logger. Nó tích hợp 4 component:
flowchart TB
App[App code]
SLF4J["SLF4J<br/>(Simple Logging Facade for Java)"]
Logback["Logback<br/>(implementation)"]
Output["Console / File / Cloud"]
App -->|"Logger.info(...)"| SLF4J
SLF4J -->|"binding"| Logback
Logback --> Output
Bridges["Bridge libs:<br/>jul-to-slf4j (java.util.logging)<br/>log4j-to-slf4j (Log4j 1.x/2.x)<br/>jcl-to-slf4j (commons-logging)"]
App -->|"3rd-party lib"| Bridges
Bridges --> SLF4J
style SLF4J fill:#fef3c7
style Logback fill:#d1fae5Layer:
- SLF4J: facade interface (
Logger,LoggerFactory). Code app chỉ dùng SLF4J — không bind vào implementation. - Logback: default implementation. Có thể đổi sang Log4j2 mà không sửa code app.
- Bridge libs: capture log từ thư viện cũ (Apache Commons Logging, java.util.logging, Log4j 1.x) → redirect qua SLF4J.
spring-boot-starter-logging (transitive của mọi starter) pull cả 4 layer:
spring-boot-starter-logging
├── logback-classic (impl)
├── logback-core
├── slf4j-api
├── jul-to-slf4j (bridge JUL)
├── log4j-to-slf4j (bridge Log4j)
└── jcl-over-slf4j (bridge commons-logging)
Hệ quả: dù bạn dùng lib X hay Y có log riêng (Tomcat dùng JUL, Hibernate dùng Log4j), tất cả log unified qua SLF4J → Logback → output.
2. SLF4J — cách dùng đúng
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void place(Order order) {
log.debug("Placing order {}", order.id()); // OK — placeholder
log.info("Order placed: id={}, total={}", order.id(), order.total());
try {
payment.charge(order);
} catch (PaymentException e) {
log.error("Payment failed for order {}", order.id(), e); // exception last param
}
}
}
4 quy tắc SLF4J:
- Static final logger per class. Hoặc dùng Lombok
@Slf4jđể generate tự động. - Placeholder
{}không string concatenation.log.debug("user " + userId + " did X"); // SAI — concat luon, ngay ca khi debug tat log.debug("user {} did X", userId); // DUNG — lazy substitute, skip neu debug tat - Exception là param cuối, không format.
log.error("failed", e); // log + stack trace log.error("failed: {}", e.getMessage()); // chi log message, MAT stack trace - Không log sensitive data (password, token, PII). Filter qua MDC hoặc custom appender.
2.1 Lombok @Slf4j
Thay vì khai báo logger manually:
@Slf4j // Lombok generate `private static final Logger log = ...`
@Service
public class OrderService {
public void place(Order order) {
log.info("Placing order {}", order);
}
}
Khoá này dùng pattern này — gọn hơn 1 dòng / class, semantic giống.
3. Log level — 5 level + hierarchy
SLF4J có 5 level, từ chi tiết nhất → quan trọng nhất:
| Level | Khi dùng | Production default |
|---|---|---|
TRACE | Đặc biệt chi tiết — entry/exit method, loop iteration | Tắt |
DEBUG | Chi tiết debugging — variable value, branch decision | Tắt |
INFO | Sự kiện business quan trọng — order placed, user login | Bật |
WARN | Vấn đề tiềm năng nhưng app vẫn chạy — retry, fallback | Bật |
ERROR | Lỗi cần điều tra — exception, request fail | Bật |
Quy tắc: log.debug(...) chỉ chạy khi level DEBUG bật. Nếu level INFO, debug call skip → no overhead (lazy substitute placeholder).
3.1 Hierarchy theo package
Logger có hierarchy theo package — child inherit level từ parent:
# application.yml
logging:
level:
root: INFO # default cho moi logger
com.olhub: DEBUG # mọi class trong com.olhub
com.olhub.security: INFO # override — security INFO
org.springframework.web: WARN # tắt verbose web log
org.hibernate.SQL: DEBUG # log SQL
org.hibernate.type.descriptor.sql: TRACE # log SQL parameters
com.olhub.security.AuthService resolve level theo:
- Tìm logger
com.olhub.security.AuthService→ không có config explicit. - Trỏ lên parent
com.olhub.security→ INFO. Apply.
Pattern hibernate SQL debug là kinh điển:
org.hibernate.SQLDEBUG → log SQL query.org.hibernate.type.descriptor.sqlTRACE → log parameter binding.
Bật khi debug N+1 hoặc query phức tạp.
3.2 Pattern log Boot default
2026-04-15T10:00:00.123 INFO 12345 --- [main] com.olhub.OrderService : Order placed: id=42, total=99.99
Decode:
2026-04-15T10:00:00.123— ISO timestamp.INFO— level (5 char fixed width, color theo level).12345— process ID (PID).---— separator.[main]— thread name (15 char fixed width).com.olhub.OrderService— logger name (40 char, abbreviated nếu dài).: Order placed...— message.
Pattern này set qua property:
logging:
pattern:
console: "%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5level %pid --- [%15.15thread] %-40.40logger{39} : %msg%n"
Hoặc hoàn toàn tự config qua logback-spring.xml.
4. Cấu hình logging qua property
Boot expose ~30 property cho logging — đủ cho 90% case mà không cần logback-spring.xml:
4.1 Properties phổ biến
logging:
# Level
level:
root: INFO
com.olhub: DEBUG
# File
file:
name: /var/log/app/app.log # log to file
# path: /var/log/app # alternative — auto-name spring.log
# Pattern
pattern:
console: "%d{HH:mm:ss.SSS} %-5level [%thread] %logger{40} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n%ex"
dateformat: "yyyy-MM-dd HH:mm:ss.SSS"
# Rotation (Logback)
logback:
rollingpolicy:
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz
max-file-size: 100MB
max-history: 30
total-size-cap: 10GB
# Charset
charset:
console: UTF-8
file: UTF-8
# Group — nhom multiple package
group:
web: org.springframework.core.codec, org.springframework.http, org.springframework.web
sql: org.hibernate.SQL, org.hibernate.orm.jdbc.bind
logging.group.web cho phép set level cho nhóm:
logging:
level:
web: DEBUG # apply cho ca 3 package trong group
4.2 Khi nào cần logback-spring.xml
90% case dùng property là đủ. Khi cần logback-spring.xml:
- Custom appender (Kafka, ELK, Loki, custom JSON encoder).
- Conditional logger theo profile trong xml (Boot 2.0+ support
<springProfile>tag). - Multiple appender với pattern khác nhau.
- Async appender cho high-throughput.
<!-- src/main/resources/logback-spring.xml -->
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<springProfile name="prod">
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/app/app.json</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/app/app.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="JSON_FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
<springProfile> tag cho phép conditional theo profile — power không có trong logback.xml thuần.
Tên file quan trọng: logback-spring.xml (Boot recognize) thay vì logback.xml — Boot có thể chỉnh tag <springProfile> trước khi Logback load.
5. Structured logging — Boot 3.4 GA
Production yêu cầu log JSON cho ELK/Splunk/Loki dễ index. Trước Boot 3.4, phải tự config Logstash encoder qua xml. Boot 3.4 thêm structured logging built-in:
logging:
structured:
format:
console: ecs # Elastic Common Schema
file: ecs
3 format:
ecs— Elastic Common Schema (Elasticsearch native).gelf— Graylog Extended Log Format.logstash— Logstash JSON.
Output:
{
"@timestamp": "2026-04-15T10:00:00.123Z",
"log.level": "INFO",
"process.pid": 12345,
"process.thread.name": "http-nio-8080-exec-1",
"log.logger": "com.olhub.OrderService",
"message": "Order placed: id=42, total=99.99",
"service.name": "order-service",
"service.version": "1.2.3",
"service.environment": "prod",
"ecs.version": "8.11"
}
ELK ingest trực tiếp — không phải parse pattern.
5.1 Custom field qua MDC
MDC (Mapped Diagnostic Context) — context per-thread cho log:
@Component
public class CorrelationIdFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-Id");
if (requestId == null) requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
MDC.put("userId", extractUserId(req)); // tu JWT
try {
chain.doFilter(req, res);
} finally {
MDC.clear(); // QUAN TRONG — tranh leak sang request khac
}
}
}
Sau filter này, mọi log trong cùng request sẽ có requestId, userId trong MDC. Pattern log + MDC:
logging:
pattern:
console: "%d{HH:mm:ss} %-5level [%X{requestId:-no-request}] %logger : %msg%n"
%X\{requestId\} lấy từ MDC. :-no-request default nếu không có.
Output:
10:00:00 INFO [a1b2c3] com.olhub.OrderService : Order placed: id=42
10:00:01 INFO [a1b2c3] com.olhub.PaymentService : Charged $99.99
10:00:01 INFO [d4e5f6] com.olhub.OrderService : Order placed: id=43
Trace 1 request qua nhiều service bằng requestId. Đây là pattern correlation ID — quan trọng cho distributed tracing manual (trước khi setup Micrometer Tracing).
5.2 MDC + Structured logging
Boot 3.4 structured logging tự include MDC fields:
{
"@timestamp": "...",
"log.level": "INFO",
"message": "Order placed: id=42",
"mdc": {
"requestId": "a1b2c3",
"userId": "user-42"
}
}
ELK query: mdc.requestId: "a1b2c3" → trace toàn bộ request. Cực mạnh cho debug production.
5.3 Log custom field
Boot 3.4 thêm LoggingApplicationListener có thể inject custom field:
logging:
structured:
format:
console: ecs
json:
add:
environment: ${ENV:dev}
region: ${REGION:asia-east}
service.version: ${BUILD_VERSION:unknown}
Mọi log line tự include 3 field này. Đỡ phải đặt MDC manual cho field static.
6. Dynamic log level — Actuator runtime
Production scenario: bug xuất hiện, bạn cần bật DEBUG cho 1 package mà không restart app. Actuator /loggers endpoint:
management:
endpoints:
web:
exposure:
include: health, loggers
GET /actuator/loggers/com.olhub.OrderService:
{
"configuredLevel": null,
"effectiveLevel": "INFO"
}
POST /actuator/loggers/com.olhub.OrderService body:
{ "configuredLevel": "DEBUG" }
→ Logger com.olhub.OrderService ngay lập tức log DEBUG. Reset bằng null:
curl -X POST http://app:8080/actuator/loggers/com.olhub.OrderService \
-H "Content-Type: application/json" \
-d '{"configuredLevel": null}'
→ Trở về level từ config file.
Pattern thực tế: bug user X gặp tại 10:00. DevOps:
- Bật DEBUG cho 1 logger cụ thể qua
/actuator/loggers. - Reproduce (call lại API, check log với
requestIdcủa user X). - Tắt DEBUG (set
null). - Tổng thời gian: 5 phút. So với restart deployment: 30 phút + impact users.
Cảnh báo: /actuator/loggers POST cần bảo vệ:
- Authentication (Spring Security).
- Tách port management khác port app:
management.server.port=9090. - Firewall: chỉ cho phép internal network access.
7. Đổi sang Log4j2
Logback default. Đổi sang Log4j2 (perf tốt hơn cho high-throughput):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
Tạo src/main/resources/log4j2-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.olhub" level="DEBUG"/>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Khi nào đổi:
- App high-throughput (>100k log/sec) — Log4j2 async logger nhanh hơn Logback ~2-3x.
- Cần feature Log4j2 specific (script-based filter, lookup mở rộng).
90% case Logback đủ. Đổi chỉ khi đo được benefit.
8. Pattern production cho ELK/Loki
Setup chuẩn 2026:
# application-prod.yml
logging:
level:
root: INFO
com.olhub: INFO
org.springframework.web: WARN
org.hibernate.SQL: WARN
structured:
format:
console: ecs # JSON cho ELK
json:
add:
environment: ${ENV:prod}
region: ${REGION}
service.name: ${spring.application.name}
service.version: ${BUILD_VERSION:unknown}
management:
endpoints:
web:
exposure:
include: health, loggers, metrics
server:
port: 9090 # tach port management
K8s deployment:
spec:
containers:
- name: app
ports:
- containerPort: 8080 # app
- containerPort: 9090 # management (internal only)
env:
- name: ENV
value: "prod"
- name: REGION
value: "asia-east"
- name: BUILD_VERSION
value: "1.2.3"
ELK pipeline:
- App in JSON ECS log → stdout.
- Container runtime (Docker/Kubernetes) capture stdout → log file.
- Filebeat/Fluentd ingest log file → Logstash hoặc trực tiếp Elasticsearch.
- Kibana query:
service.name: "order-service" AND mdc.requestId: "X".
Pattern này standard 2026 — JSON log + structured fields + correlation ID.
9. Pitfall tổng hợp
❌ Nhầm 1: String concatenation trong log call.
log.debug("user " + userId + " did " + action); // concat ngay ca khi debug tat
✅ Placeholder: log.debug("user {} did {}", userId, action); — lazy.
❌ Nhầm 2: Log exception sai cách.
log.error("failed: " + e.getMessage()); // mat stack trace
log.error("failed: {}", e); // toString cua exception, mat stack trace
✅ Exception là param cuối: log.error("failed", e); → log + stack trace đầy đủ.
❌ Nhầm 3: Log sensitive data.
log.info("Login: user={}, password={}", username, password); // password vao log
log.info("User profile: {}", user); // toString co the include email/phone
✅ Manual filter: log.info("Login: user={}", username);. Cho data structure phức tạp, override toString() mask sensitive field.
❌ Nhầm 4: Tin level INFO thấy log debug method nội bộ.
public void process() {
log.info("processing started"); // ban thuong INFO ?? Dau cua method INFO?
// ...
log.info("processing finished");
}
✅ INFO cho business event (order placed, user login). DEBUG cho method entry/exit. Đừng spam INFO.
❌ Nhầm 5: Quên MDC.clear() trong filter.
public void doFilter(...) {
MDC.put("requestId", id);
chain.doFilter(req, res);
// QUEN MDC.clear() — request sau ke thua MDC
}
✅ try-finally đảm bảo clear:
try { chain.doFilter(req, res); }
finally { MDC.clear(); }
❌ Nhầm 6: Đặt log file trong jar (không persist khi container restart).
logging:
file:
name: /app/logs/app.log # bi mat khi container restart
✅ Mount volume hoặc dùng stdout (K8s standard) → container runtime forward log đến central:
# Khong config file, log ra stdout
logging:
pattern:
console: ...
❌ Nhầm 7: Bật DEBUG cho org.hibernate.SQL ở production.
✅ DEBUG SQL chỉ tạm thời debug N+1 hoặc bug query. Production: log volume bùng nổ → cost ELK + slow app. Tắt sau khi xong.
❌ Nhầm 8: Expose /actuator/loggers không auth.
✅ Bảo vệ endpoint qua Spring Security + tách management port. Attacker bật DEBUG → log volume DDoS app.
10. 📚 Deep Dive Spring Reference
Spring Boot Reference docs:
- Spring Boot Reference — Logging — overview chính thức.
- Spring Boot Reference — Structured Logging — Boot 3.4 GA structured logging.
- Spring Boot Reference — Custom Log Configuration —
logback-spring.xml. - Spring Boot Reference — Logger Levels Endpoint — Actuator loggers.
SLF4J docs:
- SLF4J User Manual — facade API chính chủ.
- Logback Manual — implementation chi tiết.
Log format chuẩn:
- Elastic Common Schema (ECS) — schema field chuẩn.
- Logstash JSON encoder — popular library cho JSON log.
Bài viết:
- Stéphane Nicoll — Structured logging in Spring Boot 3.4 — chính chủ release blog.
- Jonas Pasquier — Logging in Spring Boot — tutorial đầy đủ.
Tool:
/actuator/loggers— runtime change level.- IntelliJ IDEA "Tail Log" — auto-follow Spring Boot log với pattern decode.
- Loki + Grafana — query log JSON với LogQL.
Ghi chú: structured logging Boot 3.4 GA là feature lớn. Trước đó phải tự config Logstash encoder + JSON layout. Boot 3.4 đơn giản hoá xuống 1 dòng property. Production deploy mới năm 2026 nên dùng structured từ đầu.
11. Tóm tắt
- Boot logging stack: SLF4J facade + Logback impl + bridge libs (capture log từ JUL/Log4j 1.x/Commons Logging).
- Code app chỉ dùng SLF4J API — không bind vào implementation. Đổi Logback → Log4j2 không sửa code.
- 4 quy tắc SLF4J: static final logger / class, placeholder
{}không concat, exception là param cuối, không log sensitive. - 5 log level: TRACE/DEBUG/INFO/WARN/ERROR. Production default: INFO+. DEBUG tạm thời cho diagnostics.
- Hierarchy theo package — child inherit level. Set group qua
logging.level.com.olhub. - ~30 property
logging.*đủ cho 90% config — pattern, file, rotation, level.logback-spring.xmlcho custom appender phức tạp. <springProfile>tag tronglogback-spring.xmlcho conditional config theo profile.- Structured logging Boot 3.4 GA:
logging.structured.format.console=ecs→ JSON output trực tiếp cho ELK. 3 format: ecs, gelf, logstash. - MDC (Mapped Diagnostic Context) per-thread cho correlation ID. Pattern
%X\{requestId\}trong layout. Filtertry-finally MDC.clear(). - Dynamic log level runtime qua
/actuator/loggersPOST — debug production không restart. - Đổi sang Log4j2 cho high-throughput app (>100k log/sec). Exclude logback starter, add log4j2 starter.
- Production pattern 2026: JSON log → stdout → container runtime capture → Filebeat/Fluentd → Elasticsearch/Loki. MDC correlation ID + ECS schema.
12. Tự kiểm tra
Q1Đoạn sau có 3 vấn đề về logging. Liệt kê + sửa.@Service
public class OrderService {
private Logger log = LoggerFactory.getLogger(OrderService.class);
public void place(Order o) {
log.info("Placing order " + o.getId() + " for user " + o.getUserId());
try {
payment.charge(o);
} catch (Exception e) {
log.error("Failed: " + e.getMessage());
}
}
}
▸
@Service
public class OrderService {
private Logger log = LoggerFactory.getLogger(OrderService.class);
public void place(Order o) {
log.info("Placing order " + o.getId() + " for user " + o.getUserId());
try {
payment.charge(o);
} catch (Exception e) {
log.error("Failed: " + e.getMessage());
}
}
}- Logger không
static final: mỗi instance class tạo logger mới — waste memory + slower lookup. Logger class-scope, không instance-scope.Hoặc Lombokprivate static final Logger log = LoggerFactory.getLogger(OrderService.class);@Slf4j. - String concatenation thay placeholder:
"Placing order " + o.getId() + ...tạo string mỗi call kể cả khi INFO tắt. Wasteful.Placeholder lazy — chỉ substitute khi level enabled.log.info("Placing order {} for user {}", o.getId(), o.getUserId()); - Log exception sai — mất stack trace:
log.error("Failed: " + e.getMessage())chỉ in message string, không có stack trace. Khi debug bug runtime, stack trace là vital.SLF4J detect param cuối làlog.error("Failed for order {}", o.getId(), e); // exception param cuoiThrowable→ log message + full stack trace.
Code đúng:
@Slf4j
@Service
public class OrderService {
public void place(Order o) {
log.info("Placing order {} for user {}", o.getId(), o.getUserId());
try {
payment.charge(o);
} catch (PaymentException e) {
log.error("Payment failed for order {}", o.getId(), e);
}
}
}Bonus: catch PaymentException cụ thể thay Exception chung — best practice.
Q2App production gặp bug user X gặp tại 10:00. Bạn cần debug nhưng không thể restart app (downtime). Quy trình diagnose dùng MDC + Actuator như thế nào?▸
Quy trình 5 bước:
- Verify MDC correlation ID đã setup: mỗi request phải có
X-Request-Idheader → MDC. Pattern log có%X{requestId}. Nếu chưa có, deploy filter này như infrastructure baseline. - User X reproduce: ask user gửi lại request, ghi nhớ
X-Request-Idtrả về (hoặc UUID gen tự động trong response header). - Search log với requestId:Trace toàn bộ request flow — controller → service → repository → external call. Identify bước fail.
# ELK query mdc.requestId: "a1b2c3-..." AND log.level: ERROR # Hoac grep cho file log grep "a1b2c3" /var/log/app/app.json | jq . - Cần thêm DEBUG log → bật runtime qua Actuator:Logger ngay lập tức log DEBUG cho mọi request đến (no restart).
POST http://app-internal:9090/actuator/loggers/com.olhub.PaymentService Body: { "configuredLevel": "DEBUG" } - Reproduce + analyze + tắt DEBUG: ask user thử lại, search log với
requestIdmới, identify root cause. Sau đó:POST http://app-internal:9090/actuator/loggers/com.olhub.PaymentService Body: { "configuredLevel": null } # reset ve config file
Tổng thời gian: ~5-15 phút. So với restart deployment: 30 phút + downtime + cache cold start.
Pre-requisite:
- MDC correlation ID filter đã deploy.
- Pattern log có
%X{requestId}hoặc structured logging include MDC. - ELK/Loki ingest log + search được.
- Actuator
/loggersexposed (qua management port internal-only). - Authentication cho Actuator endpoint.
Pattern này standard infra cho production app — invest 1 lần, dùng mãi.
Q3Structured logging Boot 3.4 GA đem lại lợi ích gì cụ thể so với pattern log truyền thống? Cho ví dụ ELK query trên 2 format.▸
Lợi ích cốt lõi: log đã là JSON sẵn sàng index — không cần parse pattern string ở Logstash/Filebeat.
Pattern truyền thống — string log:
2026-04-15T10:00:00.123 INFO 12345 --- [main] com.olhub.OrderService : Order placed: id=42, total=99.99Pipeline ingest:
- Filebeat đọc dòng log.
- Logstash apply Grok pattern:
%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{NUMBER:pid} --- \[%{DATA:thread}\] %{DATA:logger} : %{GREEDYDATA:message}. - Extract field, convert type (timestamp string → date), index Elasticsearch.
- Risk: pattern thay đổi (vd thêm trace ID) → Grok pattern fail → log không index.
Structured logging (Boot 3.4 ECS):
{
"@timestamp": "2026-04-15T10:00:00.123Z",
"log.level": "INFO",
"process.pid": 12345,
"process.thread.name": "main",
"log.logger": "com.olhub.OrderService",
"message": "Order placed: id=42, total=99.99",
"service.name": "order-service",
"service.version": "1.2.3"
}Pipeline ingest:
- Filebeat đọc dòng log.
- Filebeat parse JSON tự động → forward Elasticsearch.
- Elasticsearch index trực tiếp với field type chuẩn ECS.
- Khong Logstash, no Grok, no fragile pattern.
Query so sánh:
# Pattern log + Logstash
log_level:"INFO" AND service:"order-service" AND message:"Order placed"
# Structured ECS
log.level: "INFO" AND service.name: "order-service" AND message: "Order placed"Tương tự ngắn — nhưng structured có:
- Type-safe:
log.levellà keyword,process.pidlà number → query numeric range work. - Schema chuẩn: ECS dùng chung cho mọi service team → dashboard reuse được.
- Thêm field dễ: add custom field qua MDC hoặc
logging.structured.json.add— không động đến pattern.
Setup Boot 3.4 — 1 dòng property:
logging.structured.format.console: ecsTrước Boot 3.4, phải config Logstash encoder qua logback-spring.xml ~30 dòng. Đó là tiến bộ lớn.
Q4Bạn cấu hình:logging:
level:
root: WARN
com.olhub: INFO
com.olhub.security: DEBUG
com.olhub.security.AuthService: WARN
Logger nào active level nào?▸
logging:
level:
root: WARN
com.olhub: INFO
com.olhub.security: DEBUG
com.olhub.security.AuthService: WARNHierarchy theo package — child inherit từ parent gần nhất:
| Logger | Active level | Match config |
|---|---|---|
org.springframework.SomeBean | WARN | root |
com.olhub.OrderController | INFO | com.olhub |
com.olhub.OrderService | INFO | com.olhub |
com.olhub.security.JwtFilter | DEBUG | com.olhub.security |
com.olhub.security.PasswordEncoder | DEBUG | com.olhub.security |
com.olhub.security.AuthService | WARN | com.olhub.security.AuthService (explicit) |
com.olhub.security.AuthService.Inner | WARN | inherit từ AuthService |
com.olhub.payment.StripePayment | INFO | com.olhub |
Quy tắc resolve:
- Tìm config có name match exact logger.
- Nếu không, walk up package:
a.b.c.D→a.b.c→a.b→a→ root. - Lấy level của parent gần nhất có config.
Use case thực tế:
- Set
com.olhub: INFOdefault cho mọi class app. com.olhub.security: DEBUGđể log chi tiết security flow khi diagnose.com.olhub.security.AuthService: WARNoverride — class này log quá nhiều noise, tắt bớt.
Hierarchy cho phép fine-grained control — bật/tắt theo package level.
Q5Đoạn filter sau có bug. Tại sao? Cách fix?@Component
public class CorrelationIdFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-Id");
if (requestId == null) requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
chain.doFilter(req, res);
MDC.clear();
}
}
▸
@Component
public class CorrelationIdFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-Id");
if (requestId == null) requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
chain.doFilter(req, res);
MDC.clear();
}
}Bug: nếu chain.doFilter() throw exception, MDC.clear() không chạy → MDC leak sang request tiếp theo.
Hậu quả: trong môi trường thread pool (Tomcat reuse thread), thread T1 xử lý request A → throw → MDC không clear. Thread T1 tiếp tục xử lý request B → MDC còn requestId của A → log B có requestId của A → corrupt correlation ID.
Fix 1 — try-finally:
@Component
public class CorrelationIdFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-Id");
if (requestId == null) requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId);
try {
chain.doFilter(req, res);
} finally {
MDC.clear(); // luon clear, ke ca khi exception
}
}
}Fix 2 — try-with-resources với MDC.MDCCloseable:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-Id");
if (requestId == null) requestId = UUID.randomUUID().toString();
try (MDC.MDCCloseable mdc = MDC.putCloseable("requestId", requestId)) {
chain.doFilter(req, res);
}
}Cleaner. MDCCloseable.close() tự gọi MDC.remove(key) — clear chỉ key cụ thể, không clear toàn bộ MDC.
Quy tắc tổng: mọi lần MDC.put() phải có MDC.remove() hoặc MDC.clear() trong try-finally. Pattern lan ra mọi nơi setup ThreadLocal — luôn cleanup để tránh leak giữa request.
Bonus: Spring Boot có FilterRegistrationBean để register filter với order. Filter MDC nên ở thứ tự sớm nhất (highest precedence) để mọi filter sau có MDC.
Q6So sánh khi nào nên dùng Logback (default) vs Log4j2. Cho 1 metric cụ thể về performance + 1 lý do operational.▸
Logback default — chọn cho 90% case.
Khi nào nên đổi Log4j2:
- High-throughput app: vượt 50,000-100,000 log event/sec. Log4j2 async logger với LMAX Disruptor đạt ~10x throughput so với Logback async appender.
- Cần lazy evaluation parameter: Log4j2 có lambda API
log.debug("expensive: {}", () -> expensiveCall())— guarantee không call khi level disabled. - Cần script-based filter: Log4j2 hỗ trợ filter qua JavaScript/Groovy — Logback không có.
- Async without overhead: Log4j2 async mode default trong v3 — Logback async cần wrap appender.
Performance metric cụ thể:
- Logback async appender: ~100,000 event/sec với 1 thread.
- Log4j2 async logger (Disruptor): ~6,000,000 event/sec với 1 thread (TechEmpower benchmark).
- Khác biệt chỉ matter khi log volume thực sự cao. App thường (1,000-10,000 event/sec) Logback dư sức.
Lý do operational ngược lại — giữ Logback:
- Default Boot: không config — nhất quán với hầu hết Boot project. Onboarding dev mới dễ.
- Tài liệu nhiều: Logback có 15+ năm cộng đồng. Stack Overflow nhiều issue + giải pháp.
- Spring Boot tools tích hợp tốt: structured logging Boot 3.4 ECS support tốt cho Logback. Log4j2 cần plugin riêng.
- Security: Log4j2 có vụ Log4Shell (CVE-2021-44228) 2021. Logback không có vuln tương đương. Audit/compliance khắt khe có thể prefer Logback.
Quy tắc thực tế:
- App standard CRUD/REST API → Logback.
- App ingest log/metric pipeline (vượt 100k event/sec) → đo benchmark, có thể Log4j2.
- Migrate từ Log4j 1.x legacy code → Log4j2 ngắn cú pháp hơn.
90% project mới năm 2026 dùng Logback default — không có lý do compelling đổi.
Bài tiếp theo: Mini-challenge — trace 1 request từ main() qua từng layer + log mọi bean tạo
Bài này có giúp bạn hiểu bản chất không?
Bình luận (0)
Đang tải...