源码Java学习笔记程序员

commons-logger的原理和log4j.properti

2016-08-31  本文已影响682人  holly_wang_王小飞

任何项目工程,日志的作用都毋庸置疑得重要,监控,问题查找,统计,大数据资源来源等。在阅读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 今天是说不玩了。还是说下配置文件怎么配置吧。

主要配置的组件

# 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即可。

回家喽 好晚啦!

上一篇 下一篇

猜你喜欢

热点阅读