Bộ sưu tập

Tích hợp log4j2 với ứng dụng Spring MVC


https://codersontrang.com/2017/11/13/tich-hop-log4j2-voi-ung-dung-spring-mvc/
Logging là một việc làm cần thiết và mang lại nhiều lợi ích trong quá trình vận hành ứng dụng. Nhờ có logging, ta có thể kiểm tra lại được cái gì đã diễn ra trong ứng dụng của chúng ta và nó giúp ích rất nhiều cho chúng ta khi phải tìm lại dấu vết của một lỗi xảy ra trong hệ thống, cũng như các thông tin về hành vi của người dùng. Trong thế giới của Java, có một số các thư viện logging nổi tiếng như Log4j, Logback, JUL, Spring-jcl … Bài viết này sẽ hướng dẫn các bạn cách để tích hợp ứng dụng Spring MVC với logging framwork log4j2.

Chắc hẳn các bạn sẽ thắc mắc tại sao lại là Log4j2. Đơn giản là bởi vì đây là một bộ thư viện thực sự nổi tiếng về hiệu năng cũng như sự thuận tiện khi sử dụng. Trước đây Log4j đã nổi tiếng với phiên bản 1.x, nhưng Log4j 2.x ra đời với các cải thiện về hiệu năng thì càng làm cho bộ thư viện này nổi tiếng hơn. Các bạn có thể tìm thấy sự so sánh về thư viện Log4j 2 với các bộ thư viện logging khác ở đây. Mình có lấy ra hai hình vẽ như hình dưới đây, rõ ràng chúng ta có thể thấy sự nổi trội của Log4j2 so với các logging framework khác.

Hình vẽ trên biểu thị số lượng message trên mỗi giây được log ra. Số lượng message càng cao thì sẽ càng tốt.

Hình vẽ trên biểu thị độ trễ khi logging mỗi message sử dụng các thư viện logging khách nhau. Thời lượng càng ít càng tốt.

Ví dụ ở trong bài viết này được viết tiếp ví dụ ở trong bài viết “Một cách nhìn khác về ứng dụng web với Spring MVC, Spring Boot, Tomcat dạng nhúng và Thymeleaf“. Trong ví dụ này, ta sẽ sử dụng log4j2 qua cầu nối logging Slf4j. Để có thể hiểu hơn về slf4j thì các bạn hãy nhìn xuống hình dưới đây

Sfl4j là viết tắt của Simple Logging Facade for Java, đây là một bộ thư viện cung cấp một giao diện chung cho nhiều logging framework khác nhau như log4j, logback, hay JUL. Bằng việc sử dụng các API của slf4j thay vì sử dụng trực tiếp các API của các logging framework, ta có thể linh động hơn cho quá trình bảo trì sau này khi muốn chuyển việc sử dụng từ một logging framwork này sang một logging framework khác mà không cần thay đổi lại nhiều mã nguồn. (nó tương tự như vai trò của ORM đối với các database khác nhau vậy).

Ta nhìn lại cấu trúc của Maven project trong bài viết trước, trong bài này ta bổ sung thêm một file cấu hình cho logging là log4j2.xml như hình dưới đây

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>

    <groupId>com.codersontrang</groupId>
    <artifactId>spring-mvc-boot-embedded-log4j2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>maven-central</id>
            <name>maven central</name>
            <url>http://central.maven.org/maven2/</url>
        </repository>

        <repository>
            <id>spring-milestone</id>
            <name>spring milestone</name>
            <url>http://repo.spring.io/milestone/</url>
        </repository>

    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.0.M3</version>
            <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-thymeleaf</artifactId>
            <version>2.0.0.M3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.7</version>
        </dependency>

    </dependencies>
</project>

Nhìn ở file pom.xml ta có thể thấy ngoài các dependencies cần thiết cho log4j2 và cầu nối slf4j, ta có thêm một dependency nữa đó là disruptor, đây là một thư viện hỗ trợ cho việc sử dụng Logger không đồng bộ (async logger) với log4j2 theo mẫu thiết kế discruptor là mẫu thiết kế được triển khai trên thực tế vào hệ thống giao dịch ngoại hối của LMAX.

DemoController.java


package springmvcdemo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import springmvcdemo.model.Student;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

@Controller
public class DemoController {
    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);

    @GetMapping(value="/home")
    public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {
        logger.debug("Requesting to /home");
        Student student = new Student();
        student.setName("Coder Tien Sinh");

        List<String> books = new ArrayList<String>();
        books.add("book1");
        books.add("book2");
        student.setBooks(books);

        ModelAndView mav = new ModelAndView("index", "model", student);
        logger.debug("/home returns to the view");
        return mav;
    }

    @PostMapping(value="/submitStudentInfo")
    public ModelAndView submitStudentInfo(ModelMap model, @ModelAttribute("model") Student student){
        logger.debug("Requesting to /submitStudentInfo");
        List<String> listBooks = student.getBooks();
        boolean bookSameName = checkSameBookName(listBooks);

        String message = "Update success !!!";
        if(bookSameName){
            message = "Books must have different name !!!";
        }
        model.addAttribute("message", message);

        ModelAndView mav = new ModelAndView("index", "model", student);
        logger.debug("/submitStudentInfo returns to the view");
        return mav;
    }

    private boolean checkSameBookName(List<String> listBooks) {
        for(int i =0; i<listBooks.size(); i++){
            String firstBookName = listBooks.get(i);
            for(int j = i+1; j<listBooks.size(); j++){
                String secondBookName = listBooks.get(j);
                if(firstBookName.equalsIgnoreCase(secondBookName)){
                    return true;
                }
            }
        }
        return false;
    }
}

Như đã nói ở trên, mọi thao tác logging sẽ được dùng qua API của thư viện cầu nối slf4j. Việc bắt đầu và kết thúc xử lý ở trong các phương thức của controller trong Spring MVC sẽ được log ra bằng đối tượng org.slf4j.Logger. Đối tượng này được tạo ra từ org.slf4j.LoggerFactory.

log4j2.xml


<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <Property name="log-path">D:/logs</Property>
    </Properties>
    <Appenders>
        <RollingFile name="ROLLING-FILE"
                     fileName="${log-path}/spring-mvc-log4j2-trace.log"
                     filePattern="${log-path}/dating-trace-%d{yyyy-MM-dd}.log">
            <PatternLayout>
                <pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg %n</pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <AsyncLogger name="springmvcdemo.controller" level="DEBUG" includeLocation="false">
            <AppenderRef ref="ROLLING-FILE"/>
        </AsyncLogger>
    </Loggers>
</Configuration>

Ta có thể giải thích một số chi tiết trong file cấu hình trên như sau:

  • D:/logs: Là đường dẫn đến thư mục mà file log sẽ được sinh ra ở đó. Ta cấu hình đường dẫn này trong một biến property là log-path và biến này có thể được tham chiếu đến bằng việc sử dụng biểu thức ${log-path}.
  • Appenders: là thành phần làm nhiệm vụ trực tiếp xử lý các message khi logging. Có rất nhiều các loại appender khác nhau nhưng trong trường hợp này ta sử dụng loại appender có khả năng ghi các message log ra file, và có khả năng để cắt nội dung file để quản lý nội dung log qua từng ngày. Mỗi một appender đều có một tên để tham chiếu mà trong trường hợp này ta đặt là ROLLING-FILE. Mọi message log sẽ được ghi vào file có tên là spring-mvc-log4j2-trace.log nằm trong thư mục D:/logs mà ta đã định nghĩa ở phần trên. Qua một ngày mới thì nội dung của file này sẽ được cắt ra và tên file sẽ được đổi tên thành dating-trace-%d{yyyy-MM-dd}.log với thông tin về ngày tháng của ngày hôm trước đó. Các log message sinh ra trong ngày mới sẽ được ghi lại vào một file spring-mvc-log4j-trace.log mới hoàn toàn.
  • Pattern: Định dạng cách mà message log sẽ được ghi vào trong nội dung của file log. Ở đây có các ký hiệu biểu hiện các thông tin như sau:

    • %level: cấp độ của Logging đang được dùng để log message tương ứng vào file, gồm các cấp độ là ERROR, WARN, INFO, DEBUG, TRACE
    • %d: thời điểm message được log ra
    • %t: định danh của thread đã chạy đoạn mã để gọi đến việc logging
    • %c: Tên của lớp Java mà ở các yêu cầu logging được khai báo ở đó
    • %msg: Nội dung của message log
    • %n: Dấu xuống dòng
  • Loggers: Logger là thành phần để khai báo cách để xử lý log trên một bộ phận nào đó của ứng dụng. Ở đây với Log4j2, ta có thể khai báo một AsyncLogger có khả năng để log các message theo cơ chế không đồng bộ. Trong logger có một số thông tin quan trọng cần được khai báo như sau:
    • name: Định nghĩa phạm vi mà logger có hiệu lực. Trong trường hợp này ta khai báo là “springmvcdemo.controller” có nghĩa là tất cả các xử lý logging ở các lớp nằm trong package springmvcdemo.controller sẽ được thực hiện bởi logger này.
    • level: Là cấp độ logging thấp nhất mà Logger sẽ chấp nhận xử lý. Như nói ở trên ta có các cấp độ từ cao xuống thấp là ERROR > WARN > INFO > DEBUG > TRACE. Các yêu cầu logging ở level được khai báo ở đây hay cao hơn sẽ được xử lý bởi logger này.
    • AppenderRef: Định nghĩa Appender là thành phần trực tiếp xử lý các message log. Với một logger ta cũng có thể định nghĩa nhiều appender. Ví dụ trong code với một thao tác gọi hàm để log message từ logger, ta có thể ghi nội dung message log này ra cả file lẫn màn hình console nhờ vào khai báo 2 appender khác nhau chẳng hạn.

Nội dung các file mã nguồn khác như Student.java, Spring5MVCDemoApp.java, index.html, application.properties sẽ không có gì thay đổi, các bạn có thể tìm thấy nội dung của chúng trong bài viết trước.

Bây giờ chạy ứng dụng từ file Spring5MVCDemoApp.java, truy cập vào địa chỉ http://localhost:8080/home rồi bấm vào nút Submit để truy cập vào địa chỉ http://localhost:8080/submitStudentInfo. Vào trong thư mục D:/logs ta sẽ thấy có file log được xuất hiện trong thư mục như hình dưới đây:

Ta thấy có 2 file là date-trace-2017-11-12.logspring-mvc-log4j2-trace.log, đó là vì ta chạy ứng dụng vào đúng thời điểm sang ngày mới. Tất cả những nội dung log xảy ra ở ngày hôm trước (2017-11-12) sẽ được ghi lại vào file date-trace-2017-11-12.log, còn lại những nội dung log sinh ra trong ngày mới (2017-11-13) do ta mới thực hiện các thao tác truy cập vào ứng dụng ở trên thì sẽ được ghi lại vào file spring-mvc-log4j2-trace.log như hình dưới đây:

Nếu để ý bạn có thể thấy các nội dung log được ghi theo từng dòng và mỗi dòng sẽ có định dạng giống như những gì chúng ta khai báo trong phần pattern ở trong file cấu hình log4j2.xml ở trên.

Good luck!

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s