日志框架 log4j2 全解析
概述
logging翻译为日志记录
那问题是什么是日志?
日志实际上是日记的一种,用于记录某个时间点发生了什么事情,比如大学老师的教学日志,工作日志等
为什么要记录日志?
在实际生活中记录日志主要为了日后复查,
比如某个大学老师每天记录自己讲的什么内容,后面有学生某科成绩优异获奖了,校长想要奖励对应的老师,但由于每个老师教的班级都很多,并不一定记得是谁教的,这时候就可以查看教学日志来获取需要的信息了
再比如,工厂的生产日志,如果某个产品除了因为某个零件出现了故障,通过生成日志,可以找到与这个产品同批次的其他产品,进行返工,或是通过日志找到该零件的供应商,进行沟通解决!
程序中的日志
我们的程序开发完成后会被不同系统环境的用户下载使用,期间可能就会出现问题,直接把错误信息展示给用户看是没有任何意义的,用户看不懂也不会解决,那这时候就可以将用户执行的所有操作,以及代码运行的过程,记录到日志中,程序员通过分析日志内容,可以快速的定位问题
综上: 日志就是用来记录发生的事件的
日志并不会立即产生作用,而是当程序出现了问题时在去分析日志文件提取有用信息
java下的日志框架
门面
门面是Facade(外观模式)的实现,也称为门面模式,
是对内部多个子系统的封装,并对外提供一套统一的使用接口,从而屏蔽各个子系统在使用上的不同,大大降低了系统的使用难度,同时提高了系统的可维护性和扩展性;
实际上真正干活的还是是内部的子系统;就像给这些子系统加了一层装饰,Facede也得名于此;
图示:
因其性能优越性,实际开发中log4j是使用最多一个日志框架,也是我们需要掌握的目标;
官方性能对比:
log4j
日志级别:
日志级别其实指的就是日志信息应用场景,我们的程序会在不同的环境中运行,某些日志只有用在某些特殊场景中,例如:在开发阶段,我们为了检查错误,会输出一些调试信息,但是这些信息在生产环境下是不需要的,当然.我们可以在发布前删除这些调试代码,但这就显得非常low了,通过对日志信息区别对待,我们可以很方便的控制哪些日志在哪些场景下正常输出;
日志级别也在后续的问题定位中发挥着重要作用,当程序出现了问题,我们要根据日志来定位问题,这时便可以通过日志级别来快速过滤掉不需要的记录;
log4j日志级别:
log4j定义了8个级别,优先级从高到低依次为:
OFF>FATAL> ERROR> WARN> INFO> DEBUG> TRACE> ALL
- ALL 最低等级的 用于打开所有日志记录
- TRACE 很低的日志级别 一般不会使用
- DEBUG 该级别信息对调试应用程序是非常有帮助的 主要用于开发过程中打印 一些运行信息
- INFO 突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息 可用于生产环境中输出程序运行的一些重要信息,但是不能滥用 避免打印过多的日志
- WARN 表明会出现潜在错误的情形 有些信息不是错误信息 但是也要给程序员的一些提示
- ERROR 指出虽然发生错误事件 但仍然不影响系统的继续运行。打印错误和异常信息 如果不想输出太多日志 太多数情况下可以使用这个级别
- FATAL 指出严重的错误事件,将会导致应用程序的退出。
- OFF 最高等级的,用于关闭所有日志记录
依赖包:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.5</version> </dependency>
配置文件:
2.x往后的版本不在支持properties作为配置文件,因其无法表达较为复杂的语法结构
log4j2会在classpath下查找配置文件,如果找不到则使用基础配置(输出到控制台),log4j支持 xml,json,jsn三种格式的配置文件,并可为测试环境和生产环境编写不同的配置文件;若有多个配置文件log4j将按照以下顺序读取:
- classpath下的名为log4j2-test.json 或者log4j2-test.jsn的文件.
- classpath下的名为log4j2-test.xml的文件.
- classpath下名为log4j2.json 或者log4j2.jsn的文件.
- classpath下名为log4j2.xml的文件.
示例log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?> <!-- status="WARN" 用于设置log4j框架本身的日志级别--> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="all"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
测试代码:
@Test public void test1(){ Logger myLogger = LogManager.getLogger("myLogger"); myLogger.debug("debug msg"); }
Appenders节点:
该节点配置日志信息的输出目的地,可以是控制台,文件,邮件或数据库,控制台和文件是表常用的两个目的地;
上述案例既将日志信息输出到控制台,其子节点PatternLayout用于设置日志输出的字符传格式;
PatternLayout可用的格式化字符:
%d{HH:mm:ss.SSS} 表示输出到毫秒的时间 %t 输出当前线程名称 %-5level 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补空格 %logger 输出logger名称,因为Root Logger没有名称 %msg 日志文本 %n 换行 %F 输出所在的类文件名,如Client.java %L 输出行号 %M 输出所在方法名 %C 产生log事件的java完全限定类名 %l 输出语句所在的行数, 包括类名、方法名、文件名、行数
输出到文件:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/> </Console> <File name="fileAppender" fileName="logs/app.log" append="false"><!--默认以当前项目为相对路径--> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/> </File> </Appenders> <Loggers> <Root level="all"> <AppenderRef ref="Console"/> <AppenderRef ref="fileAppender"/><!--一个logger可配置对个appender输出到不同位置--> </Root> </Loggers> </Configuration> <!--File属性: name用于给appender指定名字,以便logger引用 fileName默认以当前项目为相对路径 append参数表示是否将是追加到文件末尾 默认为true 为false即直接覆盖原文件-->
上面的配置会将所有日志输出到同一个文件,随着时间的推移该文件会越来越大,甚至无法打开,但是实际有用的日志都是近期的产生的,太久远的日志大多数是无用的,这就用到了滚动日志,其可以帮助我们实现日志文件的切割,以及无用日志的删除操作;
滚动日志配置:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <!--自定义属性信息--> <properties> <property name="LOG_HOME">logs</property> <property name="FILE_NAME">applog</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> <!--滚动日志配置 filePattern用于设置滚动文件的命名规则 若以.zip为结尾则会自动归档日志文件 也支持其他的格式.gz, .zip, .bz2, 等--> <RollingRandomAccessFile name="RollingAppender" fileName="${LOG_HOME}/${FILE_NAME}.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <Policies> <!--滚动时间间隔 该参数需结合filePattern中的时间格式 此时表示为1分钟更换一个新文件 若时间格式为%d{yyyy-MM-dd HH}则表示每小时更换一个新文件--> <TimeBasedTriggeringPolicy interval="1"/> <!--单个日志文件最大容量 --> <SizeBasedTriggeringPolicy size="1 MB"/> </Policies> <!--最大保留的日志文件个数 默认为7个 --> <DefaultRolloverStrategy max="20"/> </RollingRandomAccessFile> </Appenders> <Loggers> <Root level="all"> <AppenderRef ref="RollingAppender"/> </Root> </Loggers> </Configuration>
上述配置的整体含义:统一使用rootLogger,所有级别日志都会被记录到文件中,文件位于项目目录下的logs中,当单个文件超过1MB或是时间超过1分钟后则更换日志文件,最多保存20个日志文件;
补充:RollingFile也是做滚动日志的,但是据官方说效率没有RollingRandomAccessFile高;
Loggers节点:
需求:某个类或模块中产生的需要同时输出到控制台和文件,其他模块则仅输出到控制台,当遇到类似需求时,就需要定义不同的logger了,然后在程序中根据名称获取所需的logger
如果在配置中找不到名称匹配的logger时使用rootLogger;
logger配置:
注意:appender使用的还是上面的例子中的
<Loggers> <Root level="all"> <AppenderRef ref="Console" /> </Root> <Logger name="fileAndConsole" level="all" additivity="false"> <AppenderRef ref="Console" /> <AppenderRef ref="RollingAppender" /> </Logger> </Loggers> <!-- additivity表示是否将日志传递给root继续输出 默认为true-->
测试代码:
@Test public void test1(){ Logger myLogger = LogManager.getLogger("myLogger");//rootlogger Logger myLogger2 = LogManager.getLogger("fileAndConsole");//fileAndConsole for (int i = 0;i < 50;i++){ myLogger.debug("debug msg1"); } for (int i = 0;i < 50;i++){ myLogger2.debug("debug msg2"); } }
msg1将只出现在控制台,而msg2同时出现在控制台和日志文件;
Filter节点:
Filter用于对日志进行过滤,一些情况下我们可能需要对日志进行更加个性化的限制,
例如:
输出日志消息包含某个字符串的
按照时间不同输出到不同文件
日志的生命周期 :
一个日志事件(LogEvent)产生后到最终输出到目的地会经过以下环节:
全局过滤器 -> logger过滤器 -> logger -> appender过滤器 -> appender->输出
无论哪个环节的过滤器,每个过滤器在匹配或是不匹配时都要明确该日志事件的处理方式,包含三种:
- ACCEPT接受(继续传递该LogEvent)
- DENY拒绝(直接丢弃该LogEvent)
- NEUTRAL中立(不清楚该怎么办继续往后传递LogEvent)
全局过滤器:评估结果为接受时,其他全局过滤器将不会再对该事件进行评估,且不再交给Logger过滤器评估
Logger过滤器:评估为拒绝时不再交给Appdener过滤器
Appdener过滤器:最终决定改日志是否输出
通常需要根据实际需求来配置过滤器:
下例配置列出了三种过滤器的示例(无实意义):
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="TRACE" monitorInterval="5" packages="com.kanq.extend.cat.log4j2"> <Filters> <!-- 全局级别Filter --> <BurstFilter level="INFO" rate="16" maxBurst="100"/> </Filters> <Appenders> <RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz"> <!-- Appender级别的Filter --> <BurstFilter level="INFO" rate="16" maxBurst="100"/> <PatternLayout> <pattern>%d %p %c{1.} [%t] %m%n</pattern> </PatternLayout> <TimeBasedTriggeringPolicy /> </RollingFile> </Appenders> <Loggers> <!-- Logger级别的Filter --> <Root level="error"> <BurstFilter level="INFO" rate="16" maxBurst="100"/> <AppenderRef ref="RollingFile"/> </Root> </Loggers> </Configuration>
过滤器案例:
通过时间过滤器实现将白天和夜晚的是指写入不同位置:
<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> <File name="DayAppender" fileName="logs/appDay.log" append="true"> <TimeFilter start="06:00:00" end="24:00:00" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/> </File> <File name="NightAppender" fileName="logs/appNight.log" append="true"> <TimeFilter start="24:00:00" end="06:00:00" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/> </File> </Appenders> <Loggers> <Root level="all"> <AppenderRef ref="Console" /> <AppenderRef ref="DayAppender" /> <AppenderRef ref="NightAppender" /> </Root> </Loggers> </Configuration>
当然官网还有其他的过滤器,如正则过滤等,大家根据需求选择即可;地址:官方手册
web项目中的使用
web环境下需要额外的依赖包:
<!-- web容器中需要添加log4j-web --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-web</artifactId> <version>2.5</version> </dependency>
controller中的使用:
@Controller @RequestMapping("/customer") public class CustomerController { //为controller添加属性 用于获取一个日志记录器(Logger) private Logger logger = LogManager.getLogger(this.getClass().getPackage().getName()); @RequestMapping("/list") public String getCustomerList(Model model, SearchInfo searchInfo){ logger.info("request this /list interface"); //... } }
若配置文件名称不是默认的跨域通过以下代码来加载:
File file = new File("/Users/jerry/LOGfj/src/main/resources/log4j3.xml"); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); ConfigurationSource source = new ConfigurationSource(in); Configurator.initialize(null, source);