1 Introduction to Logging
Because applications ultimately run on servers without IDEs or consoles, we must rely on log files to locate bugs.
Logging is an essential component in software applications, serving as a critical tool for debugging and data collection management. It enables us to monitor variable value changes and code execution trajectories in production environments.
The primary purpose is to facilitate monitoring of variable value changes and code execution trajectories in production environments.
These records are output to designated locations as files, helping us analyze errors and user request trajectoires.
In summary: Any information that needs to be recorded during development, after deployment, or during runtime belongs to logging.
Development environment: Output variable values to console for debugging purposes
Production environment: Record exception data generated by services during operation
During user interactions, log user operational behaviors for analysis purposes.
2 Common Logging Components
Popular logging frameworks include log4j, logback, and log4j2.
SpringBoot uses logback for logging by default.
2.1. Log4j and log4j2.x
Log4j is one of the most commonly used logging components, originally an open-source project from Apache. Using Log4j, we can control where log information is sent (console, files, databases, etc.), and we can control the output format of each log entry. By defining the level of each log message, we can more precisely control the logging process.
Log4j has two versions: log4j and log4j2. Log4j2 evolved from log4j, incorporating logback's design principles. It uses .xml/.json files instead of the previous .properties configuration. Log4j2 employs asynchronous logging based on the LMAX Disruptor library, achieving 10x higher throughput compared to log4j.
Log4j supports two configuration file formats: properties and xml. It contains three main components: Logger, appender, and Layout.
Log4j2 has 8 logging levels, ordered from lowest to highest: All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
All: The lowest level, used to enable all logging.
Trace: For tracking, you can write trace output as the program progresses. Traces will be numerous, but we can set the minimum logging level to prevent their output.
Debug: Provides fine-grained information events that are helpful for debugging applications.
Info: Messages that highlight the application's progress at a coarse-grained level.
Warn: Outputs warnings and logs at warn level and below.
Error: Outputs error information logs.
Fatal: Outputs logs for each critical error event that will cause the application to exit.
OFF: The highest level, used to disable all logging.
The program prints logs that are equal to or higher than the configured level. The higher the logging level, the fewer logs are printed.
2.2. Logging Facades
2.2.1. Common-logging
Common-logging, abbreviated as JCL, is a general logging API provided by Apache that allows applications to be independent of specific logging implementation tools. This logging interface provides a simple wrapper for other logging tools, including Log4J, Avalon LogKit, and JUL, enabling applications to adapt to the corresponding logging implementation at runtime.
Common-logging uses a dynamic lookup mechanism to automatically identify the actual logging library being used during program execution. This differs from slf4j, which statically binds the actual Log implementation at compile time.
2.2.2. Slf4j
slf4j is a specification, standard, and interface for all logging frameworks, not a concrete implementation of a framework. Its interface cannot be used independently and must be used with specific logging framework implementations (such as log4j, logback, log4j2).
SLF4J stands for The Simple Logging Facade for Java. It's not a specific logging solution but provides some Java Logging API through the facade pattern, similar to JCL.
When using the SLF4J logging facade, if you need to use a specific logging implementation, you must choose the correct SLF4J jar (called logging bridging). SLF4J provides a unified logging interface. You only need to record according to its provided methods, and the final log format, logging level, output method, etc., are implemented through the configuration of the specific logging system, allowing flexible switching of logging systems in applications.
Learning logging facade technology is for isolating applications or middleware development from specific logging components.
- Dependency import
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.0</version>
<scope>test</scope>
</dependency>
- Test class implementation
package com.example.loggingdemo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingTest {
private static Logger logger = LoggerFactory.getLogger(LoggingTest.class);
public static void main(String[] args) {
logger.debug("debug message...");
logger.info("info message...");
logger.warn("warning message...");
logger.error("error message...");
}
}
3 Log4j2 Implementation
3.1 Quick Start
-
Dependency configuration
<!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions><!-- Remove Spring Boot's default logback configuration --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- Add log4j2 dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- Asynchronous logging --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency> -
Add log4j2.xml to resources directory
Default configuration file name order
1. log4j2-test.json or log4j2-test.jsn files in classpath.
2. log4j2-test.xml file in classpath.
3. log4j2.json or log4j2.jsn file in classpath.
4. log4j2.xml file in classpath.
-
Create test class
package com.example.loggingdemo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Log4j2Test { private static Logger logger = LogManager.getLogger(Log4j2Test.class); public static void main(String[] args) { logger.debug("debug message..."); logger.info("info message..."); logger.warn("warning message..."); logger.error("error message..."); } }3.2 Configuration File Explanation
Simple explanation:
<appender>
Defines output locations
</appender>
<loggers>
Global and package-specific logging levels
<root>Global level</root>
<logger>Package-specific level</logger>
</loggers>
Complete configuration:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appenders>
<!-- Console output log format -->
<console name="Console" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{HH:mm:ss} %-5level %class %L %M -- %msg%n" />
</console>
<!-- fileName: output path, filePattern: naming rule -->
<!-- name: unique configuration name -->
<!-- This configuration specifies that DEBUG level logs are output to corresponding files -->
<RollingFile name="RollingFileDebug"
fileName="D:/logs/debug.log"
filePattern="D:/logs/$${date:yyyy-MM-dd}/debug-%d{yyyy-MM-dd}-%i.log">
<Filters>
<!--Filters determine if log events can be output. Filter conditions have three values: ACCEPT, DENY, or NEUTRAL.-->
<!--If accepted/denied, logging ends here. If neutral, logging continues.-->
<!--
level: The level to be filtered.
onMatch: Default is NEUTRAL
onMismatch: Default is DENY
-->
<!-- Filter DEBUG level logs -->
<ThresholdFilter level="DEBUG"/>
<!-- If current log level > info, return onMatch value; if < info, return onMismatch value -->
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL" />
</Filters>
<!-- Output format -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %class{36} %L %M - %msg%n" />
<Policies>
<!-- Single log file size limit -->
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<!-- Keep maximum 20 log files -->
<DefaultRolloverStrategy max="20" />
</RollingFile>
<RollingFile name="RollingFileInfo"
fileName="D:/logs/info.log"
filePattern="D:/logs/$${date:yyyy-MM-dd}/info-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="INFO" />
<ThresholdFilter level="WARN" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<!-- Output format -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
<Policies>
<!-- SizeBasedTriggeringPolicy: single file size limit -->
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<!-- DefaultRolloverStrategy: maximum number of files in same directory -->
<DefaultRolloverStrategy max="20" />
</RollingFile>
<RollingFile name="RollingFileWarn"
fileName="D:/logs/warn.log"
filePattern="D:/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="WARN" />
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<!--<TimeBasedTriggeringPolicy modulate="true" interval="1"/> -->
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<!-- Keep maximum 20 log files -->
<DefaultRolloverStrategy max="20" min="0" />
</RollingFile>
<RollingFile name="RollingFileError"
fileName="D:/logs/error.log"
filePattern="D:/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<Filters>
<ThresholdFilter level="ERROR" />
<ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n" />
<Policies>
<!--<TimeBasedTriggeringPolicy modulate="true" interval="1"/> -->
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<!-- Keep maximum 20 log files -->
<DefaultRolloverStrategy max="20" min="0" />
</RollingFile>
</appenders>
<loggers>
<!-- Set current default logging level -->
<root level="INFO">
<!-- Configuration effective locations -->
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
<!-- Additional logger configurations -->
<!-- Log druid-sql statements -->
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!-- Log4j2 built-in filter tags, specify specific package logging levels -->
<logger name="org.springframework.web" level="error"></logger>
<logger name="org.springframework.core" level="error"></logger>
<logger name="org.springframework.beans" level="error"></logger>
<!-- Asynchronous logging configuration -->
<!--<AsyncLogger name="org.springframework" level="info" includeLocation="true">
<AppenderRef ref="RollingFileError"></AppenderRef>
</AsyncLogger>
<AsyncLogger name="org.mybatis" level="error" includeLocation="true">
<AppenderRef ref="RollingFileError"></AppenderRef>
</AsyncLogger>
<AsyncLogger name="com.alibaba.druid" level="error" includeLocation="true">
<AppenderRef ref="RollingFileError"></AppenderRef>
</AsyncLogger>
<AsyncRoot level="debug" includeLocation="true">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</AsyncRoot>-->
</loggers>
</configuration>
Log4j2 typically uses synchronous logging during development and switches to asynchronous logging in production.
During development, frequent server restarts may cause data loss with asynchronous threads.
4 Pattern Layout Symbols
PatternLayout format symbols explanation:
%p or %level: Outputs the priority of the log message, i.e., DEBUG, INFO, WARN, ERROR, FATAL.
%d: Outputs the date or time of the log event, default format is ISO8601, or specify format like: %d{yyyy/MM/dd HH:mm:ss,SSS}.
%r: Outputs milliseconds since application startup to when this log was generated.
%t: Outputs the thread name that generated the log event.
%class: Outputs the class name of the log message, usually the full class name.
%M: Outputs the method name that generated the log message.
%F: Outputs the file name where the log message was generated.
%L: Outputs the line number in the code.
%m or%msg or%message: Outputs the specific log message specified in the code.
%n: Outputs a carriage return and line feed, "rn" for Windows, "n" for Unix.
%x: Outputs the NDC (Nested Diagnostic Context) associated with the current thread, particularly useful in multi-client, multi-threaded applications like Java servlets.
%%: Outputs a "%" character.
Additionally, modifiers can be added between % and format characters to control minimum length, maximum length, and text alignment. For example:
1)%-20: "-" indicates left alignment, fill with spaces if less than 20 characters.
2)%.30: Specifies output of category name with maximum length 30. If category name exceeds 30, left characters are truncated; if less than 30, no spaces are added.
5 Email Notification for Exceptions
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
yaml configuration
spring:
mail:
protocol: smtp
host: smtp.163.com
port: 465
username: 19937782588@163.com
password: SYRZLRNOCSLFQJKA
properties:
mail:
smtp:
auth: true
ssl:
enable: true
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appenders>
<!-- name: tag name
subject: email subject
to: recipients, multiple with comma separation aaa@163.com,bbb@qq.com
from: sender account (SMTP service must be enabled in email settings)
smtpProtocol: smtp sending protocol
smtpUsername: sender account
smtpPassword: sender password
smtpHost: server address:
POP3 server: pop.163.com
SMTP server: smtp.163.com
IMAP server: imap.163.com
smtpPort: sending port, 163 uses 465. Adding port may cause error: Got bad greeting from SMTP host smtp.163.com, port 465 , response [EOF]
-->
<SMTP name="Mail"
subject="Exception Alert[%p]"
to="admin@example.com"
from="19937782588@163.com"
smtpPassword="SYRZLRNOCSLFQJKA"
smtpUsername="19937782588@163.com"
smtpProtocol="smtp"
smtpHost="smtp.163.com"
bufferSize="10"
ignoreExceptions="false"
smtpDebug="true"
>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<pattern>%d %p [%C] [%t] [%l] %m%n</pattern>
</PatternLayout>
</SMTP>
</appenders>
<loggers>
<!-- Set current default logging level -->
<!-- Default applies to all packages under SpringBoot -->
<root level="INFO">
<!-- Configuration effective locations -->
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
<!-- Configure specific package -->
<logger name="com.example" level="debug" additivity="true">
<!-- Send logs to email -->
<appender-ref ref="Mail" level="error"/>
</logger>
</loggers>
</configuration>