commons-logger的原理和log4j.properties 配置

任何項目工程,日志的作用都毋庸置疑得重要,監(jiān)控,問題查找,統(tǒng)計,大數(shù)據(jù)資源來源等。在閱讀spring源碼過程中開啟spring的日志對于閱讀的幫助也是非常大的。

先說說apache自帶的log組件 <b>commons-logging</b>

<em>commons-logging</em>提供的是一個日志的接口(<em>interface</em>),并有一個簡單的日志實現(xiàn)SimpleLog.class。它提供給中間件/日志工具開發(fā)者一個簡單的日志操作抽象,允許程序開發(fā)人員使用不同的具體日志實現(xiàn)工具。然后對各種實現(xiàn)的日志工具如Log4j、Avalon LogKit, and JDK(具體實現(xiàn)在java.util.loggingpackage中)中選擇一個。有各種整合方式。
網上看到的一個<em>commons-logging</em>出現(xiàn)的小故事

org.apache.commons.logging.Log 和org.apache.log4j.Logger 這兩個類,通過包名我們可以發(fā)現(xiàn)它們都是 apache 的項目,既然如此,為何要動如此大的動作搞兩個東西(指的是 commons-logging 和 log4j)出來呢?事實上,在 sun 開發(fā) logger 前,apache 項目已經開發(fā)了功能強大的 log4j 日志工具,并向 sun 推薦將其納入到 jdk 的一部分,可是 sun 拒絕了 apache 的提議,sun 后來自己開發(fā)了一套記錄日志的工具。可是現(xiàn)在的開源項目都使用的是 log4j,log4j 已經成了事實上的標準,但由于又有一部分開發(fā)者在使用 sun logger,因此 apache 才推出 commons-logging,使得我們不必關注我們正在使用何種日志工具

在不使用log4j的情況只使用<em>commons-logging</em> 我們也是可以打印日志出來的,下面看下源碼:
找到

log入口.png

點擊<b>Log</b>進入源代碼

commons-logging.png

<b>Log</b>是個interface

package org.apache.commons.logging;
public interface Log {
    void debug(Object message);
    void debug(Object message, Throwable t);
    void error(Object message);
    void error(Object message, Throwable t);
    void fatal(Object message);
    void fatal(Object message, Throwable t);
    void info(Object message);
    void info(Object message, Throwable t);
    boolean isDebugEnabled();
    boolean isErrorEnabled();
    boolean isFatalEnabled();
    boolean isInfoEnabled();
    boolean isTraceEnabled();
    boolean isWarnEnabled();
    void trace(Object message);
    void trace(Object message, Throwable t);
    void warn(Object message);
    void warn(Object message, Throwable t);
}

<b>LogFactory</b>是個抽象類public abstract class LogFactory
<b>LogSource</b>是以前創(chuàng)建<b>Log</b>的class 現(xiàn)在已被遺棄
<b>LogConfigurationException</b>是 <code>LogFactory</code>或者 <code>Log</code>實例創(chuàng)建失敗的時候拋出的異常
<b>impl</b>包下的<code>AvalonLogger</code><code>Jdk13LumberjackLogger</code><code>Log4JLogger</code><code>LogKitLogger</code><code>NoOpLog</code><code>SimpleLog</code>是<code>Log</code>接口的實現(xiàn)
<b>ServletContextCleaner</b>是<b>ServletContextListener</b>的實現(xiàn) 在contextDestroyed的時候把創(chuàng)建的<b>LogFactory</b>銷毀掉。

public class ServletContextCleaner implements ServletContextListener {
    private static final Class[] RELEASE_SIGNATURE = {ClassLoader.class};
    public void contextDestroyed(ServletContextEvent sce) {
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        Object[] params = new Object[1];
        params[0] = tccl;
                 …………………
            } catch(InvocationTargetException ex) {
                // This is not expected
                System.err.println("LogFactory instance release method failed!");
                loader = null;
            }
        }
        LogFactory.release(tccl);

其中LogFactory.release(tccl)在LogFactory中這樣是這樣實現(xiàn)

public static void release(ClassLoader classLoader) {
                   …………
                    factory.release();
                    factories.remove(classLoader);
                }
            }
        }
    }

其中factory是 protected static Hashtable factories = null;
在LogFactory實例化的時候 調用 static {……factories = createFactoryStore();……} 其中<b>createFactoryStore</b>創(chuàng)建了

 private static final String WEAK_HASHTABLE_CLASSNAME ="org.apache.commons.logging.impl.WeakHashtable";

屬性 即WeakHashtable。存放LogFactory
最后 factories.release的時候相當于清空了WeakHashtable,WeakHashtable其實是基于WeakReference 實現(xiàn)的 這種弱引用在一定情況下會被jvm優(yōu)先回收掉 所以節(jié)約內存。時間久了不用jvm會回收掉后,當再次用到的時候再創(chuàng)建即可。
<b>先說了銷毀再看下是怎么創(chuàng)建和選擇Log實現(xiàn)類的</b>
private static final Log log = LogFactory.getLog(FactoryBeanTests.class);接著找到getLog

public static Log getLog(Class clazz) throws LogConfigurationException {
        return getFactory().getInstance(clazz);
 }

這里面有兩個重要的方法<b>getFactory()</b>和<b>getInstance(clazz)</b>
先說<b>getFactory()</b>
getFactory的時候會先取到ClassLoader 用戶加載所需要類的class文件
(基本各種判斷后獲得到的一般是當前類的classLoader classLoader = Thread.currentThread().getContextClassLoader()
接著LogFactory factory = getCachedFactory(contextClassLoader); 這是從上面說的WeakHashtable的實例factories中取,第一次肯定是沒有的 繼續(xù)往下會去讀取Properties文件

Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
public static final String FACTORY_PROPERTIES = "commons-logging.properties";

用戶沒配置<em>commons-logging.properties</em>的情況下取到props==null
繼續(xù)往下回去系統(tǒng)啟動的屬性中去找

String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
    public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";

啟動的時候沒有設置則factoryClass==null 繼續(xù)往下
會讀取META-INF/services/org.apache.commons.logging.LogFactory這個文件

 final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
    protected static final String SERVICE_ID =
        "META-INF/services/org.apache.commons.logging.LogFactory";

沒有配置這個文件的情況下繼續(xù)往下
到了

 if (factory == null) {
            factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
        }
 public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl";

這個地方newFactory 方法去創(chuàng)建FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"利用反射機制 默認創(chuàng)建了他的實現(xiàn)類<b>LogFactoryImpl</b>
再來看上面說的

這里面有兩個重要的方法<b>getFactory()</b>和<b>getInstance(clazz)</b>

的第二個方法getInstance 此時的getInstance是LogFactoryImpl的

public Log getInstance(Class clazz) throws LogConfigurationException {
        return getInstance(clazz.getName());
    }

接著往下走調用了<b>newInstance</b> 方法

if (logConstructor == null) {
                instance = discoverLogImplementation(name);
            }

第一次logConstructor 肯定為null所以繼續(xù)進去看

 private Log discoverLogImplementation(String logCategory) 
…………
 initConfiguration();
 String specifiedLogClassName = findUserSpecifiedLogClassName();
 if (specifiedLogClassName != null) {
………………
 for(int i=0; i<classesToDiscover.length && result == null; ++i) {
            result = createLogFromClass(classesToDiscover[i], logCategory, true);
        }

列出幾個的關鍵點initConfiguration初始化一些配置
findUserSpecifiedLogClassName 是去緩存找緩存的specifiedClass(具體的class)這里會查找這兩個,一個是以前的另一個是現(xiàn)在在用的

    public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";
    protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log";

第一次所以沒有的話繼續(xù)到

for(int i=0; i<classesToDiscover.length && result == null; ++i) {
            result = createLogFromClass(classesToDiscover[i], logCategory, true);
        }

這個是最關鍵的部分

 private static final String[] classesToDiscover = {
            LOGGING_IMPL_LOG4J_LOGGER,
            "org.apache.commons.logging.impl.Jdk14Logger",
            "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
            "org.apache.commons.logging.impl.SimpleLog"
    };

選擇默認找這三個
第一個org.apache.commons.logging.impl.Jdk14Loggerimpl包就有 遍歷的時候判斷了

result==null.png

第一次沒仔細看糾結了半天 以為三個都創(chuàng)建了 其實值創(chuàng)建了一個
好了肯定是創(chuàng)建了org.apache.commons.logging.impl.Jdk14Logger 這個類
到這個類中看 就是封裝了JDK的日志實現(xiàn)
這個類Jdk14Logger 沒有找到設置日志級別的方法
繼續(xù)往jdk實現(xiàn)中找,在java.util.logging.Logger找到

private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
        LogManager manager = LogManager.getLogManager();
        SecurityManager sm = System.getSecurityManager();
        if (sm != null && !SystemLoggerHelper.disableCallerCheck) {
            if (caller.getClassLoader() == null) {
                return manager.demandSystemLogger(name, resourceBundleName);
            }
        }
        return manager.demandLogger(name, resourceBundleName, caller);
        // ends up calling new Logger(name, resourceBundleName, caller)
        // iff the logger doesn't exist already
    }

其實決定權在LogManager manager = LogManager.getLogManager();
到LogManager 類中getLogManager方法

public static LogManager getLogManager() {
        if (manager != null) {
            manager.ensureLogManagerInitialized();
        }
        return manager;
    }

繼續(xù)找到ensureLogManagerInitialized

final void ensureLogManagerInitialized() {
        final LogManager owner = this;
        if (initializationDone || owner != manager) {
            return;
        }
  ………………
        synchronized(this) {
                      assert rootLogger == null;
                        assert initializedCalled && !initializationDone;

                        // Read configuration.
                        owner.readPrimordialConfiguration();
            …………………………
                        if (!owner.rootLogger.isLevelInitialized()) {
                            owner.rootLogger.setLevel(defaultLevel);
                        }
                       …………………………
            } finally {
                initializationDone = true;
            }
        }
    }

最重要的一句owner.rootLogger.setLevel(defaultLevel);這個defaultLevel是private final static Level defaultLevel = Level.INFO;
默認是INFO,那么怎么打印DEBUG呢
其實在靜態(tài)初始化塊中還有個重要的方法owner.readPrimordialConfiguration() 其中owner是final LogManager owner = this;即當前LogManager
繼續(xù)看<b>readPrimordialConfiguration</b>方法

 private void readPrimordialConfiguration() {
     ……………………
                        AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                                @Override
                                public Void run() throws Exception {
                                    readConfiguration();

                                    // Platform loggers begin to delegate to java.util.logging.Logger
                                    sun.util.logging.PlatformLogger.redirectPlatformLoggers();
                                    return null;
       ……………………
        }
    }

看到<b>readConfiguration</b>方法,重點就是這個方法,先會嘗試去加載 String cname = System.getProperty("java.util.logging.config.class");java.util.logging.config.class 加載不到的話回去加載配置文件String fname = System.getProperty("java.util.logging.config.file");java.util.logging.config.file 如果還加載不到的話會去java home下面加載logging.properties ,哈哈 所以只要配置logging.properties即可了
源碼

public void readConfiguration() throws IOException, SecurityException {
        checkPermission();

        // if a configuration class is specified, load it and use it.
        String cname = System.getProperty("java.util.logging.config.class");
        if (cname != null) {
            try {
                // Instantiate the named class.  It is its constructor's
                // responsibility to initialize the logging configuration, by
                // calling readConfiguration(InputStream) with a suitable stream.
                try {
                    Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
                    clz.newInstance();
                    return;
                } catch (ClassNotFoundException ex) {
                    Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass(cname);
                    clz.newInstance();
                    return;
                }
            } catch (Exception ex) {
                System.err.println("Logging configuration class \"" + cname + "\" failed");
                System.err.println("" + ex);
                // keep going and useful config file.
            }
        }

        String fname = System.getProperty("java.util.logging.config.file");
        if (fname == null) {
            fname = System.getProperty("java.home");
            if (fname == null) {
                throw new Error("Can't find java.home ??");
            }
            File f = new File(fname, "lib");
            f = new File(f, "logging.properties");
            fname = f.getCanonicalPath();
        }
        try (final InputStream in = new FileInputStream(fname)) {
            final BufferedInputStream bin = new BufferedInputStream(in);
            readConfiguration(bin);
        }
    }

那么怎么配置這個logging.properties呢 其實就是在readConfiguration(bin)方法中 進去看

    public void readConfiguration(InputStream ins) throws IOException, SecurityException {
        checkPermission();
        reset();

        // Load the properties
        props.load(ins);
        // Instantiate new configuration objects.
        String names[] = parseClassNames("config");

        for (int i = 0; i < names.length; i++) {
            String word = names[i];
            try {
                Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word);
                clz.newInstance();
            } catch (Exception ex) {
                System.err.println("Can't load config class \"" + word + "\"");
                System.err.println("" + ex);
                // ex.printStackTrace();
            }
        }

        // Set levels on any pre-existing loggers, based on the new properties.
        setLevelsOnExistingLoggers();

        // Notify any interested parties that our properties have changed.
        // We first take a copy of the listener map so that we aren't holding any
        // locks when calling the listeners.
        Map<Object,Integer> listeners = null;
        synchronized (listenerMap) {
            if (!listenerMap.isEmpty())
                listeners = new HashMap<>(listenerMap);
        }
        if (listeners != null) {
            assert Beans.isBeansPresent();
            Object ev = Beans.newPropertyChangeEvent(LogManager.class, null, null, null);
            for (Map.Entry<Object,Integer> entry : listeners.entrySet()) {
                Object listener = entry.getKey();
                int count = entry.getValue().intValue();
                for (int i = 0; i < count; i++) {
                    Beans.invokePropertyChange(listener, ev);
                }
            }
        }


        // Note that we need to reinitialize global handles when
        // they are first referenced.
        synchronized (this) {
            initializedGlobalHandlers = false;
        }
    }

找到setLevelsOnExistingLoggers() 方法進去

synchronized private void setLevelsOnExistingLoggers() {
        Enumeration<?> enum_ = props.propertyNames();
        while (enum_.hasMoreElements()) {
            String key = (String)enum_.nextElement();
            if (!key.endsWith(".level")) {
                // Not a level definition.
                continue;
            }
            int ix = key.length() - 6;
            String name = key.substring(0, ix);
            Level level = getLevelProperty(key, null);
            if (level == null) {
                System.err.println("Bad level value for property: " + key);
                continue;
            }
            for (LoggerContext cx : contexts()) {
                Logger l = cx.findLogger(name);
                if (l == null) {
                    continue;
                }
                l.setLevel(level);
            }
        }
    }

哈哈加載!key.endsWith(".level") 以.level結尾的key 這樣你就知道怎么配置了吧

那么看看simpleConfig這個類吧

其實可以將這兩個

"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",

刪除這樣就會創(chuàng)建org.apache.commons.logging.impl.SimpleLog了,我非常非常喜歡這個類 因為夠簡單
初始化的時候還是會讀取classpath下的simplelog.properties文件,這個文件主要讀取的是dateTimeFormat 方式 沒讀取到則采用默認的
看源碼

static {
        // Add props from the resource simplelog.properties
        InputStream in = getResourceAsStream("simplelog.properties");
        if(null != in) {
            try {
                simpleLogProps.load(in);
                in.close();
            } catch(java.io.IOException e) {
                // ignored
            }
        }

        showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName);
        showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName);
        showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime);

        if(showDateTime) {
            dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat",
                                               dateTimeFormat);
            try {
                dateFormatter = new SimpleDateFormat(dateTimeFormat);
            } catch(IllegalArgumentException e) {
                // If the format pattern is invalid - use the default format
                dateTimeFormat = DEFAULT_DATE_TIME_FORMAT;
                dateFormatter = new SimpleDateFormat(dateTimeFormat);
            }
        }
    }
static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz";

這個類只能用作控制臺輸出 沒有做文件輸出 所以夠簡單 看源碼就是
就是調用了

protected void write(StringBuffer buffer) {
        System.err.println(buffer.toString());
    }

System的東西 太熟悉了 就和自己寫的一樣簡單 哈哈。但是只能用于控制臺打印 呵呵吧。
還提供了

 public void setLevel(int currentLogLevel) {
        this.currentLogLevel = currentLogLevel;
    }

設置日志級別的,所以我們可以改變日志輸出級別 不過我感覺沒有人會愿意使用這個類打印日志。

這樣我們發(fā)現(xiàn)即使沒有配置其他日志組件也是可以打印日志的
我們做個測試
build path將log4j從path中remove掉 不移除掉會創(chuàng)建到Log4JLogger這個類是和log4j的封裝類這樣這樣就沒法測試了

remove-log4j.jar.png

找個打印日志的類 我找的是FactoryBeanTests 自己加了條日志
這里有個小技巧
每次判斷級別的時候log.isDebugEnabled() 是可以少創(chuàng)建個類 假如你不判斷的話debug中

 public void debug(Object message) {
        getLogger().log(FQCN, Level.DEBUG, message, null);
    }

getLogger會多走邏輯和創(chuàng)建對象的??吹交鹃_源的都是這種寫法算是個好習慣吧。

remove-test-pre.png

跑一下


remove-test-after.png

看到已經是輸出日志的 這印證了我們的說法。
其實只要檢測到了log4j便會走Log4j的打印日志的方式 后續(xù)還想說說 slfj4 今天是說不玩了。還是說下配置文件怎么配置吧。

主要配置的組件

  • Loggers(記錄器)
       Loggers組件在此系統(tǒng)中被分為五個級別:DEBUG、INFO、WARN、ERROR和FATAL。這五個級別是有順序的,DEBUG < INFO < WARN < ERROR < FATAL,分別用來指定這條日志信息的重要程度,明白這一點很重要,Log4j有一個規(guī)則:只輸出級別不低于設定級別的日志信息,假設Loggers級別設定為INFO,則INFO、WARN、ERROR和FATAL級別的日志信息都會輸出,而級別比INFO低的DEBUG則不會輸出。
  • Appenders (輸出源)
      禁用和使用日志請求只是Log4j的基本功能,Log4j日志系統(tǒng)還提供許多強大的功能,比如允許把日志輸出到不同的地方,如控制臺(Console)、文件(Files)等,可以根據(jù)天數(shù)或者文件大小產生新的文件,可以以流的形式發(fā)送到其它地方等等。常使用的類如下:org.apache.log4j.ConsoleAppender(控制臺)org.apache.log4j.FileAppender(文件)org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件)org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)配置模式:log4j.appender.appenderName = classNamelog4j.appender.appenderName.Option1 = value1…log4j.appender.appenderName.OptionN = valueN
  • Layouts(布局)
      有時用戶希望根據(jù)自己的喜好格式化自己的日志輸出,Log4j可以在Appenders的后面附加Layouts來完成這個功能。Layouts提供四種日志輸出樣式,如根據(jù)HTML樣式、自由指定樣式、包含日志級別與信息的樣式和包含日志時間、線程、類別等信息的樣式。常使用的類如下:org.apache.log4j.HTMLLayout(以HTML表格形式布局)org.apache.log4j.PatternLayout(可以靈活地指定布局模式)org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串)org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等信息)配置模式:log4j.appender.appenderName.layout =classNamelog4j.appender.appenderName.layout.Option1 = value1…log4j.appender.appenderName.layout.OptionN = valueN
    1、配置根Logger:log4j.rootLogger = [ level ] , appenderName1, appenderName2, …log4j.additivity.org.apache=false:表示Logger不會在父Logger的appender里輸出,默認為true。level :設定日志記錄的最低級別,可設的值有OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定義的級別,Log4j建議只使用中間四個級別。通過在這里設定級別,您可以控制應用程序中相應級別的日志信息的開關,比如在這里設定了INFO級別,則應用程序中所有DEBUG級別的日志信息將不會被打印出來。appenderName:就是指定日志信息要輸出到哪里??梢酝瑫r指定多個輸出目的地,用逗號隔開。例如:log4j.rootLogger=INFO,A1,B2,C32、配置日志信息輸出目的地(appender):log4j.appender.appenderName = classNameappenderName:自定義appderName,在log4j.rootLogger設置中使用;className:可設值如下:(1)org.apache.log4j.ConsoleAppender(控制臺)(2)org.apache.log4j.FileAppender(文件)(3)org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件)(4)org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)(5)org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)(1)ConsoleAppender選項:Threshold=WARN:指定日志信息的最低輸出級別,默認為DEBUG。ImmediateFlush=true:表示所有消息都會被立即輸出,設為false則不輸出,默認值是true。Target=System.err:默認值是System.out。(2)FileAppender選項:Threshold=WARN:指定日志信息的最低輸出級別,默認為DEBUG。ImmediateFlush=true:表示所有消息都會被立即輸出,設為false則不輸出,默認值是true。Append=false:true表示消息增加到指定文件中,false則將消息覆蓋指定的文件內容,默認值是true。File=D:/logs/logging.log4j:指定消息輸出到logging.log4j文件中。(3)DailyRollingFileAppender選項:Threshold=WARN:指定日志信息的最低輸出級別,默認為DEBUG。ImmediateFlush=true:表示所有消息都會被立即輸出,設為false則不輸出,默認值是true。Append=false:true表示消息增加到指定文件中,false則將消息覆蓋指定的文件內容,默認值是true。File=D:/logs/logging.log4j:指定當前消息輸出到logging.log4j文件中。DatePattern='.'yyyy-MM:每月滾動一次日志文件,即每月產生一個新的日志文件。當前月的日志文件名為logging.log4j,前一個月的日志文件名為logging.log4j.yyyy-MM。另外,也可以指定按周、天、時、分等來滾動日志文件,對應的格式如下:1)'.'yyyy-MM:每月2)'.'yyyy-ww:每周3)'.'yyyy-MM-dd:每天4)'.'yyyy-MM-dd-a:每天兩次5)'.'yyyy-MM-dd-HH:每小時6)'.'yyyy-MM-dd-HH-mm:每分鐘(4)RollingFileAppender選項:Threshold=WARN:指定日志信息的最低輸出級別,默認為DEBUG。ImmediateFlush=true:表示所有消息都會被立即輸出,設為false則不輸出,默認值是true。Append=false:true表示消息增加到指定文件中,false則將消息覆蓋指定的文件內容,默認值是true。File=D:/logs/logging.log4j:指定消息輸出到logging.log4j文件中。MaxFileSize=100KB:后綴可以是KB, MB 或者GB。在日志文件到達該大小時,將會自動滾動,即將原來的內容移到logging.log4j.1文件中。MaxBackupIndex=2:指定可以產生的滾動文件的最大數(shù),例如,設為2則可以產生logging.log4j.1,logging.log4j.2兩個滾動文件和一個logging.log4j文件。3、配置日志信息的輸出格式(Layout):log4j.appender.appenderName.layout=classNameclassName:可設值如下:(1)org.apache.log4j.HTMLLayout(以HTML表格形式布局)(2)org.apache.log4j.PatternLayout(可以靈活地指定布局模式)(3)org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串)(4)org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)(1)HTMLLayout選項:LocationInfo=true:輸出java文件名稱和行號,默認值是false。Title=My Logging: 默認值是Log4J Log Messages。(2)PatternLayout選項:ConversionPattern=%m%n:設定以怎樣的格式顯示消息
    附上個簡單的demo
# Set root logger level to Debug and its only appender to A1
log4j.rootLogger=INFO, A1,UID
log4j.category.org.springframework = info

# A1 is set to be ConsoleAppender
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] - %m%n

# A2 is set to be logfile
log4j.appender.A2=org.apache.log4j.RollingFileAppender
# Define the file name
log4j.appender.A2.File=ts.log
# Define the layout
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %x - %m%n

log4j.appender.UID=org.apache.log4j.DailyRollingFileAppender
log4j.appender.UID.File=${catalina.base}/logs/uid.log
log4j.appender.UID.layout=org.apache.log4j.PatternLayout
log4j.appender.UID.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%t] %x - %m%n

log4j.logger.com.holly.wang=DEBUG

#unify log
log4j.logger.sysunifylog=debug, sysunifylog
log4j.appender.sysunifylog=org.apache.log4j.DailyRollingFileAppender
log4j.appender.sysunifylog.DatePattern='.'yyyy-MM-dd-HH
log4j.appender.sysunifylog.File=${catalina.base}/logs/sysunifylog.log
log4j.appender.sysunifylog.layout=org.apache.log4j.PatternLayout
log4j.appender.sysunifylog.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}][%p] %m%n
log4j.additivity.sysunifylog = false

這里還要說個問題 看到同事在指定自定義類設置的時候還是用的
log4j.category.org.springframework = info 其實category已經被拋棄了哈哈,以后很有可能被刪除 所以不要用了 log4j.logger.com.holly.wang=DEBUG 這樣就可以了 就category改成logger既可。還有自定義類設置輸出的時候經常遇到重復輸出的問題 只需要設置log4j.additivity 為false即可。

回家嘍 好晚啦!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 在應用程序中添加日志記錄總的來說基于三個目的:監(jiān)視代碼中變量的變化情況,周期性的記錄到文件中供其他應用進行統(tǒng)計分析...
    時待吾閱讀 5,161評論 0 6
  • 在應用程序中添加日志記錄總的來說基于三個目的:監(jiān)視代碼中變量的變化情況,周期性的記錄到文件中供其他應用進行統(tǒng)計分析...
    時待吾閱讀 5,231評論 1 13
  • 一、Log4j簡介 Log4j有三個主要的組件:Loggers(記錄器),Appenders (輸出源)和Layo...
    默默守護閱讀 1,980評論 2 8
  • from:https://www.cnblogs.com/ITtangtang/p/3926665.html一、L...
    enshunyan閱讀 3,420評論 0 0
  • #########################################################...
    BearFaraway閱讀 1,873評論 0 51

友情鏈接更多精彩內容