Logback输出json日志,如何将异常信息包含在json体内

前言

我在前一篇文章:Logback输出json格式日志,及异常信息不在Json串内的原因分析 解释了当在logback.xml文件内定义json格式的时候,为什么打印的异常信息不在json体内,如下:

定义json格式输出:

xml 复制代码
<appender name="JSON-CONSOLE" class="ch.qos.logback.core.ConsoleAppender">  
    <encoder>  
        <pattern>{ "level":"%level", "thread": "%thread", "msg":"%msg" }\r\n</pattern>  
        <charset>UTF-8</charset>  
    </encoder>  
</appender>

实际效果:

异常信息在json体外,本文提供一个我的解决方案。

解决办法

这篇文章是顺着:Logback输出json格式日志,及异常信息不在Json串内的原因分析 写的,因此看下面的内容前,建议先看下原因分析的文章。

logback.xml中encoder和pattern默认使用的实现类是: encoder标签默认使用的实现类是ch.qos.logback.classic.encoder.PatternLayoutEncoder类

pattern标签默认使用的实现类是ch.qos.logback.classic.PatternLayout类

方法一

这个异常的转换器是被PatternLayout构造的时候,如果不存在异常转换器的时候,强制追加到后面的,那既如此,就显式指定异常转换器,如下:

效果如下,异常拼接在了正常日志的后面,也包含在了json体内。

这个方法最省事也省力,如果没有其它场景要求,是可以满足了。

方法二

既然PatternLayout构造方法,强制要保证定义的Pattern中有异常的转换器,那就自定义一个PatternLayou,强制不检查异常的转换器,这样也就不会在定义的pattern中增加一个异常的转换器。

然后增加一个msg的转换器,打印日志的时候,如果存在异常信息,就把异常信息也拼接到正常的日志后面。

定义一个MessageConverter:

java 复制代码
public class MessageConverter extends ClassicConverter {
    private static ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter();

    static {
        throwableProxyConverter.start();
    }

    @Override
    public String convert(ILoggingEvent event) {
        String message = event.getFormattedMessage();
        IThrowableProxy tp = event.getThrowableProxy();
        if (tp != null) {
            // 异常不为空,把异常信息处理一下拼接到msg后面
            String ex = throwableProxyConverter.convert(event);
            message = message + "\r\n" + ex;
        }
        return message;
    }
}

定义一个PatternLayout,这个PatternLayout的代码是从logback中的PatternLatout的代码copy出来的,基本一样,主要区别在于两个地方:

  1. 去掉构造函数,避免强制增加异常处理的转换器
  2. 使用上面定义的MessageConverter替换原始的

代码如下:

java 复制代码
public class PatternLayout extends PatternLayoutBase<ILoggingEvent> {

    public static final Map<String, String> DEFAULT_CONVERTER_MAP = new HashMap<String, String>();
    public static final Map<String, String> CONVERTER_CLASS_TO_KEY_MAP = new HashMap<String, String>();

    /**
     * @deprecated replaced by DEFAULT_CONVERTER_MAP
     */
    public static final Map<String, String> defaultConverterMap = DEFAULT_CONVERTER_MAP;

    public static final String HEADER_PREFIX = "#logback.classic pattern: ";

    static {
        DEFAULT_CONVERTER_MAP.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);

        DEFAULT_CONVERTER_MAP.put("d", DateConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("date", DateConverter.class.getName());
        // used by PrefixComposite converter
        CONVERTER_CLASS_TO_KEY_MAP.put(DateConverter.class.getName(), "date");

        DEFAULT_CONVERTER_MAP.put("ms", MicrosecondConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("micros", MicrosecondConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(MicrosecondConverter.class.getName(), "micros");

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

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

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

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

        DEFAULT_CONVERTER_MAP.put("m", com.xxd.demo.log.MessageConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("msg", com.xxd.demo.log.MessageConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("message", com.xxd.demo.log.MessageConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(com.xxd.demo.log.MessageConverter.class.getName(), "message");

        DEFAULT_CONVERTER_MAP.put("C", ClassOfCallerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("class", ClassOfCallerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(ClassOfCallerConverter.class.getName(), "class");

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

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

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

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

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

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

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

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

        DEFAULT_CONVERTER_MAP.put("caller", CallerDataConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(CallerDataConverter.class.getName(), "caller");

        DEFAULT_CONVERTER_MAP.put("marker", MarkerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(MarkerConverter.class.getName(), "marker");

        DEFAULT_CONVERTER_MAP.put("kvp", KeyValuePairConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(KeyValuePairConverter.class.getName(), "kvp");

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

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

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

        DEFAULT_CONVERTER_MAP.put("lsn", LocalSequenceNumberConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(LocalSequenceNumberConverter.class.getName(), "lsn");

        DEFAULT_CONVERTER_MAP.put("sn", SequenceNumberConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("sequenceNumber", SequenceNumberConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(SequenceNumberConverter.class.getName(), "sequenceNumber");

        DEFAULT_CONVERTER_MAP.put("prefix", PrefixCompositeConverter.class.getName());

    }

    @Override
    public Map<String, String> getDefaultConverterMap() {
        return DEFAULT_CONVERTER_MAP;
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        if (!isStarted()) {
            return CoreConstants.EMPTY_STRING;
        }
        return writeLoopOnConverters(event);
    }

    @Override
    protected String getPresentationHeaderPrefix() {
        return HEADER_PREFIX;
    }
}

基本都是原始代码copy的,主要区别就是没了构造函数,还有这里:

要让这个类生效,需要定义一个encoder,并使用自定义的PatternLayout,如下:

java 复制代码
public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {

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

}

然后在logback.xml指定使用这个encoder,如下:

xml 复制代码
    <appender name="JSON-CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="com.xxd.demo.log.PatternLayoutEncoder">
            <pattern>{ "level":"%level", "thread": "%thread", "msg":"%msg" }\r\n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

再看一下打印的效果:

这个方法适用于更多定制化的场景了,上面的MessageConverter只是个示例写的比较简单,实际场景可能还需要作其它处理,比如转义呀什么的。

我这里是因为输出的日志直接以json格式输出到kafka了,后续还有其它的业务处理,所以对日志这里作了更多的额外处理,因此采用的是这个方案。

相关推荐
杨充5 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_7 分钟前
JAVA-队列
java·开发语言
喵叔哟17 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟17 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk19 分钟前
maven环境搭建
java·maven
Daniel 大东38 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
wind瑞1 小时前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen1 小时前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)1 小时前
读写锁分离设计模式详解
java·设计模式·java-ee
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和