前言
我在前一篇文章: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出来的,基本一样,主要区别在于两个地方:
- 去掉构造函数,避免强制增加异常处理的转换器
- 使用上面定义的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了,后续还有其它的业务处理,所以对日志这里作了更多的额外处理,因此采用的是这个方案。