对于一个完整的项目而言,通过日志可以随时观察系统运行情况,日志功能是必不可少的,平时开发项目的时候想知道程序运行情况一般可以使用sysout.print(),打印一些关键的代码或者通过debug查看运行状态,使用sysout.print()会出现代码多余,于是市场上了出现许多记录运行状态的框架。
日志框架分为3部分:日志门面、日志适配器、日志库
日志门面:JCL、SLF4j、jboss-logging这些框架都是日志门面。门面设计模式是面向对象的一种设计模式,类似JDBC,也就是说这些货本身自己不干活,就是一套接口规范,让调用者不需要关心日志底层具体是什么框架在干活
日志库:也就是真实干活的人,几种常见的日志库
java.util.logging.Logger是JDK自带的日志工具类,从1.4版本开始就已经有了。由于log4j等开源的日志组件,这个Logger并没有太多展现机会。但在一些测试性的代码中,JDK自带的Logger比log4j等更方便。参考文章Java自带日志工具java.util.logging.Logger_peterwanghao的博客-CSDN博客
Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控 制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。 Log4j由三个重要的组成构成:日志记录器(Loggers),输出端(Appenders)和日志格式化器(Layout)。 1.Logger:控制要启用或禁用哪些日志记录语句,并对日志信息进行级别限制 2.Appenders : 指定了日志将打印到控制台还是文件中 3.Layout : 控制日志信息的显示格式 Log4j 中将要输出的 Log 信息定义了 5 种级别,依次为 DEBUG、INFO、WARN、ERROR 和 FATAL,当输出时,只有级别高过配置中规定的 级别的信息才能真正的输出,这样就 很方便的来配置不同情况下要输出的内容,而不需要更改代码。
简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成:logback-core,logback-classic。logback-access logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。
logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J; logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与HTTP 访问相关的功能。
Logback 优点
日志适配器:它是解决日志门面和日志库接口不兼容的,一般配套的都是兼容的
所以我们的目标是挑选一个日志门面,再挑选一个日志库,搭配使用即可,在Springboot项目中整合日志有两套方案
slf4j+Logback
Springboot默认抽象接口层使用slf4j,实现层用logback,创建了一个demo项目,
当我们引入spring-boot-starter
的时候,就默认帮我们引入了logback
、slf4j
当我们创建一个boot项目,我们没有配置任何其它配置,就看到在控制台下打印日志,Logback默认打印debug级别日志,但是我们注意到debug级别的日志没有记录下来,那是因为Spring Boot为Logback提供了默认的配置文件,base.xml,另外Spring Boot 提供了两个输出端的配置文件console-appender.xml和file-appender.xml,base.xml引用了这两个配置文件。所以Springboot默认日志级别是info,可以看到base.xml引用两个配置,
以下是从git上找到spring-boot用的base.xml
在这里, 您可以看到 Spring boot已通过将根记录器设置为INFO来覆盖 Logback 的默认日志记录级别, 这是我们在上面的示例中没有看到调试消息的原因。
Springboot日志框架配置就相当讲究了,就比如我们公司要求,所有日志必须保存180天,每个文件最大50mb,超过的另外取一个文件。info
、warn
、error
必须分开。毕竟跟学生项目不同,公司是有审计要求的,不能胡来,那么我就按这个标准配置一下
小项目可以直接在application.properties中对logback简单配置,如下
#修改指定包下的日志输出级别
logging.level.com.javayihao=trace
#当前项目下生成mylog.log日志记录输出的日志
#logging.file=mylog.log
#在指定的路径下输出日志
#logging.file=f:/mylog.log
#当前的磁盘根路径下创建spring文件家和里面的log文件夹,以spring.log作为日志文件名字
logging.path=/spring/log
#指定控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS}+++[%thread] %-5level %logger{50} - %msg%n
#执行日志文件中输出日志的格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS}===[%thread] %-5level %logger{50} - %msg%n
通过application.properties文件配置Logback,对于大多数Spring Boot应用来说已经足够了,但是对于一些大型的企业应用来说似乎有一些相对复杂的日志需求。在Spring Boot中你可以在logback.xml或者在logback-spring.xml中对Logback进行配置,相对于logback.xml,logback-spring.xml更加被偏爱。如果是其他名字,只需在application.propertions
中配置logging.config=classpath:logback-boot.xml使用自己的日志配置文件即可
${glmapper-name}
//xxxx
//xxxx
//xxxx
用来定义变量值的标签,property
标签有两个属性,name
和value
;其中name
的值是变量的名称,value
的值时变量定义的值。通过property
定义的值会被插入到logger
上下文中。定义变量后,可以使“${name}”来使用变量。如上面的xml
所示。
每个logger
都关联到logger
上下文,默认上下文名称为“default”
。但可以使用contextName
标签设置成其他名字,用于区分不同应用程序的记录
true
${logging.level}
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
30
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
appender
有两个属性 name
和class
;name
指定appender
名称,class
指定appender
的全限定名。上面声明的是名为GLMAPPER-LOGGERONE
,class
为ch.qos.logback.core.rolling.RollingFileAppender
的一个appender
。
appender 的种类
append 子标签
true
如果是 true
,日志被追加到文件结尾,如果是false
,清空现存文件,默认是true
。
filter 子标签
filter其实是appender里面的子元素。它作为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。appender
有多个过滤器时,按照配置顺序执行。
filter的class有ThresholdFilter以及LevelFilter
ThresholdFilter
临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL
;当日志级别低于临界值时,日志会被拒绝。
INFO
LevelFilter
级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath
(用于配置符合过滤条件的操作) 和 onMismatch
(用于配置不符合过滤条件的操作)接收或拒绝日志。
INFO
ACCEPT
DENY
file 子标签
file
标签用于指定被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
这个表示当前appender将会将日志写入到${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
这个目录下。
rollingPolicy 子标签
这个子标签用来描述滚动策略的。这个只有appender
的class
是RollingFileAppender
时才需要配置。这个也会涉及文件的移动和重命名(a.log->a.log.2018.07.22)。class有TimeBasedRollingPolicy和FixedWindowRollingPolicy
TimeBasedRollingPolicy
最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
30
复制代码
上面的这段配置表明每天生成一个日志文件,保存30天的日志文件
FixedWindowRollingPolicy
根据固定窗口算法重命名文件的滚动策略。
encoder 子标签
对记录事件进行格式化。它干了两件事:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
- %msg%n
UTF-8
目前encoder
只有PatternLayoutEncoder
一种类型。
appender案例
定义一个只打印error级别日志的appdener
true
error
${logging.path}/glmapper-spring-boot/glmapper-error.log
${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}
30
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
定义一个输出到控制台的appender
%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n
用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender
根logger,也是一种logger,且只有一个level属性
a):统一配置
%d %p (%file:%line\)- %m%n
UTF-8
log/sys.log
log/sys.%d.%i.log
30
10MB
%d %p (%file:%line\)- %m%n
UTF-8
b):生产和测试环境分开配置
可以在application-dev.yml
和application-pro.yml
中分别指明不同环境的logback
配置文件的目录,即可分开,也开使用一个xml文件
c:)提示信息和异常记录分开记录
${CONSOLE_LOG_PATTERN}
INFO
ACCEPT
DENY
${CONSOLE_LOG_PATTERN}
${LOG_HOME}/info.log
${LOG_HOME}/achiveLog/info-%d{yyyy-MM-dd}_%i.log
50MB
180
WARN
ACCEPT
DENY
${CONSOLE_LOG_PATTERN}
${LOG_HOME}/warn.log
${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log
50MB
180
ERROR
ACCEPT
DENY
${CONSOLE_LOG_PATTERN}
${LOG_HOME}/error.log
${LOG_HOME}/achiveLog/error-%d{yyyy-MM-dd}_%i.log
50MB
180
首先看一下根节点root
,这个level="INFO"
指的是日志登记,按日志级别从低到高分为TRACE
< DEBUG
< INFO
< WARN
< ERROR
< FATAL
,我们这边指定了INFO
,也就是说DEBUG
的日志是不会被输出出来的
根节点有4个直接点,挑一个做一下解释
WARN
ACCEPT
DENY
${CONSOLE_LOG_PATTERN}
${LOG_HOME}/warn.log
${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log
50MB
180
filter
:用于过滤日志level
:匹配等级是WARN
onMatch
:当匹配时ACCEPT
onMismatch
:不匹配时DENY
,不匹配的过滤掉因此只过滤出level
为warn
级别的日志,其他的日志信息经过这个节点会被过滤掉,也就不会进入到后续流程了。如果你不写onMatch
、onMismatch
标签,那么error
级别的因为大于warn
,也会进入到后续流程
而我们说的后续流程就是下面的标签。这些标签指明过滤出来的数据应该放到控制台
还是放到文件
中
file
:这些过滤出来的数据需要被写入到文件中fileNamePattern
:文件的目录和命名格式warn-yyyy-mm-dd_1.log
maxFileSize
:文件最大50MBMaxHistory
:保留18天四、slf4j+Log4j2
1、引入pom
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
2.2.6.RELEASE
com.lmax
disruptor
3.4.2
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-log4j2
2.2.6.RELEASE
com.lmax
disruptor
3.4.2
2、application.yml
# 引入日志配置文件
logging:
config: classpath:log4j2.xml
3、log4j2.xml 放在目录 resources 下即可
4、声明 logger 变量
package com.songo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestService {
private static final Logger logger = LoggerFactory.getLogger(TestService.class);
public void Test() {
logger.info("test...");
}
}
5、附自己使用配置文件
${sys:LOG_PATH}/apps/manager
${sys:LOG_PATH}/apps/manager
%d [%t] %-5p [%c] - %m%n
%d [%t] %-5p [%c] - %m%n
%m%n
注意:对于AsyncLogger的name属性可以是指定的类,也可以是字符串
//指定类 需要打日志地方直接调用即可
public class LogUitl{
public static final Logger log = LogManager.getLogger(LogUitl.class);
/**
* 记录
*/
public static void record(String str) {
log.info(str);
}
}
//直接使用字符串
public class Test{
//AsyncLogger的name属性也为testLog
public static final Logger log = LogManager.getLogger("testLog");
/**
* 记录
*/
public void test(String str) {
log.info(str);
}
}
log.error
还是log.warn
比如业务异常往往通过用户引导就可以恢复的,那么就只打
WARN
级别。而ERROR
级别的日志一旦出现,就需要人工介入,因此要定期检查ERROR
日志排查问题。这段是《阿里Java开发手册》写的
public class TestController{
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//日志级别,由低到高,调整日志级别,只输出高级别日志,
logger.trace("这是trace跟踪信息");
logger.debug("这是debug调试信息");
//springboot默认输出info以后的级别,也就是root级别
logger.info("这是info自定义信息");
logger.warn("这是warn警告信息");
logger.error("这是error错误信息");
}
}
看下logger.error代码就知道error有2个重载方法
public void error(String msg);
public void error(String msg, Throwable t);
上面的代码只有一个参数,因此都会被认为是调用第一种方法,这样造成的结果就是e将会被自动转成String类型,从而丢失的许多错误信息、堆栈信息,即不知道哪个方法调用了service产生的错误,也不知道错误的原因,更不知道代码抛出异常的行数。生产一旦出现问题,根本无从排起,
而正确的日志,我们即可以知道发生错误的原因,抛出异常的行数,同时也能获悉堆栈调用的关系,这样排查生产问题才有解决的可能性。
logger.error("x部分出错", e);
代码1:
@Aspect//切面
@Component//组件
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 这里把切点切再controller(web)层下的每个方法
*/
@Pointcut("execution(* com.javayihao.myweb.controller.*.*(..))")
public void log() {
}
/**
* 执行com.javayihao.myweb.controller下所有的方法之前执行这个方法
* 获取要记录的url ip 请求的方法 以及请求时候所带的参数
* @param joinPoint
*/
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
//类名.方法
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
//参数
Object[] args = joinPoint.getArgs();
//调用自己封装的对象
RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
logger.info("Request : {}", requestLog);
}
@After("log()")
public void doAfter() {
// logger.info("--------doAfter--------");
}
//执行com.javayihao.myweb.controller下所有的方法之后要执行的方法
@AfterReturning(returning = "result",pointcut = "log()")
public void doAfterRuturn(Object result) {
logger.info("Result : {}", result);
}
private class RequestLog {
private String url;
private String ip;
private String classMethod;
private Object[] args;
public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}
@Override
public String toString() {
return "{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}
}
代码2:
@Aspect
@Component
public class LogAopAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);
@Around("execution(* company.controller.*.*(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
Class> currentClass = pjp.getTarget().getClass();
MethodSignature signature = (MethodSignature) (pjp.getSignature());
String ClassName = currentClass.getSimpleName();
String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
logger.info("======= 开始执行:" + ClassName + " — " + methodName + " ========");
Object obj = pjp.proceed();
logger.info("======= 执行结束:" + ClassName + " — " + methodName + " ========");
return obj;
}
}
项目中如果要记录日志,不应该直接使用一个日志实现类记录,而是通过一个日志抽象层+一个日志抽象实现类,然后调用抽象层里面的接口记录接口,如下使用日志抽象类SLF4j
boot就是这么做的,这里主要总结一下日志抽象类SLF4j和其他日志实现类的使用
官网:SLF4J
使用方法,官网的一张图说的很明白
问题:x系统使用Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx框架实现,每个框架有不同的日志记录框架,如何统一使用都使用slf4j
解决方法
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
比如我们要使用slf4j+log4j的方式来记录日志,依赖如下
再如我们要使用slf4j+log4j2的方式来记录日志,依赖如下
org.springframework.boot
spring‐boot‐starter‐web
spring‐boot‐starter‐logging
org.springframework.boot
org.springframework.boot
spring‐boot‐starter‐log4j2
但是在boot项目中不推荐slf4j+log4j2或者slf4j+log4j组合来记录日志,springboot官方推荐slf4+logback