我们平时写的工具或应用程序,可以通过人眼查看调试来查看运行过程中出现的错误,可以通过system.out.println()来输出程序运行状态信息查看。
(1)那么,一个庞大复杂的项目,含有大量的接口和方法,执行过程冗长繁杂,还要使用上述的办法来慢慢排查问题么?
自然不是,一个设计良好的项目,肯定记录了项目运行日志log。
日志的强大之处,在于它能记录保存项目的运行状态,便于日后查阅,很是方便快捷、易于调试;更重要的是,他还能够完成跟踪调试、程序状态记录、崩溃数据恢复等工作。
但如果使用system.out.println(),则太弱了,不仅繁琐麻烦,输出的地方和多少都不易控制。
(2)那么,如何设计一个良好的日志类来应用到项目中,便于提升项目的开发和维护呢?
答案:在java的世界里,我们不用考虑和设计日志类,因为已经存在了很多优秀的日志系统供我们使用,比如:Log4j和Logback,还有sun公司的java.util.Logging,另外java项目中的各种框架(如:Spring,Mybatis,Httpclient…)等第三方包都有自己的日志系统来实现日志功能,但是这么多的日志实现系统同时存在,难以避免的就出现了各个日志系统不兼容的灾难性情况!
(3)那么,应该如何解决杂乱的日志系统不兼容的灾难情况呢?
解决办法就是出现了日志框架。
日志系统:日志类的具体实现。经典的有log4j,log4j作者推出的被高度评价的logBack,以及jdk自带的java.util.Logging等。
日志框架:为解决多个日志系统的兼容性问题而设计的框架,当然如果只有一个日志系统,就完全没必要使用日志框架了。常用的主流日志框架有commons-logging和slf4j。
下面说一下这两种主流框架的使用!
commons-logging是apache推出的日志框架,只是规定了日志的接口规范,可以理解为是一个通用的日志接口,其设计原理类似于jdk中servlet和jdbc的设计。
目前,主流的日志系统log4j和java.util.Logging都实现了commons-logging定义的接口。那么,就可以很方便的使用这些实现了 commons-logging接口的日志系统了,使用的时候不用去考虑具体的日志实现或到底是哪种日志系统,用户可以自由选择第三方日志组件作为具体实现。
举个便于理解的例子:
有这样两个实现了Log接口的类:
public class MyLog implements Log {
}
public class YourLog implements Log {
}
你可以把commons-logging框架理解为Log接口,只是一个定义了规范的接口,把实现了commons-logging框架的众多日志系统看作是实现了Log接口的实现类MyLog和YourLog。使用的时候拿着Log接口使用就行了,不用关注具体的实现类;即,使用日志系统的时候拿着日志框架commons-logging使用就行了。
第一:简化了使用和配置,为“所有的Java日志实现”提供一个统一的接口,并且能分离项目和日志。
第二:使用日志框架commons-loging可以自适应的选择日志实现系统。
不得不说的是它的自适应日志工具选择功能?
1) commons-logging首先在classpath下寻找自己的配置文件commons-logging.properties,找到则使用自己定义的Log;找不到则继续查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用自己定义的Log;
2)否则,在classpath中查找是否有Log4j的路径,若有则使用Log4j;
3)否则,使用JDK(1.4版本以后提供了日志功能)自身的日志实现类;
4)如果还没有,则使用commons-logging自己提供的一个简单的日志实现类SimpleLog;
所以,commons-logging可以保证日志功能的实现,自适应的尽可能找到一个最合适的工具,这也是它的优势所在。
可以看到,commons-logging对编程者和Log4j都非常友好。为了简化配置,一般不使用commons-logging的配置文件,也不设置与commons-logging相关的系统环境变量,而只需将Log4j的Jar包放置到classpash中就可以了。这样就很简单地完成了commons-logging与Log4j的融合。如果不想用Log4j了怎么办?只需将classpath中的Log4j的Jar包删除即可。就这么简单!
这种实现出现在各种框架里,如Spring,Webx,Mybatis等等, 一般为了避免直接依赖具体的日志实现,一般都是结合commons-logging 来实现。常见应用实现代码如下:
//1: 导入所有需的commons-logging类
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class XXX{
//2:要使用日志的类XXX中定义私有静态变量logger,,这是目前被普通认为的最好的方式;
//静态是为了避免出现多个实例,
private static Log logger = LogFactory.getLog(XXX.class);
//3:在要使用日志的方法中使用日志
public void method(args){
log.debug("111");
log.info("222");
log.warn("333");
log.error("444");
log.fatal("555");
}
}
实例化私有静态日志变量的时候,为什么不写作LogFactory.getLog(this.getClass())?因为static类成员访问不到this指针!
通过上述,可以基本满足输出日志信息,如果要对输出的信息有特殊要求,那么,就需要输出配置信息log4j.properties。
在程序中,可以通过每个类的私有静态变量log,将不同性质的日志信息输出到不同类型的日志文件或不同目的地(目的地是哪里?视配置可定,可能是stdout,也可能是文件,还可能是发送到邮件,甚至发送短信到手机……详见下文对log4j.properties的介绍):
l debug() 输出“调试”级别的日志信息;
l info() 输出“信息”级别的日志信息;
l warn() 输出“警告”级别的日志信息;
l error() 输出“错误”级别的日志信息;
l fatal() 输出“致命错误”级别的日志信息;
根据不同的性质,日志信息通常被分成不同的级别,从低到高依次是:“调试(DEBUG)”“信息(INFO)”“警告(WARN)”“错误(ERROR)”“致命错误(FATAL)”。为什么要把日志信息分成不同的级别呢?这实际上是方便我们更好的控制它。比如,通过Log4j的配置文件,我们可以设置“输出‘调试’及以上级别的日志信息”(即“调试”“信息”“警告”“错误”“致命错误”),这对项目开发人员可能是有用的;我们还可以设置“输出“警告”及以上级别的日志信息”(即“警告”“错误”“致命错误”),这对项目最终用户可能是有用的。
仅从字面上理解,也可以大致得出结论:最常用的应该是debug()和info();而warn()、error()、fatal()仅在相应事件发生后才使用。
所以要正确地应用Log4j输出日志信息,log4j.properties
log4j.rootLogger = DEBUG, CONSOLE,A1
#最最重要的一个属性, 分成两部分:第一个逗号之前的是第一部分,指定“输出级别”;后面的是第二部分,指定“输出目的地”
“输出级别”有可选的五个值,分别是DEBUG、INFO、WARN、ERROR、FATAL,它们是由Log4j系统定义的。如果此处指定的是“WARN”则仅调用warn()、error()、fatal()方法输出的日志信息才被输出到“输出目的地”,而调用debug()、info()方法输出的日志信息不被输出到“输出目的地”。
“输出目的地”就是我们自己定义的了,就在log4j.properties的后面部分,此文件定义的“输出目的地”有CONSOLE、FILE、 ROLLING_FILE、SOCKET、LF5_APPENDER、MAIL、DATABASE、A1、im。该文件之所以可作主模板,就是因为它比较 全面地定义了各种常见的输出目的地(控制台、文件、电子邮件、数据库等)。
为什么说“CONSOLE”表示将日志信息输出到“控制台”呢?那就要看一下后文的定义了:
# 应用于控制台
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.Threshold = DEBUG
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = [framework] %d-%c -%-4r [%t] %-5p %c %x-%m%n
#log4j.appender.CONSOLE.layout.ConversionPattern = [start]%d {DATE}[DATE]%n %p[PRIORITY]%n %x[NDC]%n %t[THREAD] n %c[CATEGORY] %n %m[MESSAGE] %n %n
为什么说“A1”表示将日志信息输出到“SampleMessages.log4j文件”呢?还要看后文的定义:
log4j.appender.A1 = org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File = SampleMessages.log4j
log4j.appender.A1.DatePattern = yyyyMMdd - HH '.log4j'
log4j.appender.A1.layout = org.apache.log4j.xml.XMLLayout
log4j.properties文件模板:
##Log4J的配置之简单使它遍及于越来越多的应用中了
##Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了。
##此文件(log4j.properties)内容来自网络,非本文作者liigo原创。
log4j.rootLogger = DEBUG, CONSOLE,A1
log4j.addivity.org.apache = true
# 应用于控制台
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.Threshold = DEBUG
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = [framework] %d-%c -%-4r[%t] %-5p %c %x-%m %n
#log4j.appender.CONSOLE.layout.ConversionPattern = [start] %d {DATE}[DATE]%n %p[PRIORITY] %n %x[NDC] %n %t[THREAD] n %c[CATEGORY]%n %m[MESSAGE]%n %n
#应用于文件
log4j.appender.FILE = org.apache.log4j.FileAppender
log4j.appender.FILE.File = file.log
log4j.appender.FILE.Append = false
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern = [framework] %d-%c-%-4r[%t] %-5p %c%x-%m%n
# Use this layout for LogFactor 5 analysis
# 应用于文件回滚
log4j.appender.ROLLING_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold = ERROR
log4j.appender.ROLLING_FILE.File = rolling.log
log4j.appender.ROLLING_FILE.Append = true
log4j.appender.ROLLING_FILE.MaxFileSize = 10KB
log4j.appender.ROLLING_FILE.MaxBackupIndex = 1
log4j.appender.ROLLING_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
#应用于socket
log4j.appender.SOCKET = org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost = localhost
log4j.appender.SOCKET.Port = 5001
log4j.appender.SOCKET.LocationInfo = true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout = org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern = [start] %d {DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER = org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords = 2000
# 发送日志给邮件
log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold = FATA
log4j.appender.MAIL.BufferSize = 10
log4j.appender.MAIL.From = web@www.wuset.com
log4j.appender.MAIL.SMTPHost = www.wusetu.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = web@www.wusetu.com
#SMTP发送认证的帐号名
log4j.appender.MAIL.SMTPUsername=****
#SMTP发送认证帐号的密码
log4j.appender.MAIL.SMTPPassword=******
log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
# 用于数据库
log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL = jdbc:mysql: // localhost:3306/test
log4j.appender.DATABASE.driver = com.mysql.jdbc.Driver
log4j.appender.DATABASE.user = root
log4j.appender.DATABASE.password =
log4j.appender.DATABASE.sql = INSERT INTO LOG4J (Message) VALUES ( '[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n' )
log4j.appender.DATABASE.layout = org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
log4j.appender.A1 = org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File = SampleMessages.log4j
log4j.appender.A1.DatePattern = yyyyMMdd - HH '.log4j'
log4j.appender.A1.layout = org.apache.log4j.xml.XMLLayout
#自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout = org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern = [framework] %d-%c-%-4r[%t]%-5p%c%x-%m%n
# 结束
slf4j全称为Simple Logging Facade for JAVA,一个java的简单日志门面。类似于Apache Common-Logging,是对不同日志框架提供的一个门面封装,可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合。
Logback必须配合sl4j使用。由于logback和sl4j是同一个作者,其兼容性不言而喻。但sl4j面临与其他日志框架和日志系统的兼容性问题。
常见应用实现代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XXX {
private static Log logger = LogFactory.getLog(this.getClass());
}
类似于commons-logging的输出配置文件,slf4j也同样需要一个配置文件logback.xml,通过该配置文件,在程序运行之后,slf4j会自动加载logback的日志实现,然后logback从类路径中读取logback.xml文件中的日志配置信息,实现日志的记录等功能。
一个logback.xml模板配置示例及注释详解:
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 上下文变量设置,用来定义变量值,其中name的值是变量的名称,value的值时变量定义的值。
通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="CONTEXT_NAME" value="logback-test" />
<!-- 上下文名称:<contextName>, 每个logger都关联到logger上下文,
默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。
一旦设置,不能修改。 -->
<contextName>${CONTEXT_NAME}</contextName>
<!-- <appender>是<configuration>的子节点,是负责写日志的组件。
有两个必要属性name和class。
name指定appender名称,
class指定appender的实现类。 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 对日志进行格式化。 -->
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%class|%thread|%method|%line|%msg%n
</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。 -->
<file>${logs.dir}/logback-test.log</file>
<!-- 当发生滚动时的行为 -->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- 必须包含“%i”例如,假设最小值和最大值分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz 或者 没有log%i.log.zip -->
<FileNamePattern>${logs.dir}/logback-test.%i.log</FileNamePattern>
<!-- 窗口索引最小值 -->
<minIndex>1</minIndex>
<!-- 窗口索引最大值 -->
<maxIndex>1</maxIndex>
</rollingPolicy>
<!-- 激活滚动的条件。 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!-- 活动文件的大小,默认值是10MB -->
<maxFileSize>30MB</maxFileSize>
</triggeringPolicy>
<!-- 对记录事件进行格式化。 -->
<encoder>
<charset>UTF-8</charset>
<Pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%class|%thread|%method|%line|%msg%n
</Pattern>
</encoder>
</appender>
<!-- 特殊的<logger>元素,是根logger。只有一个level属性,应为已经被命名为"root".
level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。
<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger。 -->
<root>
<level value="WARN" />
<appender-ref ref="stdout" />
<appender-ref ref="file" />
</root>
<!-- 用来设置某一个 包 或者具体的某一个 类 的日志打印级别、以及指定<appender>,
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
additivity:是否向上级logger传递打印信息。默认是true。(这个logger的上级就是上面的root)
<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。-->
<logger name="xuyihao.logback.test" level="DEBUG" additivity="true"></logger>
</configuration>
参考资料:
《java日志组件介绍(common-logging,log4j,slf4j,logback )》
《log4j和log》
《logback.xml配置模版》