Written by : Nguyễn Hòa Khiêm
Email : khiem31200@gmail.com
Phone : 0936439200
1. Giới thiệu
Log event là gì ?
Log event là nhật kí sự kiện mà người dùng hay ứng dụng tương tác với hệ thống.
Vậy tại sao ta lại phải ghi lại log event ?
Các dữ liệu từ quá trình log event có thể được coi là dấu vết khi cần phải điều tra nguyên nhân nếu xảy ra lỗi phần mềm.
Ngoài việc được sử dụng để chuẩn đoán lỗi, các dữ liệu log còn được sử dụng trong quá trình phân tích hành vi, thói quen của người sử dụng, từ đó đưa ra các chiến lược kinh doanh,v.v .
Vậy nên việc ghi lại log event là cực kì quan trọng trong quá trình phát triển phần mềm.
Trong hệ sinh thái Java mặc dù có rất nhiều framework có sẵn , nhưng Log4J
đã trở nên phổ biến nhất trong nhiều thập kỷ, vì tính linh hoạt và đơn giản của nó mang lại.Đến nay chúng ta đã có Log4J 2
là phiên bản nâng cấp của Log4J
.
Trong Log4J2
, một appender chỉ đơn giản là một điểm đến cho các log event, nó có thể đơn giản như một console và có thể phức tạp như một vài cơ sở dữ liệu quan hệ(RDBMS).Layouts xác định cách các bản ghi sẽ được trình bày và filters lọc dữ liệu theo các tiêu chí khác nhau.
Trong bài viết này, Ta sẽ hiểu được về appenders, layouts, và filters phổ biến trong Log4J2
thông qua các ví dụ cụ thể.
Bài viết được biên dịch và soạn lại từ: Intro to Log4J 2 – Appenders, Layouts and Filters của Bealdung
2. Cài đặt
Để hiểu một số thành phần ghi log và cấu hình của chúng, ta thiết lập các trường hợp sử dụng thử nghiệm khác nhau, mỗi trường hợp bao gồm file config log4J2.xml
và Class Test
với JUnit 4
.
Ta thêm hai dependency sau:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
Bên cạnh dependency log4j-core
chính, ta cần có test-jar
để có quyền truy cập vào một context để test các file config có tên không phổ biến.
3. Cấu hình mặc định
Console Appender
là cấu hình mặc định của Log4J2 . Nó ghi lại các thông báo vào console theo một mẫu đơn giản:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout
pattern="%d [%t] %-5level %logger{36} - %msg%n%throwable"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Ta thử phân tích các thành phần thẻ trong đoạn code xml trên:
Configuration
: Root element của file cấu hình Log4J2 và trạng thái thuộc tính là mức của các log event bên trong mà chúng ghi lại.Appenders
: Phần tử này đang chứa một hoặc nhiều appenders. Ở đây, ta sẽ định cấu hình một appender xuất ra console ở mức độ cơ bản.Loggers
: Phần tử này có thể bao gồm nhiều phần tử Logger được cấu hình . Với thẻ Root đặc biệt , ta có thể định cấu hình một trình ghi log tiêu chuẩn không tên sẽ nhận tất cả các thông báo nhật ký từ ứng dụng. Mỗi trình ghi log có thể được đặt ở tầng log thấp nhất.AppenderRef
: Phần tử này xác định một tham chiếu đến một phần tử từ phần Appenders . Do đó, thuộc tínhref
được liên kết với thuộc tính appendersname
.
Ta viết unit test lấy ra Logger
và in hai thông báo:
@Test
public void givenLoggerWithDefaultConfig_whenLogToConsole_thanOK()
throws Exception {
Logger logger = LogManager.getLogger(getClass());
Exception e = new RuntimeException("This is only a test!");
logger.info("This is a simple message at INFO level. " +
"It will be hidden.");
logger.error("This is a simple message at ERROR level. " +
"This is the minimum visible level.", e);
}
4.ConsoleAppender Với PatternLayout
Ta xác định một ConsoleAppender
mới với mẫu màu tùy chỉnh trong file xml
riêng biệt và đưa nó vào cấu hình chính của ta:
<?xml version="1.0" encoding="UTF-8"?>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%style{%date{DEFAULT}}{yellow}
%highlight{%-5level}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green}
%message"/>
</Console>
Đoạn code trên sử dụng một vài biến mẫu được thay thế bằng Log4J 2 trong quá trình chạy:
%style{…}{colorname}
: Thao tác này sẽ in text trong cặp dấu ngoặc đơn đầu tiên ( … ) bằng một màu nhất định ( tên màu ).%highlight{…}{FATAL=colorname, …}
: Tương tự như%style
. Nhưng một màu khác có thể được đưa ra cho mỗi cấp độ Log.%date{format}
: Định dạng ngày hiện tại theo dạng được chỉ định . Ở đây ta đang sử dụng định dạng DateTime ‘DEFAULT’, ’ yyyy -MM-dd HH: mm: ss, SSS’.%-5level
: In tầng của thông báo log theo kiểu căn phải.%message
: Thông báo log dạng thô.
Tuy nhiên chúng tồn tại nhiều biến và kiểu định dạng hơn trong PatternLayout . Bạn hãy tham khảo tài liệu chính thức của Log4J 2.
Bây giờ, chúng sẽ bao gồm console appender đã xác định vào configuration
chính của ta:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" xmlns:xi="http://www.w3.org/2001/XInclude">
<Appenders>
<xi:include href="log4j2-includes/
console-appender_pattern-layout_colored.xml"/>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Ta viết Unit test để kiểm tra :
@Test
public void givenLoggerWithConsoleConfig_whenLogToConsoleInColors_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("CONSOLE_PATTERN_APPENDER_MARKER");
logger.trace("This is a colored message at TRACE level.");
...
}
5. Không đồng bộ File Appender Với JSONLayout và BurstFilter
Đôi khi,nó rất hữu ích khi viết thông báo log theo cách không đồng bộ. Ví dụ: nếu hiệu suất ứng dụng được ưu tiên hơn tính khả dụng của các log.
Trong các trường hợp sử dụng như vậy, ta có thể sử dụng AsyncAppender.
Ví dụ,Ta đang định cấu hình file nhật ký JSON không đồng bộ . Hơn nữa,chúng sẽ bao gồm một burst filter giới hạn đầu ra log ở một tỷ lệ được chỉ định:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
...
<File name="JSONLogfileAppender" fileName="target/logfile.json">
<JSONLayout compact="true" eventEol="true"/>
<BurstFilter level="INFO" rate="2" maxBurst="10"/>
</File>
<Async name="AsyncAppender" bufferSize="80">
<AppenderRef ref="JSONLogfileAppender"/>
</Async>
</Appenders>
<Loggers>
...
<Logger name="ASYNC_JSON_FILE_APPENDER" level="INFO"
additivity="false">
<AppenderRef ref="AsyncAppender" />
</Logger>
<Root level="INFO">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Chú thích:
JSONLayout
: được định cấu hình theo cách ghi một sự kiện log cho mỗi hàng.BurstFilter
: sẽ loại bỏ mọi sự kiện có cấp độ ‘INFO’ trở lên nếu có nhiều hơn hai sự kiện trong số đó, nhưng tối đa là 10 sự kiện bị loại bỏ.AsyncAppender
: được đặt thành bộ đệm(buffer) gồm 80 thông báo log.Sau đó, bộ đệm được chuyển vào file log.
Hãy nhìn vào unit test dưới đây.Ta đang lấp đầy appended buffer trong một vòng lặp, nó ghi vào đĩa và kiểm tra số dòng của file log:
@Test
public void givenLoggerWithAsyncConfig_whenLogToJsonFile_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("ASYNC_JSON_FILE_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info("This is async JSON message #{} at INFO level.", count);
}
long logEventsCount
= Files.lines(Paths.get("target/logfile.json")).count();
assertTrue(logEventsCount > 0 && logEventsCount <= count);
}
6. RollingFile Appender and XMLLayout
Ta sẽ tạo ra một rolling log file. Sau khi cấu hình kích cỡ của file,file log sẽ được nén và xoay vòng.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<RollingFile name="XMLRollingfileAppender"
fileName="target/logfile.xml"
filePattern="target/logfile-%d{yyyy-MM-dd}-%i.log.gz">
<XMLLayout/>
<Policies>
<SizeBasedTriggeringPolicy size="17 kB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="XML_ROLLING_FILE_APPENDER"
level="INFO" additivity="false">
<AppenderRef ref="XMLRollingfileAppender" />
</Logger>
<Root level="TRACE">
<AppenderRef ref="ConsoleAppender"/>
</Root>
</Loggers>
</Configuration>
Chú thích
Rolling File có thuộc tính
filePattern
, được sử dụng để đặt tên cho các file log đã xoay vòng và có thể được định cấu hình bằng các biến giữ chỗ(placeholder variables). Trong ví dụ này, nó phải chứa ngày tháng và bộ đếm trước file suffix.Cấu hình mặc định của
XMLLayout
sẽ ghi các đối tượng log event đơn lẻ mà không có phần tử root.
@Test
public void givenLoggerWithRollingFileConfig_whenLogToXMLFile_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("XML_ROLLING_FILE_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info(
"This is rolling file XML message #{} at INFO level.", i);
}
}
7. Syslog Appender
- Giả sử ta cần gửi log đã ghi đến một máy từ xa qua mạng. Cách đơn giản nhất để làm điều đó bằng Log4J2 sẽ là sử dụng Syslog Appender:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
...
<Syslog name="Syslog"
format="RFC5424" host="localhost" port="514"
protocol="TCP" facility="local3" connectTimeoutMillis="10000"
reconnectionDelayMillis="5000">
</Syslog>
</Appenders>
<Loggers>
...
<Logger name="FAIL_OVER_SYSLOG_APPENDER"
level="INFO"
additivity="false">
<AppenderRef ref="FailoverAppender" />
</Logger>
<Root level="TRACE">
<AppenderRef ref="Syslog" />
</Root>
</Loggers>
</Configuration>
Giải thích các thuộc tính có trong Syslog tag:
name
: tên của appender, nó phải là duy nhất. Vì chúng ta có thể có nhiều trình phụ lục Syslog cho cùng một ứng dụng và cấu hình.format
: nó có thể được đặt thành BSD hoặc RFC5424 và các bản ghi Syslog sẽ được định dạng tương ứng.host & port
: tên host name và port của máy chủ Syslog từ xa.protocol
: sử dụng TCP hay UPD.facility
: Syslog facility mà sự kiện sẽ được viết.connectTimeoutMillis
: khoảng thời gian chờ kết nối được thiết lập, mặc định là 0.reconnectionDelayMillis
: thời gian chờ trước khi thử kết nối lại.
8. FailoverAppender
Hiện tại, có thể có những trường hợp một appender không xử lý được các log event nhưng ta không muốn mất dữ liệu. Trong những trường hợp như vậy, FailoverAppender rất hữu ích.
Ví dụ: Syslog appender không gửi được sự kiện đến máy từ xa, thay vì mất dữ liệu đó, chúng tôi có thể tạm thời quay lại FileAppender.
FailoverAppender có một appender chính và một số append thứ cấp . Trong trường hợp chính không thành công, nó sẽ cố gắng xử lý log event với các sự kiện phụ theo thứ tự cho đến khi một sự kiện thành công hoặc không có bất kỳ sự kiện thứ hai nào để thử.
<Failover name="FailoverAppender" primary="Syslog">
<Failovers>
<AppenderRef ref="ConsoleAppender" />
</Failovers>
</Failover>
Hãy thử viết Unit Test kiểm tra chúng :
@Test
public void givenLoggerWithFailoverConfig_whenLog_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("FAIL_OVER_SYSLOG_APPENDER");
Exception e = new RuntimeException("This is only a test!");
logger.trace("This is a syslog message at TRACE level.");
logger.debug("This is a syslog message at DEBUG level.");
logger.info("This is a syslog message at INFO level.
This is the minimum visible level.");
logger.warn("This is a syslog message at WARN level.");
logger.error("This is a syslog message at ERROR level.", e);
logger.fatal("This is a syslog message at FATAL level.");
}
9. JDBC Appender
JDBC appender gửi log events tới một RDBMS, sử dụng JDBC tiêu chuẩn. Kết nối có thể được lấy bằng cách sử dụng vài JNDI Datasource hay vài connection factory.
Cấu hình cơ bản bao gồm DataSource hoặc ConnectionFactory , ColumnConfigs và tableName:
<JDBC name="JDBCAppender" tableName="logs">
<ConnectionFactory
class="com.baeldung.logging.log4j2.tests.jdbc.ConnectionFactory"
method="getConnection" />
<Column name="when" isEventTimestamp="true" />
<Column name="logger" pattern="%logger" />
<Column name="level" pattern="%level" />
<Column name="message" pattern="%message" />
<Column name="throwable" pattern="%ex{full}" />
</JDBC>
Hãy thử viết Unit Test kiểm tra chúng :
@Test
public void givenLoggerWithJdbcConfig_whenLogToDataSource_thanOK()
throws Exception {
Logger logger = LogManager.getLogger("JDBC_APPENDER");
final int count = 88;
for (int i = 0; i < count; i++) {
logger.info("This is JDBC message #{} at INFO level.", count);
}
Connection connection = ConnectionFactory.getConnection();
ResultSet resultSet = connection.createStatement()
.executeQuery("SELECT COUNT(*) AS ROW_COUNT FROM logs");
int logCount = 0;
if (resultSet.next()) {
logCount = resultSet.getInt("ROW_COUNT");
}
assertTrue(logCount == count);
}
10. Kết luận
Bài viết trên đã đưa ra những ví dụ rất cơ bản về cách ta sử dụng logging appenders, filter and layouts với Log4J2 và cách để cấu hình chúng.
Bình luận