转载自:https://www.cnblogs.com/2015zzh/articles/4733756.html
在我们项目中,日志是必不可少的一个组件,平常我们也没去关注它的具体功能,只是用的时候引入一个默认的配置文件,修个几个参数,就能用了。其实它提供的功能远不止于此。
在我一个项目中,输入数据接入与处理模块,数据量很大。但是有时候不可避免的会出现某些问题。一般思路就是在其中插入接收与处理日志,通过打印出来的日志来定位问题。这个不失为
一个好方案,但是接入与处理数据太大,如果为每条记录都打印一条日志,那样日志文件会超大,可能一两天就把硬盘撑爆了。而且那么大的日志,也很难去找的具体的问题。那么我们希望怎样
才能达到我们的要求呢? 第一,我们希望平时,只打印一般日志或错误日志。第二,当出现问题时,我们又希望能够查看此时的具体信息。我解决思路是这样,把需要详细显示的信息配置为
debug级别,其余配置为info及以上级别。另一个就是要能实时修改日志级别,能够在不重启服务下控制日志级别(如果重启服务,有可能错误不会重现,也不真实)。
做项目因为考虑进度问题,遇到问题时首先想到的事现有的框架或工具能否解决问题。spring中提供了一个log4jLoggerListener的一个监听器,能够实时监听log4j配置文件的变化,并重新
加载,但是这个是用于web项目中,我做的项目属于一般的服务程序,该工具类用不了。但是刷新日志的逻辑应该可以模仿。spring的log4jLoggerListener是检测文件的变化,并重新加载配
置,那么我可以再服务中开启一个定时器,每过几秒去读取一下配置文件,如果发现和原先的不一样,那么重新加载,基于这个逻辑,我写了一个demo,果然,完美的实现了。附上部分代码
package com.jsits.bus.timer; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; import org.apache.log4j.PropertyConfigurator; import org.springframework.util.ResourceUtils; import org.topteam.esb.util.UrlUtil; import com.jsits.bus.Main; public class Log4jListener { /** * 日志配置文件 */ private static String log4jSetFile ; /** * 上次日志等级 */ private String lastLoggerLevel = "info"; static{ try { File f = ResourceUtils.getFile(UrlUtil.url2Utf8(UrlUtil .getDirFromClassLoader(Main.class)) + File.separatorChar + "conf" + File.separatorChar + "log4j.properties"); log4jSetFile = f.getPath(); } catch (FileNotFoundException e) { e.printStackTrace(); } } /** * 刷新当前日志等级 */ public void refresh() { if(isModify()) { loadLog4jConfig(); } } /** * 判断日志等级是否修改 * @return */ private boolean isModify() { try { String currentLeve = getLoggerLevel().toLowerCase(); if(!lastLoggerLevel.equals(currentLeve)) { lastLoggerLevel= currentLeve; return true; } else { return false; } } catch(Exception e) { e.printStackTrace(); return false; } } /** * 获取当前日志等级 * @return * @throws FileNotFoundException * @throws IOException */ private String getLoggerLevel() throws FileNotFoundException, IOException { Properties properties = new Properties(); properties.load(new FileInputStream(log4jSetFile)); String rootLogger = properties.getProperty("log4j.logger.com.jsits.bus"); String level = rootLogger.split(",")[0]; return level; } /** * 加载日志配置 */ private void loadLog4jConfig() { PropertyConfigurator.configure(log4jSetFile); } }
从这行代码中 String rootLogger = properties.getProperty("log4j.logger.com.jsits.bus");我这里只监听log4j.logger.com.jsits.bus 参数的变化。 这样做解决了动态调度日志的一个问题。但是把日志级别设成debug,也带来别的问题。那就是项目是集成了很多框架的,每个框架都有大量的debug日志。就比如httpClient中,他会打印接受信息的所有信息,这个 太头痛了,我们得从一大堆的日志文件中,找出自己想要的那条日志问题,这对分析太不利了。那么我们想要的是,日志只打印出自己想看到的日志。这个有两个解决思路: 1,把自己想打印的日志所在的包设置为自己定的级别(我那个是debug级别),把不想打印出的日志所在包的级别定位info或更高级的级别。如下面:
1 2 3 4 5 |
|
该设置把自己需要打印日志的包com.jsits.bus定位了debug级别。把org,akka,alibaba包的日志级别定位了error。这样输出的日志就只有你需要的日志。
2,把自己想要打印的日志单独输出到一个文件中。那么在那个文件中就只看到你包中的的日志信息,然后把其他的日志信息的输出appender的threshold设为error。如下面:
log4j.logger.com.jsits.bus=debug,RECORD log4j.rootLogger=error,R log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=logs/log log4j.appender.R.Encoding=UTF-8 log4j.appender.R.Threshold=error log4j.appender.R.DatePattern='-'yyyy-MM-dd HH'.log' log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n log4j.appender.file.encoding=UTF-8 log4j.appender.RECORD=org.apache.log4j.DailyRollingFileAppender log4j.appender.RECORD.File=logs/recordLog log4j.appender.RECORD.Encoding=UTF-8 log4j.appender.RECORD.DatePattern='-'yyyy-MM-dd HH'.log' log4j.appender.RECORD.layout=org.apache.log4j.PatternLayout log4j.appender.RECORD.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n log4j.appender.file.encoding=UTF-8
解决了上面两个问题,我对log4j稍微有点兴趣,于是下了哥源码,看了一下架构设计,尽管没全部看懂,不过对几个基本的实现有一点的理解
先附上日志的几个关键类图。
我们在配置中接触到的主要有logger,Appender,layout,Level,关系为,每个logger可以定义自己的Level,并且可以配置一个或多个Appender,对于appener又可以配置自己的layout和Level..通过类图对此一一解析。
顶层是几个抽象类:Priority,Category,AppenderSkeleton,Layout.
Priority :是Level的父类,类中对debug,info,error..各个级别做了定义及实现。
Layout:是所有布局的抽象父类,主要规定了layout类所必须实现的format方法。
AppenderSkeleton:从它的几个变量name,threshold,errorHandler,headFilter,tailFilter,layout中,它规定了实现类中几个关键组件,对于每个appender,可以配置他的threshold(即打印日志的最低级别),该日志的打印方式layout,对于errorhandler,headFilter,tailFilter,系统有默认配置,没去深究。
Category:是Logger的父类,可以理解为日志中的目录结构,有一个属性parent,代表他的上一级,它是和项目中的包相对应的,包中的每一层级对应唯一一个category,比如 com.bus.对应的也会有一个Category名称为com.bus,通过Category,可以配置每个包日志的级别,每个包日志打印的appender,并提供各种日志级别的打印操作。
出了几个关键的父类外,还有两个类也是极其重要的,一个是我们常用的loggerManager,另一个是Hierarchy。类图如下:
LoggerManager使用是当静态类使用,比如获取logger,以及初始化,同时LoggerManage在第一次调用的时候会加载log4j配置文件(log4j.xml或者log4j.properties)。是对外日志获取的一个入口。 Hierarchy类可以理解为层级结构的一个管理,或者是日志的一个管理容器。Hierarchy有几个主要属性: Hashtable ht, Logger root, RendererMap rendererMap; 其中Logger root,则代表日志的根节点。 HasTable ht 是一个Logger的管理容器,里面存放的是日志名称和对应日志的的键值对。在初始化日志配置时填充。 RendererMap rendererMap是一个Appender的管理容器,存放内容为日志名称和他对应的appender集合的键值对,在初始化日志配置时填充 该Hierarchy也可以配置threshold等参数,可以看出来在日志配置中,Hierarchy是一个核心管理器,loggerManager是一个入口,要获取某个目录,某个日志的相关配置,则需要从Hierarchy中获取。 通过对log4j中主要类的分析,我们能够大概了log4j的流程与设计。本文章主要描述的是日志的一个结构问题,对于日志写入过程的事件模型还没去细看,后面会及时更新。
********************************************************************
上一篇主要介绍了log4j的基本架构,对于开发者来说,其实我们更关注具体的使用。log4j的配置很灵活,基本可以实现你所想的效果。下边我就结合log4j架构来分析和解释配置的具体含义。
log4j配置有两种形式,第一种是xml格式,默认名称为log4j.xml.另一种是log4j.properties,我们项目中用后者就多,其实配置都是一样, 表现形式不同,下边就properties格式举例说明。
首先配置根目录
配置语法为:log4j.rootLogger=[Level],appender1,appender2
如:log4j.rootLogger=error,CONSOLE,R
所有的日志都是它的子类。如果没对具体子类设置日志级别,那就默认为debug的日志级别。
同样除了根目录外,你可以对你某个包定义一个日志。
配置语法为:log4j.[package]=[Level],appender1,appender2\
如:log4j.logger.com.jsits=debug,RECORD
Appender配置:
Appender可以理解为日志写到哪个地方。
其语法为:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
log4j.appender.appenderName.optionN = valueN
如:
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=logs/log //设置输出文件
log4j.appender.R.Encoding=UTF-8 //设置输出编码格式
log4j.appender.R.Threshold=error //设置输出最低级别
log4j.appender.R.DatePattern='-'yyyy-MM-dd HH'.log' //设置输出文件的组织格式(该格式为每一小时产生一个日志文件)
log4j.appender.R.layout=org.apache.log4j.PatternLayout //设置layout
log4j.appender.R.layout.ConversionPattern=[%t] %d %-5p - %m(%F.%M:%L)%n //设置日志样式
log4j.appender.file.encoding=UTF-8
其中, Log4j 提供的 appender 有以下几种:
org.apache.log4j.ConsoleAppender (控制台),
org.apache.log4j.FileAppender (文件),
org.apache.log4j.DailyRollingFileAppender (每天产生一个日志文件),
org.apache.log4j.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新的文件),可通过 log4j.appender.R.MaxFileSize=100KB 设置文件大小,还可通过 log4j.appender.R.MaxBackupIndex=1 设置为保存一个备份文件。
org.apache.log4j.WriterAppender (将日志信息以流格式发送到任意指定的地方)
Layout
其中, Log4j 提供的 layout 有以下几种:
org.apache.log4j.HTMLLayout (以 HTML 表格形式布局),
org.apache.log4j.PatternLayout (可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout (包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout (包含日志产生的时间、线程、类别等等信息)
格式化日志信息
Log4J 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息,打印参数如下:
%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 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数
主题配置大概这样。希望有所帮助