当前位置:网站首页>logback源码阅读(二)日志打印,自定义appender,encoder,pattern,converter
logback源码阅读(二)日志打印,自定义appender,encoder,pattern,converter
2022-08-02 14:00:00 【Ethan_199402】
上一篇文章已经知道了ILoggerFactory和Logger是怎么获得的,接下里一起看日志的打印机制
Logger类的成员变量
先看一下Logger类的成员变量
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
private static final long serialVersionUID = 5454405123156820674L; // 8745934908040027998L;
/** * The fully qualified name of this class. Used in gathering caller * information. */
public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();
/** * The name of this logger */
private String name;
// The assigned levelInt of this logger. Can be null.
transient private Level level;
// The effective levelInt is the assigned levelInt and if null, a levelInt is
// inherited form a parent.
transient private int effectiveLevelInt;
/** * The parent of this category. All categories have at least one ancestor * which is the root category. */
transient private Logger parent;
/** * The children of this logger. A logger may have zero or more children. */
transient private List<Logger> childrenList;
/** * It is assumed that once the 'aai' variable is set to a non-null value, it * will never be reset to null. it is further assumed that only place where * the 'aai'ariable is set is within the addAppender method. This method is * synchronized on 'this' (Logger) protecting against simultaneous * re-configuration of this logger (a very unlikely scenario). * * <p> * It is further assumed that the AppenderAttachableImpl is responsible for * its internal synchronization and thread safety. Thus, we can get away with * *not* synchronizing on the 'aai' (check null/ read) because * <p> * 1) the 'aai' variable is immutable once set to non-null * <p> * 2) 'aai' is getAndSet only within addAppender which is synchronized * <p> * 3) all the other methods check whether 'aai' is null * <p> * 4) AppenderAttachableImpl is thread safe */
transient private AppenderAttachableImpl<ILoggingEvent> aai;
/** * Additivity is set to true by default, that is children inherit the * appenders of their ancestors by default. If this variable is set to * <code>false</code> then the appenders located in the ancestors of this * logger will not be used. However, the children of this logger will inherit * its appenders, unless the children have their additivity flag set to * <code>false</code> too. See the user manual for more details. */
transient private boolean additive = true;
final transient LoggerContext loggerContext;
}
- FQCN 此类的完全限定名称。用于收集调用者信息。
- name:logger 的名字
- level:此记录器的分配 levelInt。可以为空。
- effectiveLevelInt:分配的生效 levelInt,如果为 null,则 levelInt 从父logger继承
- parent:此类别的父级。所有类别至少有一个祖先,即根类别。
- childrenList:这个记录器的子类列表。一个记录器可能有零个或多个子记录器。
- aai:appder负责日志的输出源 如 数据库 es 控制台
- additive:Additivity 默认设置为 true,即默认情况下子级继承其父级的 appender。如果此变量设置为 false,则不会使用位于此记录器父级中的appenders。然而,这个 logger 的子节点将继承它的 appender,除非子节点的additive也设置为 false。
- loggerContext:loggerContext 全局只有一个 创建时通过Binder获取注入进来的
这里解释一下loggerContext为什么是全局唯一,以及什么时候注入的:
我们已经知道在获取LoggerFactory时会执行performInitialization方法进行初始化,该方法先调用bind方法,调用StaticLoggerBinder.getSingleton();获得StaticLoggerBinder的实例,此时StaticLoggerBinder会进行类初始化,执行 private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();触发构造方法,构造方法会执行private LoggerContext defaultLoggerContext = new LoggerContext();至此看到了LoggerContext,继续看其构造方法
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
可以看到this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);new 了一个Logger实例,并且把自身(LoggerContext)传了进去;
此处涉及到类加载机制,不好理解的同学可以参考:
logback源码阅读(一)获取ILoggerFactory、Logger
JVM (八)大厂必问类加载机制,加载过程,生命周期
Logger 核心方法
接下里挑几个核心方法讲解
setLevel
设置日志级别
public synchronized void setLevel(Level newLevel) {
if (level == newLevel) {
// nothing to do;
return;
}
if (newLevel == null && isRootLogger()) {
throw new IllegalArgumentException("The level of the root logger cannot be set to null");
}
level = newLevel;
if (newLevel == null) {
effectiveLevelInt = parent.effectiveLevelInt;
newLevel = parent.getEffectiveLevel();
} else {
effectiveLevelInt = newLevel.levelInt;
}
if (childrenList != null) {
int len = childrenList.size();
for (int i = 0; i < len; i++) {
Logger child = (Logger) childrenList.get(i);
// tell child to handle parent levelInt change
child.handleParentLevelChange(effectiveLevelInt);
}
}
// inform listeners
loggerContext.fireOnLevelChange(this, newLevel);
}
- 如果新的level和现在的level相同,直接返回
- 如果新的level是null并且当前记录器是root,报错
- 如果新的level是null,那么当前记录器比不是root,则获取parent的level进行赋值,否则直接赋值为newLevel
- 如果子记录器不为空且自身level为null,通过
handleParentLevelChange递归设置为newLevel,此处保证父级日志级别只会覆盖没有设置过日志级别的子类的日志级别
filterAndLog_0_Or3Plus
我们调用info debug方法单个参数String都是进入此方法
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
if (decision == FilterReply.NEUTRAL) {
if (effectiveLevelInt > level.levelInt) {
return;
}
} else if (decision == FilterReply.DENY) {
return;
}
buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}
根据loggerContext TurboFilterList执行日志过滤器 用于过滤此日志是否放行默认返回NEUTRAL走日志级别比较 D,ENY为拒绝,ACCEPT为放行
public enum FilterReply {
DENY, NEUTRAL, ACCEPT;
}
什么是TurboFilter?
TurboFilter 是一个具有decide方法的专用过滤器,该方法采用一堆参数而不是单个事件对象。后者更干净,但第一个性能更高。有关 TurboFilter 更多信息,请参阅 http:logback.qos.chmanualfilters.htmlTurboFilter 的在线手册
public abstract class TurboFilter extends ContextAwareBase implements LifeCycle {
private String name;
boolean start = false;
/** * Make a decision based on the multiple parameters passed as arguments. * The returned value should be one of <code>{@link FilterReply#DENY}</code>, * <code>{@link FilterReply#NEUTRAL}</code>, or <code>{@link FilterReply#ACCEPT}</code>. * @param marker * @param logger * @param level * @param format * @param params * @param t * @return */
public abstract FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t);
public void start() {
this.start = true;
}
public boolean isStarted() {
return this.start;
}
public void stop() {
this.start = false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们可以自定义过滤器继承TurboFilter方法,重写decide方法来进行一些日志控制,比如日志关键词甚至是白名单效果
public class cusTurboFilter extends TurboFilter {
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
//do something
return FilterReply.ACCEPT;
}
}
并且在配置文件中添加
<turboFilter class="com.ethan.log.cusTurboFilter" />
或者
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.addTurboFilter(new cusTurboFilter());
从getTurboFilterChainDecision_0_3OrMore这个方法也可以看到,turboFilterList就是LoggerContext的一个成员变量
final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
final Object[] params, final Throwable t) {
if (turboFilterList.size() == 0) {
return FilterReply.NEUTRAL;
}
return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
}
内部的getTurboFilterChainDecision方法就是遍历这个turboFilterList并执行decide方法: 当任意一个返回放行或者拒绝直接返回
public FilterReply getTurboFilterChainDecision(final Marker marker, final Logger logger, final Level level, final String format, final Object[] params,
final Throwable t) {
final int size = size();
// if (size == 0) {
// return FilterReply.NEUTRAL;
// }
//只有一个
if (size == 1) {
try {
TurboFilter tf = get(0);
return tf.decide(marker, logger, level, format, params, t);
} catch (IndexOutOfBoundsException iobe) {
return FilterReply.NEUTRAL;
}
}
Object[] tfa = toArray();
final int len = tfa.length;
for (int i = 0; i < len; i++) {
// for (TurboFilter tf : this) {
final TurboFilter tf = (TurboFilter) tfa[i];
final FilterReply r = tf.decide(marker, logger, level, format, params, t);
//当任意一个返回放行或者拒绝直接返回
if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
return r;
}
}
return FilterReply.NEUTRAL;
}
buildLoggingEventAndAppend
private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
le.setMarker(marker);
callAppenders(le);
}
- 创建LoggingEvent对象 该类实现了slf4j LoggingEvent接口
- 调用appenders
callAppenders
调用logger的所有appenders
public void callAppenders(ILoggingEvent event) {
int writes = 0;
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
从当前logger.AppenderAttachableImpl 往父类遍历直到遇到父logger终止additive为fasle 根logger是root, 如果父logger子logger都有相同的appender 就会重复记录,如果写的次数是0 触发警告日志打印
appendLoopOnAppenders
aai就是所有appender的管理类 负责遍历输出appender
private int appendLoopOnAppenders(ILoggingEvent event) {
if (aai != null) {
return aai.appendLoopOnAppenders(event);
} else {
return 0;
}
}
appendLoopOnAppenders方法就是遍历appenderList调用doAppend方法,并传入上文创建的LoggingEvent对象
public int appendLoopOnAppenders(E e) {
int size = 0;
final Appender<E>[] appenderArray = appenderList.asTypedArray();
final int len = appenderArray.length;
for (int i = 0; i < len; i++) {
appenderArray[i].doAppend(e);
size++;
}
return size;
}
appender
前面我们看到 最终logger输出是委托给了appender 如果没有配置appender是不会输出的
打开appender接口

我们以OutputStreamAppender为例,看看如何自定义一个appender
切入点就是看看doAppend在哪里定义,并没有在OutputStreamAppender中找到这个方法,可以看到OutputStreamAppender继承了UnsynchronizedAppenderBase这个类,发现UnsynchronizedAppenderBase实现了Appender并且实现了doAppend方法
public void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (Boolean.TRUE.equals(guard.get())) {
return;
}
try {
guard.set(Boolean.TRUE);
if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard.set(Boolean.FALSE);
}
}
- 用一个ThreadLocal修饰的变量来防止 appender 重复调用它自己的 doAppend 方法
private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>(); - 如果appender的没有启动,进行拦截并发出警告
- 遍历appender的过滤器并执行decide方法,与上文提到的aai原理一致此处的过滤器是与Appender绑定的,而TurboFIlter是与日志上下文绑定的,它会过滤所有的日志请求
- 最后调用append方法
OutputStreamAppender.append
protected void append(E eventObject) {
if (!isStarted()) {
return;
}
subAppend(eventObject);
}
继续调用subAppend方法
protected void subAppend(E event) {
if (!isStarted()) {
return;
}
try {
// this step avoids LBCLASSIC-139
if (event instanceof DeferredProcessingAware) {
((DeferredProcessingAware) event).prepareForDeferredProcessing();
}
// the synchronization prevents the OutputStream from being closed while we
// are writing. It also prevents multiple threads from entering the same
// converter. Converters assume that they are in a synchronized block.
// lock.lock();
byte[] byteArray = this.encoder.encode(event);
writeBytes(byteArray);
} catch (IOException ioe) {
// as soon as an exception occurs, move to non-started state
// and add a single ErrorStatus to the SM.
this.started = false;
addStatus(new ErrorStatus("IO failure in appender", this, ioe));
}
}
此处重点是byte[] byteArray = this.encoder.encode(event);,encoder主要负责输出格式和编码的处理,下文再讲,现在我们已经知道如何自定义一个appender
自定义appender
继承UnsynchronizedAppenderBase并重写append方法
public class ExceptionMonitorLogbackAppender extends AppenderBase<ILoggingEvent> {
private static final Logger logger = Logger.getLogger(MonitorExtLogbackAppender.class.getName());
private static final String FEIGN_EXCEPTION = "XXXException";
@Override
protected void append(ILoggingEvent event) {
try {
Level level = event.getLevel();
if (level.isGreaterOrEqual(Level.ERROR)) {
logEvent((ThrowableProxy) event.getThrowableProxy());
}
} catch (Exception ex) {
logger.log(java.util.logging.Level.WARNING, "Monitor logback appender exception.", ex);
}
}
private void logEvent(ThrowableProxy info) {
if (info != null) {
Throwable exception = info.getThrowable();
if (FEIGN_EXCEPTION.equalsIgnoreCase(exception.getClass().getSimpleName())) {
String nameMsg = exception.getMessage();
if (StringUtils.isNotEmpty(nameMsg)) {
Metrics.newCounter("Feign".concat(FEIGN_EXCEPTION)).build().once(Attributes.of(AttributeKey.stringKey("name"), nameMsg.split("\\?")[0]));
}
}
}
}
}
这个appender就是收集所有XXXExceptino,并对其进行埋点
Logger必须和appender关联才可以产生作用
我们可以为Appender配置Filter做定制的过滤
encoder
encoder主要负责输出格式和编码的处理,先看一下接口
以LayoutWrappingEncoder为例
public byte[] encode(E event) {
String txt = layout.doLayout(event);
return convertToBytes(txt);
}
委托给layout来转换成字符串文本
于是我们可以自定义Layout来定义日志输出
Layout在在start方法初始化
public void start() {
PatternLayout patternLayout = new PatternLayout();
patternLayout.setContext(context);
patternLayout.setPattern(getPattern());
patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
patternLayout.start();
this.layout = patternLayout;
super.start();
}
查看doLayout方法
public String doLayout(ILoggingEvent event) {
if (!isStarted()) {
return CoreConstants.EMPTY_STRING;
}
return writeLoopOnConverters(event);
}
调用writeLoopOnConverters方法将我们配置的输出格式表达式 进行转换处理<pattern><pattern>|%p|%d{yyyy-MM-dd HH:mm:ss.SSS}|%t|%logger{10}:%line%n %m%n%n</pattern></pattern>
protected String writeLoopOnConverters(E event) {
StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
Converter<E> c = head;
while (c != null) {
c.write(strBuilder, event);
c = c.getNext();
}
return strBuilder.toString();
}
以LoggerConverter为例,我们可以自定义或者配置在xml中达到我们想要的效果
public class LoggerConverter extends NamedConverter {
protected String getFullyQualifiedName(ILoggingEvent event) {
return event.getLoggerName();
}
}
xml配置
<appender name="KafkaJsonAppender" class="com.github.danielwegener.logback.kafka.KafkaAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
"appName" : "${PROJECT_NAME}",
"env" : "${PROFILE_ACTIVE}",
"logType": "%mdc{LOG-LOGTYPE}",
"clientIp": "%mdc{LOG-CLIENTIP}",
"reqUrl": "%mdc{LOG-REQURL}",
"feignUrl": "%mdc{LOG-FEIGNURL}",
"clsMethod":"%mdc{LOG-CLSMETHOD}",
"success": "%mdc{LOG-SUCCESS}",
"code": "%mdc{LOG-CODE}",
"msg": "%mdc{LOG-MSG}",
"execTime": "%mdc{LOG-EXECTIME}",
"uid":"%mdc{LOG-UID}",
"timeout": "%mdc{LOG-TIMEOUT}",
"sample": "%mdc{LOG-SAMPLE}",
"stationId":"%mdc{LOG-STATIONID}",
"sId":"%mdc{LOG-SESSIONID}",
"level": "%level",
"traceId": "%X{traceId}",
"thread": "%thread",
"host": "${hostname}",
"className": "%logger{36}.%M",
"message": "%msg%n%rEx{full, java.lang.reflect.Method, sun.reflect, org.apache.catalina, org.springframework.aop, org.springframework.security, org.springframework.transaction, org.springframework.web, org.springframework.beans, org.springframework.cglib, net.sf.cglib, org.apache.tomcat.util, org.apache.coyote, ByCGLIB, BySpringCGLIB}"
}
</pattern>
</pattern>
</providers>
</encoder>
配置文件和converter的映射关系在PatternLayout 的static方法
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());
CONVERTER_CLASS_TO_KEY_MAP.put(DateConverter.class.getName(), "date");
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", MessageConverter.class.getName());
DEFAULT_CONVERTER_MAP.put("msg", MessageConverter.class.getName());
DEFAULT_CONVERTER_MAP.put("message", MessageConverter.class.getName());
CONVERTER_CLASS_TO_KEY_MAP.put(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("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("prefix", PrefixCompositeConverter.class.getName());
}
我们可以自定义converter给日志加上traceId
边栏推荐
猜你喜欢

关于市场后市的发展预测? 2021-05-23

Break the limit of file locks and use storage power to help enterprises grow new momentum

The future of financial services will never stop, and the bull market will continue 2021-05-28

玉溪卷烟厂通过正确选择时序数据库 轻松应对超万亿行数据

Image retrieval method based on deep learning!

配置zabbix自动发现和自动注册。

A number of embassies and consulates abroad have issued reminders about travel to China, personal and property safety

删除链表的节点

你接受不了60%的暴跌,就没有资格获得6000%的涨幅 2021-05-27

RKMPP库快速上手--(一)RKMPP功能及使用详解
随机推荐
Awesome!Alibaba interview reference guide (Songshan version) open source sharing, programmer interview must brush
About the development forecast of the market outlook?2021-05-23
你接受不了60%的暴跌,就没有资格获得6000%的涨幅 2021-05-27
微信小程序-最近动态滚动实现
Interviewer: Can you talk about optimistic locking and pessimistic locking?
矩阵中的路径
拯救流浪猫 | 「喵先锋」系列数字版权盲盒明日开抢
Image retrieval method based on deep learning!
Configure zabbix auto-discovery and auto-registration.
多个驻外使领馆发提醒 事关赴华出行、人身财产安全
未来的金融服务永远不会停歇,牛市仍将继续 2021-05-28
How to do short video food from the media?5 steps to teach you to get started quickly
How to connect DBeaver TDengine?
rust使用mysql插入数据
binary search && tree
els strip collision deformation judgment
WiFi Association&Omnipeek抓包分析
SQL函数 UPPER
2022-08-02日报:2022年7月最热的10篇AI论文
网络安全第二次作业