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了,后续还有其它的业务处理,所以对日志这里作了更多的额外处理,因此采用的是这个方案。

相关推荐
用户26851612107568 分钟前
常见的 Git 分支命名策略和实践
后端
程序员小假9 分钟前
我们来说一下 MySQL 的慢查询日志
java·后端
南囝coding11 分钟前
《独立开发者精选工具》第 025 期
前端·后端
独自破碎E30 分钟前
Java是怎么实现跨平台的?
java·开发语言
To Be Clean Coder37 分钟前
【Spring源码】从源码倒看Spring用法(二)
java·后端·spring
xdpcxq10291 小时前
风控场景下超高并发频次计算服务
java·服务器·网络
想用offer打牌1 小时前
你真的懂Thread.currentThread().interrupt()吗?
java·后端·架构
橘色的狸花猫1 小时前
简历与岗位要求相似度分析系统
java·nlp
独自破碎E1 小时前
Leetcode1438绝对值不超过限制的最长连续子数组
java·开发语言·算法
用户91743965391 小时前
Elasticsearch Percolate Query使用优化案例-从2000到500ms
java·大数据·elasticsearch