文章目录
前言
最近有个项目再写日志的时候我原本想封装一个自己的Logger来实现一个方法 ,日志多条输出。成功到是没问题。实现就是加了一个facade来做。业务部分调用facade来,facade来调用多个logger输出到不同日志中。但是里面有个配置%C 或者%class %L出现了问题。在日志中输出到了facade类对象的调用部分。不是实际业务部分代码调用。这让我比较麻烦。所以打算看一下源码并分析一下问题所在。
通过一个简单的代码作为切入口
public static void main(String[] args) {
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
Logger log = factory.getLogger("ll");
log.info("sss");
}
其中核心代码
LoggerFactory.getILoggerFactory() 获取logger工厂
与
Logger log = factory.getLogger(“ll”)与获取logger对象获取
说明一下logback的实现有很多这里我用的是:参见maven引用
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
LoggerFactory
先看LoggerFactory的实现
根据调用主要为
LoggerFactory.getILoggerFactory() ;方法
源码如下:
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
可以看出这是一个双检查的单例模式来获取logger工厂。
其中主要核心方法有两行
1. performInitialization(); //私有内部方法。可以看出就是一个初始化的方法
2. return StaticLoggerBinder.getSingleton().getLoggerFactory();//而这条才是最关键的核心获取LoggerFactory的实现方法。
先了解一下内部初始化,看看工厂再返回LoggerFactory前都做了些什么
private final static void performInitialization() {
bind(); // 核心前置方法,初始化的第一步
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck(); //一个版本检查的方法,这里这次也不说了。
}
}
关于这里已经多次出现常量
INITIALIZATION_STATE 如字意 初始化状态。
目前可知主要的有
- UNINITIALIZED 未初始化
- ONGOING_INITIALIZATION 即将初始化
- SUCCESSFUL_INITIALIZATION 初始化成功
- 其他目前不重要
看一下bind() 方法
说明一下这里的主要核心代码都在try里面。catch不作为主要阅读地方
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
判断一下是不是android 当然不是了
if (!isAndroid()) {
// 进行一个findPossibleStaticLoggerBinderPathSet 的方法调用。其实这个方法就是获取slf4j的主要实现类,“org/slf4j/impl/StaticLoggerBinder.class” 是否存在
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//报告一下找的结果。当工程中出现多个这个类的时候会报错,反正意思就是只能有一个这个类
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
//这个需要注意一下,StaticLoggerBinder类作为获取loggerFactory时核心的,只要这个类初始化完成就算前期初始化已经完成了一大半
StaticLoggerBinder.getSingleton();
//设置一下初始状态为successful
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
//这里是在报告一下 初始化StaticLoggerBinder.getSingleton()的结果
reportActualBinding(staticLoggerBinderPathSet);
//这里是修复并替换一下另外的logger ,具体意义没空看下次补。这次不管了
fixSubstituteLoggers();
//这里也是跟上面的差不多。没空看意义不知。下次补
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
//异常处理不管了。。有兴趣自己看
} catch (NoClassDefFoundError ncde) {
String msg = ncde.getMessage();
if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (java.lang.NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = FAILED_INITIALIZATION;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw nsme;
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
关于bind方法完成了。这里的主要就是找那个核心工厂创造类StaticLoggerBinder 并在这里对StaticLoggerBinder进行了初始化调用。里面一些私有方法只是做了简单意义说明,具体实现还是可以看的。比如findPossibleStaticLoggerBinderPathSet 就是一个的ClassLoader加载方式。看看顺便可以复习一下双亲委派。
StaticLoggerBinder
看一下StaticLoggerBinder 类
这个类代码不多一次就贴上再说,还是大家请看汉子说明,那是我的理解
public class StaticLoggerBinder implements LoggerFactoryBinder {
/**
* Declare the version of the SLF4J API this implementation is compiled
* against. The value of this field is usually modified with each release.
*/
// to avoid constant folding by the compiler, this field must *not* be final
public static String REQUESTED_API_VERSION = "1.7.16"; // !final
final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";
/**
* The unique instance of this class.
* 单例模式不用解释了。硬汉式的我喜欢。嘎嘎嘎就是new
*/
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
//静态初始化方法,再上面那个类的bind方法中调用了这个类那么这个初始化方法就会被执行,那么这个方法是首要被阅读的
static {
SINGLETON.init();
}
private boolean initialized = false;
//这里需要知道这个LoggerContext 是下一波的关键。
private LoggerContext defaultLoggerContext = new LoggerContext();
//这个也是下一波的关键。首先可以知道这个ContextSelectorStaticBinder 也是一个单例
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
// 单例 私有的构造函数
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
//单例 对外开放的获取实例的方法。
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
/**
* Package access for testing purposes.
* 重置方法,这几乎没人用过啊。,
*/
static void reset() {
SINGLETON = new StaticLoggerBinder();
SINGLETON.init();
}
/**
* Package access for testing purposes.
* 这个是关键核心方法了牵扯到下一步的获取iloggerfactory
*/
void init() {
try {
try {
//这啥意思?!也没返回也没啥的, 那么可以肯定的是再ContextInitializer这里肯定修改了defaultLoggerContext 这个引用的一些值了。那么先记着一会再看。
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
//这个contextSelectorBinder 的初始化。刚才说了这个是下一步的关键了。
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
//接口中的两个实现方法这个作为主要的需要看
public ILoggerFactory getLoggerFactory() {
//当init方法全部完成后会设置这个表示为true,要是设置完成了就不会返回这个defaultLoggerContext ,当没有设置完成就是再contextSelectorBinder init出现异常了那么就返回这个默认的defaultLoggerContext。
if (!initialized) {
return defaultLoggerContext;
}
// init完了当然不是null了。也算一个双重检查吧
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
}
//返回contextSelectorBinder getContextSelector的 getLoggerContext
return contextSelectorBinder.getContextSelector().getLoggerContext();
}
public String getLoggerFactoryClassStr() {
return contextSelectorBinder.getClass().getName();
}
}
大致完成这个类说明了几个问题。contextSelectorBinder 初始化失败会返回一个defaultLoggerContext 若成功则返回自己的.getContextSelector().getLoggerContext()。而再init方法中的 new ContextInitializer(defaultLoggerContext).autoConfig();到底对这个defaultLoggerContext都干了些什么呢。那么我们来看下一个
ContextInitializer
在上个类中对defaultLoggerContext 都干了些什么呢。通过调用可以看到构造函数的传值和autoConfig();的调用。
那就看看这两个主要方法:
public ContextInitializer(LoggerContext loggerContext) {
this.loggerContext = loggerContext;
}
鸡毛没干就是赋值了,那关键就是再autoConfig中了
public void autoConfig() throws JoranException {
//大致看了一下。StatusListenerConfigHelper 这个类里面用到了loggerContext 对他添加了一些监听,意思就是看看有没有什么变化什么的。这里就不在详说了,那么就可以知道这里的loggerContext 完成installIfAsked后就会有状态监听了。
StatusListenerConfigHelper.installIfAsked(loggerContext);
//这个方法有意思。就是获取配置文件在哪里,配置文件都可以叫什么名字。都在这个方法里面获取的。我们都知道在ide里面没有配置文件的时候就会打印红色的,有了配置文件就打黑色的格式文件。那么这里就是那块设置的关键了。
URL url = findURLOfDefaultConfigurationFile(true);
//通常我们都是有配置文件的,那么这URL就肯定不是null
if (url != null) {
//配置文件的应用了,就是解析你的配置文件什么的
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
这个类,主要就是加载配置文件为loggerContext增加监听状态了。
其中需要注意的是 configureByResource(url); 方法
方法源码如下。
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("URL argument cannot be null");
}
final String urlString = url.toString();
if (urlString.endsWith("groovy")) {
if (EnvUtil.isGroovyAvailable()) {
// avoid directly referring to GafferConfigurator so as to avoid
// loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
} else {
StatusManager sm = loggerContext.getStatusManager();
sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
}
} else if (urlString.endsWith("xml")) {
//这里是一个点
//主要看看你是什么类型的配置文件怎么加载,通常都使用的是xml类型的可以看到代码中
//JoranConfigurator 这个类这里就不贴代码了。有兴趣可以自己看。这里其实是装载了很多action 但是其中最重要的就是对配置文件中appender的加载。
//这里不是再而对于appender的加载时再包 ch.qos.logback.core下。
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(url);
} else {
throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
}
}
ContextSelectorStaticBinder
这个类方法也不多
再StaticLoggerBinder 的方法init中对进行了init方法 contextSelectorBinder.init(defaultLoggerContext, KEY);
看下到底干了什么
public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
// 一个key的操作就是为了安全。无视吧
if (this.key == null) {
this.key = key;
} else if (this.key != key) {
throw new IllegalAccessException("Only certain classes can access this method.");
}
//这里是看一下是获取一个环节变量叫"logback.ContextSelector" 看看你是否有设置。
String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR);
//关于这个的设置就是来看就是动态的设置 ContextSelector 。一般我没设置。具体功能回头再说,只看默认的
if (contextSelectorStr == null) {
//默认的是 DefaultContextSelector 这个对象有时一个关键了。
contextSelector = new DefaultContextSelector(defaultLoggerContext);
} else if (contextSelectorStr.equals("JNDI")) {
// if jndi is specified, let's use the appropriate class
contextSelector = new ContextJNDISelector(defaultLoggerContext);
} else {
contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr);
}
}
关于contextSelectorBinder.getContextSelector().getLoggerContext() 其实就是在上段代码中对contextSelector 的初始化操作。主要是看你的"logback.ContextSelector" 配置。默认的就是DefaultContextSelector 那么关键就在于 DefaultContextSelector 中的.getLoggerContext()都干了什么
DefaultContextSelector
这个类太无聊了。就是默认的一堆
主要都是以下方法
private LoggerContext defaultLoggerContext;
public DefaultContextSelector(LoggerContext context) {
this.defaultLoggerContext = context;
}
public LoggerContext getLoggerContext() {
return getDefaultLoggerContext();
}
public LoggerContext getDefaultLoggerContext() {
return defaultLoggerContext;
}
饶了一大圈。当你不指定"logback.ContextSelector" 就是用的是这个默认的。而默认的这个就是 再
StaticLoggerBinder中的
private LoggerContext defaultLoggerContext = new LoggerContext();
new的这个对象
那么饶了一大圈。若是采用最基本和默认的方法获取 ILoggerFactory其实就是 获取了一个 LoggerContext 的defaultLoggerContext 对象。
LoggerContext
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle
通过类声明就可以知道了这玩意就是个ILoggerFactory的实现类
而开始的代码调用
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
其实获得的就是一个LoggerContext 的实例。
这部分Logger类工厂获取 LoggerContext对象算是告一段落
附上类图:
Logger
而我们的第二部是获取Logger对象
,上面几步已经知道了 LoggerContext是关键那么通过LoggerContext 的getLogger就可以知道如何获取的Logger了
这里的这个方法是LoggerContext类中的
public final Logger getLogger(final String name) {
//第一点这个名词不能是空的,通常都是类名或者配置文件中的appender的名字
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
//判断是否已经有了。比如说某个类的
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
//有就直接返回
if (childLogger != null) {
return childLogger;
}
//下面是创建Logger的关键
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
//自旋的开始
while (true) {
//对类名的处理主要是内部类。大概知道啥意思就好了。内部类不都带$的么。这里就是把这个内部类分开获取类名
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
//加锁进行初始化
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
//这里这个create 方法作为主要的方法。
//而Logger 需要注意的是这对象还是一个树。一级一级的。这里不但创建了logger的对象还需要维护一下树的长度什么的。
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
这时看看Logger中的 createChildByName 方法
Logger createChildByName(final String childName) {
int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
if (i_index != -1) {
throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
+ " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
}
if (childrenList == null) {
childrenList = new CopyOnWriteArrayList<Logger>();
}
Logger childLogger;
//就是维护内部的一个列表其实,这个newlogger是关键了。
childLogger = new Logger(childName, this, this.loggerContext);
childrenList.add(childLogger);
childLogger.effectiveLevelInt = this.effectiveLevelInt;
return childLogger;
}
//具体的 new Logger 反而没有什么过多的内容
Logger(String name, Logger parent, LoggerContext loggerContext) {
this.name = name;
this.parent = parent;
this.loggerContext = loggerContext;
}
具体执行的log.info到底干了些什么呢?
public void info(String msg) {
filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
}
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);
}
//构建loggingevent 一个事件对象。并调用 appenders进行输出。
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);
}
// 派发事件
public void callAppenders(ILoggingEvent event) {
int writes = 0;
// 可以看出这个是树形结构。从当前点一直往父类找并输出。
//那么这个主要方法就是appendLoopOnAppenders
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
//要是配置文件中没有appender或者没有加载到appender会执行下面方法
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
//其实这里调用了就是这个方法。
private int appendLoopOnAppenders(ILoggingEvent event) {
if (aai != null) {
return aai.appendLoopOnAppenders(event);
} else {
return 0;
}
}
//关于aai对象可以看到再logger声明的时候就有了
transient private AppenderAttachableImpl<ILoggingEvent> aai;
//是一个AppenderAttachableImpl 的实现类而
// 而通过aai.appendLoopOnAppenders(event);方法可以看到其实AppenderAttachableImpl 里面保存了全部配置文件中的appender而再操作时对他们全部进行添加
AppenderAttachableImpl
中的appendLoopOnAppenders 方法
//可以看到循环调用了不同的Appender的进行了doAppend方法
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主要看一下OutputStreamAppender 就知道Append都干了些什么了
OutputStreamAppender
protected void append(E eventObject) {
if (!isStarted()) {
return;
}
subAppend(eventObject);
}
protected void subAppend(E event) {
if (!isStarted()) {
return;
}
try {
// this step avoids LBCLASSIC-139
if (event instanceof DeferredProcessingAware) {
((DeferredProcessingAware) event).prepareForDeferredProcessing();
}
//这个encode方法就是再输出前干的最后一件事情了吧。看看这个encoder
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));
}
}
那么这里的 this.encoder.encode(event); 就成为了一个点,需要知道这个encoder是怎么来的了
再这个类里面还有一段初始化的代码
//大致可以判断出来在加载完appender后还对layour也进行了装载而且这个默认的encoder就是LayoutWrappingEncoder 这个类
public void setLayout(Layout<E> layout) {
addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
lwe.setLayout(layout);
lwe.setContext(context);
this.encoder = lwe;
}
LayoutWrappingEncoder
这个类的encod方法
public byte[] encode(E event) {
//原来这个方法调用的是 OutputStreamAppender 中setleyout中传入的leyout进行的操作。
String txt = layout.doLayout(event);
//然后调用内部方法转成字节数组
return convertToBytes(txt);
}
通过debug发现了一个最简单的配置里面的layout是 PatternLayout这个类对象。
PatternLayout
这个不贴代码了。里面标明了再配置文件中各种配置对应的解析方法,这里只看一下我关系的打印调用类的那个方法
defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());
给的都是ClassOfCallerConverter 这个类。进去看看。
ClassOfCallerConverter
protected String getFullyQualifiedName(ILoggingEvent event) {
StackTraceElement[] cda = event.getCallerData();
if (cda != null && cda.length > 0) {
return cda[0].getClassName();
} else {
return CallerData.NA;
}
}
这个类方法其实调用的是这个loggingevent对象本身的getCallerData方法来实现的。反过来看看这个
LoggingEvent
public StackTraceElement[] getCallerData() {
if (callerDataArray == null) {
callerDataArray = CallerData
.extract(new Throwable(), fqnOfLoggerClass, loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
}
return callerDataArray;
}
//这里CallerData.extract 作为工具类的就是用Throwable对象中获取堆栈然后再 ClassOfCallerConverter.getFullyQualifiedName 中获取第一个就是调用的类名了。
总结一下流程。
其中牵扯到很多的类而且关于appender 与 layout的读取与设置都没有再这里说出来。但是对于日志的使用和加载和输出整体流程大概是知道了。
就是先创建类工厂,再创建类工厂的时候就把配置什么都加载了。然后获取logger对象。而调用logger的输出时,则是创建了一个loggingevent的对象,在下来就是对着这个event一顿的格式化。最后转字节数组输出。
经过阅读。我们知道了logback支持的配置文件类型。不光是xml
也知道了再appender中layout中那些配置都是如何实现的。以及logger的数据模型是树状的等等了。
在说一下我需求的实现。其实就是再调用Appender的时候把我自己的layout设置进去。而我的layout只用把那个类名称处理的地方ClassOfCallerConverter这个类获取其中的栈的第二个类名就是我实际调用的的地方了。那么这就明白了。