前言
- 最简单的方式,就是system.println.out(error) ,这样直接在控制台打印消息了。
- Java.util.logging ; 在JDK 1.4 版本之后,提供了日志的API ,可以往文件中写日志了。
- log4j , 最强大的记录日志的方式。 可以通过配置 .properties 或是 .xml 的文件, 配置日志的目的地,格式等等。
- commons-logging, 最综合和常见的日志记录方式, 经常是和log4j 结合起来使用。
如何规范的打印日志
日志文件命名
类型标识
指此日志文件的功能或者用途
web服务,记录http请求的日志通常命名为request.log或者access.log,request、access就是类型标识
java的gc日志通常命名为gc.log
通常用来记录服务的整体运行的日志一般用服务名称(serviceName、appKey)或者机器名(hostName)来命名,如 nginx.log
日志级别
打印日志的时候直接通过文件来区分级别是一种比较推荐的方式。
日志级别一般包括DEBUG、INFO、WARN、ERROR、FATAL这五个级别。
在实际编写代码中,可以采取严格匹配模式或者非严格匹配模式:
- 严格匹配模式即INFO日志文件中只打印INFO日志,ERROR日志文件只打印ERROR日志
- 非严格匹配模式即INFO日志文件可以打印INFO日志、WARN日志、ERROR日志、FATAL日志,WARN日志文件可以打印WARN日志、ERROR日志、FATAL日志
日志生成时间
在日志文件名称中附带上日志文件创建的时间,方便在查找日志文件时进行排序
日志备份编号
当进行日志切割时,如果是以文件大小进行滚动,此时可以在日志文件名称末尾加上编号
日志滚动
第一种:按照时间滚动
第二种:按照单个日志文件大小滚动
第三种:同时按照时间和单个日志文件大小滚动。
对于日志滚动策略来说,有2个比较关键的参数:
- 最大保留日志数量
- 最大磁盘占用空间
日志级别
debug/trace
打印内容较多,所以通常情况下不适用于线上生产环境使用,一般使用于前期线下环境调试。即使线上环境要使用,也需要通过开关来控制,只在定位追踪线上问题时才开启
info
一般用来记录系统运行的关键状态、关键业务逻辑或者关键执行节点。但切记一点,info日志绝不可滥用,如果info日志滥用,则和debug/trace日志没有太大区别了。
warning
一般用来记录系统运行时的一些非预期情况,是作为一种警示,提醒开发和运维人员需要关注,但是不用人为介入立刻去处理的。
error
一般用来记录系统运行时的一些普通错误,这些错误一旦出现,则表示已经影响了用户的正常访问或者使用,通常意味着需要人为介入处理。但很多时候在生产环境中,也不一定是出现error日志就需要人工立即介入处理的,通常会结合error日志的数量以及持续时间来进行综合判断。
fatal
属于系统致命错误,一般出现意味着系统基本等于挂掉了,需要人工立即介入处理。
日志打印时机的选用
- http调用或者rpc接口调用
在程序调用其他服务或者系统的时候,需要打印接口调用参数和调用结果(成功/失败)。 - 程序异常
在程序出现exception的时候,要么选择向上抛出异常,要么必须在catch块中打印异常堆栈信息。不过需要注意的是,最好不要重复打印异常日志,比如在catch块里既向上抛出了异常,又去打印错误日志(对外rpc接口函数入口处除外)。 - 特殊的条件分支
程序进入到一些特殊的条件分支时,比如特殊的else或者switch分支。 - 关键执行路径及中间状态
在一些关键的执行路径以及中间状态也需要记录下关键日志信息,比如一个算法可能分为很多步骤,每隔步骤的中间输出结果是什么,需要记录下来,以方便后续定位跟踪算法执行状态。 - 请求入口和出口
在函数或者对外接口的入口/出口处需要打印入口/出口日志,一来方便后续进行日志统计,同时也更加方便进行系统运行状态的监控。
日志的内容与格式
通常来说,一行日志应该至少包括以下几个组成部分:
- logTag、param、exceptionStacktrace:logTag为日志标识,用来标识此日志输出的场景或者原因
- param为函数调用参数
- exceptionStacktrace为异常堆栈
项目中如何正确的打日志
- 正确的定义日志
- 使用参数化形式{}占位,[] 进行参数隔离
LOG.debug(“Save order with order no:[{}], and order amount:[{}]”); - 输出不同级别的日志
几种错误的打日志方式
- 不要使用 System.out.print
- 不要使用 e.printStackTrace()
- 不要抛出异常后又输出日志
- 没有输出全部错误信息
- 不要使用错误的日志级别
- 不要在千层循环中打印日志
- 禁止在线上环境开启 debug
打印日志
java.util.logging - JDK记录日志方式
定义一个Logeer的实例,并设置log 的级别,接着添加一个fileHander ,就是把日志写到文件中。在写入文件的时候,定义一个 LogFormatter对日志进行格式的渲染。
默认状况下, 日志会打印到控制台。添加filehandler 后, 会同时写入文件。 如不指定路径,日志文件将位于项目根路径下。
public class TestLog {
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger("testlog");
logger.setLevel(Level.ALL);
FileHandler fileHandler = new FileHandler("testlog.log");
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new LogFormatter());
logger.addHandler(fileHandler);
logger.info("This is test java util log");
}
static class LogFormatter extends Formatter{
@Override
public String format(LogRecord record){
Date date = new Date();
String sDate = date.toString();
return "["+sDate+"]"+"["+record.getLevel()+"]"
+record.getClass()+record.getMessage()+"\n";
}
}
}
log4j记录日志方式
log4j.properties
日志输出的级别: info || debug || warn || error || fatal
- ERROR 为严重错误 主要是程序的错误
- WARN 为一般警告,比如session丢失
- INFO 为一般要显示的信息,比如登录登出
- DEBUG 为程序的调试信息
日志输出的目的地: stdout, logfile。 这两个名字可以随便取,比如 A, 或B都可以。
实际的配置是 org.apache.log4j.ConsoleAppender 和RollingFileAppender 用于指定是控制台还是文件。
- appender.moder1定义的是Log输出的地方:
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
日志信息的格式: appender.moder1.Layout
- org.apache.log4j.HTMLLayout(以HTML表格形式布局)
- org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
- org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
- org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
日志输出的格式: 在appender.moder1.Layout下定义的是PatternLayout才有log4j.appender.moder1.layout.ConversionPattern - -X号: X信息输出时左对齐;
- %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
- %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
- %r: 输出自应用启动到输出该log信息耗费的毫秒数
- %c: 输出日志信息所属的类目,通常就是所在类的全名
- %t: 输出产生该日志事件的线程名
- %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。 举例:Testlog4.main (TestLog4.java:10)
- %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
- %%: 输出一个"%"字符
- %F: 输出日志消息产生时所在的文件名称
- %L: 输出代码中的行号
- %m: 输出代码中指定的消息,产生的日志具体信息
- %n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行
Threshold是个全局的过滤器,它将把低于所设置的level的信息过滤不显示出来。
### 设置日志级别 ###
log4j.rootLogger=debug,stdout,logfile
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [ %p ] - [ %l ] %m%n
### 输出到日志文件 ###
log4j.appender.logfile = org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File = log4j.log
log4j.appender.logfile.MaxFileSize = 512KB
log4j.appender.logfile.MaxBackupIndex = 3
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %p ] - [ %l ] %m%n
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class TestLog4j {
public static void main(String[] args) {
// 1. create log
Logger log = Logger.getLogger(TestLog4j.class);
// 2. get log config file
PropertyConfigurator.configure("log4j.properties");
// 3. start log
log.debug("Here is some DEBUG");
log.info("Here is some INFO");
log.warn("Here is some WARN");
log.error("Here is some ERROR");
log.fatal("Here is some FATAL");
}
}
commons-logging记录日志方式
使用Commons-logging的LogFactory获取日志处理类时:
- 首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类;
- 如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类;
如果在Tomact中可以建立一个叫 CATALINA_OPTS 的环境变量,给他的值:
Dorg.apache.commons.logging.Log = org.apache.commons.logging.impl.SimpleLog
Dorg.apache.commons.logging.simplelog.defaultlog = warn - 否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;
项目同时导入log4j 和commons-logging的jar 包, 不需要配置commons-logging.properties ,只需要在classpath中配置 log4j.properties就可以使用log4j的方式记录日志。 - 否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类);
- 否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog;
先使用第一种方式来看一个实例,配置commons-logging.properties, 使用log4j来记录日志。
注意, commons-logging 要配合log4j 记录日志,必须把log4j的jar 包也导入到项目中。
- 导入log4j 和commons-logging的jar 包
- 配置commons-logging.properties 和 log4j.properties, 放入项目的classpath下(也就是src目录下)
注意: 单独使用log4j 的时候,log4j.properties 默认是放在项目的根目录下。
log4j.properties 的内容和上面完全相同。
commons-logging.properties
指定使用log4j
org.apache.commons.logging.impl.Jdk14Logger 使用JDK1.4。
org.apache.commons.logging.impl.Log4JLogger 使用Log4J。
org.apache.commons.logging.impl.LogKitLogger 使用 avalon-Logkit。
org.apache.commons.logging.impl.SimpleLog common-logging自带日志实现类。它实现了Log接口,把日志消息都输出到系统错误流System.err 中。
org.apache.commons.logging.impl.NoOpLog common-logging自带日志实现类。它实现了Log接口。 其输出日志的方法中不进行任何操作。
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class TestLogCom {
static Log log = LogFactory.getLog(TestLog.class);
public static void main(String[] args) {
log.debug("Here is some DEBUG");
log.info("Here is some INFO");
log.warn("Here is some WARN");
log.error("Here is some ERROR");
log.fatal("Here is some FATAL");
}
}