最详细、最全面的【Java日志框架】介绍,建议收藏,包含JUL、log4j、logback、log4j2等所有主流框架(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 最详细、最全面的【Java日志框架】介绍,建议收藏,包含JUL、log4j、logback、log4j2等所有主流框架(下)

3️⃣Logback组件


🍀(1)appender


输出源,一个日志可以后好几个输出源


🍀(2)encoder


一个appender有一个encoder,负责将一个event事件转换成一组byte数组,并将转换后的字节数据输出到文件中。


Encoder负责把事件转换为字节数组,并把字节数组写到合适的输出流。因此,encoder可以控制在什么时候、把什么样的字节数组写入到其拥有者维护的输出流中。Encoder接口有两个实现类,LayoutWrappingEncoder与PatternLayoutEncoder。


注意:在logback 0.9.19 版之前没有 encoder。


在之前的版本里,多数 appender 依靠 layout 来把事件转换成字符串并用 java.io.Writer 把字符串输出。在之前的版本里,用户需要在 FileAppender里嵌入一个 PatternLayout。


🍀(3)layout


格式化数据将event事件转化为字符串,解析的过程。


🍀(4)filter 过滤器

LevelFilter levelFilter = new LevelFilter();
        levelFilter.setOnMatch(FilterReply.DENY);
        levelFilter.setLevel(Level.WARN);
        levelFilter.start();
        ca.addFilter(levelFilter);

%-5level

%d{yyyy-MM-dd HH:mm:ss.SSS} 日期

%c 类的完整名称

%M 为method

%L 为行号

%thread 线程名称

%m或者%msg 为信息

%n换行


4️⃣Logback配置


🍀(1)基本配置信息

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
    %msg:日志消息,%n是换行符-->
    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread]
                                    %-5level %msg%n"/>
    <!--
      Appender: 设置日志信息的去向,常用的有以下几个
        ch.qos.logback.core.ConsoleAppender (控制台)
        ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
        ch.qos.logback.core.FileAppender (文件)
        -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <!--
  用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
                    <loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
                    name:
        用来指定受此logger约束的某一个包或者具体的某一个类。
            level:
        用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和
            OFF,
            如果未设置此属性,那么当前logger将会继承上级的级别。
            additivity:
        是否向上级loger传递打印信息。默认是true。
            <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个
            logger
            -->
    <!--
            也是<logger>元素,但是它是根logger。默认debug
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL
                和 OFF,
                <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个
                logger。
        -->
    <root level="ALL">
        <appender-ref ref="console"/>
    </root>
</configuration>

🍀(2)FileAppender配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 自定义属性 可以通过${name}进行引用-->
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M
                                    %L [%thread] %m %n"/>
    <!--
    日志输出格式:
    %d{pattern}日期
    %m或者%msg为信息
    %M为method
    %L为行号
    %c类的完整名称
    %thread线程名称
    %n换行
    %-5level
    -->
    <!-- 日志文件存放目录 -->
    <property name="log_dir" value="d:/logs"></property>
    <!--控制台输出appender对象-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder
                 class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <!--日志文件输出appender对象-->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/logback.log</file>
    </appender>
    <!-- 生成html格式appender对象 -->
    <appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern>
            </layout>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/logback.html</file>
    </appender>
    <!--RootLogger对象-->
    <root level="all">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
        <appender-ref ref="htmlFile"/>
    </root>
</configuration>

🍀(3)RollingFileAppender配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 自定义属性 可以通过${name}进行引用-->
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M
                                    %L [%thread] %m %n"/>
    <!--
    日志输出格式:
    %d{pattern}日期
    %m或者%msg为信息
    %M为method
    %L为行号
    %c类的完整名称
    %thread线程名称
    %n换行
    %-5level
    -->
    <!-- 日志文件存放目录 -->
    <property name="log_dir" value="d:/logs"></property>
    <!--控制台输出appender对象-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder
                 class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <!-- 日志文件拆分和归档的appender对象-->
    <appender name="rollFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder
                 class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>
    <!--RootLogger对象-->
    <root level="all">
        <appender-ref ref="console"/>
        <appender-ref ref="rollFile"/>
    </root>
</configuration>

🍀(4)Filter和异步日志配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 自定义属性 可以通过${name}进行引用-->
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M
                                    %L [%thread] %m %n"/>
    <!--
    日志输出格式:
    %d{pattern}日期
    %m或者%msg为信息
    %M为method
    %L为行号
    %c类的完整名称
    %thread线程名称
    %n换行
    %-5level
    -->
    <!-- 日志文件存放目录 -->
    <property name="log_dir" value="d:/logs/"></property>
    <!--控制台输出appender对象-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志格式配置-->
        <encoder
                 class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <!-- 日志文件拆分和归档的appender对象-->
    <appender name="rollFile"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志格式配置-->
        <encoder
                 class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}roll_logback.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                       class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>${log_dir}rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--filter配置-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置拦截日志级别-->
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--异步日志-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="rollFile"/>
    </appender>
    <!--RootLogger对象-->
    <root level="all">
        <appender-ref ref="console"/>
        <appender-ref ref="async"/>
    </root>
    <!--自定义logger additivity表示是否从 rootLogger继承配置-->
    <logger name="com.ydlclass" level="debug" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

5️⃣Logback-access的使用


在server.xml里的< host>标签下加上如下所示就可以了。


<Valve className="org.apache.catalina.valves.AccessLogValve"  directory="logs" prefix="localhost_access_log." suffix=".txt"  pattern="common" resolveHosts="false"/>


下面咱们逐一分析各个参数:


className 想配置访问日志?这就必须得写成这样。
directory 这个东西是日志文件放置的目录,在tomcat下面有个logs文件夹,那里面是专门放置日志文件的,当然你也可以修改,我就给改成了D:\
prefix 这个是日志文件的名称前缀,我的日志名称为localhost_access_log.2007-09-22.txt,前面的前缀就是这个localhost_access_log
suffix 这就是后缀名啦,可以改成别的
pattern 这个是最主要的参数了,具体的咱们下面讲,这个参数的内容比较丰富。
resolveHosts 如果这个值是true的话,tomcat会将这个服务器IP地址通过DNS转换为主机名,如果是false,就直接写服务器IP地址啦


logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。


(1)将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下;


(2)修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:

<Valve className="ch.qos.logback.access.tomcat.LogbackValve"/>

(3)logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- always a good activate OnConsoleStatusListener -->
    <statusListener
                    class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
    <property name="LOG_DIR" value="${catalina.base}/logs"/>
    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/access.log</file>
        <rollingPolicy
                       class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <!-- 访问日志的格式 -->
            <pattern>combined</pattern>
        </encoder>
    </appender>
    <appender-ref ref="FILE"/>
</configuration>
%h %l %u %user %date "%r" %s %b

755c9a9521004396923d62714e8abaf0.png

方配置: https://logback.qos.ch/access.html#configuration


六、 Log4j2日志框架


前已经有三个门面了,其实不管是哪里都是江湖,都想写一个门面,一统江湖,所以log42出了提供日志实现以外,也拥有一套自己的独立的门面。


目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。


1️⃣Log4j2入门


🍀(1)使用log4j-api做门面

(1)添加依赖

<!-- Log4j2 门面API-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>
</dependency>

(2)JAVA代码

public class TestLog4j2 {
    private static final Logger LOGGER = LogManager.getLogger(TestLog4j2.class);
    @Test
    public void testLog(){
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");
    }
}

(3)结果

1f13e62e70c04785a825690c9656047c.png

🍀(2)使用slf4j做门面


使用slf4j作为日志的门面,使用log4j2作为日志的实现。


(1)添加依赖

<!-- Log4j2 门面API-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>
</dependency>
<!--使用slf4j作为日志的门面,使用log4j2来记录日志 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
<!--为slf4j绑定日志实现 log4j2的适配器 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.12.1</version>
</dependency>

(2)JAVA代码

private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TestLog4j2.class);
@Test
public void testSlf4j(){
    LOG.error("error");
    LOG.warn("warn");
    LOG.debug("debug");
    LOG.info("info");
    LOG.trace("trace");
}

(3)结果

bf7ea4ec4d794cb59072242d75baf8d9.png

我们看到log4j2的默认日志级别好像是error。


2️⃣Log4j2配置


🍀默认配置


DefaultConfiguration类中提供的默认配置将设置,


通过debug可以在LoggerContext类中发现


private volatile Configuration configuration = new DefaultConfiguration();


可以看到默认的root日志的layout


1b49a7a13f0240308608a1f216de35ff.png

我们也能看到他的日志级别:


bad8ae8405504d3697d440654ae6eea1.png

我们能从默认配置类中看到一些默认的配置:

protected void setToDefault() {
    // LOG4J2-1176 facilitate memory leak investigation
    setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
    final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
        .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
        .withConfiguration(this)
        .build();
    final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
    appender.start();
    addAppender(appender);
    final LoggerConfig rootLoggerConfig = getRootLogger();
    rootLoggerConfig.addAppender(appender, null, null);
    final Level defaultLevel = Level.ERROR;
    final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL,
                                                                              defaultLevel.name());
    final Level level = Level.valueOf(levelName);
    rootLoggerConfig.setLevel(level != null ? level : defaultLevel);
}

🍀自定义配置文件位置

log4j2默认在classpath下查找配置文件,可以修改配置文件的位置。在非web项目中:

public static void main(String[] args) throws IOException {
  File file = new File("D:/log4j2.xml");
  BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
  final ConfigurationSource source = new ConfigurationSource(in);
  Configurator.initialize(null, source);
  Logger logger = LogManager.getLogger("mylog");
}

如果是web项目,在web.xml中添加:


<context-param>
    <param-name>log4jConfiguration</param-name>
    <param-value>/WEB-INF/conf/log4j2.xml</param-value>
</context-param>
<listener>
    <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>


log4j2默认加载classpath下的 log4j2.xml 文件中的配置。事实上log4j2可以通过 XML、JSON、YAML 或properties格式进行配置:https://logging.apache.org/log4j/2.x/manual/configuration.html


如果找不到配置文件,Log4j 将提供默认配置。DefaultConfiguration 类中提供的默认配置将设置:


%d{HH:mm:ss.SSS} ,表示输出到毫秒的时间

%t,输出当前线程名称

%-5level,输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0

%logger,输出logger名称,因为Root Logger没有名称,所以没有输出

%msg,日志文本

%n,换行


其他常用的占位符有:


%F,输出所在的类文件名,如Client.java

%L,输出行号

%M,输出所在方法名

%l,输出语句所在的行数, 包括类名、方法名、文件名、行数

private void reconfigure(final URI configURI) {
    Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY);
    final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
    LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                 contextName, configURI, this, cl);
    final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
    if (instance == null) {
        LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
    } else {
        setConfiguration(instance);
        /*
             * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
             * old.stop(); }
             */
        final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
        LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                     contextName, location, this, cl);
    }
}

ConfigurationFactory:

for (final ConfigurationFactory factory : getFactories()) {
    final String[] types = factory.getSupportedTypes();
    if (types != null) {
        for (final String type : types) {
            if (type.equals(ALL_TYPES)) {
                final Configuration config = factory.getConfiguration(loggerContext, name, configLocation);
                if (config != null) {
                    return config;
                }
            }
        }
    }
}

4a2f41deac36406bb0cbf501110bfc1a.png

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="5">
    <properties>
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L -
                                    -- %m%n" />
        </Console>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l
                                    %c{36} - %m%n" />
        </File>
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l
                                    %c{36} - %m%n" />
        </RandomAccessFile>
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l
                                    %c{36} - %msg%n" />
            <Policies>
                <OnStartupTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="10 MB" />
                <TimeBasedTriggeringPolicy />
            </Policies>
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
        <RollingRandomAccessFile name="MyFile"
      fileName="${LOG_HOME}/${FILE_NAME}.log"
      filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log">
      <PatternLayout
        pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
      <Policies>
        <TimeBasedTriggeringPolicy interval="1" />
        <SizeBasedTriggeringPolicy size="10 MB" />
      </Policies>
      <DefaultRolloverStrategy max="20" />
    </RollingRandomAccessFile>
    </Appenders>
    <Loggers>
        <Logger name="mylog" level="trace" additivity="false">
      <AppenderRef ref="MyFile" />
    </Logger>
    <Root level="error">
      <AppenderRef ref="Console" />
    </Root>
    </Loggers>
</Configuration>

注意根节点增加了一个monitorInterval属性,含义是每隔300秒重新读取配置文件,可以不重启应用的情况下修改配置,还是很好用的功能。


RollingRandomAccessFile的属性:


fileName 指定当前日志文件的位置和文件名称

filePattern 指定当发生Rolling时,文件的转移和重命名规则

SizeBasedTriggeringPolicy 指定当文件体积大于size指定的值时,触发Rolling

DefaultRolloverStrategy 指定最多保存的文件个数

TimeBasedTriggeringPolicy 这个配置需要和filePattern结合使用,注意filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i,最小的时间粒度是mm,即分钟。

TimeBasedTriggeringPolicy指定的size是1,结合起来就是每1分钟生成一个新文件。如果改成%d{yyyy-MM-dd

HH},最小粒度为小时,则每一个小时生成一个文件。


3️⃣Log4j2异步日志


log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用 log4j2的异步日志。


🍀同步日志

ba0f671a62624527835253527cfc5445.png


🍀异步日志

6bee94802e5a45a8a0a7cf9600d26a36.png289c34d2a8ce4a8f9c3d20b06b515bb1.png

 

Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应前面我们说的Appender组件和Logger组件。

注意:配置异步日志需要添加依赖

<!--异步日志依赖-->
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.4</version>
</dependency>

(1)AsyncAppender方式

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <properties>
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
        </File>
        <Async name="Async">
            <AppenderRef ref="file"/>
        </Async>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Async"/>
        </Root>
    </Loggers>
</Configuration>

(2)AsyncLogger方式


AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。


全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置。


Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <properties>
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
        </File>
        <Async name="Async">
            <AppenderRef ref="file"/>
        </Async>
    </Appenders>
    <Loggers>
        <AsyncLogger name="com.ydlclass" level="trace"
                     includeLocation="false" additivity="false">
            <AppenderRef ref="file"/>
        </AsyncLogger>
        <Root level="info" includeLocation="true">
            <AppenderRef ref="file"/>
        </Root>
    </Loggers>
</Configuration>

如上配置: com.ydlclass 日志是异步的,root日志是同步的。


使用异步日志需要注意的问题:


如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和AsyncAppender一致,降至最低。

设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

for (int i = 0; i < 1000000; i++) {
    LOGGER.fatal("fatal");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
2970

七、怎么打印日志


🍀基本格式


必须使用参数化信息的方式:

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);

不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。反例(不要这么做):

logger.debug("Processing trade with id: " + id + " symbol: " + symbol);

使用[]进行参数变量隔离,如有参数变量,应该写成如下写法:

logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol)


这样的格式写法,可读性更好,对于排查问题更有帮助。


不同级别的使用如下:


🍀ERROR,影响到程序正常运行、当前请求正常运行的异常情况


打开配置文件失败

所有第三方对接的异常(包括第三方返回错误码)

所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)

不应该出现的情况,比如要使用阿里云传图片,但是未响应

如果有Throwable信息,需要记录完成的堆栈信息

log.error("获取用户[{}]的用户信息时出错",userName,e);

说明,如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理:

反例(不要这么做):

try{
    ....
}catch(Exception ex){
    String errorMessage=String.format("Error while reading information of user [%s]",userName);
    logger.error(errorMessage,ex);
    throw new UserServiceException(errorMessage,ex);
}

🍀WARN,不应该出现但是不影响程序、当前请求正常运行的异常情况


有容错机制的时候出现的错误情况

找不到配置文件,但是系统能自动创建配置文件

即将接近临界值的时候,例如:缓存池占用达到警告线,业务异常的记录,比如:用户锁定异常

🍀INFO,系统运行信息


Service方法中对于系统/业务状态的变更

主要逻辑中的分步骤:1,初始化什么 2、加载什么

外部接口部分

客户端请求参数(REST/WS)

调用第三方时的调用参数和调用结果

对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)

调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)

说明:并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。


反例(不要这么做):

public List listByBaseType(Integer baseTypeId) {
    log.info("开始查询基地");
    BaseExample ex=new BaseExample();
    BaseExample.Criteria ctr = ex.createCriteria();
    ctr.andIsDeleteEqualTo(IsDelete.USE.getValue());
    Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo);
    log.info("查询基地结束");
    return baseRepository.selectByExample(ex);
}

🍀DEBUG,可以填写所有的想知道的相关信息(但不代表可以随便写,debug信息要有意义,最好有相关参数)


生产环境需要关闭DEBUG信息

如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启

说明:如果代码中出现以下代码,可以进行优化:


获取用户基本薪资

获取用户休假情况

计算用户应得薪资

logger.debug("开始获取员工[{}] [{}]年基本薪资",employee,year);
logger.debug("获取员工[{}] [{}]年的基本薪资为[{}]",employee,year,basicSalary);
logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况",employee,year,month);
logger.debug("员工[{}][{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]",employee,year,month,annualLeaveDays,sickLeaveDays,noPayLeaveDays);
logger.debug("开始计算员工[{}][{}]年[{}]月应得薪资",employee,year,month);
logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]",employee,year,month,actualSalary);

🍀TRACE,特别详细的系统运行完成信息,业务代码中,不要使用.(除非有特殊用意,否则请使用DEBUG级别替代)

规范示例说明:

@Override
@Transactional
public void createUserAndBindMobile(@NotBlank String mobile, @NotNull User user) throws CreateConflictException{
    boolean debug = log.isDebugEnabled();
    if(debug){
        log.debug("开始创建用户并绑定手机号. args[mobile=[{}],user=[{}]]", mobile, LogObjects.toString(user));
    }
    try {
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        userRepository.insertSelective(user);
        if(debug){
            log.debug("创建用户信息成功. insertedUser=[{}]",LogObjects.toString(user));
        }
        UserMobileRelationship relationship = new UserMobileRelationship();
        relationship.setMobile(mobile);
        relationship.setOpenId(user.getOpenId());
        relationship.setCreateTime(new Date());
        relationship.setUpdateTime(new Date());
        userMobileRelationshipRepository.insertOnDuplicateKey(relationship);
        if(debug){
            log.debug("绑定手机成功. relationship=[{}]",LogObjects.toString(relationship));
        }
        log.info("创建用户并绑定手机号. userId=[{}],openId=[{}],mobile[{}]",user.getId(),user.getOpenId(),mobile);    // 如果考虑安全,手机号记得脱敏
    }catch(DuplicateKeyException e){
        log.info("创建用户并绑定手机号失败,已存在相同的用户. openId=[{}],mobile=[{}]",user.getOpenId(),mobile);
        throw new CreateConflictException("创建用户发生冲突, openid=[%s]",user.getOpenId());
    }
}


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
27天前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
27天前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
22天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
33 4
|
1月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
42 2
|
1月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
47 0
|
1月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
286 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
10天前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
|
2月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
318 3
|
20天前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
2月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1698 14
下一篇
DataWorks