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

任何项目工程,日志的作用都毋庸置疑得重要,监控,问题查找,统计,大数据资源来源等。在阅读spring源码过程中开启spring的日志对于阅读的帮助也是非常大的。

先说说apache自带的log组件 <b>commons-logging</b>

<em>commons-logging</em>提供的是一个日志的接口(<em>interface</em>),并有一个简单的日志实现SimpleLog.class。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。然后对各种实现的日志工具如Log4j、Avalon LogKit, and JDK(具体实现在java.util.loggingpackage中)中选择一个。有各种整合方式。
网上看到的一个<em>commons-logging</em>出现的小故事

org.apache.commons.logging.Log 和org.apache.log4j.Logger 这两个类,通过包名我们可以发现它们都是 apache 的项目,既然如此,为何要动如此大的动作搞两个东西(指的是 commons-logging 和 log4j)出来呢?事实上,在 sun 开发 logger 前,apache 项目已经开发了功能强大的 log4j 日志工具,并向 sun 推荐将其纳入到 jdk 的一部分,可是 sun 拒绝了 apache 的提议,sun 后来自己开发了一套记录日志的工具。可是现在的开源项目都使用的是 log4j,log4j 已经成了事实上的标准,但由于又有一部分开发者在使用 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>是以前创建<b>Log</b>的class 现在已被遗弃
<b>LogConfigurationException</b>是 <code>LogFactory</code>或者 <code>Log</code>实例创建失败的时候抛出的异常
<b>impl</b>包下的<code>AvalonLogger</code><code>Jdk13LumberjackLogger</code><code>Log4JLogger</code><code>LogKitLogger</code><code>NoOpLog</code><code>SimpleLog</code>是<code>Log</code>接口的实现
<b>ServletContextCleaner</b>是<b>ServletContextListener</b>的实现 在contextDestroyed的时候把创建的<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中这样是这样实现

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

其中factory是 protected static Hashtable factories = null;
在LogFactory实例化的时候 调用 static {……factories = createFactoryStore();……} 其中<b>createFactoryStore</b>创建了

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

属性 即WeakHashtable。存放LogFactory
最后 factories.release的时候相当于清空了WeakHashtable,WeakHashtable其实是基于WeakReference 实现的 这种弱引用在一定情况下会被jvm优先回收掉 所以节约内存。时间久了不用jvm会回收掉后,当再次用到的时候再创建即可。
<b>先说了销毁再看下是怎么创建和选择Log实现类的</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中取,第一次肯定是没有的 继续往下会去读取Properties文件

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

用户没配置<em>commons-logging.properties</em>的情况下取到props==null
继续往下回去系统启动的属性中去找

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

启动的时候没有设置则factoryClass==null 继续往下
会读取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";

没有配置这个文件的情况下继续往下
到了

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

这个地方newFactory 方法去创建FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"利用反射机制 默认创建了他的实现类<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所以继续进去看

 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)这里会查找这两个,一个是以前的另一个是现在在用的

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

第一次所以没有的话继续到

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

第一次没仔细看纠结了半天 以为三个都创建了 其实值创建了一个
好了肯定是创建了org.apache.commons.logging.impl.Jdk14Logger 这个类
到这个类中看 就是封装了JDK的日志实现
这个类Jdk14Logger 没有找到设置日志级别的方法
继续往jdk实现中找,在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;
    }

继续找到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呢
其实在静态初始化块中还有个重要的方法owner.readPrimordialConfiguration() 其中owner是final LogManager owner = this;即当前LogManager
继续看<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",

删除这样就会创建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;
    }

设置日志级别的,所以我们可以改变日志输出级别 不过我感觉没有人会愿意使用这个类打印日志。

这样我们发现即使没有配置其他日志组件也是可以打印日志的
我们做个测试
build path将log4j从path中remove掉 不移除掉会创建到Log4JLogger这个类是和log4j的封装类这样这样就没法测试了

remove-log4j.jar.png

找个打印日志的类 我找的是FactoryBeanTests 自己加了条日志
这里有个小技巧
每次判断级别的时候log.isDebugEnabled() 是可以少创建个类 假如你不判断的话debug中

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

getLogger会多走逻辑和创建对象的。看到基本开源的都是这种写法算是个好习惯吧。

remove-test-pre.png

跑一下


remove-test-after.png

看到已经是输出日志的 这印证了我们的说法。
其实只要检测到了log4j便会走Log4j的打印日志的方式 后续还想说说 slfj4 今天是说不玩了。还是说下配置文件怎么配置吧。

主要配置的组件

  • Loggers(记录器)
       Loggers组件在此系统中被分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL,分别用来指定这条日志信息的重要程度,明白这一点很重要,Log4j有一个规则:只输出级别不低于设定级别的日志信息,假设Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
  • Appenders (输出源)
      禁用和使用日志请求只是Log4j的基本功能,Log4j日志系统还提供许多强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。常使用的类如下:org.apache.log4j.ConsoleAppender(控制台)org.apache.log4j.FileAppender(文件)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)配置模式:log4j.appender.appenderName = classNamelog4j.appender.appenderName.Option1 = value1…log4j.appender.appenderName.OptionN = valueN
  • Layouts(布局)
      有时用户希望根据自己的喜好格式化自己的日志输出,Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据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:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。例如: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(将日志信息以流格式发送到任意指定的地方)(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:指定可以产生的滚动文件的最大数,例如,设为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即可。

回家喽 好晚啦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,911评论 0 6
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,821评论 1 13
  • 一、Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layo...
    默默守护阅读 1,864评论 2 8
  • from:https://www.cnblogs.com/ITtangtang/p/3926665.html一、L...
    enshunyan阅读 3,180评论 0 0
  • #########################################################...
    BearFaraway阅读 1,739评论 0 51