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。