1、日志规范
1.1、背景
用户行为留痕、调用链追踪,大数据分析,线上问题定位和解决都离不开合理的日志规范,所有应用按同样的规范打印日志可为后续的工作带来极大的便利性。
1.2、日志框架选择
Slf4j 是为 Java 提供的简单日志门面。它允许用户以自己的喜好,在工程中通过 Slf4j 接入不同的日志系统。
现在常用的是logback和log4j2,其中logback是slf4j的原生实现框架,与log4j相比性能更加出众,
spring boot默认是支持logback的;log4j2出生更晚,已经不仅仅是log4j的升级,它还参考了logback的设计,并且据说在异步方面性能更加出众。如果需要用log4j2,需要在相关的spring包里剔除log相关的依赖。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
logback-classic
ch.qos.logback
1.3、打印时机
1、 系统完成初始化,比如加载必要的配置信息完成时;
2、 接口的入口和出口,入口需要打印参数信息;
3、 业务流程中不应该出现的地方,暗示数据或逻辑可能有错误的地方;
4、 数据库存储过程中出现异常,比如唯一索引冲突,列格式不正确,事务需要回滚等;
5、 需要保留痕迹的地方,比如用户行为留痕,为合规或审计留痕等;
6、 重要的业务流程状态变化,以及对应的分支处。
1.4、日志级别
我们使用常用的日志级别:error、info、warn、debug,其他级别不建议使用。
1、 error级别应该在业务逻辑出现异常、数据持久化出现异常、数据不匹配等场景下使用,打印该日志意味着系统出现潜在问题,需要报警,并且需要相关人员马上介入解决。error日志需要包含问题出现时必要的信息,比如用户信息,调用栈信息等。error级别的日志文件可以独立,以便报警处理。
2、 info级别用于用户行为留痕、调用链跟踪以及问题定位方面,一般在核心或者重要的方法入口需要打印,还有一些重要的代码分支也需要打印。
3、 warn级别在产生不符合预期的结果,但业务并未受损的情况下可以使用,warn级别的日志可不产生短信报警,可与公司及时消息打通报警或者邮件报警,解决的时效性要求低,但需要关注。
4、 debug是在测试和本地环境使用的。为了方便定位问题,我们可以在非线上环境使用debug这个日志级别,这样可以减少线上不必要的日志。
1.5、注意事项
1、 禁止直接使用日志系统的API(比如log4j日志系统),应该使用门面模式提供的接口。可以安装lombok插件,类上使用@Slf4j注解,然后代码中直接使用log.info(“message:{}”, msg);
2、 异常日志的打印不能丢失调用栈信息,可使用:log.error("Exception message:{}",msg, ex);
3、 日志打印使用debug时应该注意,生产环境是不打印出来的,考虑效率应该加入判断:
if(log.isDebugEnabled()){
log.debug("message: {}", object);
}
4、 测试环境和线上环境禁止使用禁用 System.out.println 和 System.err.println;
5、 日志打印的上下文中如果有用户ID、业务ID或者流水号的,一定要打印出来,方便追踪。
2、sfl4j和log4j和lobback有什么区别
我在多点的时候用的是logback,并且将日志输出到额graylog中,小米的时候使用的是log4j2
slf4j
是打日志的。可以使用各种日志系统存储。Log4j
和logback
就是那个日志存储系统(Log4j它自带打日志,因为自己本身就是一个日志系统。所以不能够切换日志系统)。但是slf4j 是可以随时切换到任何日志系统,所以一般我们打日志都用SLF4J进行打日志吧!!!
SLF4J
:即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统(Log4j logback)。 在使用SLF4J的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统,SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
log4j
:日志系统
logback
:logback
和 log4j
非常相似, Logback
的内核重写了,在一些关键执行路径上性能提升10倍以上。而且 logback
不仅性能提升了,初始化内存加载也更小。但是说白了也是个日志系统
3、获取日志错误的行号,方法,报错信息
StackTraceElement s= e.getStackTrace()[0];
记录报错的文件:s.getFileName()
记录报错的方法:s.getMethodName()
记录报错的行号:s.getLineNumber()
记录报错的信息(不全面)“ e.getMessage()
互利报错的类名字:e.getClassName()
打印详细的堆栈信息:logger.error("错误堆栈",e);
3.1、 StackTraceElements=e.getStackTrace()[0];
public static void main(String[] args) {
try {
int i =1/0 ;
}catch (Exception e){
log(e,ExceptionLogUtils.class );
}
}
public static void log(Throwable e,Class c){
Logger logger = LoggerFactory.getLogger(c);
StackTraceElement s= e.getStackTrace()[0];//数组长度为 1
logger.error("\n\n-----------------"+
"\n报错文件名:"+s.getFileName()+
"\n报错的类:"+s.getClassName()+
"\n报错方法::"+s.getMethodName()+
"\n报错的行:"+ s.getLineNumber()+
"\n报错的message:"+ e.getMessage()+
"\n错误堆栈:\n"+getStackTrace(e)+
"\n------------------\n\n");
}
//获取堆栈信息
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try
{
throwable.printStackTrace(pw);
return sw.toString();
} finally
{
pw.close();
}
}
11:40:53.732 [main] ERROR com.duodian.youhui.admin.utils.ExceptionLogUtils -
-----------------
报错文件名:ExceptionLogUtils.java
报错的类:com.duodian.youhui.admin.utils.ExceptionLogUtils
报错方法::main
报错的行:68
报错的message:/ by zero
错误堆栈:
java.lang.ArithmeticException: / by zero
at com.duodian.youhui.admin.utils.ExceptionLogUtils.main(ExceptionLogUtils.java:68)
------------------
3.2、 Thread.currentThread().getStackTrace()
public static void main(String[] args) {
logInfo("HealerJean",ExceptionLogUtils.class);
}
public static void logInfo(String msg,Class c){
Logger logger = LoggerFactory.getLogger(c);
String location="";
StackTraceElement[] stacks = Thread.currentThread().getStackTrace();
System.out.println(stacks.length); //长度为3
for(StackTraceElement stackTraceElement:stacks){
logger.info("\n\n**************"+
"\n打印文件名:"+stackTraceElement.getFileName() +
"\n打印类名:"+ stackTraceElement.getClassName() +
"\n方法名:" + stackTraceElement.getMethodName() +
"\n行号:" + stackTraceElement.getLineNumber() +
"\n打印内容:"+msg+
"\n**************\n\n");
System.out.println(location);
}
}
11:44:47.685 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -
**************
打印文件名:Thread.java
打印类名:java.lang.Thread
方法名:getStackTrace
行号:1559
打印内容:HealerJean
**************
11:44:47.689 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -
**************
打印文件名:ExceptionLogUtils.java
打印类名:com.duodian.youhui.admin.utils.ExceptionLogUtils
方法名:logInfo
行号:31
打印内容:HealerJean
**************
11:44:47.689 [main] INFO com.duodian.youhui.admin.utils.ExceptionLogUtils -
**************
打印文件名:ExceptionLogUtils.java
打印类名:com.duodian.youhui.admin.utils.ExceptionLogUtils
方法名:main
行号:49
打印内容:HealerJean
**************
4、Throwable、Exception、Error
Throwable是java.lang包中一个专门用来处理异常的类。它有两个子类,即Error 和Exception,它们分别用来处理两组异常。
4.1、Error
*用来处理程序运行环境方面的异常,Error无法预期的错误因此,这是不可捕捉的,无法采取任何恢复的操作,一般只能显示错误的信息 *
比如,虚拟机错误、装载错误和连接错误,这类异常主要是和硬件有关的,而不是由程序本身抛出的。
比如 OutOfMemoryError,试多少次很大概率出错的。
4.2、Exception
java提供了两类主要的异常:运行时异常runtime exception和一般异常checked exception。
但是在逻辑上又科分成检查异常和非检查异常
4.2.1、正常分类
4.2.1.1、运行时异常
Java
程序运行时常常遇到的各种异常的处理,其中包括隐式异常。比如,程序中除数为0引起的错误、数组下标越界错误等,这类异常也称为运行时异常,,因为它们虽然是由程序本身引起的异常,但不是程序主动抛出的,而是在程序运行中产生的。运行时异常我们可以不处理。这样的异常由虚拟机接管。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终
4.2.1.2、一般异常
这些异常也称为显式异常。它们都是在程序中用语句抛出、并且也是用语句进行捕获的,比如,文件没找到引起的异常、类没找到引起的异常等。
JAVA要求程序员对其进行catch。所以,面对这种异常不管我们是否愿意,只能 catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。
4.2.1、逻辑分类:
逻辑分类:checked检查异常和unchecked非检查异常。
checkedException
就是在写代码的时候,IDE(比如Eclipse)会要求你写try catch的那种Exception,比如IOException。这种Exception是Java的设计者要求你的程序去处理的。这种异常一般不会影响程序的主体,容易手动诊断修复,所以Java要求你在catch下面写出处理的代码,以保证程序遇到此类exception之后还可以正常运行
unchecked
这一类就是你在代码处理了checked exception之后,你在运行时候依然会遇到的exception,所以又叫做RunTimeException,比如NullPointerException, IndexOutOfBoundsException。此类exception相较于前面那种更容易影响程序运行,从设计者角度不提倡从程序中catch出来并处理,当然你也可以这么做。
/**
* 将CheckedException转换为UncheckedException
*/
public static RuntimeException toUncheckedException(Exception e) {
if (e instanceof RuntimeException) {
return (RuntimeException) e;
} else {
return new RuntimeException(e);
}
}
5、Logback
5.1、logback.xml
xml version="1.0" encoding="UTF-8"?>
-->
name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg -%logger{50}.%M[%L]%n "/>
name="LOG_PATH" value="/Users/healerjean/Desktop/logs"/>
name="FILE_PATH_INFO" value="${LOG_PATH}/hlj-logback.log"/>
name="FILE_PATH_ERROR" value="${LOG_PATH}/hlj-logback-error.log"/>
name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
class="com.hlj.proj.controller.config.LogbackJsonFilter"/>
charset="UTF-8" >
${LOG_PATTERN}
name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
${FILE_PATH_INFO}
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
${FILE_PATH_INFO}.%d{yyyy-MM-dd}.%i.log
1MB
5GB
10
class="ch.qos.logback.classic.filter.ThresholdFilter">
INFO
${LOG_PATTERN}
name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
${FILE_PATH_ERROR}
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
${FILE_PATH_ERROR}.%d{yyyy-MM-dd}.%i.log
60MB
5GB
10
class="ch.qos.logback.classic.filter.ThresholdFilter">
ERROR
${LOG_PATTERN}
level="info">
ref="STDOUT" />
ref="FILE-ERROR"/>
ref="FILE-INFO"/>
5.2、Logback日志到数据库
5.2.1、创建数据库表
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
)
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(150) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
5.2.3、日志配置
xml version="1.0" encoding="UTF-8"?>
name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
name="DB" class="ch.qos.logback.classic.db.DBAppender">
class="ch.qos.logback.core.db.DriverManagerConnectionSource">
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/healerjean?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
healerjean
healerjean
level="info">
ref="STDOUT" />
ref="DB"/>
5.2.3、查看日志
SELECT * from logging_event;
5.3、LogBack打印Json数据
name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
class="com.hlj.proj.controller.config.LogbackJsonFilter"/>
charset="UTF-8" >
${LOG_PATTERN}
package com.hlj.proj.controller.config;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import com.hlj.proj.controller.utils.JsonUtils;
public class LogbackJsonFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getLoggerName().startsWith("com.hlj")) {
Object[] params = event.getArgumentArray();
for (int index = 0; index < params.length; index++) {
Object param = params[index];
// class.isPrimitive() 8种基本类型的时候为 true,其他为false
if (!param.getClass().isPrimitive()) {
params[index] = JsonUtils.toJsonString(param);
}
}
}
return FilterReply.ACCEPT;
}
}
6、Log4j
6.1、 log4j.properties
## 必填内容,info/all/.., stdout 为必填,后面的根据log4j.appender.内容进行填写,如果下面有内容则,这里必须加上,
# level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。
log4j.rootLogger=info, stdout, log, errorlog,proj
#%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
#%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
#%t: 输出产生该日志事件的线程名
#%C: 输出日志信息所属的类目,通常就是所在类的全名
#%M: 输出代码中指定的消息,产生的日志具体信息
#%F: 输出日志消息产生时所在的文件名称
#%L: 输出代码中的行号
#%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
#%r: 输出自应用启动到输出该log信息耗费的毫秒数
#%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
#%%: 输出一个”%”字符
#%n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行
#%hostName : 本地机器名
#%hostAddress : 本地ip地址-->
#可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
#1) c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
#2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-”号指定左对齐。
#3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
#4) .30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉
# RollingFileAppender按log文件最大长度限度生成新文件
# DailyRollingFileAppender按日期生成新文件,不能根据大小清除历史日志,但是我们可以自定义来实现
# %d{yyyy-MM-dd HH:mm:ss,SSS}日期 %p級別 %t当前线程名称 %m日志信息 [%C.%M]类名加方法 %L行数 %n换行
# 举例 # 2019-07-13 09:05:14,674 [INFO]-[http-nio-8888-exec-1] info日志================== com.hlj.proj.controler.Log4jController.log4j][25]
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
## 根据日期生成配置文件当前log.log 如果时间超过了设置的格式的时间DatePattern 则会在后面加上 log.log.2019-07-12.log
# 解释:也就是说log文件会暂存每天的日志,到第二天时会再加上yyyy-MM,产生当天的完整日志文件
### Log info
log4j.appender.log = org.apache.log4j.DailyRollingFileAppender
log4j.appender.log.File = /Users/healerjean/Desktop/logs/hlj-log4j.log
log4j.appender.log.Append = true
log4j.appender.log.Threshold = INFO
#超过日期则讲历史日志加上后缀日期用于区分
log4j.appender.log.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.log.layout = org.apache.log4j.PatternLayout
log4j.appender.log.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
## 5 按照文件大小进行日志切分 文件历史日志依次 error.log.1 error.log.2
log4j.appender.errorlog=org.apache.log4j.RollingFileAppender
log4j.appender.errorlog.File=/Users/healerjean/Desktop/logs/error.log
log4j.appender.errorlog.Append=true
log4j.appender.errorlog.Threshold=error
#设置日志文件的大小
log4j.appender.errorlog.MaxFileSize=2000KB
#保存200个备份文件
log4j.appender.errorlog.MaxBackupIndex=200
log4j.appender.errorlog.layout=org.apache.log4j.PatternLayout
log4j.appender.errorlog.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L %n
log4j.appender.proj=com.hlj.proj.utils.RoolingAndDateFileAppender
log4j.appender.proj.file=/Users/healerjean/Desktop/logs/logRecoed.log
log4j.appender.proj.Append=true
log4j.appender.proj.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.proj.Threshold=error
#设置日志文件的大小
log4j.appender.proj.MaxFileSize=5KB
#最大保留多少个文件,超过之后会进行重新命名,所以尽量不要超过
log4j.appender.proj.maxIndex=10
#只保留多长时间的
log4j.appender.proj.expirDays=1
log4j.appender.proj.layout=org.apache.log4j.PatternLayout
log4j.appender.proj.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
7、Log4j2
log4j一直存在两个问题,一是打日志影响到系统性能效率,二是有多线程的时候,日志会比较乱
log4j2是log4j 1.x 的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
性能提升, log4j2相较于log4j 1和logback都具有很明显的性能提升,后面会有官方测试的数据。
自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。
无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
7.1、 log4j2.xml
xml version="1.0" encoding="UTF-8"?>
status="error" monitorInterval="30">
name="level">info
name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n
name="logDri">/Users/healerjean/Desktop/logs
name="logFileName">hlj-client
name="infoLogDri">${logDri}/info
name="infoLogGz">${infoLogDri}/gz
name="infoLogFileName">${logFileName}.log
name="errorLogDri">${logDri}/error
name="errorLogGz">${errorLogDri}/gz
name="errorLogFileName">${logFileName}.error
name="Console" target="SYSTEM_OUT">
level="${level}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
pattern="${LOG_PATTERN}"/>
name="infoFile" fileName="${infoLogDri}/${infoLogFileName}"
filePattern="${infoLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${infoLogFileName}.gz">
pattern="${LOG_PATTERN}"/>
size="500 MB"/>
interval="6" modulate="true"/>
level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
level="info" onMatch="ACCEPT" onMismatch="DENY"/>
max="2000"/>
name="errorFile" fileName="${errorLogDri}/${errorLogFileName}"
filePattern="${errorLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${errorLogFileName}.gz">
pattern="${LOG_PATTERN}"/>
size="500 MB"/>
interval="6" modulate="true"/>
level="error" onMatch="ACCEPT" onMismatch="DENY"/>
max="2000"/>
level="${level}" additivity="false" includeLocation="true">
ref="Console"/>
ref="infoFile"/>
ref="errorFile"/>
7.1.1、日志打印位置
main方法和服务器日志都在一起
8、日志唯一标识追踪
1、Controll入参和出参打印
2、唯一标识
8.1、日志格式
8.1.1、log4j
%d{yyyy-MM-dd HH:mm:ss} %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n
8.1.1、logback
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg -%logger{50}.%M[%L]%n
8.2、过滤器 Log4j2ReqUidFilter
package com.healerjean.proj.config.filter;
import org.slf4j.MDC;
import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;
/**
* @author HealerJean
* @ClassName Log4j2Filter
* @date 2020/6/15 20:12.
* @Description
*/
public class Log4j2ReqUidFilter implements Filter {
private static final String REQ_UID = "REQ_UID";
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));
filterChain.doFilter(servletRequest, servletResponse);
MDC.remove(REQ_UID);
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
@Bean
public FilterRegistrationBean log4j2Fiter() {
FilterRegistrationBean fitler = new FilterRegistrationBean();
fitler.setFilter(new Log4j2Filter());
fitler.addUrlPatterns("/*");
fitler.setName("log4j2Fiter");
fitler.setDispatcherTypes(DispatcherType.REQUEST);
return fitler;
}
9、日志Controller出参入参打印
9.1、pom依赖
org.springframework.boot
spring-boot-starter-aop
9.2、自定义注解标识方法名字
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterfaceName {
String value() default "";
}
@InterfaceName("demo控制器--------demo实体")
@ApiOperation(value = "demo实体",
notes = "demo实体",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
response = DemoDTO.class)
@GetMapping(value = "demo/get", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public ResponseBean get(DemoDTO demoDTO) {
String validate = ValidateUtils.validate(demoDTO, ValidateGroup.HealerJean.class);
if (!validate.equals(CommonConstants.COMMON_SUCCESS)) {
throw new BusinessException(ResponseEnum.参数错误, validate);
}
return ResponseBean.buildSuccess(demoEntityService.getMmethod(demoDTO));
}
9.3、AOP拦截
package com.healerjean.proj.config.aspect;
import com.healerjean.proj.annotation.InterfaceName;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
@Order(1)
public class ControllerLogAspect {
@Around("execution(* com.healerjean.proj.controller.*Controller.*(..))")
public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Signature signature = proceedingJoinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = proceedingJoinPoint.getArgs();
String value = "";
Method method = ((MethodSignature) signature).getMethod();
if (method.isAnnotationPresent(InterfaceName.class)) {
value = "请求接口:【" + method.getAnnotation(InterfaceName.class).value() + "】,";
}
long start = System.currentTimeMillis();
try {
log.info("请求开始:{}类名:【{}】,方法名:【{}】, 参数:【{}】", value, className, methodName, args);
Object result = proceedingJoinPoint.proceed();
long timeCost = System.currentTimeMillis() - start;
log.info("请求结束:{}类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", value, className, methodName, args, result, timeCost);
return result;
} catch (Exception e) {
long timeCost = System.currentTimeMillis() - start;
log.info("请求出错:{}类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", value, className, methodName, args, timeCost);
throw e;
}
}
}
10、dubbo日志追踪
10.1、服务提供者
10.1.1、aop切面pom依赖
org.springframework.boot
spring-boot-starter-aop
10.1.2、 ServiceLogAspect
接口出入参日志打印
package com.healerjean.proj.config.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
@Order(1)
public class ServiceLogAspect {
@Around("execution(* com.healerjean.proj.service.*Service.*(..))")
public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Signature signature = proceedingJoinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = proceedingJoinPoint.getArgs();
long start = System.currentTimeMillis();
try {
log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);
Object result = proceedingJoinPoint.proceed();
long timeCost = System.currentTimeMillis() - start;
log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);
return result;
} catch (Exception e) {
long timeCost = System.currentTimeMillis() - start;
log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);
throw e;
}
}
}
10.1.3、 ProviderRpcTraceFilter
dubbo日志追踪过滤器
package com.healerjean.proj.config.dubbo;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import java.util.UUID;
@Activate(group = Constants.PROVIDER, order = 1)
public class ProviderRpcTraceFilter extends FutureFilter {
private static final String DUBBO_REQ_UID = "REQ_UID";
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
String reqUid = RpcContext.getContext().getAttachment(DUBBO_REQ_UID);
if (StringUtils.isBlank(reqUid)) {
//传递丢失
reqUid = "CUSTOM:" + UUID.randomUUID().toString().replace("-", "");
}
MDC.put(DUBBO_REQ_UID, reqUid);
RpcContext.getContext().setAttachment(DUBBO_REQ_UID, reqUid);
try {
return invoker.invoke(invocation);
} finally {
MDC.remove(DUBBO_REQ_UID);
}
}
}
10.1.4、配置dubbo过滤器
com.alibaba.dubbo.rpc.Filter
ProviderRpcTraceFilter=com.healerjean.proj.config.dubbo.ProviderRpcTraceFilter
10.2、服务消费者
10.2.1、aop切面pom依赖
org.springframework.boot
spring-boot-starter-aop
10.2.2、 ControllerLogAspect
controller出入参打印
package com.healerjean.proj.config.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
@Order(1)
public class ControllerLogAspect {
@Around("execution(* com.healerjean.proj.controller.*Controller.*(..))")
public Object handleControllerLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Signature signature = proceedingJoinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = proceedingJoinPoint.getArgs();
long start = System.currentTimeMillis();
try {
log.info("请求开始:类名:【{}】,方法名:【{}】, 参数:【{}】", className, methodName, args);
Object result = proceedingJoinPoint.proceed();
long timeCost = System.currentTimeMillis() - start;
log.info("请求结束:类名:【{}】, 方法名:【{}】, 参数:【{}】, 返回值:{}, 耗时:{}ms。", className, methodName, args, result, timeCost);
return result;
} catch (Exception e) {
long timeCost = System.currentTimeMillis() - start;
log.info("请求出错:类名:【{}】,方法名:【{}】, 参数:【{}】, 耗时:【{}】ms。", className, methodName, args, timeCost);
throw e;
}
}
}
10.2.3、日志追踪过滤器
package com.healerjean.proj.config.filter;
import org.slf4j.MDC;
import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;
/**
* @author HealerJean
* @ClassName Log4j2Filter
* @date 2020/6/15 20:12.
* @Description
*/
public class Log4j2ReqUidFilter implements Filter {
private static final String REQ_UID = "REQ_UID";
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
MDC.put(REQ_UID, UUID.randomUUID().toString().replace("-", ""));
filterChain.doFilter(servletRequest, servletResponse);
MDC.remove(REQ_UID);
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
@Bean
public FilterRegistrationBean log4j2ReqUidFilter() {
FilterRegistrationBean fitler = new FilterRegistrationBean();
fitler.setFilter(new Log4j2ReqUidFilter());
fitler.addUrlPatterns("/*");
fitler.setName("Log4j2ReqUidFilter");
fitler.setDispatcherTypes(DispatcherType.REQUEST);
return fitler;
}
}
10.2.4、 ConsumerRpcTraceFilter
dubbo日志追踪过滤器
package com.healerjean.proj.config.dubbo;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import java.util.UUID;
@Activate(group = Constants.CONSUMER, order = 1)
public class ConsumerRpcTraceFilter extends FutureFilter {
private static final String DUBBO_REQ_UID = "REQ_UID";
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
RpcContext.getContext().setAttachment(DUBBO_REQ_UID, MDC.get(DUBBO_REQ_UID));
return invoker.invoke(invocation);
}
}
10.2.5、配置dubbo过滤器
com.alibaba.dubbo.rpc.Filter
ConsumerRpcTraceFilter=com.healerjean.proj.config.dubbo.ConsumerRpcTraceFilter