日志
日志就是Logging,它的目的是为了取代System.out.println()
输出日志,而不是用System.out.println(),有以下几个好处:
(1)可以设置输出样式,避免自己每次都写“ERROR: ” + var
(2)可以设置输出级别,禁止某些级别输出。例如:只输出错误日志
(3)可以被重定向到文件,这样可以在程序运行结束后查看日志
(4)可以按包名控制日志级别,只输出某些包打的日志
日志及分类(三类)
日志根据记录内容不同,分为三类
(1)SQL日志:记录系统执行的SQL语句
(2)异常日志:记录系统运行中发生的异常事件
(3)业务日志:记录系统运行过程,如用户登录、操作记录
JDK Logging(7个级别)
Java标准库内置了日志包java.util.logging,可以直接用
JDK的Logging定义了7个日志级别,从严重到普通:
①server
②warning
③info(默认)
④config
⑤fine
⑥finer
⑦finest
默认级别是INFO。因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出
Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;配置不太方便,需要在JVM启动时传递参数。因此,Java标准库内置的Logging使用并不是非常广泛
Logger logger = Logger.getGlobal();
logger.severe("");
logger.warning("");
logger.info("");
logger.config("");
logger.fine("");
logger.finer("");
logger.finest("");
输出
Mar 02, 2019 6:32:13 PM Hello main
INFO: start process...
Mar 02, 2019 6:32:13 PM Hello main
WARNING: memory is running out...
Mar 02, 2019 6:32:13 PM Hello main
SEVERE: process will be terminated...
使用日志最大的好处是,它自动打印了时间、调用类、调用方法等很多有用的信息
(1)日志是为了替代System.out.println(),可以定义格式,重定向到文件等
(2)日志可以存档,便于追踪问题
(3)日志记录可以按级别分类,便于打开或关闭某些级别
(4)可以根据配置文件调整日志,无需修改代码
(5)Java标准库提供了java.util.logging来实现日志功能
Commons Logging和log4j
(1)通过Commons Logging实现日志,不需要修改代码即可使用Log4j
(2)使用Log4j只需要把log4j2.xml和相关jar放入classpath
(3)如果要更换Log4j,只需要移除log4j2.xml和相关jar
(4)只有扩展Log4j时,才需要引用Log4j的接口(例如,将日志加密写入数据库的功能,需要自己开发)
Commons Logging(6个级别)
和Java标准库提供的日志不同,Commons Logging是一个第三方日志库,它是由Apache创建的日志模块
Commons Logging的特色:可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Logging自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging(需要导入commons logging.jar)
commons-logging有6级日志,但是apache建议使用4级,即 ERROR、WARN、INFO、DEBUG。什么情况下输出日志由程序中写日志的方法决定,输出什么级别以上的日志和输出到哪里由配置文件决定
级别 | 描述 |
---|---|
①FATAL | 记录具有致命日志级别的消息 |
②ERROR | 记录具有错误日志级别的消息 |
③WARN | 表明会出现潜在错误的情形 |
④INFO | 使用信息日志级别记录消息(默认) |
⑤DEBUG | 记录调试日志级别的错误 |
⑥TRACE | 使用跟踪日志级别记录消息 |
需要和两个类打交道,并且只有两步
第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log = LogFactory.getLog(Main.class); // Main为本类
log.info("start...");
log.warn("end.");
类名 | 描述 |
---|---|
LogFactory | 抽象类,日志工厂,获取日志类 |
LogFactoryImpl | LogFactory的实现类,真正获取日志对象的地方 |
Log4JLogger | 对log4j的日志对象封装 |
Jdk14Logger | 对JDK1.4的日志对象封装 |
Jdk13LumberjackLogger | 对JDK1.3以及以前版本的日志对象封装 |
SimpleLog | commons-logging自带日志对象 |
使用Commons Logging时,如果在静态方法中引用Log,通常直接定义一个静态类型变量
在静态方法中引用Log
public class Main {
static final Log log = LogFactory.getLog(Main.class);
static void foo() {
log.info("foo");
}
}
在实例方法中引用Log,通常定义一个实例变量
在实例方法中引用Log
public class Person {
protected final Log log = LogFactory.getLog(getClass());
void foo() {
log.info("foo");
}
}
注意到实例变量log的获取方式是LogFactory.getLog(getClass()),虽然也可以用LogFactory.getLog(Person.class),但是前一种方式有个非常大的好处,就是子类可以直接使用该log实例
父类使用getLog(getClass()),子类可以直接使用log实例
在子类中使用父类实例化的log
public class Student extends Person {
void bar() {
log.info("bar");
}
}
由于Java类的动态特性,子类获取的log字段实际上相当于LogFactory.getLog(Student.class),但却是从父类继承而来,并且无需改动代码
Commons Logging的日志方法,例如info(),除了标准的info(String)外,还提供了一个非常有用的重载方法:info(String, Throwable),这使得记录异常更加简单:
try {
} catch (Exception e) {
log.error("got exception!", e);
}
log4j
Commons Logging,可以作为“日志接口”来使用。而真正的“日志实现”可以使用Log4j。
Log4j是一种非常流行的日志框架,是一个组件化设计的日志系统,它的架构大致如下
使用Log4j输出一条日志时,Log4j自动通过不同的Appender把同一条日志输出到不同的目的地
(1)console:输出到屏幕
(2)file:输出到文件
(3)socket:通过网络输出到远程计算机
(4)jdbc:输出到数据库
Log4j也是一个第三方库,需要从这里下载Log4j
下载jar文件:https://logging.apache.org/log4j/2.x/download.html
以XML配置为例,使用Log4j的时候,把一个log4j2.xml的文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<!-- 定义日志格式 -->
<Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level
%logger{36}%n%msg%n%n</Property>
<!-- 定义文件名变量 -->
<Property name="file.err.filename">log/err.log</Property>
<Property name="file.err.pattern">log/err.%i.log.gz</Property>
</Properties>
<!-- 定义Appender,即目的地 -->
<Appenders>
<!-- 定义输出到屏幕 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 日志格式引用上面定义的log.pattern -->
<PatternLayout pattern="${log.pattern}" />
</Console>
<!-- 定义输出到文件,文件名引用上面定义的file.err.filename -->
<RollingFile name="err" bufferedIO="true"
fileName="${file.err.filename}" filePattern="${file.err.pattern}">
<PatternLayout pattern="${log.pattern}" />
<Policies>
<!-- 根据文件大小自动切割日志 -->
<SizeBasedTriggeringPolicy size="1 MB" />
</Policies>
<!-- 保留最近10份 -->
<DefaultRolloverStrategy max="10" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<!-- 对info级别的日志,输出到console -->
<AppenderRef ref="console" level="info" />
<!-- 对error级别的日志,输出到err,即上面定义的RollingFile -->
<AppenderRef ref="err" level="error" />
</Root>
</Loggers>
</Configuration>
虽然配置Log4j比较繁琐,但一旦配置完成,使用起来就非常方便。对上面的配置文件,凡是INFO级别的日志,会自动输出到屏幕,而ERROR级别的日志,不但会输出到屏幕,还会同时输出到文件。并且,一旦日志文件达到指定大小(1MB),Log4j就会自动切割新的日志文件,并最多保留10份
在开发阶段,始终使用Commons Logging接口来写入日志,并且开发阶段无需引入Log4j。如果需要把日志写入文件, 只需要把正确的配置文件和Log4j相关的jar包放入classpath,就可以自动把日志切换成使用Log4j写入,无需修改任何代码
log4j.properties
在CLASSPATH 下建立log4j.properties
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p %c{1}:%L - %m%n
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=${catalina.home}/logs/ddoMsg.log
#log4j.appender.file.File=D:/SmgpAppService/logs/smgpApp.log
log4j.appender.file.MaxFileSize=1024KB
log4j.appender.file.MaxBackupIndex=100
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern= %d{yyyy-MM-dd HH:mm:ss} %5p %c %t: - %m%n
#INFO WARN ERROR DEBUG
log4j.rootLogger=WARN, file, stdout
#log4j.rootLogger=INFO,stdout
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
#org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester=debug
#org.apache.commons.logging.simplelog.log.org.apache.commons.digester.ObjectCreateRule=debug
#org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester.sax=info
log4j.logger.com.jason.ddoMsg=debug
logger配置说明
log4j.rootLogger=INFO, stdout , R
此句为将等级为INFO的日志信息输出到stdout和R这两个目的地,stdout和R的定义在下面的代码,可以任意起名。
等级可分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL,如果配置OFF则不打出任何信息,如果配置为INFO这样只显示INFO, WARN, ERROR的log信息,而DEBUG信息不会被显示
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
此句为定义名为stdout的输出端是哪种类型,可以是
org.apache.log4j.ConsoleAppender(控制台),
org.apache.log4j.FileAppender(文件),
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
此句为定义名为stdout的输出端的layout是哪种类型,可以是
org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n
如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern,打印参数如下:
格式 | 描述 |
---|---|
[QC] | log信息的开头,可以为任意字符,一般为项目简称 |
%m | 输出代码中指定的消息 |
%p | 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL |
%r | 输出自应用启动到输出该log信息耗费的毫秒数 |
%c | 输出所属的类目,通常就是所在类的全名 |
%t | 输出产生该日志事件的线程名 |
%n | 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n” |
%d | 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 |
%l | 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 |
输出的信息
[TS] DEBUG [main] AbstractBeanFactory.getBean(189) | Returning cached instance of singleton bean ‘MyAutoProxy’
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
此句与第3行一样。定义名为R的输出端的类型为每天产生一个日志文件
log4j.appender.R.File=D:\Tomcat 5.5\logs\qc.log
此句为定义名为R的输出端的文件名为D:\Tomcat 5.5\logs\qc.log可以自行修改
log4j.appender.R.layout=org.apache.log4j.PatternLayout
与第4行相同
log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n
与第5行相同
log4j.logger.com. neusoft =DEBUG
指定com.neusoft包下的所有类的等级为DEBUG。
可以把com.neusoft改为自己项目所用的包名。
log4j.logger.com.opensymphony.oscache=ERROR
log4j.logger.net.sf.navigator=ERROR
这两句是把这两个包下出现的错误的等级设为ERROR,如果项目中没有配置EHCache,则不需要这两句
log4j.logger.org.apache.commons=ERROR
log4j.logger.org.apache.struts=WARN
这两句是struts的包
log4j.logger.org.displaytag=ERROR
这句是displaytag的包。(QC问题列表页面所用)
log4j.logger.org.springframework=DEBUG
此句为spring的包
log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN
log4j.logger.org.hibernate=DEBUG
此两句是hibernate的包
以上这些包的设置可根据项目的实际情况而自行定制
log4j.rootLogger=DEBUG,console
#以下是rootLogger的配置,子类默认继承,但是子类重写下面配置=rootLogger+自己配置
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#设置输出样式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#日志输出信息格式为
log4j.appender.console.layout.ConversionPattern=[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n
SLF4J和Logback
Commons Logging和Log4j(Simple logging这一对好基友,它们一个负责充当日志API,一个负责实现日志底层,搭配使facade for Java)用非常便于开发
SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现
SLF4J对Commons Logging的接口有何改进。在Commons Logging中,要打印日志,有时候得这么写:
Log log = LogFactory.getLog(Main.class); // Main为本类
int score = 99;
p.setScore(score);
log.info("Set score " + score + " for Person " + p.getName() + " ok.");
拼字符串是一个非常麻烦的事情,所以SLF4J的日志接口改进成这样了:
Logger log = LoggerFactory.getLogger(Main.class); // Main为本类
int score = 99;
p.setScore(score);
logger.info("Set score {} for Person {} ok.", score, p.getName());
SLF4J的日志接口传入的是一个带占位符的字符串,用后面的变量自动替换占位符,所以看起来更加自然
SLF4J的接口实际上和Commons Logging几乎一模一样
Commons Logging | SLF4J |
---|---|
org.apache.commons.logging.Log | org.slf4j.Logger |
org.apache.commons.logging.LogFactory | org.slf4j.LoggerFactory |
不同之处就是Log变成了Logger,LogFactory变成了LoggerFactory |
logback同log4j相比具有众多优势
(1)更快的实现
(2)自动重新装载日志配置文件
(3)更好的过滤器(filter)
(4)自动压缩归档的日志文件
(5)堆栈跟踪里包括了Java包(jar文件)的信息
(6)自动删除旧日志归档文件
SLF4J:http://www.slf4j.org/download.html
Logback:https://logback.qos.ch/download.html
使用SLF4J的Logger和LoggerFactory即可。和Log4j类似,仍然需要一个Logback的配置文件,把logback.xml放到classpath下
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<file>log/output.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>log/output.log.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1MB</MaxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
从目前的趋势来看,越来越多的开源项目从Commons Logging加Log4j转向了SLF4J加Logback
(1)SLF4J和Logback可以取代Commons Logging和Log4j
(2)始终使用SLF4J的接口写入日志,使用Logback只需要配置,不需要修改代码
<!--
configuration为根元素,包含三个属性:
debug,指是否开启logback内部日志,没有设置此属性或设置其值为空、null或false时,表示不开启,否则,开启;
scan,设置是否定时扫描配置文件
scanPeriod,设置扫描周期,与scan属性配合使用,默认是60000毫秒,如果该属性值没有带单位,则单位为毫秒,可带的单位有milli/millisecond/second/seconde/minute/hour/day,可忽略大小写
-->
<configuration debug="true" scan="true" scanPeriod="600 seconds">
<!--
appender表示日志输出的方式,该元素必须包含name、classs属性;
name,表示appender的唯一标识
class一般常见有ch.qos.logback.core.FileAppender、ch.qos.logback.core.rolling.RollingFileAppender、ch.qos.logback.core.ConsoleAppender
-->
<!-- 下面appender表示输出到控制台 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 设置级别过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- level级别的日志才会被处理,其他级别不处理 -->
<level>DEBUG</level>
<!-- 用于配置符合条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- encoder指定编码格式,并根据指定的pattern输出日志信息 -->
<encoder charset="UTF-8">
<!-- pattern指定日志的输出格式 -->
<pattern>%d{HH:mm:ss.SSS}[%-5level][%thread][%logger{20}]-%msg%n</pattern>
</encoder>
</appender>
<!-- 下面是将日志输入到指定的文件中 -->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!-- 指定的日志文件名 -->
<file>logFile.log</file>
<!-- 是否追加到文件末尾,默认true -->
<append>true</append>
<encoder>
<pattern>%-4r [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<!-- 下面是以滚动的方式生成日志文件 -->
<appender name="rollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 下面是设置的临界值过滤器 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 小于level级别的日志会被过滤掉 -->
<level>INFO</level>
</filter>
<!-- rollingPolicy表示滚动策略,下面表示以时间来指定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 如果用TimeBasedRollingPolicy,则fileNamePattern元素必须包含,指定日志的名称 -->
<fileNamePattern>E:/demo-%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 文件的最大保留数,超过此数字会自动删除 -->
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS}[%-5level][%thread][%logger{20}]-%msg%n</pattern>
<!-- 是否立即清空输出流,设置为false可以提高性能,但日志可能会丢失 -->
<immediateFlush>false</immediateFlush>
</encoder>
</appender>
<!-- root与logger类似,它表示根logger,只有大于等于level级别的日志才交由appender处理,level默认为DEBUG -->
<root level="INFO">
<appender-ref ref="stdout" />
<appender-ref ref="rollingFile" />
<appender-ref ref="file" />
</root>
<!--
logger元素用来设置某包或者类的日志输出级别
name:表示某包或类名称
level:表示日志级别,如果没有此属性,那么当前的logger会继承上级的日志级别
-->
<logger name="com.erayt" level="INFO" />
<logger name="com.erayt.solar2" level="DEBUG" />
<logger name="com.googlecode" level="WARN" />
<logger name="com.ibatis" level="WARN" />
<logger name="com.opensymphony" level="WARN" />
<logger name="com.opensymphony.xwork2" level="WARN" />
<logger name="net.sf" level="WARN" />
<logger name="org.apache" level="INFO" />
<logger name="org.apache.struts2" level="WARN" />
<logger name="org.codehaus" level="WARN" />
<logger name="org.jgroups" level="WARN" />
<logger name="org.springframework" level="WARN" />
<logger name="java.sql.Connection" level="WARN" />
<logger name="java.sql.PreparedStatement" level="WARN" />
<logger name="java.sql.ResultSet" level="WARN" />
</configuration>