概述
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);
原文地址:https://www.cnblogs.com/yangyuanhu/p/12336999.html