基于AOP和ThreadLocal实现日志记录
基于AOP和ThreadLocal实现的一个日志记录的例子
主要功能实现 : 在API每次被请求时,可以在整个方法调用链路中记录一条唯一的API请求日志,可以记录请求中绝大部分关键内容。并且可以自定义实现对日志收集(直接标准输出,或写入到文件或数据库)。
比如传参,响应,请求url,请求方法,clientIp,耗时,请求成功或异常,请求头等等。
实现的核心为AOP以及ThreadLocal。
- AOP 会切所有被
@AopLog
注解的方法,会记录一个线程中唯一一个LogData对象,读取AOP中的方法信息(入参,方法等等) - 抓取请求的内容和HttpServletRequest中的内容,解析入参。
- 日志收集(自定义实现,建议该过程异步)
- 记录无论目标方法成功或失败,在执行完成后都将对ThreadLocal中的资源进行释放。
LogData记录的内容
字段 |
类型 |
注释 |
是否默认记录 |
---|---|---|---|
clientIp |
String |
请求客户端的Ip |
是 |
reqUrl |
String |
请求地址 |
是 |
headers |
Object |
请求头部信息(可选择记录) |
是,默认记录user-agent,content-type |
type |
String |
操作类型 |
是,默认值undefined |
content |
StringBuilder |
步骤内容信息 |
否,方法内容,可使用LogData.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> |
指定日志收集器 |
默认空的收集器不指定 |
例子使用说明
@AopLog注解使用
直接在Controller 方法或类上加上注解@AopLog
,可以对该Controller中所有方法进行日志记录与收集
例如 :
@AopLog(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();
}
}
LogData.step 记录详细步骤内容
这里调用了service方法,LogData.step 方法记录每一个步骤详细内容
/**
* @author EalenXie Created on 2020/1/16 10:49.
*/
@Service
@Slf4j
public class DemoService {
/**
* 测试方法, 使用LogData.step记录步骤
*/
public void sayHello(Map<String, Object> words) {
LogData.step("1. 请求来了,执行业务动作");
log.info("do somethings");
LogData.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:hometemp日志.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/aop-log
项目命名为aop-log, 有时间会一直维护和优化。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一个简单的Android轨迹动画
- Android自定义圆环倒计时控件
- Android 使用URLConnection下载音频文件的方法
- Android自定义TimeButton实现倒计时按钮
- android自定义圆形倒计时显示控件
- android实现上下左右滑动界面布局
- Android使用MediaCodec将摄像头采集的视频编码为h264
- Android开发人脸识别登录功能
- Android利用碎片fragment实现底部标题栏(Github模板开源)
- Android MediaPlayer 播放音频的方式
- Android切圆角的几种常见方式总结
- Android DSelectorBryant 单选滚动选择器的实例代码
- Android 拍照选择图片并上传功能的实现思路(包含权限动态获取)
- Android Canvas的drawText()与文字居中方案详解
- JeecgCloud版,部署项目。