掘金 后端 ( ) • 2024-03-20 10:16

theme: smartblue

本篇文章中涉及到的所有代码都已经上传到gitee中: https://gitee.com/sss123a/log/tree/master/matio-log4j/src/main/java/com/matio/log4j

上一篇

# 01 log4j入门及xml配置详解

log4j组件

在Log4j中,主要由三个重要组件构成:

  • Logger:负责捕捉日志记录信息;
  • Appender:负责把格式好的日志信息输出到指定地方,可以是控制台、磁盘文件等;
  • Layout:负责发布不同风格的日志信息;

Logger

负责处理日志记录。实例的命名就是类的全限定名。Logger 的名字大小写敏感。其命名有继承机制。如:org.apache.commons 的 logger 会继承 org.apache 的 logger。

Log4j 中,有一个特殊的 logger,叫 root,它是所有的 logger 的根。root logger 可以通过 Logger.getRootLogger() 方法获取。

Level

日记记录的优先级priority,优先级由高到低分为

OFF , FATAL , ERROR , WARN , INFO , DEBUG , ALL。

log4j建议使用FATAL ,ERROR ,WARN ,INFO ,DEBUG这五个级别。

  • OFF 为最高等级 关闭了日志信息
  • FATAL 为可能导致应用中止的严重事件错误
  • ERROR 为严重错误 主要是程序的错误
  • WARN 为一般警告,比如session丢失
  • INFO 为一般要显示的信息,比如登录登出
  • DEBUG 为程序的调试信息
  • TRACE 为比DEBUG更细粒度的事件信息
  • ALL 为最低等级,将打开所有级别的日志

Log4j 默认级别是 debug,只要日志级别高于 debug,都会输出

每个Logger可关联多个Appender,Appender作为日志输出的目的地处理器,对应于JUL的Handler,可以输出到如控制台,文件,网络等地方,其关联了对应的Layout,用于格式化日志输出。

Appender(配置日志信息输出位置),同jul中的Handler

  • ConsoleAppender: 日志输出到控制台;
  • FileAppender:输出到文件
  • RollingFileAppender:输出到文件,文件达到一定阈值时,自动备份日志文件
  • DailyRollingFileAppender:可定期备份日志文件,默认一天一个文件,也可设置为每分钟一个、每小时一个
  • WriterAppender:可自定义日志输出位置。

这里的测试DEMO都是以xml配置文件为基础的,如果不清楚的可以详见我之前的文章: https://juejin.cn/post/7347617413984927778

Appender实现原理:

public class Logger extends Category {
    AppenderAttachableImpl aai;
    // 可以向每一个Logger注册多个Appender对象
    synchronized public void addAppender(Appender newAppender) {  
        if(aai == null) {  
            aai = new AppenderAttachableImpl();  
        }  
        aai.addAppender(newAppender);  
        repository.fireAddAppenderEvent(this, newAppender);  
    }
    
    // logger在打印日志时会调用到该方法
    public void callAppenders(LoggingEvent event) {  
        int writes = 0;  

        for(Category c = this; c != null; c=c.parent) {  
            // Protected against simultaneous call to addAppender, removeAppender,...  
            synchronized(c) {  
                if(c.aai != null) {  
                    // 遍历各个Appender的doAppend()
                    writes += c.aai.appendLoopOnAppenders(event);  
                }  
                if(!c.additive) {  
                    break;  
                }  
            }  
        }  
        
        if(writes == 0) {  
            repository.emitNoAppenderWarning(this);  
        }  
    }

}

FileAppender

将日志保存到一个文件中,不支持日志备份,可能会导致这个日志文件越来越庞大,所以这个appender使用很少

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.xml.DOMConfigurator;  
  
public class FileAppenderTest {  
    public static void main(String[] args) {  
        // 加载resources/log4j-fileappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-fileappender.xml"));  
        Logger logger = Logger.getLogger("fff");  
        logger.info("测试info");  
    }  
}

log4j-fileappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="fff" additivity="false">  
        <appender-ref ref="xxx"/>  
        <level value="info"/>  
    </logger>  

    <!-- FileAppender:将日志保存到一个文件中 -->  
    <appender name="xxx" class="org.apache.log4j.FileAppender">  
        <!-- file:日志文件 -->  
        <param name="file" value="D:/test/bis-file.log"/>  
        <!-- append:是否以追加的方式记录日志,默认true -->  
        <param name="append" value="true"/>  
        <!-- bufferedIO:是否启用缓冲区,这时日志并不会立即刷新到文件中,可能会存在延迟或程序停止后丢失(因为日志还在缓冲区中),默认false -->  
        <param name="bufferedIO" value="false"/>  
        <!-- bufferedIO:当bufferedIO=true时有效,指定缓冲区的大小,默认8k -->  
        <param name="bufferSize" value="1024"/>  

        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!-- threshold:日志级别 -->  
        <param name="threshold" value="debug"/>  
        <!-- encoding:文件内容编码 -->  
        <param name="encoding" value="UTF-8"/>  
        <!-- errorHandler:输出日志时如果出现异常就交给该handler处理 -->  
        <errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>  
        <!-- immediateFlush:每来一条日志就刷新到文件中-->  
        <param name="immediateFlush" value="true"/>  
    </appender>  
</log4j:configuration>

DailyRollingFileAppender

按照指定频率对日志文件进行备份,默认每天备份一次

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.xml.DOMConfigurator;  
  
import java.util.Random;  
  
public class DailyRollingFileAppenderTest {  
    public static void main(String[] args) throws InterruptedException { 
        // 加载resources/log4j-dailyrollingfileappender.xml文件
        DOMConfigurator.configure(Loader.getResource("log4j-dailyrollingfileappender.xml"));  

        Logger logger = Logger.getLogger("aaa");  
        int index = 0;  
        while (true) {  
            logger.info("test" + (++index));  
            Thread.sleep(new Random().nextInt(1000));  
        }  
    }  
}

log4j-dailyrollingfileappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="aaa" additivity="false">  
        <appender-ref ref="xxx"/>  
        <level value="info"/>  
    </logger>  

    <!-- DailyRollingFileAppender:按照指定频率对日志文件进行备份,默认每天备份一次 -->  
    <appender name="xxx" class="org.apache.log4j.DailyRollingFileAppender">  
        <!-- file:日志文件 -->  
        <param name="file" value="D:/test/bis.log"/>  
        <!-- datePattern:按照什么频率来滚动日志  
            '.'yyyy-MM 对应monthly(每月)  
            '.'yyyy-ww 对应weekly(每周)  
            '.'yyyy-MM-dd 对应daily(每天) 默认  
            '.'yyyy-MM-dd-a 对应half-daily(每半天)  
            '.'yyyy-MM-dd-HH 对应hourly(每小时)  
            '.'yyyy-MM-dd-HH-mm 对应minutely(每分钟)  
        -->  
        <param name="datePattern" value="'.'yyyy-MM-dd-HH-mm"/>  
        <!-- append:是否以追加的方式记录日志,默认true -->  
        <param name="append" value="true"/>  
        <!-- bufferedIO:是否启用缓冲区,这时日志并不会立即刷新到文件中,可能会存在延迟或程序停止后丢失(因为日志还在缓冲区中),默认false -->  
        <param name="bufferedIO" value="false"/>  
        <!-- bufferedIO:当bufferedIO=true时有效,指定缓冲区的大小,默认8k -->  
        <param name="bufferSize" value="1024"/>  

        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>
        </layout>  
        <!-- threshold:日志级别 -->  
        <param name="threshold" value="debug"/>  
        <!-- encoding:文件内容编码 -->  
        <param name="encoding" value="UTF-8"/>  
        <!-- errorHandler:输出日志时如果出现异常就交给该handler处理 -->  
        <errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>  
        <!-- immediateFlush:每来一条日志就刷新到文件中-->  
        <param name="immediateFlush" value="true"/>  
    </appender>  
</log4j:configuration>

RollingFileAppender

在日志文件达到一定大小时对其进行备份

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.xml.DOMConfigurator;  
  
import java.util.Random;  
  
public class RollingFileAppenderTest {  
    public static void main(String[] args) throws InterruptedException {  
        // 加载resources/log4j-rollingfileappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-rollingfileappender.xml"));  

        Logger logger = Logger.getLogger("aaa");  
        int index = 0;  
        while (true) {  
            logger.info("test" + (++index));  
            Thread.sleep(new Random().nextInt(1000));  
        }  
    }  
}

log4j-rollingfileappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="aaa" additivity="false">  
        <appender-ref ref="xxx"/>  
        <level value="info"/>  
    </logger>  

    <!-- RollingFileAppender:在日志文件达到一定大小时对其进行备份 -->  
    <appender name="xxx" class="org.apache.log4j.RollingFileAppender">  
        <!-- maxBackupIndex:最多允许存在多少个备份,默认1个 -->  
        <param name="maxBackupIndex" value="2"/>  
        <!-- maxFileSize:当日志文件超过阈值后备份,默认阈值为10M -->  
        <param name="maxFileSize" value="1024"/>  
        <!-- file:日志文件位置 -->  
        <param name="file" value="D:/test/bis-rolling.log"/>  
        <!-- append:是否以追加的方式记录日志,默认true -->  
        <param name="append" value="true"/>  
        <!-- bufferedIO:是否启用缓冲区,这时日志并不会立即刷新到文件中,可以会存在延迟或程序停止后丢失(因为日志还在缓冲区中),默认false -->  
        <param name="bufferedIO" value="false"/>  
        <!-- bufferedIO:当bufferedIO=true时有效,指定缓冲区的大小,默认8k -->  
        <param name="bufferSize" value="1024"/>  

        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!-- threshold:日志级别 -->  
        <param name="threshold" value="debug"/>  
        <!-- encoding:文件内容编码 -->  
        <param name="encoding" value="UTF-8"/>  
        <!-- errorHandler:输出日志时如果出现异常就交给该handler处理 -->  
        <errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>  
        <!-- immediateFlush:每来一条日志就刷新到文件中-->  
        <param name="immediateFlush" value="true"/>  
    </appender>  
  
</log4j:configuration>

ConsoleAppender

输出日志到console

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.xml.DOMConfigurator;  
  
import java.util.Random;  
  
public class ConsoleAppenderTest {  
    public static void main(String[] args) throws InterruptedException {  
        // 加载resources/log4j-consoleappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-consoleappender.xml"));  

        Logger logger = Logger.getLogger("aaa");  
        int index = 0;  
        while (true) {  
            logger.info("test" + (++index));  
            Thread.sleep(new Random().nextInt(1000));  
        }  
    }  
}

log4j-consoleappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="aaa" additivity="false">  
        <appender-ref ref="consoleAppender1"/>  
    </logger>  

    <!-- consoleAppender:日志输出到console上 -->  
    <appender name="consoleAppender1" class="org.apache.log4j.ConsoleAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!-- 设置该appender的threshold -->  
        <param name="threshold" value="info"/>  
        <!-- immediateFlush:默认true  
            如果为true,则appender将在每次输出日志时进行刷新;  
            如果为false,那么底层流可以将写入物理介质的时间推迟到以后,避免在每次追加结束时执行刷新操作,可以获得10%到20%的性能增益。  
            然而,跳过flush会带来安全方面的权衡。事实上,当跳过flush时,当应用程序退出时,最后几个日志事件很可能不会记录在磁盘上。  
        -->  
        <param name="immediateFlush" value="true"/>  
        <!-- encoding:内容编码 -->  
        <param name="encoding" value="UTF-8"/>  
        <!-- param:System.out(默认)或者System.err-->  
        <param name="target" value="System.out"/>  
        <!-- follow:确定该appender是否接受在配置后重新分配的System.out或System.err -->  
        <param name="follow" value="true"/>  
    </appender>  
</log4j:configuration>

ExternallyRolledFileAppender

启动一个serversocket监听指定port,当收到RollOver命令后才会备份日志

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.varia.ExternallyRolledFileAppender;  
import org.apache.log4j.xml.DOMConfigurator;  
  
import java.io.*;  
import java.net.Socket;  
import java.util.Random;  
  
public class ExternallyRolledFileAppenderTest {  
    public static void main(String[] args) {  
        // 每隔5s备份一次日志  
        new Thread(() -> {  
            while (true) {  
                try {  
                    // 5s后启动一个socket客户端发送RollOver命令  
                    Thread.sleep(5000);  
                    connect();  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        }).start();  

        // 加载resources/log4j-externallyrolledfileappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-externallyrolledfileappender.xml"));  

        Logger logger = Logger.getLogger("aaa");  
        int index = 0;  
        while (true) {  
        logger.info("test" + (++index));  
            try {  
                Thread.sleep(new Random().nextInt(1000));  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
    // 启动一个socket客户端发送RollOver命令  
    private static void connect() throws IOException {  
        Socket socket = new Socket("localhost", 55555);  
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());  

        // 发生备份命令  
        dos.writeUTF(ExternallyRolledFileAppender.ROLL_OVER);  
        dos.flush();  
        System.out.println("Send: " + ExternallyRolledFileAppender.ROLL_OVER);  

        // 接收响应  
        DataInputStream dis = new DataInputStream(socket.getInputStream());  
        String respone = dis.readUTF();  
        System.out.println("Received: " + respone);  

        dos.close();  
        dis.close();  
    }  
  
}

log4j-externallyrolledfileappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="aaa" additivity="false">  
        <appender-ref ref="xxx"/>  
        <level value="info"/>  
    </logger>  

    <!-- ExternallyRolledFileAppender:启动一个serversocket监听指定port,当收到RollOver命令后才会备份日志 -->  
    <appender name="xxx" class="org.apache.log4j.varia.ExternallyRolledFileAppender">  
        <!-- port:启动serversocket监听该端口 -->  
        <param name="port" value="55555"/>  
        <!-- maxBackupIndex:最多允许存在多少个备份,默认1个 -->  
        <param name="maxBackupIndex" value="3"/>  
        <!-- maxFileSize:当日志文件超过阈值后备份,默认阈值为10M -->  
        <param name="maxFileSize" value="1024"/>  
        <!-- file:日志文件位置 -->  
        <param name="file" value="D:/test/bis-ext.log"/>  
        <!-- append:是否以追加的方式记录日志,默认true -->  
        <param name="append" value="true"/>  
        <!-- bufferedIO:是否启用缓冲区,这时日志并不会立即刷新到文件中,可能会存在延迟或程序停止后丢失(因为日志还在缓冲区中),默认false -->  
        <param name="bufferedIO" value="false"/>  
        <!-- bufferedIO:当bufferedIO=true时有效,指定缓冲区的大小,默认8k -->  
        <param name="bufferSize" value="1024"/>  

        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!-- threshold:日志级别 -->  
        <param name="threshold" value="debug"/>  
        <!-- encoding:文件内容编码 -->  
        <param name="encoding" value="UTF-8"/>  
        <!-- errorHandler:输出日志时如果出现异常就交给该handler处理 -->  
        <errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>  
        <!-- immediateFlush:默认true  
            如果为true,则appender将在每次输出日志时进行刷新;  
            如果为false,那么底层流可以将写入物理介质的时间推迟到以后,避免在每次追加结束时执行刷新操作,可以获得10%到20%的性能增益。  
            然而,跳过flush会带来安全方面的权衡。事实上,当跳过flush时,当应用程序退出时,最后几个日志事件很可能不会记录在磁盘上。  
        -->  
        <param name="immediateFlush" value="true"/>  
    </appender>  

</log4j:configuration>

AsyncAppender

异步输出日志

该类在被实例化的时候会启动一个守护线程Dispatcher, 在该appender收到日志的时候会将这条日志优先放进缓冲区(ArrayList)中,如果这个缓冲区满了就会阻塞(可配置), 然后异步线程Dispatcher去缓冲区中取出日志交给真正的appender去输出日志。所以它依赖真正输出日志的appender

package com.matio.log4j.appender;  
  
import org.apache.log4j.*;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.xml.DOMConfigurator;  
  
public class AsyncAppenderTest {  
    public static void main(String[] args) throws InterruptedException {  
        // testAsyncAppender();  
        readXmlConfig();  
    }  
  
    private static void readXmlConfig() throws InterruptedException {  
        // 加载绝对路径下文件  
        // DOMConfigurator.configure("E:/WorkspaceIdea/01src/log/matio-log4j/src/main/resources/log4j-asyncappender.xml");  
        // 加载resources/log4j-asyncappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-asyncappender.xml"));  

        Logger logger = Logger.getLogger("com.matio.log4j");  
        logger.info("测试INFO日志1");  
        logger.info("测试INFO日志2");  
        int index = 1;  
        while (true) {  
            logger.info("测试INFO日志" + (index++));  
            Thread.sleep(100);  
            if (index >= 100) {  
                break;  
            }  
        }  
    }  
  
    private static void testAsyncAppender() {  
        // LogLog.setQuietMode(false);  
        // LogLog.setInternalDebugging(true);  

        Logger logger = Logger.getLogger("test");  
        AsyncAppender asyncAppender = new AsyncAppender();  
        asyncAppender.setThreshold(Level.DEBUG);  

        ConsoleAppender consoleAppender1 = new ConsoleAppender(new PatternLayout(), "System.out");  
        consoleAppender1.setName("consoleAppender1");  
        consoleAppender1.setThreshold(Level.INFO);  
        consoleAppender1.setEncoding("UTF-8");  
        consoleAppender1.setImmediateFlush(true);  
        asyncAppender.addAppender(consoleAppender1);  

        logger.addAppender(asyncAppender);  

        logger.debug("debug");  
        logger.warn("warn");  
        logger.info("info");  
        logger.error("error");  
    }  
}

log4j-asyncappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="com.matio.log4j" additivity="false">  
        <appender-ref ref="asyncAppender1"/>  
    </logger>  

    <!-- AsyncAppender:该类在被实例化的时候会启动一个守护线程Dispatcher,  
        在该appender收到日志的时候会将这条日志优先放进缓冲区(ArrayList)中,如果这个缓冲区满了就会阻塞(可配置),  
        然后异步线程Dispatcher去缓冲区中取出日志交给真正的appender去输出日志。所以它依赖真正输出日志的appender  
    -->  
    <appender name="asyncAppender1" class="org.apache.log4j.AsyncAppender">  
        <!-- AsyncAppender其实他只负责将日志保存到缓冲区中,  
            再由异步线程去缓冲区中取出日志交给真正的appender去输出日志  
        -->  
        <appender-ref ref="consoleAppender1"/>  

        <!-- blocking:默认true,  
            如果为true,当缓冲区满了的时候,是否会阻塞  
            如果为false,在缓冲区满的时候会将日志放到一个map中,步线程Dispatcher也会去这个map中取出日志  
        -->  
        <param name="blocking" value="true"/>  
        <!-- bufferSize:缓冲区能存放的最大日志数,默认128  
            如果缓冲区满了,看看blocking解释  
            如果bufferSize<=0,记录日志的时候就不会先将日志存放到缓冲区,而是直接把日志交给它依赖的appender去处理,相当于关闭异步功能  
        -->  
        <param name="bufferSize" value="128"/>  
        <!-- locationInfo:如果true,可以通过LoggingEvent捕获请求调用的位置,默认false -->  
        <param name="locationInfo" value="false"/>  
    </appender>  

        <!-- consoleAppender:必须要设置layout -->  
    <appender name="consoleAppender1" class="org.apache.log4j.ConsoleAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <!-- 设置该appender的日志级别 -->  
        <param name="threshold" value="info"/>  
        <!-- immediateFlush:默认true  
            如果为true,则appender将在每次输出日志时进行刷新;  
            如果为false,那么底层流可以将写入物理介质的时间推迟到以后,避免在每次追加结束时执行刷新操作,可以获得10%到20%的性能增益。  
            然而,跳过flush会带来安全方面的权衡。事实上,当跳过flush时,当应用程序退出时,最后几个日志事件很可能不会记录在磁盘上。  
        -->  
        <param name="immediateFlush" value="true"/>  
        <param name="encoding" value="UTF-8"/>  
        <!-- param:System.out(默认)或者System.err-->  
        <param name="target" value="System.out"/>  
        <!-- follow:确定该appender是否接受在配置后重新分配的System.out或System.err -->  
        <param name="follow" value="true"/>  
    </appender>  
  
</log4j:configuration>

JDBCAppender

将日志保存到db中

package com.matio.log4j.appender;  
  
import org.apache.log4j.Logger;  
import org.apache.log4j.helpers.Loader;  
import org.apache.log4j.jdbc.JDBCAppender;  
import org.apache.log4j.xml.DOMConfigurator;  
  
public class JDBCAppenderTest {  
    public static void main(String[] args) {  
    // test();  
        readXmlConfig();  
    }  

    private static void readXmlConfig() {  
        // 加载resources/log4j-jdbcappender.xml文件  
        DOMConfigurator.configure(Loader.getResource("log4j-jdbcappender.xml"));  
        Logger logger = Logger.getLogger("com.matio.log4j.jdbcappender");  
        logger.info("测试info");  

        // 因为我们在xml文件设置缓冲区大小为128,所以需要我们手动刷新缓冲区中的数据保存到db中  
        JDBCAppender jdbcAppender1 = (JDBCAppender) logger.getAppender("jdbcAppender1");  
        jdbcAppender1.close();  
    }  

    private static void test() {  
        JDBCAppender jdbcAppender = new JDBCAppender();  
        jdbcAppender.setDriver("com.mysql.cj.jdbc.Driver");  
        jdbcAppender.setURL("jdbc:mysql://192.168.30.13:3306/things_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B0");  
        jdbcAppender.setUser("shuncom");  
        jdbcAppender.setPassword("sh_clighting");  
        jdbcAppender.setBufferSize(100); // 当日志数达到100条时再执行flushBuffer(),否则日志都会存储在一个ArrayList中  
        // 将日志保存到test_log4j表中  
        jdbcAppender.setSql("insert into test_log4j(log) values('%m');");  
        jdbcAppender.setLocationInfo(false);  

        Logger logger = Logger.getLogger(JDBCAppenderTest.class);  
        logger.setAdditivity(false);  
        logger.addAppender(jdbcAppender);  
        logger.info("你好2");  
        jdbcAppender.flushBuffer();  
    }  

}

log4j-jdbcappender.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  
    <logger name="com.matio.log4j.jdbcappender" additivity="false">  
        <appender-ref ref="jdbcAppender1"/>  
    </logger>  

    <!-- jdbcAppender:可以将日志保存到db中 -->  
    <appender name="jdbcAppender1" class="org.apache.log4j.jdbc.JDBCAppender">  
        <layout class="org.apache.log4j.PatternLayout">  
            <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n"/>  
        </layout>  
        <param name="driver" value="com.mysql.cj.jdbc.Driver"/>  
        <param name="URL" value="jdbc:mysql://192.168.30.13:3306/things_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>  
        <param name="user" value="shuncom"/>  
        <param name="password" value="sh_clighting"/>  
        <!-- sql:当缓冲区满后调用flushBuffer()去执行这个sql -->  
        <param name="sql" value="insert into test_log4j(log) values('%m');"/>  
        <!-- bufferSize:当缓冲区日志条数达到bufferSize时再执行flushBuffer(),否则日志都会存储在这个缓冲区中。默认为1 -->  
        <param name="bufferSize" value="128"/>  
        <!-- locationInfo:捕获日志记录请求调用的位置,默认false -->  
        <param name="locationInfo" value="true"/>  
        <!-- threshold:设置该appender的level -->  
        <param name="threshold" value="info"/>  
        <!-- errorHandler:默认是OnlyOnceErrorHandler-->  
        <errorHandler class="org.apache.log4j.helpers.OnlyOnceErrorHandler"/>  

        <!-- filter:一个appender中可以关联多个filter,它们以单向链表的形式存在-->  
        <filter class="org.apache.log4j.varia.LevelRangeFilter">  
            <!-- levelMin:最低级别,默认无下限-->  
            <param name="levelMin" value="debug"/>  
            <!-- levelMax:最高级别,默认无上限-->  
            <param name="levelMax" value="warn"/>  
            <!-- acceptOnMatch:默认false  
                如果true:必须立即记录日志事件,而无需咨询链表中后续的过滤器(如果有的话)。  
                如果为false:事件对此筛选器是可以的;交给后续的filter过滤  
            -->  
            <param name="acceptOnMatch" value="true"/>  
        </filter>  
    </appender>  
</log4j:configuration>

其他Appender

其他appender的DEMO也已经上传到gitee上,附上链接:https://gitee.com/sss123a/log/tree/master/matio-log4j/src/main/java/com/matio/log4j/appender

Layout(布局)

配置日志信息的格式 (常用格式:org.apache.log4j.PatternLayOut)

org.apache.log4j.HTMLLayout(以HTML表格形式布局) org.apache.log4j.PatternLayout(可以灵活地指定布局模式), org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

输出格式

日志信息格式中几个符号所代表的含义:

  • -X号: X信息输出时左对齐;
  • %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
  • %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
  • %r: 输出自应用启动到输出该log信息耗费的毫秒数
  • %c: 输出日志信息所属的类目,通常就是所在类的全名
  • %t: 输出产生该日志事件的线程名
  • %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main (TestLog4.java:10)
  • %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
  • %%: 输出一个"%"字符
  • %F: 输出日志消息产生时所在的文件名称
  • %L: 输出代码中的行号
  • %m: 输出代码中指定的消息,产生的日志具体信息
  • %n: 输出一个回车换行符,Windows平台为"/r/n",Unix平台为"/n"输出日志信息换行

可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:

  • %20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
  • %-20c: 指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。
  • %.30c: 指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
  • %20.30c: 如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边较远输出的字符截掉。

上一篇

# 01 log4j入门及xml配置详解