基于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) @ ...
随机推荐
- ES6背记手册
ES6规范 阮一峰的ES6在线教程 在线图书--Exploring ES6 ES6 tutorials babel在线教程--https://babeljs.io/docs/en/learn.html ...
- linux 最基本的命令
1.说一些你比较常用linux指令 1.1.ls/ll.cd.mkdir.rm-rf.cp.mv.ps -ef | grep xxx.kill.free-m.tar -xvf file.tar.(说那 ...
- Java实现 LeetCode 496 下一个更大元素 I
496. 下一个更大元素 I 给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集.找到 nums1 中每个元素在 nums2 中的下一个比其大的值. nu ...
- Java实现 蓝桥杯VIP 算法提高 色盲的民主
算法提高 色盲的民主 时间限制:1.0s 内存限制:256.0MB 色盲的民主 问题描述 n个色盲聚在一起,讨论一块布的颜色.尽管都是色盲,却盲得各不相同.每个人都有自己的主张,争论不休.最终,他 ...
- Java实现 LeetCode 66 加一
66. 加一 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一. 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字. 你可以假设除了整数 0 之外,这个整数不会以零开头. 示 ...
- Java实现简易计算器
import java.util.Scanner; public class Demo_1 { public static void main(String[] args) { //输入的两个数字进行 ...
- java实现指数问题
3^n mod 19 求n次幂,对19取模 ================ (3 * 3) * (3 * 3) * 3 public class A { // 分治 public static in ...
- 支持MMDVM的DMR手台
只要是数字机,都支持,但是,有便宜又好用的吗?当然有,店主做那么久肯定知道哪些机好用 1.MD760(UV双段) 刷固件支持#切换不同的TG,可以手动改机子的DMR ID,可以下载联系人,可以升级!关 ...
- iOS -iOS9中提示框(UIAlertController)的常见使用
iOS 8 之前提示框主要使用 UIAlertView和UIActionSheet:iOS 9 将UIAlertView和UIActionSheet合二为一为:UIAlertController . ...
- 2.vue-常用指令
1.v-html:刷新的时候是整个DOM元素都会跟着一起进行刷新 v-text:直接刷新DOM种的text文本内容2.如果想在vue绑定html中的属性使用的是v-bind进行绑定的 v-bind:h ...