Logging hay log là một trong những thành phần cực kỳ quan trọng của mã nguồn, phần mềm, hệ thống. Nó lưu lại lịch sử thực thi các hàm để chúng ta có thể gỡ lỗi khi cần thiết. Trong bài này Dũng sẽ cùng các bạn tìm hiểu về logging và spring-jcl một thư viện siêu nhỏ của spring để làm cầu nối giữa ứng dụng các thư viện log.

Khởi tạo module

Chúng ta sẽ khởi tạo một module có tên logging

Cấu hình dự án

Để có thể sử dụng log, chúng ta cần bổ sung thư viện spring-jcl vào dự án như sau:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>vn.techmaster</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>logging</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jcl</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>

Nếu bạn xem trong phần các phụ thuộc thì thư viện này cũng chỉ có vài lớp, phản ánh đúng vai trò của nó là một thư viện cầu nối.

Bắt đầu sử dụng log

Để có thể sử dụng log thì chúng ta cần sử dụng lớp LogFactory, đây là một lớp sử dụng factory design pattern để tạo ra một đối tương log thay vì dùng từ khoá new, điều này nhằm làm che giấu sự phức tạp của việc khởi tạo đối tượng log ở bên dưới, mà đúng là nó phức tạp thật. Chúng ta có thể log ra một dòng hello world bằng mã nguồn dưới đây.

package vn.techmaster.logging;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LoggingStartUp {

    public static void main(String[] args) {
        Log log = LogFactory.getLog(LoggingStartUp.class);
        log.info("hello world");
    }
}

Khi chạy chương trình với lớp LoggingStartUp kết quả chúng ta nhận được là:

Jun 17, 2024 9:45:03 AM vn.techmaster.logging.LoggingStartUp main
INFO: hello world

Trên thực tế thì việc in ra dòng log không phải do spring, mà là do thư viện java Logger. Bởi vì spring cố gắng đi kiểm tra sự tồn tại của các thư viện log thông qua việc kiểm tra sự tồn tại của các lớp dưới đây.

private static final boolean log4jSpiPresent = isPresent("org.apache.logging.log4j.spi.ExtendedLogger");
private static final boolean log4jSlf4jProviderPresent = isPresent("org.apache.logging.slf4j.SLF4JProvider");
private static final boolean slf4jSpiPresent = isPresent("org.slf4j.spi.LocationAwareLogger");
private static final boolean slf4jApiPresent = isPresent("org.slf4j.Logger");

Tuy nhiên do chúng ta chưa bổ sung bất kỳ một thư viện log nào, vậy nên spring sẽ mặc định sử dụng 1 lớp có tên là JavaUtilAdapter để tạo ra một đối tượng của lớp JavaUtilLog và lớp JavaUtilLog này lại gọi đến Logger của java.

Logback

Trong dự án thực tế, việc sử dụng log đòi hỏi nhiều các tính năng ví dụ như hiển thị log ra console, ghi log ra file, gửi log đến các hệ thống monitor để theo dõi, gửi log đến các hệ thống lưu trữ để xem lại khi cần, nghĩa là chúng ta cần một thư viện nào đó được cập nhật dễ dàng hơn là thư viên java Logger vốn phụ thuộc vào các phiên bản của Java rất mất thời gian và rất khó để nâng cấp. Logback là một thư viện đến từ cộng đồng và nó nâng cấp tách biệt với java là thứ chúng ta cần.

Bổ sung phụ thuộc

Chúng ta sẽ cần bổ sung phụ thuộc logback-classic vào trong danh sách các thư viện phụ thuộc của tập tin logging/pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>vn.techmaster</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>logging</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jcl</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>
</project>

Bây giờ hãy chạy lại chương trình qua lớp LoggingStartUp chúng ta sẽ nhận được kết quả như sau:

09:52:43.186 [main] INFO vn.techmaster.logging.LoggingStartUp -- hello world

Bạn có thể thấy là cấu trúc của nội dung log đã thay đổi, đây là do chương trình của chúng ta đã sử dụng logback thay vì java logger. Nguyên nhân là do spring-jcl đã tìm thấy lớp org.slf4j.Logger. Và nó sẽ sử dụng Slf4jAdapter để tạo ra Slf4jLog và sử dụng org.slf4j.Logger.

Cấu hình logback.xml

Ngoài sử dụng các cấu hình mặc định thì logback cũng cho chúng ta tuỳ chỉnh các cấu hình thông qua việc sử dụng tập tin logback.xml, ví dụ:

<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </Pattern>
        </layout>
    </appender>
    <root level="info">
        <appender-ref ref="console"/>
    </root>
</configuration>

Trong tập tin này chúng ta đã nghĩa ra 1 appender dạng console và cho phép hiển thị log level từ infor trở lên. Ngoài ra chúng ta cũng có thể bổ sung các appender đã được cung cấp sẵn bởi logback.

<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </Pattern>
        </layout>
    </appender>
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/archived/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d %p %c{1.} [%t] %m%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </root>
</configuration>

Tạo một lớp Appender

Nhu cầu log trong thực tế rất đa dạng, đặc biệt là đối với các log lỗi cần phải theo dõi và xử lý ngay, chính vì thế chúng ta cũng có nhu cầu tạo các appender của riêng mình, dưới đây là một appender ví dụ cho việc gửi mail các log có level từ error trở lên.

package vn.techmaster.logging.appender;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.AppenderBase;

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MailLogAppender extends AppenderBase<ILoggingEvent> {

    @Override
    protected void append(ILoggingEvent event) {
        Level level = event.getLevel();
        if (level.isGreaterOrEqual(Level.ERROR)) {
            String eceptionDetails = "";
            IThrowableProxy throwable = event.getThrowableProxy();
            if (throwable != null) {
                eceptionDetails += throwable.getMessage();
                eceptionDetails += " " + Stream
                    .of(throwable.getStackTraceElementProxyArray())
                    .map(StackTraceElementProxy::toString)
                    .collect(Collectors.joining());
            }
            System.out.println(
                "sent mail log error: " + event.getFormattedMessage() +
                    ", exception: " + eceptionDetails
            );
        }
    }
}
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            </Pattern>
        </layout>
    </appender>
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/archived/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d %p %c{1.} [%t] %m%n</pattern>
        </encoder>
    </appender>
    <appender name="mail" class="vn.techmaster.logging.appender.MailLogAppender"/>
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
        <appender-ref ref="mail"/>
    </root>
</configuration>

Tổng kết

Vậy là chúng ta đã cùng nhau:

  1. Tìm hiểu về spring jcl.
  2. Tìm hiểu về cấu hình, sử dụng cũng như tạo ra lớp appender cho logback.

Sách tham khảo

Để có thể hiểu về factory, adapter design pattern bạn có thể tham khảo cuốn làm chủ các mẫu thiết kế kinh điển trong lập trình và đừng quên nhập mã giảm giá Tech10 để được giảm giá 10% nhé.


Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a