基于AOP和ThreadLocal实现日志记录
基于AOP和ThreadLocal实现的一个日志记录的例子
主要功能实现 : 在API每次被请求时,可以在整个方法调用链路中记录一条唯一的API请求日志,可以记录请求中绝大部分关键内容。并且可以自定义实现对日志收集(直接标准输出,或写入到文件或数据库)。
比如传参,响应,请求url,请求方法,clientIp,耗时,请求成功或异常,请求头等等。
实现的核心为AOP以及ThreadLocal。
- AOP 会切所有被
@Log4a注解的方法,会记录一个线程中唯一一个Log4对象,读取AOP中的方法信息(入参,方法等等) - 抓取请求的内容和HttpServletRequest中的内容,解析入参。
- 日志收集(自定义实现,建议该过程异步)
- 记录无论目标方法成功或失败,在执行完成后都将对ThreadLocal中的资源进行释放。
Log4 记录的内容
| 字段 | 类型 | 注释 | 是否默认记录 |
|---|---|---|---|
| clientIp | String | 请求客户端的Ip | 是 |
| reqUrl | String | 请求地址 | 是 |
| headers | Object | 请求头部信息(可选择记录) | 是,默认记录user-agent,content-type |
| type | String | 操作类型 | 是,默认值undefined |
| content | StringBuilder | 步骤内容信息 | 否,方法内容,可使用Log4.step进行内容步骤记录 |
Log4a 注解选项说明
| 字段 | 类型 | 注释 | 默认 |
|---|---|---|---|
| type | String | 操作类型 | 默认值"undefined" |
| method | boolean | 是否记录请求的本地java方法 | true |
| costTime | boolean | 是否记录整个方法耗时 | true |
| headers | String[] | 记录的header信息 | 默认"User-Agent","content-type" |
| args | boolean | 是否记录请求参数 | true |
| respBody | boolean | 是否记录响应参数 | true |
| stackTrace | boolean | 当目标方法发生异常时,是否追加异常堆栈信息到content | false |
| costTime | boolean | 是否记录整个方法耗时 | true |
| collector | Class<? extends LogCollector> | 指定日志收集器 | 默认空的收集器不指定 |
例子使用说明
@Log4a注解使用
直接在Controller 方法或类上加上注解@Log4a,可以对该Controller中所有方法进行日志记录与收集
例如 :
@Log4a(type = "测试API", stackTrace = true)
@RestController
public class DemoController {
@Resource
private DemoService demoService;
/**
* JSON数据测试
*/
@PostMapping("/sayHello")
public ResponseEntity<?> sayHello(@RequestBody Map<String, Object> request) {
demoService.sayHello(request);
return ResponseEntity.ok(request);
}
/**
* RequestParam 参数测试
*/
@PostMapping("/params")
public ResponseEntity<?> params(@RequestParam Integer a) {
return ResponseEntity.ok(a);
}
/**
* 无参测试
*/
@GetMapping("/noArgs")
public ResponseEntity<?> noArgs() {
return ResponseEntity.ok().build();
}
/**
* XML 格式数据测试
*/
@PostMapping(value = "/callXml", consumes = {MediaType.APPLICATION_XML_VALUE})
public XmlDataDTO callXml(@RequestBody XmlDataDTO dataDTO) {
return dataDTO;
}
/**
* 特殊对象测试
*/
@GetMapping("/callHttpServletRequest")
public ResponseEntity<?> callHttpServletRequest(HttpServletRequest request) {
return ResponseEntity.ok().build();
}
}
Log4.step 记录详细步骤内容
这里调用了service方法,Log4.step 方法记录每一个步骤详细内容
/**
* @author EalenXie Created on 2020/1/16 10:49.
*/
@Service
@Slf4j
public class DemoService {
/**
* 测试方法, 使用Log4.step记录步骤
*/
public void sayHello(Map<String, Object> words) {
Log4.step("1. 请求来了,执行业务动作");
log.info("do somethings");
Log4.step("2. 业务动作执行完成");
}
}
自定义的全局日志收集器
本例中写了一个最简单的直接append写入到文件中,你可以选择自定义的方式进行日志收集(例如写入到数据库或者日志文件,或日志收集框架中,这个过程建议异步处理,可在collect方法上面加入注解@Async)
@Component
public class DemoLogCollector implements LogCollector {
@Override
public void collect(Log4 log4) throws LogCollectException {
try {
File file = new File("D:\\home\\temp\\日志.txt");
if (!file.getParentFile().exists()) {
FileUtils.forceMkdir(file.getParentFile());
}
try (FileWriter fw = new FileWriter(file, true)) {
fw.append(log4.toString());
}
} catch (IOException e) {
throw new LogCollectException(e);
}
}
}
测试后 , 可以从 D:\home\temp\日志.txt中获取到记录的日志内容。
json格式的数据记录(参数JSON):
{
"args": {
"id": 999,
"value": "content"
},
"clientIp": "192.168.1.54",
"content": "1. 请求来了,执行业务动作\n2. 业务动作执行完成\n",
"costTime": 2,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/json"
},
"logDate": 1593341797293,
"method": "name.ealen.demo.controller.DemoController#sayHello",
"reqUrl": "http://localhost:9527/sayHello",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": {
"id": 999,
"value": "content"
},
"statusCode": "OK"
},
"success": true,
"type": "测试API"
}
XML格式的数据(参数XML):
{
"args": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><xml><message>1111 </message><username>zhangsan</username></xml>",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 4,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/xml"
},
"logDate": 1593394523000,
"method": "name.ealen.demo.controller.DemoController#callXml",
"reqUrl": "http://localhost:9527/callXml",
"respBody": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><xml><message>1111 </message><username>zhangsan</username></xml>",
"success": true,
"type": "测试API"
}
form参数格式的数据(以参数键值对形式):
{
"args": "z=11&a=1",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 1,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)",
"Content-Type": "application/x-www-form-urlencoded"
},
"logDate": 1593342114342,
"method": "name.ealen.demo.controller.DemoController#params",
"reqUrl": "http://localhost:9527/params",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": 1,
"statusCode": "OK"
},
"success": true,
"type": "测试API"
}
特殊参数格式(目前暂为键值对形式,参数默认取对象的toString()方法):
{
"args": "request=org.apache.catalina.connector.RequestFacade@754f30c3",
"clientIp": "192.168.1.54",
"content": "",
"costTime": 1,
"headers": {
"User-Agent": "Apache-HttpClient/4.5.10 (Java/11.0.5)"
},
"logDate": 1593342220880,
"method": "name.ealen.demo.controller.DemoController#callHttpServletRequest",
"reqUrl": "http://localhost:9527/callHttpServletRequest",
"respBody": {
"headers": {},
"statusCodeValue": 200,
"body": null,
"statusCode": "OK"
},
"success": true,
"type": "测试API"
}
Github项目地址 :https://github.com/EalenXie/Log4a
目前暂时项目命名为Log4a(Log for API), 有时间会一直维护和优化。
基于AOP和ThreadLocal实现日志记录的更多相关文章
- 基于AOP和ThreadLocal实现的一个简单Http API日志记录模块
Log4a 基于AOP和ThreadLocal实现的一个简单Http API日志记录模块 github地址 : https://github.com/EalenXie/log4a 在API每次被请求时 ...
- 来一手 AOP 注解方式进行日志记录
系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会. 通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题. 系统日志同样也是数据统计/建模的重要依据,通过分 ...
- 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil
封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil,代码比较简单,主要是把MongoTarget的配置.FileTarget的配置集成到类中,同时利用缓存依赖来判断是否需要重新创 ...
- 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil,nloglogutil
封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil,代码比较简单,主要是把MongoTarget的配置.FileTarget的配置集成到类中,同时利用缓存依赖来判断是否需要重新创 ...
- Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!
Go/Python/Erlang编程语言对比分析及示例 本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...
- 利用AOP与ToStringBuilder简化日志记录
刚学spring的时候书上就强调spring的核心就是ioc和aop blablabla...... IOC到处都能看到...AOP么刚开始接触的时候使用在声明式事务上面..当时书上还提到一个用到ao ...
- springboot整合aop实现网站访问日志记录
目的: 统一日志输出格式,统计访问网站的ip. 思路: 1.针对不同的调用场景定义不同的注解,目前想的是接口层和服务层. 2.我设想的接口层和服务层的区别在于: (1)接口层可以打印客户端IP,而服务 ...
- spring aop通过注解实现日志记录
首先是几个概念:连接点(Joinpoint).切点(Pointcut).增强(Advice).切面(Aspect) 另外也要使用到注解. 需求:通过注解定义LogEnable.然后程序运行能够识别定义 ...
- 使用AOP+Annotation实现操作日志记录
先创建注解 OperInfo @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @ ...
随机推荐
- 【Linux】文件权限,ssh免密登录
1.文件/文件夹权限 例子: -rw-r--r--. 1 root root 12288 Aug 21 09:50 aliases.db drwxr-xr-x. 2 root root 4096 Au ...
- Redis 入门到分布式 (四) 瑞士军刀Redis其他功能
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 目录: 慢查询 Pipeline 发布订阅 Bitmap(位图) HyperLogLog GEO 一.慢 ...
- 蓝桥杯 算法训练 P0505(Java解法)
一个整数n的阶乘可以写成n!,它表示从1到n这n个整数的乘积.阶乘的增长速度非常快,例如,13!就已经比较大了,已经无法存放在一个整型变量中:而35!就更大了,它已经无法存放在一个浮点型变量中.因此, ...
- Java实现 洛谷 P1426 小鱼会有危险吗
import java.util.LinkedList; import java.util.Scanner; public class Main { private static Scanner ci ...
- java实现数字的值返回
以下的静态方法实现了:把串 s 中第一个出现的数字的值返回. 如果找不到数字,返回-1 例如: s = "abc24us43" 则返回 2 s = "82445adb5& ...
- awardRotate转盘插件文字模糊问题和图片加载问题
前言 最近在做一个转盘抽奖页面,使用了awardRotate.js发现字体和图片都有模糊,绘制的时候图片绘制不全,搜索一下之后发现针对awardRotate的解决方法比较少,针对canvas的比较多, ...
- Mysql添加索引及索引的优缺点
一.什么是索引? 索引是对数据库表中的一列或多列值进行排序的一种结构,使用索引可以快速访问数据库表中的特定信息. 二.索引的作用? 索引相当于图书上的目录,可以根据目录上的页码快速找到所需的内容,提高 ...
- 使用Volo.Abp.MailKit发送邮件
Volo.Abp.MailKit封装继承MailKit库,为Abp邮件发送提供了快捷实现. 邮箱配置 qq邮箱支持smtp功能,需要去申请开通.参考qq邮箱设置,最重要的是smtp发送邮件,qq邮箱对 ...
- Windows下C,C++开发环境搭建指南
Windows下C,C++开发环境搭建指南 前情提要 基于近一段时间很多网友发邮件反馈,说一些项目编译出现问题,诸如此类的情况. 就觉得很有必要写一篇C,C++开发环境的小指南,统一回复. 1.君欲善 ...
- 从零开始的Spring Boot(5、Spring Boot整合Thymeleaf)
Spring Boot整合Thymeleaf 写在前面 从零开始的Spring Boot(4.Spring Boot整合JSP和Freemarker) https://www.cnblogs.com/ ...