Logger - 日志写出器,供程序员输出日志信息
Appender - 日志目的地,把格式化好的日志信息输出到指定的地方去
ConsoleAppender - 目的地为控制台的Appender
FileAppender - 目的地为文件的Appender
RollingFileAppender - 目的地为大小受限的文件的Appender
Layout - 日志格式化器,用来把程序员的logging request格式化成字符串
PatternLayout - 用指定的pattern格式化logging request的Layout
日志级别
每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:
A:off 最高等级,用于关闭所有日志记录。
B:fatal 指出每个严重的错误事件将会导致应用程序的退出。
C:error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
D:warm 表明会出现潜在的错误情形。
E:info 一般和在粗粒度级别上,强调应用程序的运行全程。
F:debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
G:all 最低等级,用于打开所有日志记录。
上面这些级别是定义在org.apache.log4j.Level类中。Log4j只建议使用4个级别,优先级从高到低分别是 error,warn,info和debug。通过使用日志级别,可以控制应用程序中相应级别日志信息的输出。例如,如果使用了info级别,则应用程 序中所有低于info级别的日志信息(如debug)将不会被打印出来。
(二)源码解析
基本步骤:
1、LogManager类对log4j.xml或者log4j.properties文件进行解析(xml文件优先,若存在xml文件则不对properties文件进行解析)
2、逐个解析配置文件中的logger对象,并将这些对象放入一个Hierarchy的实例中(Hierarchy类中定义了一个hashtable来存储所有的logger)
a、取得RootLogger
b、取得RootLogger对应的Appender (必须在配置文件中指明rootLogger的Appender)
c、取得Appender的Layout,将Layout追加到Appender中
d、将Appender追加到RootLogger中
e、将RootLogger添加进Hierarchy中
f、依照上面abcde的步骤将其他logger添加进Hierarchy中
3、通过LogManager.getLogger(String name)或LogManager.getLogger(Class clazz)获取获取到指定的logger对象(事实上,传递进去的clazz也会被转换为字符串,不过要加上它的包名,如Test.Class-->com.test.Test)
a、通过传递进来的参数,判断Hierarchy中是否已经含有此logger;(通过hashtable.get(key)获取再判断)
b、若该logger已经存在于Hierarchy中则将Hierarchy的logger返回
c、通过new Logger(String name)或new Logger(Class clazz)创建一个新的logger,设置这个logger的parent属性,将此对象放入Hierarchy中并返回
//为使代码更简洁明了,此段代码我做了部分的删减
private final void updateParents(Logger cat)
{
String name = cat.name;
int length = name.length();
boolean parentFound = false;
//由此段代码可看出,这里是根据包名判定父节点的
for(int i = name.lastIndexOf('.', length - 1); i >= 0; i = name.lastIndexOf('.', i - 1))
{
String substr = name.substring(0, i);
CategoryKey key = new CategoryKey(substr);
Object o = ht.get(key);
if(o instanceof Category)
{
parentFound = true;
cat.parent = (Category)o;
break;
}
}
//若没找到父节点,则默认其父节点为root
if(!parentFound)
cat.parent = root;
}
4、通过logger的error(Object message, Throwable t)等方法(包括error,warn,info,debug)输出日志
a、判断你要输入的日志级别是否高于该logger的level,若高于,则调用该logger的各个appender输出日志,若低于,则不进行输出
注:如果没有设置日志记录器(Logger)的级别,那么它将会继承最近的祖先的级别。
//获取有效的日志级别
//若该logger没有定义level则使用其最近的祖先定义的level
// Loger类继承于Category
public Level getEffectiveLevel()
{
for(Category c = this; c != null; c = c.parent)
if(c.level != null)
return c.level;
return null;
}
b、逐个调用该logger的appender输出日志
注:appender中也有级别限制,只有满足这个限制的日志才能被输出
c、 若该logger的additive属性为true,则调用父类的appender输出日志(additive属性默认为true)
public void callAppenders(LoggingEvent event)
{
category;
JVM INSTR monitorexit ;
continue;
exception;
throw exception;
int writes = 0;
//由以下代码可以看出如果additive属性为true则会逐层调用祖先的appender,直到root或者遇到additive属性为false的祖先
label0:
for(Category c = this; c != null; c = c.parent)
{
label1:
{
synchronized(c)
{
if(c.aai != null)
writes += c.aai.appendLoopOnAppenders(event);
if(c.additive)
break label1;
}
break label0;
}
}
if(writes == 0)
repository.emitNoAppenderWarning(this);
return;
}
(三)关于日记文件的备份
RollFileAppender
当日志文件大小超出了指定值时,log4j会将我们之前存放在指定文件(如:normal.log)的日志备份(即转移)到一个新的日记文件(如normal_1.log)
DailyRollFileAppender
按指定的时间频道,将之前存放在指定文件(如:normal.log)的日志备份(即转移)到一个新的日志文件(如 normal_20120101.log)
在DailyRollingFileAppender中可以指定monthly(每月)、 weekly(每周)、daily(每天)、half-daily(每半天)、hourly(每小时)和minutely(每分钟)六个频度,这是通过为 DatePattern选项赋予不同的值来完成的。DatePattern选项的有效值为:
- '.'yyyy-MM,对应monthly(每月)
- '.'yyyy-ww,对应weekly(每周)
- '.'yyyy-MM-dd,对应daily(每天)
- '.'yyyy-MM-dd-a,对应half-daily(每半天)
- '.'yyyy-MM-dd-HH,对应hourly(每小时)
- '.'yyyy-MM-dd-HH-mm,对应minutely(每分钟)
PS:以上种方式是分别通过大小和时间进行日志的备份,如果在实际项目开发中需要根据实际条件指定新的备份方式,我们可以参照log4j.jar中的RollFileAppender和DailyRollFileAppender的源码,自己进行编写一个Appender类就行了
(四)SSH与Log4j整合的好处
1.配置文件(log4j.properties或者log4j.xml)可以不用放在class-path中,而可以让我们指定,一般我们会把该配置文件与项目中的其他配置文件放在一起
在web.xml中的详细设定如下:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/log4j.properties</param-value>
</context-param>
2. 日志文件的存放路径可以用相对路径指定,比如说放在当前项目下的WEB-INF/logs文件夹中,而不需要用绝对路径定义日志存放位置
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>web.root</param-value>
</context-param>
<!-- 需要添加spring-web.jar包-->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
这里涉及到了另一个知识点,Spring通过 org.springframework.web.util.WebAppRootListener 这个监听器将我们当前项目的所在路径配置进了系统的环境变量中,
也就是相当于在系统环境变量中配置了一个叫“web.root”的变量,这个变量指向了我们当前项目的物理路径。这样,我们在项目中就可以通过System.getProperty("web.root")来获取当前项目的物理路径,同时,我们也可以在log4j.properties 里这样定义logfile位置log4j.appender.logfile.File=${webapp.root}/WEB-INF/logs/mylog.log
如果在web.xml中已经配置了 org.springframework.web.util.Log4jConfigListener
这个监听器,则不需要配置WebAppRootListener了。因为Log4jConfigListener已经包含了WebAppRootListener的功能
部署在同一容器中的Web项目,要配置不同的<param-value>,不能重复,因为这个值事实上就是相当于path环境变量中的一个key,key自然不能重复。
3、动态的改变记录级别和策略,即修改log4j.properties,不需要重启Web应用,这需要在web.xml中设置自动扫描log4j文件的间隔(每隔一段时间刷新一次配置)
在web.xml中的配置如下:
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
补充扩展
正式上线的项目中应尽量少用system.out.println(),因为该语句会把内容打印到tomcat默认的日志文件中,不便于日志的管理,而且容易占据系统资源;
hibernate自带的showsql功能也是默认将对应的sql语句输出到tomcat默认的日志文件中,hibernate的日志输出也是依赖于log4j实现的,所以我们也可以通过配置将该日志输出控制起来。