使用说明
com.xxx包下 加了@RestController注解的controller
打印的日志规格如下: 包含:ip地址、url、全限定类名+方法名、请求时间、请求参数(支持多个)、响应时间、响应参数、响应时间(毫秒)、关键字、序列号(用于和响应打印匹配)
# 请求打印
2020-12-22 16:15:08.473 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_request: {
"ipaddr":"127.0.0.1:9600",
"url":"/testcfg4",
"method":"com.xxx.biz.api.DictController.testcfg4",
"requestTime":"2020-12-22 16:15:08",
"request":"[{\"a\":\"aa\",\"b\":\"bb\",\"c\":\"cc\"}]",
"keyword":"zxp",
"sn":"1608624908470_58"
}
# 响应打印
2020-12-22 16:15:08.474 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: {
"ipaddr":"127.0.0.1:9600",
"url":"/testcfg4",
"method":"com.xxx.biz.api.DictController.testcfg4",
"responseTime":"2020-12-22 16:15:08",
"response":"{\"code\":200,\"msg\":\"\"}",
"rt":4,
"keyword":"zxp",
"sn":"1608624908470_58"
}
打印个性配置@PrintControllerLog
不打印请求报文
@RequestMapping(value = "/testdown", method = RequestMethod.GET)
@PrintControllerLog(notPrintRequest = true)
public Result downloadFile(HttpServletResponse response) {
不打印响应报文
@RequestMapping(value = "/testdown", method = RequestMethod.GET)
@PrintControllerLog(notPrintResponse = true)
public Result downloadFile(HttpServletResponse response) {
都不打印
@PrintControllerLog(notPrintResponse = true,notPrintRequest = true)
配置keyword
@RequestMapping(value = "/testdown", method = RequestMethod.GET)
@PrintControllerLog(keyword = "zxp")
public Result downloadFile(HttpServletResponse response) {
输出
2020-12-22 16:15:08.474 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: {
"ipaddr":"127.0.0.1:9600",
"url":"/testcfg4",
"method":"com.xxxx.biz.api.DictController.testcfg4",
"responseTime":"2020-12-22 16:15:08",
"response":"{\"code\":200,\"msg\":\"\"}",
"rt":4,
"keyword":"zxp",
"sn":"1608624908470_58"
}
配置pretty
可以配置输出是否格式化json,默认格式化
@PrintControllerLog(pretty = false)
2020-12-22 17:02:07.922 INFO 13516 --- [nio-9600-exec-1] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: {
"ipaddr":"127.0.0.1:9600",
"url":"/dict/batchcode",
"method":"com.xxx.biz.api.DictController.getBatchCode",
"authorization":"Bearer 02c28b9e-e554-453d-836d-0968f9c48e3c",
"responseTime":"2020-12-22 17:02:07",
"rt":287,
"keyword":"",
"sn":"1608627727565_70",
"response":{
"code":200,
"data":{
"opLogLevel":{
"1":"提示",
"2":"警告",
"3":"严重",
"4":"致命"
}
},
"msg":""
}
}
关键实现思路
- 切面切RestController,且可以限定包名
- 通过ThreadLocal实现rt计算以及sn,并在完成计算后remove ThreadLocal
- 可以根据PrintControllerLog做一些更灵活的配置
注解PrintControllerLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrintControllerLog {
//是否美化json输出
public boolean pretty() default true;
//是否打印请求
public boolean notPrintRequest() default false;
//是否打印返回
public boolean notPrintResponse() default false;
//斌哥提的需求,设置keyword方便统一查找
public String keyword() default "";
}
切面类DefaltControllerPrintInputOutputAcpect实现
@Aspect
@Component
@Slf4j
public class DefaltControllerPrintInputOutputAcpect {
private ThreadLocal<PrintRunnerInfo> SN_CONTEXT = new ThreadLocal<>();
/**
* XXX包下的切面
*/
@Pointcut("within(com.XXX..*)")
public void anController0() {
}
/**
* 加了RestController注解的切面
*/
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void anController99() {
}
@Before("anController99() && anController0()")
public void before(JoinPoint joinPoint) {
try{
printBaseAndRequest(joinPoint);
}catch (Exception e){ }
}
@AfterReturning(returning = "ret",pointcut="anController99() && anController0()")
public void after(JoinPoint joinPoint,Object ret){
try{
printResponse(ret,joinPoint);
}catch (Exception e){ }
}
/**
* 执行前打印
* @param joinPoint
*/
public void printBaseAndRequest(JoinPoint joinPoint) {
PrintReqInfo printReqInfo = new PrintReqInfo();
//设置开始时间
printReqInfo.setRequestTime(genNow());
//获取一个sn,并对TL中的执行情况对象做相应设置
printReqInfo.setSn(getAndSetupSn());
// 设定方法路径
printReqInfo.setMethod(getMethod(joinPoint));
// 取配置
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
PrintControllerLog printControllerLog = methodSignature.getMethod().getAnnotation(PrintControllerLog.class);
PrintCfgInfo printCfgInfo = getCfg(printControllerLog);
printReqInfo.setKeyword(printCfgInfo.getKeyword());
//设置url
printReqInfo.setUrl(getUrl());
//设置Authorization
printReqInfo.setAuthorization(getAuthorization());
//设置IpAddr
printReqInfo.setIpaddr(getIpAddr());
//设置请求参数
fillPrintReqInfo(joinPoint,printReqInfo,printCfgInfo.notPrintRequest);
//打印请求参数
if (!printCfgInfo.isNotPrintRequest()) {
if(printCfgInfo.isPretty()){
log.info("xxxx_cloud_aop_request: {}", JSON.toJSONString(printReqInfo,true));
}else{
log.info("xxxx_cloud_aop_request: {}", JSON.toJSONString(printReqInfo));
}
}
}
/**
* 获取配置
* @param printControllerLog
* @return
*/
private PrintCfgInfo getCfg(PrintControllerLog printControllerLog){
PrintCfgInfo printCfgInfo = new PrintCfgInfo();
if (printControllerLog != null) {
printCfgInfo.setKeyword(printControllerLog.keyword());
printCfgInfo.setNotPrintRequest(printControllerLog.notPrintRequest());
printCfgInfo.setNotPrintResponse(printControllerLog.notPrintResponse());
printCfgInfo.setPretty(printControllerLog.pretty());
}else{
printCfgInfo.setKeyword("");
printCfgInfo.setNotPrintRequest(false);
printCfgInfo.setNotPrintResponse(false);
printCfgInfo.setPretty(true);
}
return printCfgInfo;
}
/**
* 执行后打印
* @param joinPoint
*/
public void printResponse(Object ret,JoinPoint joinPoint) {
PrintResInfo printResInfo = new PrintResInfo();
//设置开始时间
printResInfo.setResponseTime(genNow());
//获取一个sn,并对TL中的执行情况对象做相应设置
printResInfo.setSn(getAndSetupSn());
//设置rt
printResInfo.setRt(getRt());
//清理TL
cleanTL();
// 设定方法路径
printResInfo.setMethod(getMethod(joinPoint));
// 取配置
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
PrintControllerLog printControllerLog = methodSignature.getMethod().getAnnotation(PrintControllerLog.class);
PrintCfgInfo printCfgInfo = getCfg(printControllerLog);
printResInfo.setKeyword(printCfgInfo.getKeyword());
//设置url
printResInfo.setUrl(getUrl());
//设置Authorization
printResInfo.setAuthorization(getAuthorization());
//设置IpAddr
printResInfo.setIpaddr(getIpAddr());
//设置返回参数
fillPrintResInfo(ret,printResInfo,printCfgInfo.notPrintResponse);
//打印返回结果
if (!printCfgInfo.isNotPrintResponse()) {
if(printCfgInfo.isPretty()) {
log.info("xxxx_cloud_aop_response: {}", JSON.toJSONString(printResInfo, true));
}else{
log.info("xxxx_cloud_aop_response: {}", JSON.toJSONString(printResInfo));
}
}
}
/**
* 填充剩余信息
* @param joinPoint
* @param printReqInfo
* @param notPrintReq
*/
private void fillPrintReqInfo(JoinPoint joinPoint,PrintReqInfo printReqInfo,boolean notPrintReq){
Object[] args = joinPoint.getArgs();
if(args != null && args.length > 0 ) {
List<Object> objects = Arrays.asList(args).stream().filter(s -> !isFile(s)).collect(Collectors.toList());
if (objects != null && objects.size() > 0 && !notPrintReq) {
try {
printReqInfo.setRequest(args);
}catch(Exception e){}
}
}
}
/**
* 填充剩余信息
* @param ret
* @param printResInfo
* @param notPrintRes
*/
private void fillPrintResInfo(Object ret,PrintResInfo printResInfo,boolean notPrintRes){
if (ret != null && !notPrintRes) {
try {
printResInfo.setResponse(ret);
}catch(Exception e){}
}
}
private boolean isFile(Object obj){
if(obj instanceof MultipartFile){
return true;
}
return false;
}
/**
* 获取一个sn,并对TL中的执行情况对象做相应设置
* 当第二次执行TL中已经有相应信息
* 此sn不能保证唯一,为了对应打印日志的请求和响应
* @return
*/
private String getAndSetupSn(){
if(SN_CONTEXT.get() != null && !StringUtils.isEmpty(SN_CONTEXT.get().getSn())){
SN_CONTEXT.get().setEnd(System.currentTimeMillis());
SN_CONTEXT.get().setRt(SN_CONTEXT.get().getEnd()-SN_CONTEXT.get().getStart());
return SN_CONTEXT.get().getSn();
}else{
String sn = System.currentTimeMillis()+"_"+new Random().nextInt(100);
SN_CONTEXT.set(PrintRunnerInfo.builder().sn(sn).start(System.currentTimeMillis()).build());
return sn;
}
}
/**
* 获取rt
* @return
*/
private Long getRt(){
if(SN_CONTEXT.get() != null){
return SN_CONTEXT.get().getRt();
}else{
return 0L;
}
}
/**
* 清楚TL
*/
private void cleanTL(){
SN_CONTEXT.remove();
}
private String genNow(){
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
}
/**
* 获取当前请求的url
* @return
*/
private String getUrl(){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String requestURL = request.getRequestURI();
return requestURL;
}
/**
* 获取当前请求的Authorization
* @return
*/
private String getAuthorization(){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
return request.getHeader("Authorization");
}
/**
* 获取IpAddr
* @return
*/
private String getIpAddr(){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 取得服务器IP
String ip = request.getLocalAddr();
// 取得服务器端口
int port = request.getLocalPort();
return ip+":"+port;
}
/**
* 获得方法名称
* @param joinPoint
* @return
*/
private String getMethod(JoinPoint joinPoint){
String method = "";
try{
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodPackage = methodSignature.getDeclaringTypeName();
method = methodPackage;
if(methodSignature.getMethod() != null){
method+="."+methodSignature.getMethod().getName();
}
return method;
}catch (Exception e){
return method;
}
}
/**
* 配置对象 from PrintControllerLog
*/
@Data
private static class PrintCfgInfo{
boolean pretty = true;
//不打印基础信息
boolean notPrintRequest = false;
//不打印基础信息
boolean notPrintResponse = false;
//日志关键字
String keyword = "";
}
/**
* 运行数据
*/
@Data
@Builder
private static class PrintRunnerInfo{
//此sn不能保证唯一,为了对应打印日志的请求和响应
private String sn;
private Long start;
private Long end;
private Long rt;
}
/**
* 请求打印
*/
@Data
private static class PrintReqInfo{
//ipaddr
@JSONField(ordinal = 1)
String ipaddr = "";
//此sn不能保证唯一,为了对应打印日志的请求和响应
@JSONField(ordinal = 8)
String sn = "";
//url
@JSONField(ordinal = 2)
String url = "";
//日志关键字
@JSONField(ordinal = 7)
String keyword = "";
//方法名(含全限定类名)
@JSONField(ordinal = 3)
String method = "";
//请求参数
@JSONField(ordinal = 10)
Object[] request;
//请求时间
@JSONField(ordinal = 5)
String requestTime = "";
//Authorization
@JSONField(ordinal = 4)
String authorization = "";
}
/**
* 响应打印
*/
@Data
private static class PrintResInfo{
//ipaddr
@JSONField(ordinal = 1)
String ipaddr = "";
//此sn不能保证唯一,为了对应打印日志的请求和响应
@JSONField(ordinal = 9)
String sn = "";
//url
@JSONField(ordinal = 2)
String url = "";
//日志关键字
@JSONField(ordinal = 8)
String keyword = "";
//方法名(含全限定类名)
@JSONField(ordinal = 3)
String method = "";
//返回参数
@JSONField(ordinal = 10)
Object response = "";
//响应时间
@JSONField(ordinal = 5)
String responseTime = "";
//RT ms
@JSONField(ordinal = 7)
Long rt = 0L;
//Authorization
@JSONField(ordinal = 4)
String authorization = "";
}
}