logback源码阅读(二)日志打印,自定义appender,encoder,pattern,converter

上一篇文章已经知道了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;
}
  1. FQCN 此类的完全限定名称。用于收集调用者信息。
  2. name:logger 的名字
  3. level:此记录器的分配 levelInt。可以为空。
  4. effectiveLevelInt:分配的生效 levelInt,如果为 null,则 levelInt 从父logger继承
  5. parent:此类别的父级。所有类别至少有一个祖先,即根类别。
  6. childrenList:这个记录器的子类列表。一个记录器可能有零个或多个子记录器。
  7. aai:appder负责日志的输出源 如 数据库 es 控制台
  8. additive:Additivity 默认设置为 true,即默认情况下子级继承其父级的 appender。如果此变量设置为 false,则不会使用位于此记录器父级中的appenders。然而,这个 logger 的子节点将继承它的 appender,除非子节点的additive也设置为 false。
  9. 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);
    }
  1. 如果新的level和现在的level相同,直接返回
  2. 如果新的level是null并且当前记录器是root,报错
  3. 如果新的level是null,那么当前记录器比不是root,则获取parent的level进行赋值,否则直接赋值为newLevel
  4. 如果子记录器不为空且自身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);
    }
  1. 创建LoggingEvent对象 该类实现了slf4j LoggingEvent接口
  2. 调用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);
        }
    }
  1. 用一个ThreadLocal修饰的变量来防止 appender 重复调用它自己的 doAppend 方法private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();
  2. 如果appender的没有启动,进行拦截并发出警告
  3. 遍历appender的过滤器并执行decide方法,与上文提到的aai原理一致此处的过滤器是与Appender绑定的,而TurboFIlter是与日志上下文绑定的,它会过滤所有的日志请求
  4. 最后调用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值