slf4J橋接JDK-LOG原理

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。

相關文章
相關標籤/搜索