日常的记录日志,不会还有人还在每个方法都重复书写log.info log....,还有些更初级的sout 哈哈哈,我们要学会用Spring中的利器,那就是AOP思想,替我们把这些重复的工作做了。AOP思想其实我们接触得很多了,像日志记录,权限控制,事务管理啊,基本都用过,里面运用的核心就是AOP。具体AOP详细的介绍和使用我这里就不赘述了,网上有很多资料。
今天我想学做个比较细致些的日志记录方式
org.springframework.boot
spring-boot-starter-aop
com.alibaba
fastjson
1.2.70
org.projectlombok
lombok
true
CommonUtil
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CommonUtil {
private static final String UNKNOWN = "unknown";
/**
* 获取ip地址
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
return ip;
}
public static String getUserAgent(HttpServletRequest request){
return request.getHeader("User-Agent");
}
}
GetUserUtils
public class GetUserUtils {
public static User getCurrentUser(){
return new User("123","admin");
}
}
RequestHolder
public class RequestHolder {
public static HttpServletRequest getHttpServletRequest(){
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
}
StringUtil
public class StringUtil {
/** 空字符串 */
private static final String NULLSTR = "";
/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @param end 结束
* @return 结果
*/
public static String substring(final String str, int start, int end)
{
if (str == null)
{
return NULLSTR;
}
if (end < 0)
{
end = str.length() + end;
}
if (start < 0)
{
start = str.length() + start;
}
if (end > str.length())
{
end = str.length();
}
if (start > end)
{
return NULLSTR;
}
if (start < 0)
{
start = 0;
}
if (end < 0)
{
end = 0;
}
return str.substring(start, end);
}
}
ThrowableUtil
public class ThrowableUtil {
/**log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
* 获取堆栈信息
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
}
}
@Data
public class OperationLog {
private String operId;//主键
private String module;//功能模块
private String type;//操作类型
private String requestMethod;//请求方式
private String operDesc;//操作描述
private String operUserAgent;//操作描述
private String operRequstParam;//请求参数
private String operResponseParam;//返回参数
private String operUserId;//操作员id
private String operUsername;//操作员名称
private String operLocation;//操作地点
private Integer status;//操作状态
private String operMethod;//操作方法
private String operURL;//请求URL
private String operIp;//请求ID
private String excName;//异常名称
private String errorMsg;//错误信息
private Date createTime;//操作时间
private Long costTime;//耗时
}
@Data
public class User {
private String userId;
private String username;
public User(String userId, String username) {
this.userId = userId;
this.username = username;
}
}
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
public @interface Log {
String operModule() default ""; // 操作模块
OperType operType() default OperType.ADD; // 操作类型
String operDesc() default ""; // 操作说明
}
public enum OperType {
//操作类型
TEST("测试"),
ADD("新增"),DELETE("删除"),QUERY("查询")
;
private String type;
OperType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
@Aspect
@Component
@Slf4j
public class OperLogAspect {
ThreadLocal currentTime = new ThreadLocal<>();
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.example.operlog.annotation.Log)")
public void operLogPointCut() {
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
*/
@Pointcut("execution(* com.example.operlog.controller..*.*(..))")
public void operExceptionLogPointCut() {
}
@Around("operLogPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
currentTime.set(System.currentTimeMillis());
//正常处理开始
result = joinPoint.proceed();
//正常处理结束
Long time=System.currentTimeMillis() - currentTime.get();
currentTime.remove();
handleLog(joinPoint,null,result,time);
return result;
}
@AfterThrowing(pointcut = "operExceptionLogPointCut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint,e,null,null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult,Long time){
try
{
// 获取RequestAttributes
/* RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);*/
HttpServletRequest request = RequestHolder.getHttpServletRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//获取注解
Log aopLog = method.getAnnotation(Log.class);
// 获取当前的用户
User currentUser = GetUserUtils.getCurrentUser();
OperationLog operLog = new OperationLog();
String id = UUID.randomUUID().toString().replaceAll("-", "");
operLog.setOperId(id);
operLog.setOperURL(request.getRequestURI());
operLog.setStatus(1);//成功
operLog.setCreateTime(new Date());
operLog.setCostTime(time);
operLog.setOperUserAgent(CommonUtil.getUserAgent(request));
// 请求的地址
operLog.setOperIp(CommonUtil.getIp(request));
// 返回参数
operLog.setOperResponseParam(JSON.toJSONString(jsonResult));
if (currentUser != null)
{
operLog.setOperUserId(currentUser.getUserId());
operLog.setOperUsername(currentUser.getUsername());
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
//设置请求方法名
operLog.setOperMethod(className + "." + methodName + "()");
// 设置请求方式GET..
operLog.setRequestMethod(request.getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, aopLog, operLog);
//利用是否有异常定性记录失败信息
if (e != null)
{
operLog.setStatus(0);//失败
operLog.setExcName(e.getClass().getName());
operLog.setErrorMsg(ThrowableUtil.getStackTrace(e));
log.error("耗时:{} 用户id:{} 用户名username: {} 请求ip:{} User-Agent:{} 方法路径:{} 方法参数:{}",
operLog.getCostTime(),
operLog.getOperUserId(),
operLog.getOperUsername(),
operLog.getOperId(),
operLog.getOperUserAgent(),
methodName,
operLog.getOperRequstParam());
log.error("==控制层方法通知异常==");
log.error("异常信息:{}", e.getMessage());
//e.printStackTrace();
return;
}
log.info(JSON.toJSONString(operLog));
// 保存数据库
//可以借助异步持久化AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
log.info("耗时:{} 用户id:{} 用户名username: {} 请求ip:{} User-Agent:{} 方法路径:{} 方法参数:{}",
operLog.getCostTime(),
operLog.getOperUserId(),
operLog.getOperUsername(),
operLog.getOperId(),
operLog.getOperUserAgent(),
methodName,
operLog.getOperRequstParam());
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperationLog operLog) throws Exception
{
if(log!=null){
operLog.setModule(log.operModule());
operLog.setType(log.operType().getType());
operLog.setOperDesc(log.operDesc());
}else {
//用于无注解的控制层方法 异常抛出 记录
operLog.setModule("无默认模块");
operLog.setType("无默认类别");
operLog.setOperDesc("无默认描述");
}
// 获取参数的信息
setRequestValue(joinPoint, operLog);
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperationLog operLog) throws Exception
{
String params = argsArrayToString(joinPoint.getArgs());
//避免过长的无用信息
operLog.setOperRequstParam(StringUtil.substring(params, 0, 2000));
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
StringBuilder params = new StringBuilder();
if (paramsArray != null && paramsArray.length > 0)
{
for (int i = 0; i < paramsArray.length; i++)
{
//排除不需要记录的参数
if (!isFilterObject(paramsArray[i])){
Object value=paramsArray[i];
if (value instanceof MultipartFile) {
MultipartFile file = (MultipartFile) value;
Object jsonObj = JSON.toJSON(file.getOriginalFilename());
params.append(jsonObj.toString()).append(" ");
}
else if (value instanceof MultipartFile[]) {
MultipartFile[] files = (MultipartFile[]) value;
for(MultipartFile file:files){
Object jsonObj = JSON.toJSON(file.getOriginalFilename());
params.append(jsonObj.toString()).append(" ");
}
}else{
Object jsonObj = JSON.toJSON(value);
params.append(jsonObj.toString()).append(" ");
}
}
}
}
return params.toString();
}
/**
* 判断是否需要过滤
* @param o 对象信息。
* @return 是需要过滤的对象,则返回true;否则返回false。
*/
public boolean isFilterObject(final Object o)
{
return o instanceof HttpServletRequest || o instanceof HttpServletResponse;
}
/*
//用于未定义log注解,抛出异常也需要记录耗时情况
@Before("operExceptionLogPoinCut()")
public void beforeMethod(JoinPoint joinPoint){
currentTime.set(System.currentTimeMillis());
}
@After("operExceptionLogPoinCut()")
public void afterMethod(JoinPoint joinPoint){
currentTime.remove();
}*/
}
@RestController
@RequestMapping("/test")
public class TestController {
//在带注解,抛出异常
@GetMapping("/1")
@Log(operModule = "查询",operDesc = "就查查而已",operType = OperType.QUERY)
public void get(){
throw new RuntimeException("test");
}
//注解,正常执行
@GetMapping("/2")
@Log(operModule = "查询",operDesc = "就查查而已222222",operType = OperType.QUERY)
public void get2(){
System.out.println("=======get2");
}
//不带注解,抛出异常
@GetMapping("/3")
public void get3(){
throw new RuntimeException("get3");
}
//不带注解,正常执行与返回数据
@GetMapping("/4")
public Object get4(){
System.out.println("=======get4");
return new User("123","aaaaaa");
}
//带文件参数测试
@PostMapping("/file")
@Log
public void uploadFile(String param,MultipartFile[] file){
System.out.println("=======upload file");
}
@PostMapping("/file1")
@Log
public void uploadFile1(@RequestBody User param){
System.out.println("=======upload file1");
}
}
测试结果
test/1
2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect : 耗时:null 用户id:123 用户名username: admin 请求ip:547985c7451d4aeaa7632f57cf128044 User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get 方法参数:
2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect : ==控制层方法通知异常==
2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect : 异常信息:test
2020-11-14 14:19:46.818 ERROR 52588 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: test] with root cause
java.lang.RuntimeException: test
at com.example.operlog.controller.TestController.get(TestController.java:27) ~[classes/:na]
test/2
2020-11-14 14:22:12.976 INFO 52588 --- [nio-8080-exec-2] com.example.operlog.aop.OperLogAspect : {"costTime":0,"createTime":1605334932952,"module":"查询","operDesc":"就查查而已222222","operId":"ec33d56eb10a442cbb75663034575544","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.get2()","operRequstParam":"","operResponseParam":"null","operURL":"/test/2","operUserAgent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36","operUserId":"123","operUsername":"admin","requestMethod":"GET","status":1,"type":"查询"}
2020-11-14 14:22:12.977 INFO 52588 --- [nio-8080-exec-2] com.example.operlog.aop.OperLogAspect : 耗时:0 用户id:123 用户名username: admin 请求ip:ec33d56eb10a442cbb75663034575544 User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get2 方法参数:
test/3
2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect : 耗时:null 用户id:123 用户名username: admin 请求ip:eba1a0c0dd1f43ba999416a29202c497 User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get3 方法参数:
2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect : ==控制层方法通知异常==
2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect : 异常信息:get3
2020-11-14 14:23:09.489 ERROR 52588 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: get3] with root cause
java.lang.RuntimeException: get3
test/file
=======upload file
2020-11-14 14:25:14.444 INFO 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect : {"costTime":0,"createTime":1605335114443,"module":"","operDesc":"","operId":"372f9bfad79b4938b2424dda659cfc89","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.uploadFile()","operRequstParam":"123 2 (1).png 312312312.png ","operResponseParam":"null","operURL":"/test/file","operUserAgent":"PostmanRuntime/6.1.6","operUserId":"123","operUsername":"admin","requestMethod":"POST","status":1,"type":"新增"}
2020-11-14 14:25:14.444 INFO 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect : 耗时:0 用户id:123 用户名username: admin 请求ip:372f9bfad79b4938b2424dda659cfc89 User-Agent:PostmanRuntime/6.1.6 方法路径:uploadFile 方法参数:123 2 (1).png 312312312.png
test/file1
=======upload file1
2020-11-14 14:25:53.541 INFO 52588 --- [nio-8080-exec-6] com.example.operlog.aop.OperLogAspect : {"costTime":0,"createTime":1605335153537,"module":"","operDesc":"","operId":"dd808ebd88a34b83996b72f22e48a351","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.uploadFile1()","operRequstParam":"{\"userId\":\"123\",\"username\":\"222\"} ","operResponseParam":"null","operURL":"/test/file1","operUserAgent":"PostmanRuntime/6.1.6","operUserId":"123","operUsername":"admin","requestMethod":"POST","status":1,"type":"新增"}
2020-11-14 14:25:53.541 INFO 52588 --- [nio-8080-exec-6] com.example.operlog.aop.OperLogAspect : 耗时:0 用户id:123 用户名username: admin 请求ip:dd808ebd88a34b83996b72f22e48a351 User-Agent:PostmanRuntime/6.1.6 方法路径:uploadFile1 方法参数:{"userId":"123","username":"222"}