深入分析logback之日志格式化layout

Posted 一个懒惰的程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入分析logback之日志格式化layout相关的知识,希望对你有一定的参考价值。

在上文中简单介绍了一下logback日志框架的使用和原理,那在使用的过程如何根据业务变化情况如何打印出我们想要的日志格式呢?今天就这篇文章就让我们一起来学习分析下


一,logback默认日志格式化打印layouts的使用


先让我们来回顾一下上文中的日志输出如下:

2018-08-18 21:16:25,911 [main] [ERROR] [com.zhiliao.demo.DemoHelloword:16] - Hello world.
2018-08-18 21:16:25,914 [main] [DEBUG] [com.zhiliao.demo.DemoHelloword:17] - Hello world.
2018-08-18 21:16:25,915 [main] [WARN] [com.zhiliao.demo.DemoHelloword:18] - Hello world.
2018-08-18 21:16:25,915 [main] [INFO] [com.zhiliao.demo.DemoHelloword:19] - Hello world.

对比看一下encoder使用的pattern如下:

%date [%thread] [%level] [%logger:%line] - %msg%n

可以发现有时间,线程,日志级别,日志名称,行号,日志内容,换行


以上其实包含我们常用的一些日志格式化了,但是在某些情况下我们可能还想知道日志的类名方法名,执行时长,甚至想替换一些打印信息以及时间格式化

让我们把logback.xml修改如下:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder 
            by default -->

        <encoder>
            <pattern>%date{yyyy-MM-dd HH:mm:ss} [%thread] [%level] [%logger:%C:%M:%line] %r %msg %replace(%msg){'\world', '中国'} %n            </pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>


执行:


private static Logger logger = LoggerFactory.getLogger(DemoHelloword.class);

    public static void main(String[] args)  {
        logger.error("Hello world.");
        logger.info("Hello world.");
    }


结果如下:

2018-08-18 22:49:45 [main] [ERROR] [com.zhiliao.demo.DemoHelloword:com.zhiliao.demo.DemoHelloword:main:17] 163 Hello world. Hello 中国.
2018-08-18 22:49:45 [main] [INFO] [com.zhiliao.demo.DemoHelloword:com.zhiliao.demo.DemoHelloword:main:18] 166 Hello world. Hello 中国. 


二,logback默认日志格式化打印PatternLayoutEncoder原理分析


logback默认的layout使用的是ch.qos.logback.classic.encoder.PatternLayoutEncoder如下:


public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent{

    @Override
    public void start() {
        PatternLayout patternLayout = new PatternLayout();
        patternLayout.setContext(context);
        patternLayout.setPattern(getPattern());
        patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
        patternLayout.start();
        this.layout = patternLayout;
        super.start();
    }

}


我们进入到PatternLayout中可以发现有一个默认的defaultConverterMap如下:


static {
        defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);

        defaultConverterMap.put("d", DateConverter.class.getName());
        defaultConverterMap.put("date", DateConverter.class.getName());

        defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
        defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());

        defaultConverterMap.put("level", LevelConverter.class.getName());
        defaultConverterMap.put("le", LevelConverter.class.getName());
        defaultConverterMap.put("p", LevelConverter.class.getName());

        defaultConverterMap.put("t", ThreadConverter.class.getName());
        defaultConverterMap.put("thread", ThreadConverter.class.getName());

        defaultConverterMap.put("lo", LoggerConverter.class.getName());
        defaultConverterMap.put("logger", LoggerConverter.class.getName());
        defaultConverterMap.put("c", LoggerConverter.class.getName());

        defaultConverterMap.put("m", MessageConverter.class.getName());
        defaultConverterMap.put("msg", MessageConverter.class.getName());
        defaultConverterMap.put("message", MessageConverter.class.getName());

        defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
        defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());

        defaultConverterMap.put("M", MethodOfCallerConverter.class.getName());
        defaultConverterMap.put("method", MethodOfCallerConverter.class.getName());

        defaultConverterMap.put("L", LineOfCallerConverter.class.getName());
        defaultConverterMap.put("line", LineOfCallerConverter.class.getName());

        defaultConverterMap.put("F", FileOfCallerConverter.class.getName());
        defaultConverterMap.put("file", FileOfCallerConverter.class.getName());

        defaultConverterMap.put("X", MDCConverter.class.getName());
        defaultConverterMap.put("mdc", MDCConverter.class.getName());

        defaultConverterMap.put("ex", ThrowableProxyConverter.class.getName());
        defaultConverterMap.put("exception", ThrowableProxyConverter.class.getName());
        defaultConverterMap.put("rEx", RootCauseFirstThrowableProxyConverter.class.getName());
        defaultConverterMap.put("rootException", RootCauseFirstThrowableProxyConverter.class.getName());
        defaultConverterMap.put("throwable", ThrowableProxyConverter.class.getName());

        defaultConverterMap.put("xEx", ExtendedThrowableProxyConverter.class.getName());
        defaultConverterMap.put("xException", ExtendedThrowableProxyConverter.class.getName());
        defaultConverterMap.put("xThrowable", ExtendedThrowableProxyConverter.class.getName());

        defaultConverterMap.put("nopex", NopThrowableInformationConverter.class.getName());
        defaultConverterMap.put("nopexception", NopThrowableInformationConverter.class.getName());

        defaultConverterMap.put("cn", ContextNameConverter.class.getName());
        defaultConverterMap.put("contextName", ContextNameConverter.class.getName());

        defaultConverterMap.put("caller", CallerDataConverter.class.getName());

        defaultConverterMap.put("marker", MarkerConverter.class.getName());

        defaultConverterMap.put("property", PropertyConverter.class.getName());

        defaultConverterMap.put("n", LineSeparatorConverter.class.getName());

        defaultConverterMap.put("black", BlackCompositeConverter.class.getName());
        defaultConverterMap.put("red", RedCompositeConverter.class.getName());
        defaultConverterMap.put("green", GreenCompositeConverter.class.getName());
        defaultConverterMap.put("yellow", YellowCompositeConverter.class.getName());
        defaultConverterMap.put("blue", BlueCompositeConverter.class.getName());
        defaultConverterMap.put("magenta", MagentaCompositeConverter.class.getName());
        defaultConverterMap.put("cyan", CyanCompositeConverter.class.getName());
        defaultConverterMap.put("white", WhiteCompositeConverter.class.getName());
        defaultConverterMap.put("gray", GrayCompositeConverter.class.getName());
        defaultConverterMap.put("boldRed", BoldRedCompositeConverter.class.getName());
        defaultConverterMap.put("boldGreen", BoldGreenCompositeConverter.class.getName());
        defaultConverterMap.put("boldYellow", BoldYellowCompositeConverter.class.getName());
        defaultConverterMap.put("boldBlue", BoldBlueCompositeConverter.class.getName());
        defaultConverterMap.put("boldMagenta", BoldMagentaCompositeConverter.class.getName());
        defaultConverterMap.put("boldCyan", BoldCyanCompositeConverter.class.getName());
        defaultConverterMap.put("boldWhite", BoldWhiteCompositeConverter.class.getName());
        defaultConverterMap.put("highlight", HighlightingCompositeConverter.class.getName());

        defaultConverterMap.put("lsn", LocalSequenceNumberConverter.class.getName());

    }


这边配置的就是默认的所有支持的日志格式化,有的格式同时还支持多种简写配置,比如%date可以简写成%d,%method可以简写成%M等,详细的大家可以进入到源码中查看,也可以到官方文档中查阅https://logback.qos.ch/manual/layouts.html


三,自定义layout打印日志


如果你觉得默认的PatternLayout还是无法满足你的业务需求,那么也没关系,logback也支持自定义Layout


自定义Layout只需要继承LayoutBase这个类,这里我们新增一个MyLayout01如下:


public class MyLayout01 extends LayoutBase<ILoggingEvent> {

    @Override
    public String doLayout(ILoggingEvent event) {
        StringBuffer sbuf = new StringBuffer(128);
        sbuf.append("我是自定义Layout打印日志:");
        sbuf.append(event.getTimeStamp());
        sbuf.append(" ");
        sbuf.append(event.getLevel());
        sbuf.append(" [");
        sbuf.append(event.getThreadName());
        sbuf.append("] ");
        sbuf.append(event.getLoggerName());
        sbuf.append(" - ");
        sbuf.append(event.getFormattedMessage());
        sbuf.append(CoreConstants.LINE_SEPARATOR);
        return sbuf.toString();
    }

}


对应的配置文件修改为如下:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
             <layout class="com.zhiliao.demo.layout.MyLayout01" />
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>


运行打印效果如下:

我是自定义Layout打印日志:1534647234939 ERROR [main] com.zhiliao.demo.MyLayoutConfig - Hello world.
我是自定义Layout打印日志:1534647234942 INFO [main] com.zhiliao.demo.MyLayoutConfig - Hello world.

怎么样,是不是炒鸡简单

再让我们来看一个稍微复杂一点的,新建一个MyLayout02如下:


public class MyLayout02 extends LayoutBase<ILoggingEvent{

    private String contextName;
    private String userName;

    public void setContextName(String contextName) {
        this.contextName = contextName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }


    @Override
    public String doLayout(ILoggingEvent event) {
        StringBuffer sbuf = new StringBuffer(128);
        sbuf.append("我是自定义Layout打印日志:");
        sbuf.append(" contextName = ").append(contextName);
        sbuf.append(" ");
        sbuf.append(" userName = ").append(userName);
        sbuf.append(" ");
        sbuf.append(event.getTimeStamp());
        sbuf.append(" ");
        sbuf.append(event.getLevel());
        sbuf.append(" [");
        sbuf.append(event.getThreadName());
        sbuf.append("] ");
        sbuf.append(event.getLoggerName());
        sbuf.append(" - ");
        sbuf.append(event.getFormattedMessage());
        sbuf.append(CoreConstants.LINE_SEPARATOR);
        return sbuf.toString();
    }

}


配置文件修改为如下:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<!--              <layout class="com.zhiliao.demo.layout.MyLayout01" /> -->
            <layout class="com.zhiliao.demo.layout.MyLayout02">
                <contextName>myContext</contextName>
                <userName>zhiliao</userName>
            </layout>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>


运行结果如下:

我是自定义Layout打印日志: contextName = myContext  userName = zhiliao 1534647574305 ERROR [main] com.zhiliao.demo.MyLayoutConfig - Hello world.
我是自定义Layout打印日志: contextName = myContext  userName = zhiliao 1534647574307 INFO [main] com.zhiliao.demo.MyLayoutConfig - Hello world.

从结果中我们可以发现我们在配置文件中配置的contextName和userName都打印到了对应的日志中了


到这里不得不佩服logback作者设计厉害之处,是不是迫不及待的想回去试一把了,呵呵,那赶紧把logback的这些特性运用到项目中去吧


今天logback关于日志格式化layout就到此了,更多logback高级功能敬请期待......



以上是关于深入分析logback之日志格式化layout的主要内容,如果未能解决你的问题,请参考以下文章

Logback日志使用详解

7.Logback主要标签

你可能需要了解的 logback

日志框架之Logback的使用与详细配置

SpringBoot日志记录组件logback的配置解释

让你的spring-boot应用日志随心所欲--spring boot日志深入分析