热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

统一接口日志处理(日志入库)

统一接口日志处理(日志入库)1.环境搭建日志数据库--接口日志信息表CREATETABLE`log_note`(`id`bigint(10)NOTNULLAUTO_INCREMEN




统一接口日志处理(日志入库)


1.环境搭建

日志数据库

-- 接口日志信息表
CREATE TABLE `log_note` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT ''主键'',
`interface_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT ''接口名称'',
`interface_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''请求地址'',
`interface_request` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''请求内容'',
`interface_request_time` bigint(20) DEFAULT NULL COMMENT ''请求时间'',
`interface_reponse_time` bigint(20) DEFAULT NULL COMMENT ''响应时间'',
`interface_length_time` bigint(20) DEFAULT NULL COMMENT ''消耗时长(ms)'',
`interface_reponse` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''响应内容'',
`interface_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''服务执行地址'',
`interface_error` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''服务异常信息'',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

日志实体类

package com.chif.goingplus.aop.log.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 日志信息实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LogNote {
private Long id;
private String InterfaceName;
private String InterfaceUrl;
private String InterfaceRequest;
private Long InterfaceRequestTime;
private Long InterfaceReponseTime;
private Long InterfaceLengthTime;
private String InterfaceReponse;
private String InterfaceIp;
private String InterfaceError;
}

日志记录业务类

package com.chif.goingplus.aop.log.service;
import com.alibaba.fastjson.JSON;
import com.chif.goingplus.aop.log.IPUtil;
import com.chif.goingplus.aop.log.mapper.LogNoteMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author liudachu
* @description: 日志记录业务类
* @date 2021/1/18
*/
@Service
@Slf4j
public class LogNoteService {
@Resource
public LogNoteMapper logNoteMapper;
/**
* 日志插入(后期可做异步处理)
*
* @param joinPoint
* @param data
* @param logNote
*/
public void logInsert(JoinPoint joinPoint, Object data, com.chif.goingplus.aop.log.pojo.LogNote logNote) {
logNote.setInterfaceUrl(getUrl(joinPoint));
logNote.setInterfaceRequest(getRequestContent(joinPoint.getArgs()));
logNote.setInterfaceReponseTime(System.currentTimeMillis());
logNote.setInterfaceReponse(data == null ? "" : JSON.toJSONString(data));
logNote.setInterfaceLengthTime(logNote.getInterfaceReponseTime() - logNote.getInterfaceRequestTime());
logNote.setInterfaceIp(IPUtil.getLocalIP());
logNoteMapper.insertLogNote(logNote);
}
/**
* 获取访问地址
*
* @param joinPoint
* @return
*/
private String getUrl(JoinPoint joinPoint) {
StringBuffer url = new StringBuffer();
Method methodSignature = ((MethodSignature) joinPoint.getSignature()).getMethod();
Object target = joinPoint.getTarget();
methodSignature.getParameters();
RequestMapping requestMapping = target.getClass().getAnnotation(RequestMapping.class);
if (requestMapping != null && requestMapping.value().length > 0) {
url.append(requestMapping.value()[0]);
}
PostMapping postMapping = methodSignature.getAnnotation(PostMapping.class);
if (postMapping != null && postMapping.value().length > 0) {
url.append(postMapping.value()[0]);
}
GetMapping getMapping = methodSignature.getAnnotation(GetMapping.class);
if (getMapping != null && getMapping.value().length > 0) {
url.append(getMapping.value()[0]);
}
RequestMapping request = methodSignature.getAnnotation(RequestMapping.class);
if (request != null && request.value().length > 0) {
url.append(request.value()[0]);
}
DeleteMapping deleteMapping = methodSignature.getAnnotation(DeleteMapping.class);
if (deleteMapping != null && deleteMapping.value().length > 0) {
url.append(deleteMapping.value()[0]);
}
PutMapping putMapping = methodSignature.getAnnotation(PutMapping.class);
if (putMapping != null && putMapping.value().length > 0) {
url.append(putMapping.value()[0]);
}
return url.toString();
}
/**
* 获取请求内容
*
* @param objects
* @return
*/
private String getRequestContent(Object[] objects) {
StringBuffer stringBuffer = new StringBuffer();
AtomicInteger i = new AtomicInteger(1);
Arrays.stream(objects).forEach(item -> {
if (!Objects.isNull(item) && objects.length > 1) {
stringBuffer.append("参数" + (i.getAndIncrement()) + ":");
}
stringBuffer.append(JSON.toJSONString(item));
});
return stringBuffer.toString();
}
}

开启AOP配置

package com.chif.goingplus.aop;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
// 开启AOP
@EnableAspectJAutoProxy
public class AOPConfig {
}

2. 自定义@Log注解

package com.chif.goingplus.aop.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义日志注解(用于方法上)
* @author liudachu
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface LogNote {
String value() default "";
}

3.定义切面

package com.chif.goingplus.aop.log;
import com.chif.goingplus.aop.log.service.LogNoteService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 统一接口日志处理(日志入库)
* @author liudachu
*/
@Aspect
@Component
public class LogAspect {
/**
* 日志集合,用于存储不同线程下的LogNote(日志记录)
*/
private static Map logNoteMap = new ConcurrentHashMap<>();
@Resource
LogNoteService logNoteService;
/**
* 定义切点,切所有的Controller中的接口方法
*/
@Pointcut("execution(public * com.chif.goingplus.controller.*.*(..))")
public void LogAspect() {
}
/**
* 在目标方法完全执行后(return后)再执行
* @param joinPoint
* @param data
*/
@AfterReturning(value = "LogAspect()", returning = "data")
public void doAfterReturning(JoinPoint joinPoint, Object data) {
logNote(joinPoint, data);
}
/**
* 包裹目标方法
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("LogAspect()")
public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
Method methodSignature = ((MethodSignature) joinPoint.getSignature()).getMethod();
LogNote loggerInfo = methodSignature.getAnnotation(LogNote.class);
if (loggerInfo != null) {
//记录接口执行信息
Thread.currentThread().setName(UUID.randomUUID().toString());
com.chif.goingplus.aop.log.pojo.LogNote logNote = new com.chif.goingplus.aop.log.pojo.LogNote();
logNote.setInterfaceRequestTime(System.currentTimeMillis());
logNote.setInterfaceName(loggerInfo.value());
logNoteMap.put(Thread.currentThread().getName(), logNote);
}
Object obj;
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
//持久化异常日志
logNote(joinPoint, e);
e.printStackTrace();
throw e;
}
return obj;
}
/**
* 日志记录(持久化)
*
* @param joinPoint
* @param data
* @param e
*/
private void logNote(JoinPoint joinPoint, Object data, Throwable e) {
if (!logNoteMap.containsKey(Thread.currentThread().getName())) {
//当前线程无日志记录,结束执行该持久化操作
return;
}
//持久化日志到数据库,内存(map集合)中剔除该日志
com.chif.goingplus.aop.log.pojo.LogNote logNote = logNoteMap.get(Thread.currentThread().getName());
logNote.setInterfaceError(e == null ? "" : getErrorMsg(e));
logNoteMap.remove(Thread.currentThread().getName());
logNoteService.logInsert(joinPoint, data, logNote);
}
/**
* 获取异常信息打印日志
* 格式化
* @param e
* @return
*/
private String getErrorMsg(Throwable e) {
if (e == null) {
return "";
}
StackTraceElement[] trace = e.getStackTrace();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(e);
for (int i = 0; i <8; i++) {
stringBuffer.append("\r\n at " + trace[i]);
}
return stringBuffer.toString();
}
/**
* 持久化接口异常日志
* @param joinPoint
* @param e
*/
private void logNote(JoinPoint joinPoint, Throwable e) {
logNote(joinPoint, null, e);
}
/**
* 持久化接口运行日志
* @param joinPoint
* @param data
*/
private void logNote(JoinPoint joinPoint, Object data) {
logNote(joinPoint, data, null);
}
}

4.测试

1


5.最终实现效果

2



推荐阅读
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 本文详细介绍了一种利用 ESP8266 01S 模块构建 Web 服务器的成功实践方案。通过具体的代码示例和详细的步骤说明,帮助读者快速掌握该模块的使用方法。在疫情期间,作者重新审视并研究了这一未被充分利用的模块,最终成功实现了 Web 服务器的功能。本文不仅提供了完整的代码实现,还涵盖了调试过程中遇到的常见问题及其解决方法,为初学者提供了宝贵的参考。 ... [详细]
  • 分享一款基于Java开发的经典贪吃蛇游戏实现
    本文介绍了一款使用Java语言开发的经典贪吃蛇游戏的实现。游戏主要由两个核心类组成:`GameFrame` 和 `GamePanel`。`GameFrame` 类负责设置游戏窗口的标题、关闭按钮以及是否允许调整窗口大小,并初始化数据模型以支持绘制操作。`GamePanel` 类则负责管理游戏中的蛇和苹果的逻辑与渲染,确保游戏的流畅运行和良好的用户体验。 ... [详细]
  • 本文以 www.域名.com 为例,详细介绍如何为每个注册用户提供独立的二级域名,如 abc.域名.com。实现这一功能的核心步骤包括:首先,确保域名支持泛解析,即将 A 记录设置为 *.域名.com,以便将所有二级域名请求指向同一服务器。接着,在服务器端使用 ASP.NET 2.0 进行配置,通过解析 HTTP 请求中的主机头信息,动态识别并处理不同的二级域名,从而实现个性化内容展示。此外,还需在数据库中维护用户与二级域名的对应关系,确保每个用户的二级域名都能正确映射到其专属内容。 ... [详细]
  • NOIP2000的单词接龙问题与常见的成语接龙游戏有异曲同工之妙。题目要求在给定的一组单词中,从指定的起始字母开始,构建最长的“单词链”。每个单词在链中最多可出现两次。本文将详细解析该题目的解法,并分享学习过程中的心得体会。 ... [详细]
  • 洛谷 P4035 [JSOI2008] 球形空间生成器(高斯消元法 / 模拟退火算法)
    本文介绍了洛谷 P4035 [JSOI2008] 球形空间生成器问题的解决方案,主要使用了高斯消元法和模拟退火算法。通过这两种方法,可以高效地求解多维空间中的球心位置。文章提供了详细的算法模板和实现代码,适用于 ACM 竞赛和其他相关应用场景。数据范围限制在 10 以内,确保了算法的高效性和准确性。 ... [详细]
  • 在Django中提交表单时遇到值错误问题如何解决?
    在Django项目中,当用户提交包含多个选择目标的表单时,可能会遇到值错误问题。本文将探讨如何通过优化表单处理逻辑和验证机制来有效解决这一问题,确保表单数据的准确性和完整性。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • Objective-C 中的委托模式详解与应用 ... [详细]
  • Squaretest:自动生成功能测试代码的高效插件
    本文将介绍一款名为Squaretest的高效插件,该工具能够自动生成功能测试代码。使用这款插件的主要原因是公司近期加强了代码质量的管控,对各项目进行了严格的单元测试评估。Squaretest不仅提高了测试代码的生成效率,还显著提升了代码的质量和可靠性。 ... [详细]
  • 在洛谷 P1344 的坏牛奶追踪问题中,第一问要求计算最小割,而第二问则需要找到割边数量最少的最小割。通过为每条边附加一个单位权值,可以在求解最小割时优先选择边数较少的方案,从而同时解决两个问题。这种策略不仅简化了问题的求解过程,还确保了结果的最优性。 ... [详细]
  • 第六章:枚举类型与switch结构的应用分析
    第六章深入探讨了枚举类型与 `switch` 结构在编程中的应用。枚举类型(`enum`)是一种将一组相关常量组织在一起的数据类型,广泛存在于多种编程语言中。例如,在 Cocoa 框架中,处理文本对齐时常用 `NSTextAlignment` 枚举来表示不同的对齐方式。通过结合 `switch` 结构,可以更清晰、高效地实现基于枚举值的逻辑分支,提高代码的可读性和维护性。 ... [详细]
  • Vue ElementUI 实现邮箱地址自动补全功能详解 ... [详细]
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
author-avatar
唐旭阳一一一滴泪15
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有