2、Log4j组件

Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件 等);Layout 控制日志信息的输出格式。

1、Loggers

日志记录器:负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制:例如:name为com.ydlclass.service的logger会继承 name为com.ydlclass的logger,和JUL一致。

Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自root。root logger可以用Logger.getRootLogger()方法获取。 JUL是不是也有一个名为.的根。

2、Appenders

Appender和JUL的Handler很像,用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地 有以下几种:
输出端类型作用
ConsoleAppender将日志输出到控制台
FileAppender将日志输出到文件中
DailyRollingFileAppender将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大 小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender把日志信息保存到数据库中

3、Layouts

可以自己自定义输出格式,log4j也有实现了些Layout,比如:PatternLayout,SimpleLayout
package com.example.test;

import org.apache.log4j.*;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;

import java.io.PrintWriter;

public class TestLog4j {

    private static final Logger logger = Logger.getLogger(TestLog4j.class); // 日志记录输出

    @Test
    public void testLog4j(){
        Logger root = Logger.getRootLogger();
        ConsoleAppender consoleAppender = new ConsoleAppender();
        consoleAppender.setWriter(new PrintWriter(System.out));
        Layout layout = new Layout() {
            @Override
            public String format(LoggingEvent loggingEvent) {
                return loggingEvent.getThreadName() + ">>>>" + loggingEvent.getMessage() + "\r\n";
            }

            @Override
            public boolean ignoresThrowable() {
                return false;
            }

            @Override
            public void activateOptions() {

            }
        };
        consoleAppender.setLayout(layout);
        root.addAppender(consoleAppender);
        logger.info("hello log4j"); // 日志级别
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃 和终止运行
        logger.error("error"); // 错误信息,但不会影响系统运行
        logger.warn("warn"); // 警告信息,可能会发生问题
        logger.info("info"); // 程序运行信息,数据库的连接、网 络、IO操作等
        logger.debug("debug"); // 调试信息,一般在开发阶段使 用,记录程序的变量、参数等
        logger.trace("trace"); // 追踪信息,记录程序的所有流程 信息
    }
}

在这里插入图片描述

有一些默认的实现类:Layout layout = new SimpleLayout();

package com.example.test;

import org.apache.log4j.*;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;

import java.io.PrintWriter;

public class TestLog4j {

    private static final Logger logger = Logger.getLogger(TestLog4j.class); // 日志记录输出

    @Test
    public void testLog4j(){
        Logger root = Logger.getRootLogger();
        ConsoleAppender consoleAppender = new ConsoleAppender();
        consoleAppender.setWriter(new PrintWriter(System.out));
        Layout layout = new SimpleLayout();
        consoleAppender.setLayout(layout);
        root.addAppender(consoleAppender);
        logger.info("hello log4j"); // 日志级别
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃 和终止运行
        logger.error("error"); // 错误信息,但不会影响系统运行
        logger.warn("warn"); // 警告信息,可能会发生问题
        logger.info("info"); // 程序运行信息,数据库的连接、网 络、IO操作等
        logger.debug("debug"); // 调试信息,一般在开发阶段使 用,记录程序的变量、参数等
        logger.trace("trace"); // 追踪信息,记录程序的所有流程 信息
    }
}

在这里插入图片描述

他的实现太简单了:

 public String format(LoggingEvent event) {
        this.sbuf.setLength(0);
        this.sbuf.append(event.getLevel().toString());
        this.sbuf.append(" - ");
        this.sbuf.append(event.getRenderedMessage());
        this.sbuf.append(LINE_SEP);
        return this.sbuf.toString();
    }

还有一个比较常用的Layout,就是PatternLayout这个实现类,能够根据特定的占位符进行转化,和JUL很像,但是又不一样,我们庖丁解牛研究一番,首先看他的构造器,构造器中如果传入一个pattern字符串,他会根据这个pattern创建一个链表,这个链表具体干什么咱们慢慢往后看:

 public PatternLayout(String pattern) {
        this.BUF_SIZE = 256;
        this.MAX_CAPACITY = 1024;
        this.sbuf = new StringBuffer(256);
        this.pattern = pattern;
        this.head = this.createPatternParser(pattern == null ? "%m%n" : pattern).parse();
    }

将步骤拆解开来看,首先创建了一个解析器:

    protected PatternParser createPatternParser(String pattern) {
        return new PatternParser(pattern);
    }

查看parse方法,这个方法比较复杂我们简化来看:

public PatternConverter parse() {
        this.i = 0;

        while(true) {
            while(true) {
                while(this.i < this.patternLength) {
                    char c = this.pattern.charAt(this.i++);
                    switch(this.state) {
                    case 0:
                        if (this.i == this.patternLength) {
                            this.currentLiteral.append(c);
                        } else if (c == '%') {
                            switch(this.pattern.charAt(this.i)) {
                            case '%':
                                this.currentLiteral.append(c);
                                ++this.i;
                                break;
                            case 'n':
                                this.currentLiteral.append(Layout.LINE_SEP);
                                ++this.i;
                                break;
                            default:
                                if (this.currentLiteral.length() != 0) {
                                    this.addToList(new PatternParser.LiteralPatternConverter(this.currentLiteral.toString()));
                                }

                                this.currentLiteral.setLength(0);
                                this.currentLiteral.append(c);
                                this.state = 1;
                                this.formattingInfo.reset();
                            }
                        } else {
                            this.currentLiteral.append(c);
                        }
                        break;
                    case 1:
                        this.currentLiteral.append(c);
                        switch(c) {
                        case '-':
                            this.formattingInfo.leftAlign = true;
                            break;
                        case '.':
                            this.state = 3;
                            break;
                        default:
                            if (c >= '0' && c <= '9') {
                                this.formattingInfo.min = c - 48;
                                this.state = 4;
                            } else {
                                this.finalizeConverter(c);
                            }
                        }
                    case 2:
                    default:
                        break;
                    case 3:
                        this.currentLiteral.append(c);
                        if (c >= '0' && c <= '9') {
                            this.formattingInfo.max = c - 48;
                            this.state = 5;
                            break;
                        }

                        LogLog.error("Error occured in position " + this.i + ".\n Was expecting digit, instead got char \"" + c + "\".");
                        this.state = 0;
                        break;
                    case 4:
                        this.currentLiteral.append(c);
                        if (c >= '0' && c <= '9') {
                            this.formattingInfo.min = this.formattingInfo.min * 10 + (c - 48);
                        } else if (c == '.') {
                            this.state = 3;
                        } else {
                            this.finalizeConverter(c);
                        }
                        break;
                    case 5:
                        this.currentLiteral.append(c);
                        if (c >= '0' && c <= '9') {
                            this.formattingInfo.max = this.formattingInfo.max * 10 + (c - 48);
                        } else {
                            this.finalizeConverter(c);
                            this.state = 0;
                        }
                    }
                }

                if (this.currentLiteral.length() != 0) {
                    this.addToList(new PatternParser.LiteralPatternConverter(this.currentLiteral.toString()));
                }

                return this.head;
            }
        }
    }

而finalizeConverter做的工作大家就能看的很清楚了:

protected void finalizeConverter(char c) {
        PatternConverter pc = null;
        switch(c) {
        case 'C':
            pc = new PatternParser.ClassNamePatternConverter(this.formattingInfo, this.extractPrecisionOption());
            this.currentLiteral.setLength(0);
            break;
        case 'D':
        case 'E':
        case 'G':
        case 'H':
        case 'I':
        case 'J':
        case 'K':
        case 'N':
        case 'O':
        case 'P':
        case 'Q':
        case 'R':
        case 'S':
        case 'T':
        case 'U':
        case 'V':
        case 'W':
        case 'Y':
        case 'Z':
        case '[':
        case '\\':
        case ']':
        case '^':
        case '_':
        case '`':
        case 'a':
        case 'b':
        case 'e':
        case 'f':
        case 'g':
        case 'h':
        case 'i':
        case 'j':
        case 'k':
        case 'n':
        case 'o':
        case 'q':
        case 's':
        case 'u':
        case 'v':
        case 'w':
        default:
            LogLog.error("Unexpected char [" + c + "] at position " + this.i + " in conversion patterrn.");
            pc = new PatternParser.LiteralPatternConverter(this.currentLiteral.toString());
            this.currentLiteral.setLength(0);
            break;
        case 'F':
            pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1004);
            this.currentLiteral.setLength(0);
            break;
        case 'L':
            pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1003);
            this.currentLiteral.setLength(0);
            break;
        case 'M':
            pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1001);
            this.currentLiteral.setLength(0);
            break;
        case 'X':
            String xOpt = this.extractOption();
            pc = new PatternParser.MDCPatternConverter(this.formattingInfo, xOpt);
            this.currentLiteral.setLength(0);
            break;
        case 'c':
            pc = new PatternParser.CategoryPatternConverter(this.formattingInfo, this.extractPrecisionOption());
            this.currentLiteral.setLength(0);
            break;
        case 'd':
            String dateFormatStr = "ISO8601";
            String dOpt = this.extractOption();
            if (dOpt != null) {
                dateFormatStr = dOpt;
            }

            Object df;
            if (dateFormatStr.equalsIgnoreCase("ISO8601")) {
                df = new ISO8601DateFormat();
            } else if (dateFormatStr.equalsIgnoreCase("ABSOLUTE")) {
                df = new AbsoluteTimeDateFormat();
            } else if (dateFormatStr.equalsIgnoreCase("DATE")) {
                df = new DateTimeDateFormat();
            } else {
                try {
                    df = new SimpleDateFormat(dateFormatStr);
                } catch (IllegalArgumentException var7) {
                    LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, var7);
                    df = (DateFormat)OptionConverter.instantiateByClassName("org.apache.log4j.helpers.ISO8601DateFormat", DateFormat.class, (Object)null);
                }
            }

            pc = new PatternParser.DatePatternConverter(this.formattingInfo, (DateFormat)df);
            this.currentLiteral.setLength(0);
            break;
        case 'l':
            pc = new PatternParser.LocationPatternConverter(this.formattingInfo, 1000);
            this.currentLiteral.setLength(0);
            break;
        case 'm':
            pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2004);
            this.currentLiteral.setLength(0);
            break;
        case 'p':
            pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2002);
            this.currentLiteral.setLength(0);
            break;
        case 'r':
            pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2000);
            this.currentLiteral.setLength(0);
            break;
        case 't':
            pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2001);
            this.currentLiteral.setLength(0);
            break;
        case 'x':
            pc = new PatternParser.BasicPatternConverter(this.formattingInfo, 2003);
            this.currentLiteral.setLength(0);
        }

        this.addConverter((PatternConverter)pc);
    }

addConverter就是一个典型的链表结构的构建了:

protected void addConverter(PatternConverter pc) {
        this.currentLiteral.setLength(0);
        this.addToList(pc);
        this.state = 0;
        this.formattingInfo.reset();
    }
private void addToList(PatternConverter pc) {
        if (this.head == null) {
            this.head = this.tail = pc;
        } else {
            this.tail.next = pc;
            this.tail = pc;
        }

    }

构建完转化器链表之后,就是循环这个链表,一次处理对应的占位符了,他的核心的格式化的方法也是format方法,在format方法中是通过一个转化器链来完成转化的:

 public String format(LoggingEvent event) {
        if (this.sbuf.capacity() > 1024) {
            this.sbuf = new StringBuffer(256);
        } else {
            this.sbuf.setLength(0);
        }

        for(PatternConverter c = this.head; c != null; c = c.next) {
            c.format(this.sbuf, event);
        }

        return this.sbuf.toString();
    }

这里就是通过一个pattern字符串,这个字符串可能张这个样子(%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -[%p] %m%n),使用
createPatternParser().parse()方法构建一个处理器的链表,这个每个处理器处理一个占位符比如(%d)。进入PatternLayout.format()方法,会进入c.format,这是我们会进入一个抽象类PatternConverter中的format方法,里边的核心就是如下代码:重要是String s = this.convert(e); 发现convert是个抽象方法,所以此时会根据不同的占位符选择不同的处理器。

 public void format(StringBuffer sbuf, LoggingEvent e) {
        String s = this.convert(e);
        if (s == null) {
            if (0 < this.min) {
                this.spacePad(sbuf, this.min);
            }

        } else {
            int len = s.length();
            if (len > this.max) {
                sbuf.append(s.substring(len - this.max));
            } else if (len < this.min) {
                if (this.leftAlign) {
                    sbuf.append(s);
                    this.spacePad(sbuf, this.min - len);
                } else {
                    this.spacePad(sbuf, this.min - len);
                    sbuf.append(s);
                }
            } else {
                sbuf.append(s);
            }

        }
    }
%m 输出代码中指定的日志信息 
%p 输出日志级别,及 DEBUG、INFO 等 
%n 换行符(Windows平台的换行符为 "\n"Unix 平台为 "\n"%r 输出自应用启动到输出该 log 信息耗费的毫秒数 
%c 输出打印语句所属的类的全名 
%t 输出产生该日志的线程全名 
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如: %d{yyyy年MM月dd日HH:mm:ss} 
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如: Test.main(Test.java:10) 
%F 输出日志消息产生时所在的文件名称 
%L 输出代码中的行号 
%% 输出一个 "%" 字符 
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对 其方式。如: 
%5c 输出category名称,最小宽度是5,category<5,默认的情况下右 对齐
%-5c 输出category名称,最小宽度是5,category<5"-"号指定左 对齐,会有空格 
%.5c 输出category名称,最大宽度是5,category>5,就会将左边多 出的字符截掉,<5不会有空格 
%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边 交远销出的字符截掉

尝试写一个:

package com.example.test;

import org.apache.log4j.*;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;

import java.io.PrintWriter;

public class TestLog4j {

    private static final Logger logger = Logger.getLogger(TestLog4j.class); // 日志记录输出

    @Test
    public void testLog4j(){
        Logger root = Logger.getRootLogger();
        ConsoleAppender consoleAppender = new ConsoleAppender();
        consoleAppender.setWriter(new PrintWriter(System.out));
        Layout layout = new PatternLayout("%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -[%p] %m%n");
        consoleAppender.setLayout(layout);
        root.addAppender(consoleAppender);
        logger.info("hello log4j"); // 日志级别
        logger.fatal("fatal"); // 严重错误,一般会造成系统崩溃 和终止运行
        logger.error("error"); // 错误信息,但不会影响系统运行
        logger.warn("warn"); // 警告信息,可能会发生问题
        logger.info("info"); // 程序运行信息,数据库的连接、网 络、IO操作等
        logger.debug("debug"); // 调试信息,一般在开发阶段使 用,记录程序的变量、参数等
        logger.trace("trace"); // 追踪信息,记录程序的所有流程 信息
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值