此项目已经过时了,请看最新的项目,地址:https://github.com/lisiwei1/spring-boot-starter-log
新项目优化了一些代码,而且写成starter,上手更便捷
此demo项目用AOP统一日志管理,并接入ELK实现日志秒查,可以根据日志任意字段进行查询,其中还可以实现不同节点间的链路追踪
默认日志格式如下,如有需要可以自行修改代码。因为开发者技术水平有限,若有bug或者不合理的地方请见谅!
{
"classMethod": "api",
"hostName": "LAPTOP-572VT37S",
"consumeTime": 57,
"hostIp": "192.168.0.118",
"responseTime": "2023-05-06 13:39:35.840",
"requestParams": "{\"code\":\"22\"}",
"serverName": "admin-server-01",
"requestIp": "127.0.0.1",
"className": "com.logdemo.test.web.TestController",
"httpMethod": "GET",
"url": "http://127.0.0.1:20230/test/api",
"sqls": [],
"requestTime": "2023-05-06 13:39:35.783",
"responseParams": "调用成功!",
"traceToken": "e9ff6423-d14c-470e-be32-3f953f89b2f3",
"currentOrder": 1,
"desc": "测试API接口"
}
控制台输出:
ELK界面(下面会讲如何进行ELK配置和简单使用)
直接启动项目,执行请求127.0.0.1:20230/test/getMethod,然后可以到控制台查看日志输出
若不需要用到ELK则可以去掉logstash-logback-encoder模块,若不用到WEB也可以去掉spring-boot-starter-web,因为此日志系统不仅仅记录web请求日志,还可以记录定时任务的日志,甚至还可以记录任意方法的执行日志,只要对应方法加上@LogOperation注解即可,但要注意的是当前项目有个bug。比如controller标有@GetMapping的方法A调用Service层的B方法,而且B方法有@LogOperation注解,那么只会记录B方法的日志,A方法的日志直接被B方法日志覆盖,应该是因为日志是通过threadlocal实现的,因此同一个线程下执行的方法都会被最后执行的方法被覆盖。因此要注意下此问题。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!--集成logstash-->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.6</version>
</dependency>
启动类请加上@EnableAsync以实现异步打印日志
application.yml上加上此配置,用于记录当前服务的名称,每一个服务应该都不一样,方面后面区分和查询
logback.xml文件若用不上ELK则直接将右边两处的红框的内容删掉即可。其中127.0.0.1:4560是指logstah的地址和端口,根据实际情况进行修改。
日志系统核心代码就是core包下这块,其中LogAspect类就是设置切点,有需要可以自行修改源码修改实现细节
使用ELK记得将logback.xml的那两处注释取消掉
自行安装ELK(elasticsearch、Logstash和kibana),此处使用的是7.15版本,而且项目和ELK都是本机环境下运行的,如果不是请自行修改对应配置的地址和端口。
在logstash的bin目录下新增logstash.conf文件
往文件里面添加下面内容。其中input-tcp下的host和port要跟logbacj.xml中的destination配置一致。
然后修改output-elasticsearch-index的内容,即"weblog-info-test-%{+YYYY.MM.dd}",自定义索引名称请改weblog-info-test这部分,此处是设置日志索引名称的,后面创建索引模式需要用到,到时需要用到 weblog-info-test-*
input {
tcp {
mode => "server"
host => "0.0.0.0"
port => 4560
codec => json_lines
}
}
filter {
json {
# 不加这段会只有message字段,值都到message里面,没有各个字段的索引
source => "message"
remove_field => ["message"]
}
}
output {
elasticsearch {
hosts => "127.0.0.1:9200"
index => "weblog-info-test-%{+YYYY.MM.dd}"
}
}
1.运行elasticsearch
双击执行bin目录下的elasticsearch.bat
启动后可以打开http://localhost:9200查看是否启动成功
2.运行Logstash
切换到bin目录,在地址栏输入cmd回车后,执行logstash -f logstash.conf
3.运行kibana
执行bin目录下的kibana.bat
然后打开http://localhost:5601/,此界面就是ELK的使用界面
ELK是可以设置密码的,有需要请自行设置
启动ELK后,用浏览器打开http://localhost:5601/
点击左侧的stack management
然后就可以在ELK上愉快的查询日志了,可以指定时间,指定日志里面的字段进行查询,而且是秒查。
使用tracetoken进行查询,就可以查到一个请求的完整链路,而且还可以根据currentOrder字段查看调用顺序,1为首次请求,2为第二次,依次递加。
用于跟踪请求在不同节点之间的调用链,直接在ELK上选择tracetoken(需要此功能在进行http调用的地方进行代码配置)
原理:每一个日志都会自动判断请求头有没有带traceToken字段,若没有则生成一个UUID,currentOrder也是如此,没有则设置为1.若此请求还调用其他服务节点,则在调用前header上附上当前tracetoken和currentOrder。
比如在创建HTTP请求时,先在header附上tracetoken和currentOrder。请参考下面代码:
private HttpHeaders createHttpHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(LogVariableKey.TRACE_TOKEN, LogPackageHolder.getCurrentTraceToken());
httpHeaders.add(LogVariableKey.TRACE_TOKEN, LogPackageHolder.getCurrentOrder().toString());
return httpHeaders;
}
此项目并未记录SQL日志,如有需要,请调用下面方法将SQL信息添加到日志
LogPackageHolder.addSQL(sqltext); // sqltext指具体sql信息
实际效果
如果一些请求入参或者出参过大,不想记录,或者指定一些方法都不记录日志,直接添加@LogOperation注解来实现,比如@LogOperation(value = "测试API接口", skipReq= true)这个注解就记录当前注解所在的方法的方法描述为 "测试API接口",对应日志的desc字段,而skipReq= true表示此方法跳过日志入参,skipLog= true则是不记录日志。
/**
* 日志注解
* @Author lsw
* @Date 2023/5/6 13:04
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
String value() default "";//接口名字
boolean skipLog() default false;//不记录整体日志
boolean skipReq() default false;//不记录请求日志
boolean skipRsp() default false;//不记录响应日志
boolean skipSql() default false;//跳过sql
}
直接指定一些类跳过日志记录,在此处代码添加即可。
只记录某些注解下的日志或者不记录某些注解下的,比如指记录Get方法的日志,或者只记录@Scheduled下定时任务的日志
那直接修改代码,找到core.log包下的LogAspect文件进行修改。
比如不记录标有@GetMapping下的方法,那就删除对应切点(下图红框处)
获取到方法说明写入日志的desc字段
举例:我已经用了swagger的注解给web接口写上了,我想获取这些注解里面的注释写入日志里面要怎么做?
那请看core.log包下的LogAspect文件下的getAnnotationText方法,这段代码逻辑是根据方法获取指定注解的内容。
@LogOperation的value值优先级最高。
若要获取swagger注解@ApiOperation("查询在线用户")的信息
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
if (apiOperation != null) {
logPackage.putVariable(LogVariableKey.DESC, apiOperation.value());
}
如下图,比如有个注解@MethodDesc,那新增一个配置类,把指定注解传进去,那么AOP就能获取这个注解的信息
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodDesc {
// 方法说明
String value() ;
String note() default "";
String[] tags() default "";
}
@Configuration
public class LogConfig {
@Bean
public LogConfiguration logConfiguration(){
LogConfiguration configuration = new LogConfiguration<>();
configuration.setAnnotationClass(MethodDesc.class);
return configuration;
}
}
但是不能获取注解的指定信息,只能获取注解的整体信息。因此最好按照上面方式修改代码