slf4j为jdk-log,common-log,log4j提供的桥接器,用来替代原有的日志实现。slf4j和logback结合使用能够方便的替换掉jdk-log,common-log,log4j日志实现。java
1、替换jdk-lognode
1.获取Logger实例json
Logger logger = Logger.getLogger(Test.class.getName());
首先看java.util.logger的实现;getLogger调用manager.demandLogger(name, resourceBundleName, caller);这个方法中作了不少安全校验工做,这个留到后面再深刻的学习。而后都调用了manager.demandLogger(name, resourceBundleName, caller),在这个方法内部:安全
// Find or create a specified logger instance. If a logger has // already been created with the given name it is returned. // Otherwise a new logger instance is created and registered // in the LogManager global namespace. // This method will always return a non-null Logger object. // Synchronization is not required here. All synchronization for // adding a new Logger object is handled by addLogger(). // // This method must delegate to the LogManager implementation to // add a new Logger or return the one that has been added previously // as a LogManager subclass may override the addLogger, getLogger, // readConfiguration, and other methods. Logger demandLogger(String name, String resourceBundleName, Class<?> caller) { Logger result = getLogger(name); if (result == null) { // only allocate the new logger once Logger newLogger = new Logger(name, resourceBundleName, caller, this, false); do { if (addLogger(newLogger)) { // We successfully added the new Logger that we // created above so return it without refetching. return newLogger; } // We didn't add the new Logger that we created above // because another thread added a Logger with the same // name after our null check above and before our call // to addLogger(). We have to refetch the Logger because // addLogger() returns a boolean instead of the Logger // reference itself. However, if the thread that created // the other Logger is not holding a strong reference to // the other Logger, then it is possible for the other // Logger to be GC'ed after we saw it in addLogger() and // before we can refetch it. If it has been GC'ed then // we'll just loop around and try again. result = getLogger(name); } while (result == null); } return result; }
这个方法首先根据name去查找Logger是否已经建立,若是建立就直接返回;不然就new Logger();less
2.打印日志ide
首先看方法入口oop
logger.info("hello world!");
而后点进去看,根据日志的输出级别输出日志学习
public void info(String msg) { log(Level.INFO, msg); }
继续往下看,根据级别盘点是否能够输出,若是不能够输出直接return;fetch
public void log(Level level, String msg) { if (!isLoggable(level)) { return; } LogRecord lr = new LogRecord(level, msg); doLog(lr); }
在new LogRecord设置了一些记录参数,而后执行doLog(lr);ui
// private support method for logging. // We fill in the logger name, resource bundle name, and // resource bundle and then call "void log(LogRecord)". private void doLog(LogRecord lr) { lr.setLoggerName(name); final LoggerBundle lb = getEffectiveLoggerBundle(); final ResourceBundle bundle = lb.userBundle; final String ebname = lb.resourceBundleName; if (ebname != null && bundle != null) { lr.setResourceBundleName(ebname); lr.setResourceBundle(bundle); } log(lr); }
在doLog方法里面,首先检查有没有相关的配置文件,包括父类的
/** * Log a LogRecord. * <p> * All the other logging methods in this class call through * this method to actually perform any logging. Subclasses can * override this single method to capture all log activity. * * @param record the LogRecord to be published */ public void log(LogRecord record) { if (!isLoggable(record.getLevel())) { return; } Filter theFilter = filter; if (theFilter != null && !theFilter.isLoggable(record)) { return; } // Post the LogRecord to all our Handlers, and then to // our parents' handlers, all the way up the tree. Logger logger = this; while (logger != null) { final Handler[] loggerHandlers = isSystemLogger ? logger.accessCheckedHandlers() : logger.getHandlers(); for (Handler handler : loggerHandlers) { handler.publish(record); } final boolean useParentHdls = isSystemLogger ? logger.useParentHandlers : logger.getUseParentHandlers(); if (!useParentHdls) { break; } logger = isSystemLogger ? logger.parent : logger.getParent(); } }
而后执行log(lr)方法,在log方法里面,开始仍然是检查是否能够输出日志,而后调用handler.public()方法来打印日志。
for (Handler handler : loggerHandlers) { handler.publish(record); }
在JDK中,Handler的实现类有三个,里面有具体打印日志的方法。
3、桥接原理
讲到这里,log4j-over-slf4j如何桥接java.util.logger应该比较容易理解了。log4j-over-slf4j包只有一个类就是SLF4JBridgeHandler,这个类继承了handle的类。
要使用slf4j桥接,首先须要调用这个方法。
SLF4JBridgeHandler.install();
再看看install作了什么
/** * Adds a SLF4JBridgeHandler instance to jul's root logger. * <p/> * <p/> * This handler will redirect j.u.l. logging to SLF4J. However, only logs enabled * in j.u.l. will be redirected. For example, if a log statement invoking a * j.u.l. logger is disabled, then the corresponding non-event will <em>not</em> * reach SLF4JBridgeHandler and cannot be redirected. */ public static void install() { LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler()); }
为名字为“”的Logger加了个handler。LogManager是一个单例模式,有惟一的一个实例manager。
接下来回过头来看,打印日志的逻辑
public void log(LogRecord record) { if (!isLoggable(record.getLevel())) { return; } Filter theFilter = filter; if (theFilter != null && !theFilter.isLoggable(record)) { return; } // Post the LogRecord to all our Handlers, and then to // our parents' handlers, all the way up the tree. Logger logger = this; while (logger != null) { final Handler[] loggerHandlers = isSystemLogger ? logger.accessCheckedHandlers() : logger.getHandlers(); for (Handler handler : loggerHandlers) { handler.publish(record); } final boolean useParentHdls = isSystemLogger ? logger.useParentHandlers : logger.getUseParentHandlers(); if (!useParentHdls) { break; } logger = isSystemLogger ? logger.parent : logger.getParent(); } }
这里是难点和重点,若是没有设置handler,那么就去找parent的handler,在SLF4JBridgeHandler设置了一个给名字为空字符串“”的Logger设置了handler,那么能够猜想,名字为空字符串“”Logger应该是全部Logger的parent,那么程序在哪里设置的呢?咱们来看前面的代码
Logger demandLogger(String name, String resourceBundleName, Class<?> caller) { Logger result = getLogger(name); if (result == null) { // only allocate the new logger once Logger newLogger = new Logger(name, resourceBundleName, caller, this, false); do { if (addLogger(newLogger)) { // We successfully added the new Logger that we // created above so return it without refetching. return newLogger; } ....}
看addLoger,点进去看(补充说明:每次在根据名字建立Logger的时候,JDK的logManager都会建立名字为““和”global”的logger)。
public boolean addLogger(Logger logger) { final String name = logger.getName(); if (name == null) { throw new NullPointerException(); } drainLoggerRefQueueBounded(); LoggerContext cx = getUserContext(); if (cx.addLocalLogger(logger)) { // Do we have a per logger handler too? // Note: this will add a 200ms penalty loadLoggerHandlers(logger, name, name + ".handlers"); return true; } else { return false; }
看是if语句if (cx.addLocalLogger(logger))的方法,藏得很隐秘,点进去再点进去
// Add a logger to this context. This method will only set its level // and process parent loggers. It doesn't set its handlers. synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) { // addDefaultLoggersIfNeeded serves to break recursion when adding // default loggers. If we're adding one of the default loggers // (we're being called from ensureDefaultLogger()) then // addDefaultLoggersIfNeeded will be false: we don't want to // call ensureAllDefaultLoggers again. // // Note: addDefaultLoggersIfNeeded can also be false when // requiresDefaultLoggers is false - since calling // ensureAllDefaultLoggers would have no effect in this case. if (addDefaultLoggersIfNeeded) { ensureAllDefaultLoggers(logger); } final String name = logger.getName(); if (name == null) { throw new NullPointerException(); } LoggerWeakRef ref = namedLoggers.get(name); if (ref != null) { if (ref.get() == null) { // It's possible that the Logger was GC'ed after a // drainLoggerRefQueueBounded() call above so allow // a new one to be registered. ref.dispose(); } else { // We already have a registered logger with the given name. return false; } } // We're adding a new logger. // Note that we are creating a weak reference here. final LogManager owner = getOwner(); logger.setLogManager(owner); ref = owner.new LoggerWeakRef(logger); namedLoggers.put(name, ref); // Apply any initial level defined for the new logger, unless // the logger's level is already initialized Level level = owner.getLevelProperty(name + ".level", null); if (level != null && !logger.isLevelInitialized()) { doSetLevel(logger, level); } // instantiation of the handler is done in the LogManager.addLogger // implementation as a handler class may be only visible to LogManager // subclass for the custom log manager case processParentHandlers(logger, name); // Find the new node and its parent. LogNode node = getNode(name); node.loggerRef = ref; Logger parent = null; LogNode nodep = node.parent; while (nodep != null) { LoggerWeakRef nodeRef = nodep.loggerRef; if (nodeRef != null) { parent = nodeRef.get(); if (parent != null) { break; } } nodep = nodep.parent; } if (parent != null) { doSetParent(logger, parent); } // Walk over the children and tell them we are their new parent. node.walkAndSetParent(logger); // new LogNode is ready so tell the LoggerWeakRef about it ref.setNode(node); return true; }
看到getNode(name)这一行,点进去
// Gets a node in our tree of logger nodes. // If necessary, create it. LogNode getNode(String name) { if (name == null || name.equals("")) { return root; } LogNode node = root; while (name.length() > 0) { int ix = name.indexOf("."); String head; if (ix > 0) { head = name.substring(0, ix); name = name.substring(ix + 1); } else { head = name; name = ""; } if (node.children == null) { node.children = new HashMap<>(); } LogNode child = node.children.get(head); if (child == null) { child = new LogNode(node, this); node.children.put(head, child); } node = child; } return node; } }
这里的root就是一个名字为“”的节点,而后将参数name的节点设置为root的子节点。接下来的逻辑就很好理解了。而后在addLocalLoger设置成Node节点就好了,这里使用了弱引用weekRef。应该是方便GC。