Chào mừng bạn đến với Hướng dẫn cấu hình Log4j2. Nếu bạn hỏi một dev có kinh nghiệm rằng điều gì gây khó chịu nhất khi làm việc với một ứng dụng, có lẽ câu trả lời sẽ liên quan đến việc ghi log. Nếu ứng dụng không có hệ thống log phù hợp, việc bảo trì sẽ trở thành ác mộng.
Phần lớn các ứng dụng đều trải qua các giai đoạn như kiểm thử trong quá trình phát triển (development testing), kiểm thử đơn vị (unit testing), kiểm thử tích hợp (integration testing). Nhưng khi vận hành thực tế (production), bạn sẽ luôn gặp những tình huống và ngoại lệ không lường trước. Cách duy nhất để tìm ra nguyên nhân trong từng trường hợp cụ thể là phân tích log.
Nhiều framework có cung cấp cơ chế ghi log mặc định, nhưng tốt nhất vẫn là sử dụng những công cụ ghi log theo tiêu chuẩn của ngành. Log4j là một trong những framework ghi log phổ biến nhất. Log4j 2 là phiên bản kế nhiệm, với hiệu năng và tính năng vượt trội so với Log4j cũ.
Nội dung chính của hướng dẫn Log4j2
Trong bài hướng dẫn này, bạn sẽ học cách bắt đầu với Apache Log4j2. Chúng ta cũng sẽ khám phá các khái niệm như kiến trúc Log4j2, cấu hình, các cấp độ log, appender, filter và nhiều nội dung khác.
- Tổng quan về Log4j2
- Kiến trúc Log4j2
- Cấu hình Log4j2
- Các cấp độ trong Log4j2
- Log4j2 Lookups
- Appender trong Log4j2
- Log4j2 Filters
- Log4j2 Layouts
- Nên sử dụng cấp độ Log4j2 nào?
- Tóm tắt hướng dẫn Log4j2
Tổng quan về Log4j2
Việc sử dụng API ghi log trong ứng dụng không phải là điều xa xỉ, mà là điều bắt buộc. Log4j là một thư viện mã nguồn mở được phát hành và cấp phép bởi Apache Software Foundation.
Bạn có thể debug một ứng dụng bằng Eclipse Debugger hoặc các công cụ khác, nhưng điều đó không đủ và không khả thi trong môi trường vận hành thực tế. Cơ chế ghi log mang lại nhiều lợi ích mà việc debug thông thường không thể đáp ứng.
So sánh: Gỡ lỗi (Debugging) vs Ghi log (Logging)
| Tiêu chí / Hoạt động | Debugging | Logging |
|---|---|---|
| Cần can thiệp của con người | Có. Cần thao tác thủ công. | Không. Hệ thống ghi lại tự động. |
| Tích hợp với bộ nhớ lưu trữ | Không. Không thể tích hợp với bộ nhớ bền vững. | Có thể. Lưu vào file, cơ sở dữ liệu, NoSQL, v.v. |
| Dùng cho mục đích kiểm toán | Không khả thi. | Có thể dùng nếu được cấu hình hợp lý. |
| Phù hợp với luồng xử lý phức tạp | Không đủ, dễ bị lạc trong luồng logic. | Đủ khả năng theo dõi chi tiết mọi bước xử lý. |
| Năng suất | Thấp | Cao, dễ theo dõi, hỗ trợ bảo trì. |
Như bạn thấy ở bảng trên, sử dụng cơ chế ghi log sẽ hiệu quả hơn nhiều, đồng thời giảm chi phí bảo trì.
Apache Log4j là công cụ đi đầu trong việc ghi log cho các ứng dụng Java. Và bạn nên ưu tiên sử dụng framework này trong các dự án của mình.
Kiến trúc Log4j2
Trước khi đi vào phần ví dụ cụ thể về Log4j2, chúng ta hãy cùng tìm hiểu kiến trúc của Log4j2. Hình minh họa (không có ở đây) cho thấy các lớp quan trọng trong API của Log4j2.

Dưới đây là phần giải thích chi tiết về kiến trúc đó:
- Ứng dụng sẽ yêu cầu một Logger từ
LogManagervới một tên cụ thể. LogManagersẽ xác định LoggerContext phù hợp, sau đó lấy hoặc tạo Logger từ LoggerContext đó.- Nếu Logger chưa tồn tại, nó sẽ được tạo mới và gắn với một
LoggerConfig, dựa trên ba lựa chọn sau:
- Logger sẽ được gắn với
LoggerConfigcó tên trùng khớp.
Ví dụ: getLogger(App.class) sẽ tạo ra Logger với tên là "com.journaldev.App". Tên LoggerConfig sẽ là tên đầy đủ của lớp (Fully Qualified Class Name).
- Logger sẽ được gắn với
LoggerConfigcó package cha tương ứng.
Ví dụ: getLogger("com.journaldev") sẽ được gắn với LoggerConfig tên "com.journaldev" nếu nó tồn tại.
- Logger sẽ được gắn với Root
LoggerConfignếu: Không có file cấu hình hoặc không có logger nào được định nghĩa khớp với tên đã cung cấp.
- Các đối tượng
LoggerConfigđược tạo ra từ phần khai báo logger trong file cấu hình. LoggerConfigchịu trách nhiệm xử lý cácLogEventsvà ủy quyền việc xuất log cho cácAppenderđã định nghĩa Log4j2 Appenders.- Root logger là trường hợp ngoại lệ vì nó luôn tồn tại, nằm ở đỉnh của hệ thống logger.
- Bạn có thể lấy root logger bằng một trong hai cách sau:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
Logger logger = LogManager.getRootLogger();
- Lưu ý: Tên logger trong Log4j2 có phân biệt chữ hoa – chữ thường.
- Ngoại trừ root loger, chúng ta có thể lấy tất cả các logger khác thông qua việc truyền tên của chúng vào phương thức
LogManager.getLogger(). LoggerContextlà trung tâm của hệ thống ghi log, vì một ứng dụng có thể có nhiều LoggerContext khác nhau. MỗiLoggerContextcần phải được gán một cấu hình đang hoạt động.- Cấu hình Log4j2 chứa tất cả các thành phần của hệ thống ghi log, bao gồm:
LoggerConfig,Appender,Filtervà các thành phần khác. - Việc gọi
LogManager.getLogger()với cùng một tên logger sẽ luôn trả về cùng một thể hiện logger đã được tạo trước đó, đảm bảo việc sử dụng tài nguyên hiệu quả và thống nhất. - Cấu hình hệ thống ghi log thường được thực hiện ngay khi ứng dụng khởi động. Việc cấu hình có thể được thực hiện: bằng mã lệnh (programmatically), hoặc thông qua file cấu hình.
Mỗi logger đều được gắn với một đối tượng LoggerConfig. Tập hợp các LoggerConfig tạo nên một cấu trúc phân cấp (Hierarchy) của các logger – còn gọi là Logger Hierarchy. Cấu trúc này có mối quan hệ cha-con, trong đó thành phần cao nhất luôn là Root Logger.
Nếu Log4j2 không tìm thấy file cấu hình, nó sẽ chỉ sử dụng Root Logger để ghi log, với mức log mặc định là ERROR. Trong trường hợp này, bạn sẽ nhận được cảnh báo như hình minh họa sau: Error StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

Bảng sau minh họa mối quan hệ cha – con trong Cấu trúc Phân cấp Logger (Logger Hierarchy):
| LoggerConfig (là) | Root | com | com.journaldev | com.journaldev.logging |
|---|---|---|---|---|
| Root | X | Con | hậu duệ | hậu duệ |
| com | Cha | X | Con | hậu duệ |
| com.journaldev | Tổ tiên | Cha | X | Con |
| com.journaldev.logging | Tổ tiên | Tổ tiên | Cha | X |
Giải thích quan hệ Cha – Con theo bảng trên:
Rootlà cha củacom.Rootlà tổ tiên (ancestor) củacom.journaldev.Rootcũng là tổ tiên củacom.journaldev.logging.comlà con củaRoot.comlà cha củacom.journaldev.comlà tổ tiên củacom.journaldev.logging.com.journaldev.logginglà con củacom.journaldev, và tương tự cho các mức khác.
Một thể hiện của LoggerConfig được gọi là tổ tiên của một LoggerConfig khác nếu tên của nó, kèm theo dấu chấm, là tiền tố của tên LoggerConfig hậu duệ.
Một LoggerConfig được gọi là cha của một LoggerConfig khác nếu không có tên xen giữa nào tồn tại giữa chúng trong cấu trúc phân cấp.
Cấu hình Log4j2
Có nhiều cách để cấu hình Log4j2 trong ứng dụng của bạn:
- Sử dụng tệp cấu hình được viết bằng XML, JSON, YAML hoặc file
.properties. - Cấu hình bằng mã lập trình, thông qua việc tạo một configuration factory và configuration implementation.
- Cấu hình bằng API, thông qua các phương thức được cung cấp bởi giao diện cấu hình.
- Cấu hình nội bộ, thông qua việc gọi các phương thức trực tiếp trên lớp
Loggernội bộ.
Trong hướng dẫn này, chúng ta sẽ chủ yếu tập trung vào cách cấu hình bằng tệp. Tuy nhiên, việc biết được cách cấu hình bằng mã cũng rất hữu ích, đặc biệt khi bạn cần áp dụng một chiến lược logging cụ thể cho một logger nhất định.
Trước tiên, chúng ta sẽ xem xét trường hợp không cung cấp tệp cấu hình. Khi đó, Log4j2 sẽ giả định rằng có một biến hệ thống tên là log4j.configurationFile trỏ đến vị trí của tệp cấu hình.
package com.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class App
{
public static void main( String[] args ) {
Logger logger = LogManager.getRootLogger();
logger.trace("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile"));
}
}
Ví dụ đơn giản về file cấu hình Log4j2 (configuration.xml):
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Giải thích đoạn mã:
- Ứng dụng tham chiếu đến
Root loggerbằng cách gọi phương thứcLogManager.getRootLogger(). - Khi logger được tham chiếu, hệ thống Log4j bắt đầu khởi động.
- Log4j sẽ kiểm tra biến hệ thống
log4j.configurationFileđể xác định tệp cấu hình cần sử dụng. - Tệp cấu hình Log4j có thể được viết bằng định dạng XML, JSON, hoặc YAML.
- Bạn có thể thiết lập biến hệ thống
log4j.configurationFilethông qua gọi lệnh trong Java:System.setPropertiess("log4j.configurationFile","FILE_PATH")hoặc thông qua JVM parameter khi chạy chương trình.Lưu ý: tiền tố
file:cần có khi chỉ định đường dẫn tuyệt đối đến file cấu hình.
Trường hợp không định nghĩa biến hệ thống nào, thứ tự ưu tiên tìm kiếm tệp cấu hình sẽ như sau:
- Property ConfigurationFactory sẽ tìm
log4j2-test.propertiestrong classpath. - YAML ConfigurationFactory sẽ tìm
log4j2-test.yamlhoặclog4j2-test.ymltrong classpath. - JSON ConfigurationFactory sẽ tìm
log4j2-test.jsnhoặclog4j2-test.jsontrong classpath. - XML ConfigurationFactory sẽ tìm
log4j2-test.xmltrong classpath. - Property ConfigurationFactory sẽ tìm
log4j2.propertiestrong classpath. - YAML ConfigurationFactory sẽ tìm
log4j2.yamlhoặclog4j2.ymltrong classpath. - JSON ConfigurationFactory sẽ tìm
log4j2.jsonhoặclog4j2.jsntrong classpath. - XML ConfigurationFactory sẽ tìm
log4j2.xmltrong classpath.
Nếu không tìm thấy bất kỳ tệp cấu hình nào, cấu hình mặc định (DefaultConfiguration) sẽ được áp dụng và dẫn đến một số hành vi mặc định sau:
- Root Logger sẽ được sử dụng.
- Mức log của Root Logger sẽ được thiết lập là
ERROR. - Root Logger sẽ gửi thông điệp log ra console.
PatternLayoutmặc định sẽ là:%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
Sử dụng tệp cấu hình log4j2 giúp đơn giản hóa quá trình cấu hình. Tuy nhiên, ta cũng có thể cấu hình bằng lập trình. Dưới đây là ví dụ cấu hình thông qua ConfigurationFactory:
package com.journaldev;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
public class App
{
public static void main( String[] args ) throws FileNotFoundException, IOException {
// Get instance of configuration factory; your options are default ConfigurationFactory, XMLConfigurationFactory,
// YamlConfigurationFactory & JsonConfigurationFactory
ConfigurationFactory factory = XmlConfigurationFactory.getInstance();
// Locate the source of this configuration, this located file is dummy file contains just an empty configuration Tag
ConfigurationSource configurationSource = new ConfigurationSource(new FileInputStream(new File("C:/dummyConfiguration.xml")));
// Get a reference from configuration
Configuration configuration = factory.getConfiguration(configurationSource);
// Create default console appender
ConsoleAppender appender = ConsoleAppender.createDefaultAppenderForLayout(PatternLayout.createDefaultLayout());
// Add console appender into configuration
configuration.addAppender(appender);
// Create loggerConfig
LoggerConfig loggerConfig = new LoggerConfig("com",Level.FATAL,false);
// Add appender
loggerConfig.addAppender(appender,null,null);
// Add logger and associate it with loggerConfig instance
configuration.addLogger("com", loggerConfig);
// Get context instance
LoggerContext context = new LoggerContext("JournalDevLoggerContext");
// Start logging system
context.start(configuration);
// Get a reference for logger
Logger logger = context.getLogger("com");
// LogEvent of DEBUG message
logger.log(Level.FATAL, "Logger Name :: "+logger.getName()+" :: Passed Message ::");
// LogEvent of Error message for Logger configured as FATAL
logger.log(Level.ERROR, "Logger Name :: "+logger.getName()+" :: Not Passed Message ::");
// LogEvent of ERROR message that would be handled by Root
logger.getParent().log(Level.ERROR, "Root Logger :: Passed Message As Root Is Configured For ERROR Level messages");
}
}
Giải thích đoạn mã:
- Bạn có thể sử dụng bất kỳ ConfigurationFactory nào được cung cấp bởi Log4j2, hoặc dùng factory mặc định. Ở đây ta dùng
XmlConfigurationFactory. Factorysẽ cung cấp một thể hiện củaConfiguration, dựa trên tệp cấu hình được truyền vào.- Thể hiện
Configurationsẽ được sử dụng cùng vớiLoggerContextđể khởi chạy hệ thống logging. - Một
ConsoleAppendermặc định đã được thêm vào cấu hình, với layout mặc định để in log ra console. LoggerConfigđược tạo ra với tên “com”, mứcFATAL, không filter. Appender đã tạo được gán cho LoggerConfig này.- LoggerConfig được thêm vào configuration.
- Tạo một
LoggerContextmới với tên tùy chọn. - Gán cấu hình cho
LoggerContextvà khởi động context. - Từ
LoggerContext, lấy ra logger với tên “com”. - Logger bắn ra ba sự kiện:
FATAL: được ghi lại (được phép theo cấu hình).ERROR: không được ghi lại bởi logger “com” vì mức FATAL không chấp nhận ERROR.- Một log ERROR khác được ghi lại bởi Root logger (vì Root mặc định nhận mức ERROR).
Cấu hình tương tự cũng có thể được thực hiện thông qua YAML, JSON hoặc tệp properties.
Tuy nhiên, cấu hình tệp properties trong Log4j2 khác với Log4j phiên bản cũ, vì vậy hãy đảm bảo rằng bạn không dùng tệp cấu hình của Log4j (1.x) cho Log4j2, nếu không bạn sẽ gặp lỗi sau:
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.Khi chạy đoạn mã trên, bạn sẽ nhận được kết quả sau:
Logger Name :: com :: Passed Message :: 00:01:27.705 [main] ERROR - Root Logger:: Passed Message As Root Is Configured For ERROR Level messages- Dòng đầu tiên là log từ logger có tên
com, được ghi ở mứcFATAL. - Dòng thứ hai là log từ Root Logger, ghi ở mức
ERROR.
Lưu ý rằng thông điệp
ERRORtừ loggercomkhông được in ra, vì logger này chỉ được cấu hình để ghi các thông điệp ở mức FATALCác cấp độ trong Log4j2
Như bạn thấy trong các ví dụ mã trước, mỗi lần định nghĩa một
LoggerConfig, bạn đều phải chỉ địnhlogging level(mức ghi log).Mặc định, Log4j2 có cơ chế “additive”, tức là các logger cấp cha sẽ cùng được sử dụng khi một logger con được kích hoạt.
Hình ảnh minh họa sẽ giúp bạn hình dung rõ hơn:

Giải thích các điểm chính
- Như đã đề cập trước đó, mỗi logger được liên kết với một thể hiện của
LoggerConfig. Cấu hình củaLoggerConfigđược định nghĩa trong tệp cấu hình của Log4j2. - Mức độ ghi log (
Level) được xác định ở cấp độ củaLoggerConfig. - Bạn có thể tạo logger theo:
- Tên đầy đủ của logger (ví dụ:
com.journaldev.logging), - Gói cha của nó (ví dụ:
com.journaldev,com), - Hoặc sử dụng trực tiếp Root Logger (là nút cao nhất trong cây LoggerConfig).
- Tên đầy đủ của logger (ví dụ:
- Khi bạn lấy logger
com.journaldevvà phát mộtlogEvent, thì:- LoggerConfig tương ứng với
com.journaldevsẽ ghi log. - Log event này sẽ tiếp tục được truyền lên logger cha (
com) vàRoot Logger, bỏ qua mức độ log của các logger cha. - Nghĩa là: nếu
Root Loggerđược thiết lập ghi log ở mức thấp hơn, nó vẫn sẽ ghi lại thông điệp.
- LoggerConfig tương ứng với
- Tương tự, nếu bạn sử dụng logger
com, thông điệp log:- Sẽ được ghi bởi
LoggerConfigcủacom. - Sau đó được truyền lên Root Logger để ghi lại tiếp.
- Sẽ được ghi bởi
- Tất cả logger con (ví dụ
com.journaldev.logging) đều hoạt động theo cơ chế additive nếu không được cấu hình ngược lại. - Bạn có thể ngăn chặn việc truyền log lên Logger cha bằng cách:
- Thiết lập thuộc tính
additivity="false"trong LoggerConfig. - Hoặc sử dụng
Filterđể chặn log theo điều kiện.
- Nếu mức
levelcủaLoggerConfigcao hơn mức log của sự kiện, logger sẽ bỏ qua log event đó.
Bây giờ, chúng ta sẽ cùng xem ví dụ minh họa về cơ chế additivity:
import net.NetApp;
import net.journaldev.NetJournalDevApp;
import com.ComApp;
import com.journaldev.ComJournalDevApp;
public class Main {
public static void main(String [] args){
new ComApp();
new ComJournalDevApp();
new NetApp();
new NetJournalDevApp();
}
}
package com.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ComJournalDevApp {
public ComJournalDevApp(){
Logger logger = LogManager.getLogger(ComJournalDevApp.class);
logger.trace("COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::");
}
}
package net;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NetApp {
public NetApp(){
Logger logger = LogManager.getLogger(NetApp.class);
logger.error("NET :: LEVEL :: NetApp ERROR Message ::");
}
}
package net.journaldev;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class NetJournalDevApp {
public NetJournalDevApp(){
Logger logger = LogManager.getLogger(NetJournalDevApp.class);
logger.error("NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::");
}
}
Tệp cấu hình log4j2 trông như sau:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Kết quả xuất ra khi chạy chương trình:
10:34:47.168 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
10:34:47.168 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.170 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
10:34:47.171 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
10:34:47.171 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
10:34:47.171 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Giải thích đoạn mã:
- Tệp cấu hình
log4j2.xmlđịnh nghĩa 5 thực thểLoggerConfig:Root,com,com.journaldev,net, vànet.journaldev. - Mức log (
level) được cấu hình như sau:Root:ERROR(mặc định)com,com.journaldev:TRACEnet,net.journaldev:ERROR
- Logger của các class
ComAppvàComJournalDevAppin ra log nhiều lần (2–3 lần) vì:- Log được ghi bởi logger chính (ví dụ:
com.journaldev) - Sau đó được truyền lên logger cha (
com) vàRoot), do thuộc tínhadditivity=truetheo mặc định.
- Log được ghi bởi logger chính (ví dụ:
- Tương tự, các logger trong package
netcũng ghi lại log nhiều lần vì lý do tương tự.
Cấp độ log và Logger Hierarchy cùng phối hợp xác định log nào được ghi.
Nếu bạn thay đổi mức log của LoggerConfig dành cho package com từ TRACE thành INFO mà vẫn giữ nguyên toàn bộ chương trình, thì bạn sẽ nhận được kết quả như sau:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="INFO">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Kết quả khi chạy chương trình:
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.305 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
11:08:10.307 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
11:08:10.307 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
11:08:10.308 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Chắc chắn bạn sẽ nhận thấy rằng log event của ComApp đã bị bỏ qua, và lý do là bởi mức log (level) được khai báo trong LoggerConfig của package com là INFO (400), thấp hơn so với mức của log event là TRACE (600). Do đó, thông điệp log từ ComApp sẽ không được hiển thị nữa.
Để hiển thị được log này, bạn cần chỉnh sửa mức LoggerConfig của com thành TRACE (600) hoặc ALL (Integer.MAX_VALUE).
Để một log event được hiển thị, LoggerConfig.level phải lớn hơn hoặc bằng log event.level.
Bảng dưới đây liệt kê các mức log của Log4j2 và trọng số tương ứng:
Nhưng điều gì sẽ xảy ra nếu chúng ta loại bỏ LoggerConfig của com.journaldev khỏi cấu hình và thay vào đó thêm một LoggerConfig mới cho com.journaldev.logging, khiến file cấu hình trông như sau:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Bạn có thể thấy hình minh họa dưới đây sẽ giúp bạn dễ hiểu hơn điều gì đã xảy ra trong cấu hình Log4j2 ở trên.

Một số điểm làm rõ cho sơ đồ phía trên và cách nó ảnh hưởng đến hành vi của các sự kiện ghi log:
- Khi một sự kiện log được phát ra bởi một Logger có tên là
com.journaldev.logging, LoggerConfig tương ứng với tên đó (tức làcom.journaldev.logging) sẽ được dùng để xử lý và in ra thông báo. - Vì thuộc tính
additivecủa LoggerConfigcom.journaldev.loggingđược đặt mặc định làtrue, sự kiện log sẽ được truyền lên Logger cha – trong trường hợp này làcom.journaldev. - Tuy nhiên, vì LoggerConfig
com.journaldevkhông được định nghĩa trong file cấu hình, nên sẽ không có hành động nào xảy ra tại đây, và sự kiện log sẽ tiếp tục được truyền lêncom, rồi tiếp tục lênRoot. comvàRootsẽ nhận sự kiện log và in ra thông báo, bất kể cấp độ (level) của sự kiện đó là gì.
Kết quả đầu ra sẽ như sau:
14:08:37.634 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:08:37.634 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:08:37.636 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:08:37.636 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.637 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:08:37.638 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:08:37.638 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:08:37.640 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Những điểm cần lưu ý:
- Sự kiện log tại package
comxuất hiện 2 lần: một lần từ LoggerConfigcom, và một lần từRoot. - Sự kiện log tại
com.journaldevcũng xuất hiện 2 lần: một lần từcomvà một lần từRoot. Trước đây từng xuất hiện 3 lần, nhưng giờ LoggerConfig củacom.journaldevđã bị loại bỏ, nên không còn xử lý log tại mức này nữa — sự kiện được truyền tiếp lêncomvàRoot. - Sự kiện log tại
com.journaldev.loggingxuất hiện 3 lần: một lần từ LoggerConfigcom.journaldev.logging, một lần từcom, và một lần từRoot. Theo nguyên tắc Logger Hierarchy, đáng lý nó sẽ xuất hiện 4 lần, nhưng do thiếu LoggerConfigcom.journaldev, nên chỉ còn 3.
Trong trường hợp bạn đã định nghĩa một LoggerConfig cho com.journaldev mà không chỉ định cấp độ (Level), thì nó sẽ kế thừa cấp độ từ Logger cha của nó.
Điều gì sẽ xảy ra nếu bạn định nghĩa LoggerConfig cho com.journaldev trong file cấu hình nhưng không chỉ định level?
May mắn thay, khái niệm Logger Hierarchy sẽ giúp bạn. Trong trường hợp này, com.journaldev sẽ kế thừa cấp độ log từ LoggerConfig cha của nó. Dưới đây là một ví dụ về file cấu hình cùng bảng chỉ ra level tương ứng cho từng Logger:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
| Tên Logger | LoggerConfig được gán | Mức LoggerConfig | Mức Logger |
|---|---|---|---|
| Root | Root | ERROR | ERROR |
| com | com | TRACE | TRACE |
| com.journaldev | com | TRACE | TRACE |
| com.journaldev.logging | com.journaldev.logging | TRACE | TRACE |
- Gói
com.journaldev.loggingđã được gán với một LoggerConfig có mức log là TRACE. - Gói
com.journaldevđã được gán với một LoggerConfig nhưng không chỉ định mức log, nên nó sẽ kế thừa mức log từ LoggerConfig cha, và trong trường hợp này giá trị đó là TRACE từ góicom. - Gói
comđã được gán với một LoggerConfig có mức log là TRACE. - Mặc định, LoggerConfig gốc (Root) có mức log là ERROR.
- Trong trường hợp không khai báo gói
com, LoggerConfig củacom.journaldevsẽ kế thừa mức log từ Root.
Dưới đây là kết quả khi thực thi chương trình trong trường hợp com.journaldev kế thừa mức log từ com:
14:41:37.419 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:41:37.419 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.421 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.422 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:41:37.423 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:41:37.423 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:41:37.423 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Và đây là kết quả nếu bạn xóa khai báo LoggerConfig cho gói com:
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.809 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
14:43:28.811 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:43:28.811 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
14:43:28.812 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Bạn có thể nhận thấy rằng không có thông điệp nào được log cho gói com và com.journaldev. Dưới đây là lý do:
- Việc xóa LoggerConfig liên kết với gói
comkhiến mọi sự kiện log ở gói này bị bỏ qua. - Vì LoggerConfig cho
comkhông được định nghĩa, LoggerConfig củacom.journaldevsẽ cố gắng kế thừa mức log từ LoggerConfig cha. Nhưng vìcomkhông có, Logger Hierarchy sẽ đi lên đến Logger gốc (Root). Root có mức log là ERROR (200), trong khi sự kiện log ởcom.journaldevcó mức TRACE (600) — như trong lớpComJournalDevApp. Theo công thức đã đề cập, LoggerConfig chỉ xử lý sự kiện log khi mức LoggerConfig lớn hơn hoặc bằng mức sự kiện log. Trường hợp này không đúng, do đó thông điệp log không được hiển thị.
Cuối cùng nhưng không kém phần quan trọng, bảng dưới đây cho thấy tất cả các trường hợp log có thể xảy ra khi sử dụng hệ thống Logging:
| LoggerConfig Level | OFF(0) | FATAL(100) | ERROR(200) | WARN(300) | INFO(400) | DEBUG(500) | TRACE(600) | ALL(MAX) |
|---|---|---|---|---|---|---|---|---|
| Event Level | ||||||||
| OFF(0) | YES | NO | NO | NO | NO | NO | NO | NO |
| FATAL(100) | NO | YES | YES | YES | YES | YES | YES | YES |
| ERROR(200) | NO | NO | YES | YES | YES | YES | YES | YES |
| WARN(300) | NO | NO | NO | YES | YES | YES | YES | YES |
| INFO(400) | NO | NO | NO | NO | YES | YES | YES | YES |
| DEBUG(500) | NO | NO | NO | NO | NO | YES | YES | YES |
| TRACE(600) | NO | NO | NO | NO | NO | NO | YES | YES |
| ALL(MAX) | NO | NO | NO | NO | NO | NO | NO | YES |
Không có phương thức trực tiếp nào để ghi log ở mức OFF/ALL. Thông thường, để ghi log ở hai mức này, bạn có thể sử dụng logger.log(Level.OFF, “Msg”) or logger.log(LEVEL.ALL,“Msg”).
Phương thức log() sẽ chịu trách nhiệm xử lý sự kiện log dựa trên công thức đã nêu trước đó:
Nếu mức LoggerConfig lớn hơn hoặc bằng mức của sự kiện log, thì sự kiện đó sẽ được chấp nhận để xử lý tiếp theo.
Việc sự kiện log được chấp nhận để xử lý tiếp theo là rất quan trọng, vì bạn vẫn có thể ngăn sự kiện đó được xử lý, ngay cả khi nó đã được chấp nhận, bằng cách sử dụng Log4j2 Filters.
Bạn cũng có thể thiết lập thuộc tính additivity="false" để ngăn việc lan truyền sự kiện log đến các Logger cha. Dưới đây là ví dụ giống như trước, nhưng lần này có thêm thuộc tính additivity, để bạn có thể thấy được sự khác biệt.
<?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{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="com.journaldev.logging" level="TRACE" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="net" level="ERROR" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<logger name="net.journaldev" level="ERROR" additivity="false">
<AppenderRef ref="Console"/>
</logger>
</Loggers>
</Configuration>
Và đây là kết quả sau khi thực thi:
17:55:30.558 [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
17:55:30.560 [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
17:55:30.561 [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
17:55:30.561 [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
17:55:30.562 [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Bạn có thể thấy rằng không có sự lan truyền sự kiện log lên các Logger cha do thuộc tính additivity="false" đã được đặt.
Log4j2 Lookups
Về lý tưởng, bạn có thể định nghĩa Lookups như là cách để truyền giá trị vào file cấu hình Log4j2. Log4j2 cung cấp một tập hợp các loại Lookups mà bạn có thể sử dụng độc lập để lấy giá trị từ các ngữ cảnh khác nhau:
- Context Map Lookup
- Date Lookup
- Environment Lookup
- Java Lookup
- JNDI Lookup
- JVM Input Argument Lookup (JMX)
- Main Arguments Lookup
- Map Lookup
- Structured Data Lookup
- System Properties Lookup
- Web Lookup
Bạn có thể tham khảo tài liệu chính thức của Log4j2 để hiểu rõ hơn từng loại Lookup, nhưng hãy cùng xem một ví dụ đơn giản dưới đây để hiểu cơ bản về Lookup trong Log4j2.
Ví dụ về Environment Lookup là cách bạn truyền một biến môi trường (qua Linux /etc/profile, biến môi trường hệ thống Windows, hoặc script khởi động ứng dụng). Hầu hết chúng ta đều biết rằng có thể định nghĩa các biến môi trường để ứng dụng sử dụng.
- Thiết lập biến môi trường bằng tiện ích môi trường của Windows:
- Nhấp chuột phải vào biểu tượng “This PC” (hoặc “Computer”) và chọn “Properties”.
Bảng điều khiển hệ thống (Control Panel Home) sẽ được hiển thị.
- Nhấn vào “Advanced system settings” (Cài đặt hệ thống nâng cao), sau đó mở cửa sổ Environment Variables (Biến môi trường).
- Trong phần System Variables (Biến hệ thống), tạo một biến mới có tên là
JournalDevVarvà gán giá trị làJournalDev. - Cập nhật
PatternLayouttrong tập tinlog4j2.xmlcủa bạn để sử dụng biến môi trường vừa tạo.

- Thiết lập biến môi trường bằng cách sử dụng script khởi động (Startup script):
- Thay vì sử dụg các script mặc định thông thường, bạn có thể sử dụng tiện ích chạy script của Eclipse IDE. Vào menu Run, chọn Run Configurations…
- Điều hướng đến tab Environment. Tại đây, thêm biến môi trường mới.

Hãy cùng xem tệp log4j2.xml đã được chỉnh sửa và cách sử dụng biến môi trường trong đó:
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} $${env:JournalDevVar} $${env:JournalDevSecondVar} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
Kết quả khi chạy chương trình sẽ trông như sau:
23:57:02.511 JournalDev www.journaldev.com [main] TRACE com.ComApp - COM :: LEVEL :: ComApp TRACE Message ::
23:57:02.517 JournalDev www.journaldev.com [main] TRACE com.journaldev.ComJournalDevApp - COM :: JournalDev :: LEVEL :: ComJournalDevApp TRACE Message ::
23:57:02.520 JournalDev www.journaldev.com [main] TRACE com.journaldev.logging.ComJounralDevLoggingApp - COM :: JournalDev :: LOGGING :: LEVEL :: ComJounralDevLoggingApp TRACE Message ::
23:57:02.523 JournalDev www.journaldev.com [main] ERROR net.NetApp - NET :: LEVEL :: NetApp ERROR Message ::
23:57:02.527 JournalDev www.journaldev.com [main] ERROR net.journaldev.NetJournalDevApp - NET :: JournalDev :: LEVEL :: NetJournalDevApp ERROR Message ::
Tuy nhiên, bạn có thể gặp một vấn đề nhỏ. Đó là cơ chế cache biến môi trường trong Eclipse. Lý tưởng là bạn có thể kiểm tra các biến môi trường mà Eclipse đang sử dụng tại: Run – Run Configuration – Environment tab – Click Select button

Vì vậy, bạn có thể cảm thấy bối rối khi đã định nghĩa biến môi trường nhưng ứng dụng lại không nhận ra nó. Ngay cả khi bạn khởi động lại Eclipse, vấn đề vẫn chưa được giải quyết. Để khắc phục, bạn phải chạy Eclipse bằng lệnh eclipse.exe -clean tại thư mục cài đặt Eclipse.
Kiểm tra biến môi trường đã được định nghĩa đúng cách
Để đảm bảo rằng biến môi trường đã được định nghĩa đúng và hệ thống của bạn có thể tìm thấy nó, bạn có thể sử dụng plugin tương ứng mà Log4j2 API cung cấp.
Cụ thể, bạn có thể tạo một instance của EnvironmentLookup và yêu cầu nó tra cứu biến bạn đã định nghĩa. Nếu biến tồn tại, bạn sẽ thấy giá trị của nó được trả về dễ dàng:
EnvironmentLookup lookup = new EnvironmentLookup();
LogManager.getRootLogger().error(lookup.lookup("JournalDevSecondVar"));
Các Appender trong Log4j2
Ở phần trước, bạn đã thấy cách sử dụng Lookups để truyền biến môi trường vào tệp cấu hình. Tuy nhiên, đôi khi bạn không muốn ghi log ra console mà muốn chuyển hướng thông điệp sang file hoặc cơ sở dữ liệu để đảm bảo các bản ghi được lưu giữ lâu dài.
Log4j2 đã cung cấp rất nhiều Appender để ++hỗ trợ điều này. Bạn có thể tham khảo tài liệu chính thức của Log4j2 để biết thêm chi tiết, nhưng dưới đây là danh sách tóm tắt các loại Appender có sẵn:
- ConsoleAppender
- AsyncAppender
- FailoverAppender
- FileAppender
- FlumeAppender
- JDBCAppender
- JMSAppender
- JPAAppender
- MemoryMappedFileAppender
- NoSQLAppender
- OutputStreamAppender
- RandomAccessFileAppender
- RewriteAppender
- RollingFileAppender
- RollingRandomAccessFileAppender
- RoutingAppender
- SMTPAppender
- SocketAppender
- SyslogAppender
Trong số đó, những phương tiện ghi log phổ biến nhất là: Console, File, Database. Vì file thường lưu lại phần tin nhắn của bạn, database thường được dùng cho mục đích audit. Chính vì vậy, phần tiếp theo sẽ tập trung vào cách sử dụng JDBCAppender một cách hiệu quả để lưu log vào cơ sở dữ liệu.
JDBCAppender
Mục tiêu chính của JDBCAppender là ghi các sự kiện log vào bảng quan hệ (relational table) thông qua các kết nối JDBC. Bài viết này không đi sâu vào việc tối ưu hóa connection pool, vì đó không phải là mục tiêu chính. Tuy nhiên, bạn sẽ có một ví dụ hoàn chỉnh giúp ghi log vào cơ sở dữ liệu một cách thực tế.
Trước khi tiếp tục, hãy cùng xem các tham số cần thiết để cấu hình JDBCAppender cùng với mô tả của từng tham số:
| Tên Tham Số | Kiểu Dữ Liệu | Mô Tả |
|---|---|---|
Name |
String |
Bắt buộc. Tên của Appender. |
ignoreExceptions |
boolean |
Mặc định là true, nghĩa là các exception phát sinh sẽ được ghi log rồi bỏ qua. Nếu đặt là false, exception sẽ được truyền ngược về caller. |
filter |
Filter |
Bộ lọc được sử dụng để quyết định liệu sự kiện log có được xử lý bởi Appender này hay không. |
bufferSize |
int |
Mặc định là 0, tức không buffer sự kiện log. Nếu lớn hơn 0, Appender sẽ buffer các sự kiện log và flush chúng khi đạt đến giới hạn. |
connectionSource |
ConnectionSource |
Bắt buộc. Nguồn kết nối dùng để truy xuất kết nối cơ sở dữ liệu. |
tableName |
String |
Bắt buộc. Tên bảng nơi các sự kiện log sẽ được lưu. |
columnConfigs |
ColumnConfig[] |
Bắt buộc. Dùng để định nghĩa thông tin chi tiết cho từng cột và cách dữ liệu được lưu lên mỗi cột. Được cấu hình bằng nhiều phần tử <Column>. |
| Tên Tham Số | Kiểu Dữ Liệu | Mô Tả |
|---|---|---|
jndiName |
String |
Bắt buộc. Tên đầy đủ của JNDI mà javax.sql.DataSource được liên kết tới. |
| Tên Tham Số | Kiểu Dữ Liệu | Mô Tả |
|---|---|---|
class |
String |
Bắt buộc. Tên đầy đủ của class có chứa phương thức tĩnh để tạo kết nối JDBC. |
method |
String |
Bắt buộc. Tên của phương thức tĩnh đó. |
| Tên Tham Số | Kiểu Dữ Liệu | Mô Tả |
|---|---|---|
name |
String |
Bắt buộc. Tên của cột trong cơ sở dữ liệu. |
pattern |
String |
Cho phép định nghĩa bất kỳ pattern hợp lệ nào để định dạng sự kiện log. |
literal |
String |
Giá trị cố định (literal) cho cột, ví dụ: SEQ.NEXTVAL. |
isEventTimestamp |
boolean |
Chỉ định cột này có lưu timestamp của sự kiện log hay không. |
isUnicode |
boolean |
Dùng để lưu dữ liệu Unicode. Tham khảo thêm trong tài liệu Log4j2. |
isClob |
boolean |
Dùng để lưu Character Large Object (CLOB). Tham khảo thêm trong tài liệu Log4j2. |
Vì bạn bắt buộc phải sử dụng JNDI, ví dụ của chúng ta sẽ cấu hình nguồn dữ liệu kết nối (DataSource) cho cơ sở dữ liệu Oracle và Apache Tomcat 7.
- Nếu bạn chưa cài đặt Oracle Database trong môi trường làm việc, rất khuyến khích bạn cài đặt. Nếu bạn chưa quen với Oracle, mình đề xuất bạn cài đặt phiên bản Oracle Express Edition để sử dụng.
- Cài đặt Apache Tomcat 7 vào môi trường của bạn.
- Tạo một dự án Maven WebApp trong Eclipse.

- Hãy đảm bảo rằng dự án của bạn đã được tạo thành công, và nếu bạn phát hiện bất kỳ lỗi nào trong file
pom.xml, hãy chắc chắn rằng bạn đã sửa chúng đúng cách. - Thêm các dependency cho Log4j2:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.2</version>
</dependency>
- Cấu hình ngữ cảnh (context) của bạn để khai báo nguồn dữ liệu MySQL:
Theo tài liệu chính thức của Apache, file cấu hình này phải được đặt trong thư mục META-INF của ứng dụng web (Web Application).

<Context path="/JournalDevWebLogging"
privileged="true" antiResourceLocking="false" antiJARLocking="false">
<Resource name="jdbc/JournalDevDB" auth="Container"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/journaldev" />
</Context>
- Hãy cấu hình cơ sở dữ liệu của bạn và tạo bảng ghi log bằng câu lệnh sau:

CREATE TABLE `logging` (
`EVENT_ID` int(11) NOT NULL AUTO_INCREMENT,
`EVENT_DATE` datetime DEFAULT NULL,
`LEVEL` varchar(45) DEFAULT NULL,
`LOGGER` varchar(45) DEFAULT NULL,
`MSG` varchar(45) DEFAULT NULL,
`THROWABLE` varchar(45) DEFAULT NULL,
PRIMARY KEY (`EVENT_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
- Hãy cấu hình
log4j2.xmlcủa bạn để trông giống như:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{HH:mm:ss.SSS} $${env:JournalDevVar} $${env:JournalDevSecondVar} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<JDBC name="databaseAppender" tableName="journaldev.logging">
<DataSource jndiName="java:/comp/env/jdbc/JournalDevDB" />
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MSG" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console" />
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
</Loggers>
</Configuration>
Hãy tạo một tài nguyên Web (Servlet) giúp bạn lấy được đối tượng logger và ghi một sự kiện log:
package com.journaldev;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class JournalDevServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
Logger logger = LogManager.getLogger(JournalDevServlet.class);
logger.trace("JournalDev Database Logging Message !");
}
}
Bạn cũng có thể cấu hình một ServletContextListener để đảm bảo DataSource được khởi tạo đúng cách:
package com.journaldev;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.logging.log4j.LogManager;
public class JournalDevServletContextListener implements ServletContextListener{
private InitialContext context = null;
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
try {
// Get initial context
context = new InitialContext();
// Get a reference for sub context env
Context envContext = (Context)context.lookup("java:comp/env");
// Get a reference for sub context jdbc and then locating the data source defined
LogManager.getRootLogger().error(((Context)envContext.lookup("jdbc")).lookup("JournalDevDB"));
} catch (NamingException e) {
LogManager.getRootLogger().error(e);
}
}
}
- Sau đó, hãy khai báo Servlet của bạn trong tệp
web.xml. - Khi chạy ứng dụng và truy cập Servlet đã định nghĩa ở trên, bạn sẽ thấy các log như sau xuất hiện trong console hoặc file log (tùy cấu hình):
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\\Program Files\\Java\\jdk1.6.0_26\\bin;C:\\Windows\\Sun\\Java\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Program Files\\Java\\jdk1.6.0_26\\jre\\bin;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/bin/server;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/bin;C:/Program Files/Java/jdk1.6.0_26/bin/../jre/lib/amd64;D:\\OracleWebCenter\\OracleWC\\Oracle11g\\app\\oracle\\product\\11.2.0\\server\\bin;;C:\\Program Files\\Common Files\\Microsoft Shared\\Windows Live;C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\Windows Live;D:\\OracleDB\\app\\product\\11.2.0\\dbhome_1\\bin;org.C:\\Program Files (x86)\\Common Files\\NetSarang;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Intel\\OpenCL SDK\\2.0\\bin\\x86;C:\\Program Files (x86)\\Intel\\OpenCL SDK\\2.0\\bin\\x64;D:\\SpringRoo\\spring-roo-1.2.5.RELEASE\\bin;D:\\Ant\\apache-ant-1.9.2\\bin;C:\\Python27;C:\\Program Files\\Java\\jdk1.6.0_26\\bin;D:\\Maven\\apache-maven-3.2.1/bin;D:\\bower-master\\bin;C:\\Program Files (x86)\\Git\\cmd;C:\\Program Files\\nodejs\\;C:\\Program Files\\Microsoft Windows Performance Toolkit\\;D:\\Grails\\grails-2.4.0\\bin;D:\\Gradle\\gradle-2.0\\bin;C:\\Program Files (x86)\\Windows Live\\Shared;C:\\Program Files\\TortoiseSVN\\bin;D:\\Strawberry\\perl\\bin;D:\\Strawberry\\perl\\site\\bin;D:\\Strawberry\\c\\bin;C:\\Users\\mohammad.amr\\AppData\\Roaming\\npm;D:\\JournalDev\\eclipse;;.
Mar 15, 2015 2:31:41 PM org.apache.tomcat.util.digester.SetPropertiesRule begin
WARNING: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.j2ee.server:JournalDevWebLogging' did not find a matching property.
Mar 15, 2015 2:31:41 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
Mar 15, 2015 2:31:41 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["ajp-bio-8009"]
Mar 15, 2015 2:31:41 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 1020 ms
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service Catalina
Mar 15, 2015 2:31:41 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/7.0.35
14:31:43.847 [localhost-startStop-1] ERROR - org.apache.tomcat.jdbc.pool.DataSource@10fd0a62{ConnectionPool[defaultAutoCommit=null; defaultReadOnly=null; defaultTransactionIsolation=-1; defaultCatalog=null; driverClassName=com.mysql.jdbc.Driver; maxActive=100; maxIdle=30; minIdle=10; initialSize=10; maxWait=10000; testOnBorrow=false; testOnReturn=false; timeBetweenEvictionRunsMillis=5000; numTestsPerEvictionRun=0; minEvictableIdleTimeMillis=60000; testWhileIdle=false; testOnConnect=false; password=root; url=jdbc:mysql://localhost:3306/journaldev; username=root; validationQuery=null; validatorClassName=null; validationInterval=30000; accessToUnderlyingConnectionAllowed=true; removeAbandoned=false; removeAbandonedTimeout=60; logAbandoned=false; connectionProperties=null; initSQL=null; jdbcInterceptors=null; jmxEnabled=true; fairQueue=true; useEquals=true; abandonWhenPercentageFull=0; maxAge=0; useLock=false; dataSource=null; dataSourceJNDI=null; suspectTimeout=0; alternateUsernameAllowed=false; commitOnReturn=false; rollbackOnReturn=false; useDisposableConnectionFacade=true; logValidationErrors=false; propagateInterruptState=false; }
Mar 15, 2015 2:31:43 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Mar 15, 2015 2:31:43 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Mar 15, 2015 2:31:43 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 1909 ms

Bộ lọc (Filter) trong Log4j2
Ngay cả khi đã có một LoggerConfig được xác định để xử lý một Log event, bạn vẫn có thể cấu hình để ngăn không cho Log event đó được chuyển tiếp đến các Appender phía sau. Việc này có thể thực hiện bằng cách sử dụng Filter của Log4j2.
Phần này không nhằm mục đích cung cấp một tài liệu chuyên sâu, toàn diện hoặc đào sâu vào từng loại bộ lọc có trong Log4j2 – vì mỗi bộ lọc có thể cần một bài hướng dẫn riêng biệt. Tuy nhiên, tại đây bạn sẽ được thấy cách sử dụng một bộ lọc đơn giản nhất để hiểu rõ khái niệm về Filter là gì và cách hoạt động của nó.
Một trong những bộ lọc đơn giản nhất bạn có thể sử dụng là BurstFilter – bộ lọc này cung cấp cơ chế kiểm soát tốc độ xử lý các LogEvent bằng cách âm thầm loại bỏ các log vượt quá giới hạn định sẵn.
Dưới đây là các tham số cần thiết để cấu hình BurstFilter:
| Tên tham số | Kiểu dữ liệu | Mô tả |
|---|---|---|
level |
String |
Mức log cần lọc (Level của thông điệp log). |
rate |
float |
Số lượng log tối đa trung bình được phép xử lý mỗi giây. |
maxBurst |
integer |
Tổng số log tối đa có thể xảy ra trước khi các log vượt ngưỡng bị loại bỏ. Mặc định là 10 lần giá trị rate. |
onMatch |
String |
Hành động sẽ thực hiện khi log khớp với bộ lọc. Giá trị có thể là ACCEPT, DENY, hoặc NEUTRAL. Mặc định là NEUTRAL. |
onMismatch |
String |
Hành động sẽ thực hiện khi log không khớp với bộ lọc. Giá trị tương tự như onMatch, mặc định là NEUTRAL. |
Ví dụ: Đặt BurstFilter trong Appender ghi log vào cơ sở dữ liệu
<?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{36} - %msg%n" />
</Console>
<JDBC name="databaseAppender" tableName="journaldev.logging">
<DataSource jndiName="java:/comp/env/jdbc/JournalDevDB" />
<BurstFilter level="TRACE" rate="20" maxBurst="2"/>
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MSG" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console" />
</Root>
<logger name="com" level="TRACE" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
<logger name="com.journaldev" additivity="false">
<AppenderRef ref="databaseAppender" />
</logger>
</Loggers>
</Configuration>
Appender ghi vào cơ sở dữ liệu (Database Appender) có xét đến BurstFilter, trong khi Appender ghi ra console (Console Appender) thì không.
Việc sử dụng Console logger sẽ khiến tất cả các log event đều được ghi ra, nhưng Appender ghi vào cơ sở dữ liệu thì không như vậy, vì BurstFilter sẽ chặn một số log không cho tiếp tục được xử lý.
Việc từ chối các LogEvent này vẫn xảy ra ngay cả khi Logger đã được định cấu hình để xử lý những log đó. Điều này hoàn toàn hợp lý, như minh họa trong ví dụ JournalDevServlet dưới đây.


package com.journaldev;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class JournalDevServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
Logger logger = LogManager.getLogger(JournalDevServlet.class);
for(int i = 0 ; i < 1000 ; i++){
logger.trace("Index :: "+i+" :: JournalDev Database Logging Message !");
LogManager.getRootLogger().error("Index :: "+i+" :: JournalDev Database Logging Message !");
}
}
}
Mặc dù các LoggerConfig đó là ứng viên cho việc xử lý các Log Event được tạo ra ở đó, nhưng bộ lọc (Filter) đã ngăn không cho một số log được xử lý và ghi lại. Bạn có thể xem đây là một phần của khái niệm “Không gian ghi log” (Logging Space) để hiểu toàn diện hơn về cách thức hoạt động của hệ thống log.
Layouts trong Log4j2
Do các Appender khác nhau sẽ tiêu thụ các Log Event theo những cách khác nhau, Layout được tạo ra nhằm định dạng lại các Log Event sao cho phù hợp với mục đích sử dụng của Appender đó. Trong Log4j 1.x và cả API của Logback, việc chuyển đổi Layout sẽ đưa Log Event thành chuỗi ký tự (String). Tuy nhiên, trong Log4j2, việc chuyển đổi Layout được thực hiện theo một cách hoàn toàn mới: LogEvent được chuyển thành mảng byte (byte array). Việc sử dụng byte array buộc bạn phải cấu hình đúng bảng mã ký tự (Charset) để đảm bảo dữ liệu được chuyển đổi chính xác. Bạn nên truy cập trang chính thức của Apache Log4j2 để tìm hiểu thêm về các loại Layout mà Log4j2 hỗ trợ.
Trong phần này, chúng ta sẽ cùng tìm hiểu về Layout phổ biến nhất, được nhiều lập trình viên sử dụng – PatternLayout.
Log4j2 PatternLayout
PatternLayout là một kiểu định dạng linh hoạt, có thể cấu hình bằng cách sử dụng các chuỗi mẫu (pattern strings) nhằm định dạng các Log Event theo một kiểu cụ thể.
Việc định dạng này dựa vào khái niệm “chuỗi chuyển đổi” (conversion pattern).
Phần này sẽ giới thiệu đến bạn những tính năng quan trọng nhất mà PatternLayout cung cấp.
Khái niệm chuỗi chuyển đổi trong PatternLayout khá giống với cách mà printf trong ngôn ngữ C hoạt động.
Nói một cách đơn giản, chuỗi chuyển đổi gồm có:
- Phần văn bản cố định (literal text)
- Và các biểu thức điều khiển định dạng gọi là các chỉ thị chuyển đổi (conversion specifiers)
Hình minh họa dưới đây sẽ giúp bạn hình dung một chuỗi chuyển đổi gồm những thành phần nào:

Hình minh họa phía trên là một nỗ lực nhằm đơn giản hóa chuỗi chuyển đổi (Conversion Pattern), tuy nhiên chắc chắn sẽ tốt hơn nếu bạn tham khảo thêm tài liệu chính thức của Apache Log4j2 để hiểu rõ hơn về Layouts nói chung và Pattern Layout nói riêng.
Ngoài ra, bạn cũng có thể nhìn lại các Log Event ở phần trên và quan sát xem trong mỗi lần ghi log, chuỗi chuyển đổi nào đã được sử dụng để định dạng thông điệp.
Nên dùng cấp độ Log4j2 nào?
Một trong những câu hỏi quan trọng nhất mà bạn có thể đặt ra đó là: nên dùng cấp độ ghi log nào trong từng trường hợp cụ thể?
Trong môi trường phát triển (development), thông thường chúng ta sử dụng cấp độ DEBUG, trong khi đó ở môi trường vận hành (production), nên ưu tiên INFO hoặc WARN.
Bảng dưới đây sẽ giúp bạn định hướng rõ hơn:
| Cấp độ Log Event | Khi nào nên sử dụng |
|---|---|
OFF |
Khi bạn không muốn ghi bất kỳ log nào |
FATAL |
Khi xảy ra lỗi nghiêm trọng khiến ứng dụng không thể tiếp tục chạy |
ERROR |
Khi xảy ra lỗi trong ứng dụng, có thể phục hồi |
WARN |
Khi có sự kiện cảnh báo có thể dẫn tới lỗi |
INFO |
Khi cần ghi lại thông tin hệ thống, các sự kiện đáng lưu ý |
DEBUG |
Khi cần gỡ lỗi chung, theo dõi các luồng logic của chương trình |
TRACE |
Khi cần ghi log chi tiết, rất tinh vi, để theo dõi luồng xử lý |
ALL |
Khi muốn ghi lại tất cả mọi thứ |
Tóm tắt hướng dẫn Log4j2
Log4j2 là phiên bản cải tiến mạnh mẽ của framework ghi log Apache Log4j. Nó mang đến nhiều tính năng mới và cải tiến hiệu suất đáng kể so với Log4j 1.x.
Mục tiêu của bài hướng dẫn này là giúp bạn nắm được tổng quan đầy đủ tại một nơi duy nhất, từ đó bạn có thể bắt đầu áp dụng Log4j2 vào ứng dụng của mình.
Do một số khái niệm không dễ để trình bày hết trong một lần duy nhất, nên bài viết đã cố gắng giải thích từng phần bằng lý thuyết kết hợp với ví dụ minh họa rõ ràng. Các phần như Appenders, Filters, Layouts và Lookups đều được đề cập theo hướng này.
Một vài điểm cần lưu ý để đảm bảo ứng dụng chạy tốt:
- IDE Eclipse của bạn phải hỗ trợ Maven
- Apache Tomcat của bạn phải chứa JAR mysql-connector trong thư mục
lib - Bạn cần biết cách sử dụng Maven cơ bản
Vậy là bạn đã hoàn thành bài hướng dẫn Log4j2!
Hy vọng những điểm quan trọng nhất đã được trình bày đầy đủ để giúp bạn bắt đầu áp dụng Log4j2 vào dự án thực tế.