国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

Java日志

2019-11-15 01:03:30
字體:
供稿:網(wǎng)友
java日志

日志對于一個系統(tǒng)來說非常重要,查找異常信息、分析系統(tǒng)運(yùn)行情況等都需要用到日志。所以無論是JDK還是第三方都提供了關(guān)于日志的相關(guān)工具,本文分別介紹以下幾種工具,以及各種工具間的整合、原理。

  • JDK的java.util.logging包
  • 第三方日志工具(commons-logging/slf4j,log4j/logback)

JDK的java.util.logging包

JDK1.4引入了java.util.logging包,包中主要的類包括:Logger、LogManager、Handler、Formatter。首先看一段比較簡單的示例代碼:

package me.likeyao.jdk.logger;import java.util.logging.Formatter;import java.util.logging.Handler;import java.util.logging.Level;import java.util.logging.LogRecord;import java.util.logging.Logger;public class JDKLoggerTest {    public static void main(String[] args) {        Logger logger = Logger.getLogger("logger");        logger.info("hello world");        Handler handler = new Handler() {            @Override            public void publish(LogRecord record) {            }            @Override            public void flush() {            }            @Override            public void close() throws SecurityException {            }        };        handler.setFormatter(new Formatter() {            @Override            public String format(LogRecord record) {                return null;            }        });        logger.setLevel(Level.INFO);        logger.log(Level.FINEST, "hello world");    }}

通過Logger.getLogger(name)方法可以獲取logger對象,logger對象有三個比較重要的概念:level、handler、formatter。level稱為日志級別,在java.util.logging包中定義了java.util.logging.Level類,里面包含SEVERE/WARNING/INFO/CONFIG/FINE/FINER/FINEST(從高到低)7種日志級別。設(shè)置日志級別會過濾掉一部分日志,例如當(dāng)日志級別設(shè)置為INFO級別時,CONFIG/FINE/FINER/FINEST級別的日志就會被忽略。handler解決的問題是日志輸出到哪里,是到控制臺(java.util.logging.ConsoleHandler),還是到文件(java.util.logging.FileHandler),或者是寫到Socket中(java.util.logging.SocketHandler)。formatter定義了日志輸出的格式,可以是xml(java.util.logging.XMLFormatter),也可以自己實現(xiàn)JSON格式的Fomatter。

logger對象是如何生成的

生成logger對象涉及到j(luò)ava.util.logging.LogManager類,LogManager中用到了單例模式,在static塊中初始化了LoggerManager實例對象。生成logger對象的過程:

從圖中可以看到,logger對象是在LoggerManager中創(chuàng)建的。LoggerManager中有一個叫userContext的LoggerContext對象,userContext緩存了所有的logger對象(緩存在namedLoggers中),并維護(hù)了一套logger對象的父子結(jié)構(gòu)。namedLoggers的定義:

PRivate final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>();

LoggerWeakRef繼承自java.lang.ref.WeakReference,namedLoggers并不直接持有l(wèi)ogger對象,當(dāng)持有l(wèi)ogger的對象被垃圾回收之后,只有一個weekreference指向logger,方便垃圾回收logger對象。

JDK logger其他一些有意思的東西

  • 有一個Logger.getLogger方法是兩參數(shù)的,public static Logger getLogger(String name, String resourceBundleName),第二個參數(shù)最終會變成java.util.ResourceBundle對象,可以用來做國際化。
  • java.util.logging.Handler可以設(shè)置java.util.logging.Filter更靈活的過濾日志。

第三方日志工具(commons-logging/slf4j,log4j/logback)

首先把四個工具分成了兩組,commons-logging/slf4j和log4j/logback。log4j/logback功能與java.util.logging包類似,提供實際的日志功能。commons-logging/slf4j是門面,作用是統(tǒng)一日志操作,屏蔽底層不同日志組件的差異。

commons-logging

commons-logging是apache的項目,使用commons-logging的代碼:

package me.likeyao.java.logger;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class JCLTest {    private static Log logger = LogFactory.getLog(JCLTest.class);    public static void main(String[] args) throws Exception {        logger.info("hello world");    }}

代碼中用到了commons-logging日志對象(org.apache.commons.logging.Log),日志對象工廠(org.apache.commons.logging.LogFactory)。前面有說過commons-logging是一個統(tǒng)一操作的門面,不涉及具體的日志功能,那日志對象是怎么產(chǎn)生的?查看Log對象的繼承關(guān)系:

commons-logging分別為支持的日志工具提供了一個Log類的實現(xiàn)類,在列表中看到了Log4JLogger和Jdk14Logger等,意味著commons-logging可以log4j、java.util.logging組合使用。因為沒有對應(yīng)的logback實現(xiàn),所以也就無法一起使用。下圖解釋commons-logging如何決定具體生成哪種Logger對象:

LogFactory在static塊中初始化了HashTable對象factories,以ClassLoader為key,LogFactory為value緩存了所有LogFactory對象。主要看一下LogFactoryImpl的discoverLogImplementation方法是如何發(fā)現(xiàn)底層使用的日志工具(省略了方法一部分內(nèi)容):

private static final String[] classesToDiscover = {            LOGGING_IMPL_LOG4J_LOGGER, //org.apache.commons.logging.impl.Log4JLogger            "org.apache.commons.logging.impl.Jdk14Logger",            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",            "org.apache.commons.logging.impl.SimpleLog"    };private Log discoverLogImplementation(String logCategory)    ...    Log result = null;    //查看commons-logging.properties和System.getProperty是否配置了org.apache.commons.logging.log    String specifiedLogClassName = findUserSpecifiedLogClassName();    if (specifiedLogClassName != null) {        ...        //如果有配置,直接使用配置的類創(chuàng)建Log        result = createLogFromClass(specifiedLogClassName,                                    logCategory,                                    true);        ...        return result;    }    ...    //如果沒有,遍歷classesToDiscover數(shù)組,如果使用指定的ClassLoader Class.forName能加載到類,就創(chuàng)建Log對象    for(int i=0; i<classesToDiscover.length && result == null; ++i) {        result = createLogFromClass(classesToDiscover[i], logCategory, true);    }    if (result == null) {        throw new LogConfigurationException                    ("No suitable Log implementation");    }    return result;}

整個初始化Log對象的流程:

commons-logging中的classloader

網(wǎng)上搜commons-logging,可以看到不少文章都是在講關(guān)于classloader的問題,貼出比較詳細(xì)的一篇的鏈接:《Taxonomy of class loader problems encountered when using Jakarta Commons Logging》。文章中用的commons-logging本版較老,這里用commons-logging-1.2+log4k-1.2.17模擬一種場景,看看新commons-logging有什么改變。代碼:

package me.likeyao.java.logger;import java.io.File;import java.net.URL;import java.net.URLClassLoader;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class JCLTest3 {    public static void main(String[] args) throws Exception {        //自定義classloader,模仿web容器classloader,自己加載優(yōu)先        ChildClassLoader childClassLoader = new ChildClassLoader(                new URL[] { new File("c:/tmpclass/commons-logging-1.2.jar").toURL(),                            new File("C:/respository3/log4j/log4j/1.2.17/log4j-1.2.17.jar").toURL()});        Thread.currentThread().setContextClassLoader(childClassLoader);        Log log = LogFactory.getLog(JCLTest3.class);        log.error("hello world");    }}class ChildClassLoader extends URLClassLoader {    public ChildClassLoader(URL[] urls) {        super(urls, ClassLoader.getSystemClassLoader());    }    @Override    protected Class<?> loadClass(String name, boolean resolve)            throws ClassNotFoundException {        synchronized (getClassLoadingLock(name)) {            Class c = findLoadedClass(name);            try{                c = findClass(name);                if (resolve) {                    resolveClass(c);                }            }catch(Exception e){                            }            if (c == null) {                c = super.loadClass(name, resolve);            }            return c;        }    }}

命令行執(zhí)行:java -cp .;commons-logging-1.2.jar me.likeyao.java.logger.JCLTest3。輸出結(jié)果:執(zhí)行沒有拋異常,日志是通過JDK java.util.logging打出來了,而不是log4j。

可以從代碼中分析出為什么會產(chǎn)生這樣的結(jié)果。這里用到了兩種類加載器:系統(tǒng)類加載器(AppClassLoader)和自定義的ChildClassLoader,系統(tǒng)類加載器能加載到當(dāng)前編譯目錄的class文件和commons-logging.jar,ChildClassLoader加載了commons-logging.jar和log4j.jar,線程上下文加載器被設(shè)置為ChildClassLoader。當(dāng)運(yùn)行java命令時,隨著程序運(yùn)行會使用系統(tǒng)類加載器加載Log類、LogFactory類,查看上面discoverLogImplementation方法源碼,由于沒有配置org.apache.commons.logging.log屬性,具體使用哪個Log的實現(xiàn)類會通過遍歷classesToDiscover數(shù)組決定。下面看一下classesToDiscover[0]時的情況:

private Log createLogFromClass(String logAdapterClassName, //org.apache.commons.logging.impl.Log4JLogger                               String logCategory,                               boolean affectState)    throws LogConfigurationException {    ...    //通過logAdapterClassName所制定的類創(chuàng)建的對象,方法返回的結(jié)果    Log logAdapter = null;    ...    //獲取classloader,這里會返回ChildClassLoader,即main方法中設(shè)置的線程上下文加載器    ClassLoader currentCL = getBaseClassLoader();    for(;;) {        try {            ...            Class c;            try {                //因為ChildClassLoader加載器可以獲取到org.apache.commons.logging.impl.Log4JLogger類,正常得到c                c = Class.forName(logAdapterClassName, true, currentCL);            } catch (ClassNotFoundException originalClassNotFoundException) {                ...            }            //創(chuàng)建org.apache.commons.logging.impl.Log4JLogger對象,這里o的classloader是ChildClassLoader            constructor = c.getConstructor(logConstructorSignature);            Object o = constructor.newInstance(params);            // 注意下面的注釋,因為Log和o的類加載器不一致,所以不進(jìn)入if分支            // Note that we do this test after trying to create an instance            // [rather than testing Log.class.isAssignableFrom(c)] so that            // we don't complain about Log hierarchy problems when the            // adapter couldn't be instantiated anyway.            if (o instanceof Log) {                logAdapterClass = c;                logAdapter = (Log) o;                break;            }            // Oops, we have a potential problem here. An adapter class            // has been found and its underlying lib is present too, but            // there are multiple Log interface classes available making it            // impossible to cast to the type the caller wanted. We            // certainly can't use this logger, but we need to know whether            // to keep on discovering or terminate now.            //            // The handleFlawedHierarchy method will throw            // LogConfigurationException if it regards this problem as            // fatal, and just return if not.            // 根據(jù)allowFlawedHierarchy參數(shù)判斷是否拋出LogConfigurationException,如果不拋異常,只是簡單return,進(jìn)入下一個循環(huán)            handleFlawedHierarchy(currentCL, c);        } catch (NoClassDefFoundError e) {            ...            break;        } catch (ExceptionInInitializerError e) {            ...            break;        } catch (LogConfigurationException e) {            // call to handleFlawedHierarchy above must have thrown            // a LogConfigurationException, so just throw it on            throw e;        } catch (Throwable t) {            ...        }        if (currentCL == null) {            break;        }        // try the parent classloader        // currentCL = currentCL.getParent();        currentCL = getParentClassLoader(currentCL);    }    ...    return logAdapter;}

classesToDiscover[0]的調(diào)用中,logAdapter并沒有指向org.apache.commons.logging.impl.Log4JLogger對象,而是返回了null。所以進(jìn)入下一次循環(huán)classesToDiscover[1](org.apache.commons.logging.impl.Jdk14Logger),這一次因為Log類和org.apache.commons.logging.impl.Jdk14Logger類都是由系統(tǒng)類加載器加載,所以最終執(zhí)行的結(jié)果日志由java.util.logging打出。commons-logging使用classloader來加載Resource和發(fā)現(xiàn)底層具體日志工具,在web容器或者OSGI這些需要用類加載器做隔離的情況下的確會出現(xiàn)一些問題。

SLF4J/LOG4J

另一種日志門面SLF4J,它采用“靜態(tài)綁定”的方式避免了commons-logging中有關(guān)類加載的一些問題。下面的把SLF4J和LOG4J放到一起,首先還是比較簡單的使用代碼:

package me.likeyao.slf4j.logger;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class SLF4JTest {    public static void main(String[] args) {        Logger logger = LoggerFactory.getLogger(SLF4JTest.class);        logger.error("hello world");    }}

使用方式上看與commons-logging類似,除了類名上有一些區(qū)別。下圖的是SLF4J整合LOG4J時Logger初始化時序圖:

這里對幾個類用了不同的顏色,藍(lán)色代表類在slf4j-api包中,黃色代表類在slf4j-log4j12包中,紅色代表類在log4j包中。相比commons-logging運(yùn)行時通過classloader來發(fā)現(xiàn)底層日志工具,SLF4J是通過在不同的“橋梁包”里放置同名類org.slf4j.impl.StaticLoggerBinder來實現(xiàn)日志工具的綁定。例如,如果需要將SLF4J與java.util.logging整合,需要加入slf4j-jdk14-1.7.12,包中的StaticLoggerBinder類返回的loggerfactory是org.slf4j.impl.JDK14LoggerFactory;如果是SLF4J與log4j整合,需要加入slf4j-log4j12-1.7.12,包中的StaticLoggerBinder類返回的loggerfactory是org.slf4j.impl.Log4jLoggerFactory。這就是SLF4J的靜態(tài)綁定。

LOG4J是常用的日志工具,與java.util.logging(jul)非常像,一些概念也是共通的。例如logger都是父子結(jié)構(gòu)的、jul的handler對應(yīng)log4j的appender、formater對應(yīng)layout、也都存在filter。為了理清log4j中各個類的關(guān)系,整理一份類圖:

整個類圖的核心是org.apache.log4j.Category,但從1.2版本后log4j不會直接產(chǎn)生Category對象,而是Logger對象。Logger類繼承自Category,擴(kuò)展了一個日志級別:trace。當(dāng)調(diào)用Logger對象的info/warn等方法時會生成一個LoggerEvent對象,AppenderAttachableImpl會遍歷所有的appender調(diào)用doAppend方法。如果event沒有被Filter過濾掉,那最終會經(jīng)過Layout格式化,輸出到appender指定的地方。LOG4J提供了很多appender供使用,這一點比JDK的hander強(qiáng)大很多。

Appender的初始化在LogManager的static塊中進(jìn)行,最終解析發(fā)生在org.apache.log4j.xml.DOMConfigurator類中,logger、root、appender都在這里解析。

    if (tagName.equals(CATEGORY) || tagName.equals(LOGGER)) {      parseCategory(currentElement);    } else if (tagName.equals(ROOT_TAG)) {      parseRoot(currentElement);    } else if(tagName.equals(RENDERER_TAG)) {      parseRenderer(currentElement);    } else if(tagName.equals(THROWABLE_RENDERER_TAG)) {        if (repository instanceof ThrowableRendererSupport) {            ThrowableRenderer tr = parseThrowableRenderer(currentElement);            if (tr != null) {                ((ThrowableRendererSupport) repository).setThrowableRenderer(tr);            }        }    } else if (!(tagName.equals(APPENDER_TAG)            || tagName.equals(CATEGORY_FACTORY_TAG)            || tagName.equals(LOGGER_FACTORY_TAG))) {        quietParseUnrecognizedElement(repository, currentElement, props);    }

LOGBACK

logback是log4j的一個替代品,初始化和打日志代碼流程相似。為什么要從log4j切換到logback,logback網(wǎng)站上已經(jīng)給出了(Reasons to prefer logback over log4j)。要使用SLF4J+logback需要引入三個包:slf4j-api、logback-core、logback-classic。這里和log4j對比,介紹一下兩種日志工具的父子結(jié)構(gòu)。無論是java.util.logging還是log4j/logback都為logger對象提供了父子結(jié)構(gòu),這樣做有什么好處?我覺得主要是這樣logger對象會有一個樹形的層次結(jié)構(gòu),底層的logger可以復(fù)用父logger中的一些配置,比如日志級別,appender等。從log4j和logback的配置文件中,也可以看出這一點,通過為某個包名的logger指定appender和日志級別,可以作用所有這個包下的logger。例如:

<logger name="me.likeyao" additivity="false">         <level value="WARN" />         <appender-ref ref="CONSOLE" />     </logger>

log4j和logback在處理父子結(jié)構(gòu)時有一些差別,先看log4j的代碼:

synchronized(ht) {      Object o = ht.get(key);      if(o == null) {        logger = factory.makeNewLoggerInstance(name);        logger.setHierarchy(this);        ht.put(key, logger);        updateParents(logger);        return logger;      } else if(o instanceof Logger) {        return (Logger) o;      } else if (o instanceof ProvisionNode) {        //System.out.println("("+name+") ht.get(this) returned ProvisionNode");        logger = factory.makeNewLoggerInstance(name);        logger.setHierarchy(this);        ht.put(key, logger);        updateChildren((ProvisionNode) o, logger);        updateParents(logger);        return logger;      }      else {        // It should be impossible to arrive here        return null;  // but let's keep the compiler happy.      }}

log4j在創(chuàng)建logger時,如果父節(jié)點是一個logger,那只維護(hù)一個子logger對父logger的引用。如果父節(jié)點不存在,就創(chuàng)建一些ProvisionNode對象(這是一個Vector的子類),保存所有下面子的logger。舉個例子:當(dāng)獲取名稱為x.y.z的logger時,首先會創(chuàng)建logger(x.y.z);然后查找父logger x.y,如果x.y不存在,就創(chuàng)建一個ProvisionNode對象,把logger(x.y.z)放到ProvisionNode對象中;然后繼續(xù)向上搜索名稱為x的logger,就將logger(x.y.z)的parent設(shè)置為logger(x)。當(dāng)調(diào)用logger(x.y.z)對象的info/warn等方法時,如果logger(x.y.z)沒有設(shè)置日志級別和appender,就是沿著parent向上搜索,直到rootLogger為止。

logback的結(jié)構(gòu)稍微有一些區(qū)別,所有的節(jié)點都是Logger對象,對象中有指向parent和children的引用,并且創(chuàng)建節(jié)點時,會把parent的日志級別直接復(fù)制到自己對象中。

    // 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) {          childLogger = logger.createChildByName(childName);          loggerCache.put(childName, childLogger);          incSize();        }      }      logger = childLogger;      if (h == -1) {        return childLogger;      }    }

總結(jié)

SL4J/COMMONS-LOGGING、LOG4J、LOGBACK、JUL都是常用的Java日志工具,基本思想都是通過Factory生成logger對象,然后由LogManager管理/緩存logger對象,同時維護(hù)一個父子結(jié)構(gòu)方便復(fù)用配置。logger對象包含三要素:日志級別、輸出到哪里、格式化。


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 融水| 界首市| 曲水县| 郧西县| 南川市| 盐城市| 汶上县| 荔波县| 贡嘎县| 政和县| 边坝县| 永福县| 高阳县| 桂东县| 玛纳斯县| 林甸县| 洪洞县| 丁青县| 偏关县| 衡阳市| 北票市| 辽宁省| 穆棱市| 永兴县| 霸州市| 任丘市| 龙门县| 五河县| 无锡市| 乌兰县| 清水河县| 晋宁县| 瑞金市| 邵阳市| 大厂| 沐川县| 江城| 利津县| 延吉市| 新安县| 铜山县|