通过自定义注解记录日志
主要是使用切面来实现
1.自定义注解类 Loggable
import com...enums.LogScopeEnum; import com...enums.LogTypeEnum; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) //注解作用于方法级别 @Retention(RetentionPolicy.RUNTIME) //运行时起作用 public @interface Loggable { /** * 是否输出日志 */ boolean loggable() default true; /** * 日志信息描述,可以记录该方法的作用等信息。 */ String descp() default ""; /** * 请求所属模块。 */ String module() default ""; /** * 日志类型,可能存在多种接口类型都需要记录日志,比如dubbo接口,web接口 */ LogTypeEnum type() default LogTypeEnum.WEB; /** * 日志等级 */ String level() default "INFO"; /** * 日志输出范围,用于标记需要记录的日志信息范围,包含入参、返回值等。 * ALL-入参和出参, BEFORE-入参, AFTER-出参 */ LogScopeEnum scope() default LogScopeEnum.ALL; /** * 入参输出范围,值为入参变量名,多个则逗号分割。不为空时,入参日志仅打印include中的变量 */ String include() default ""; /** * 是否存入数据库 */ boolean db() default true; /** * 是否输出到控制台 * * @return */ boolean console() default false; }
用到的枚举类 LogScopeEnum和LogTypeEnum BaseConstants
/** * 日志作用范围枚举 */ public enum LogScopeEnum { ALL, BEFORE, AFTER; public boolean contains(LogScopeEnum scope) { if (this == ALL) { return true; } else { return this == scope; } } @Override public String toString() { String str = ""; switch (this) { case ALL: break; case BEFORE: str = "REQUEST"; break; case AFTER: str = "RESPONSE"; break; default: break; } return str; } }
import com...enums.LogScopeEnum; import com...enums.LogTypeEnum; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) //注解作用于方法级别 @Retention(RetentionPolicy.RUNTIME) //运行时起作用 public @interface Loggable { /** * 是否输出日志 */ boolean loggable() default true; /** * 日志信息描述,可以记录该方法的作用等信息。 */ String descp() default ""; /** * 请求所属模块。 */ String module() default ""; /** * 日志类型,可能存在多种接口类型都需要记录日志,比如dubbo接口,web接口 */ LogTypeEnum type() default LogTypeEnum.WEB; /** * 日志等级 */ String level() default "INFO"; /** * 日志输出范围,用于标记需要记录的日志信息范围,包含入参、返回值等。 * ALL-入参和出参, BEFORE-入参, AFTER-出参 */ LogScopeEnum scope() default LogScopeEnum.ALL; /** * 入参输出范围,值为入参变量名,多个则逗号分割。不为空时,入参日志仅打印include中的变量 */ String include() default ""; /** * 是否存入数据库 */ boolean db() default true; /** * 是否输出到控制台 * * @return */ boolean console() default false; }
public enum BaseConstants { YES("YES"), NO("NO"); private String label; private BaseConstants(String s) { this.label = s; } public static BaseConstants fromCode(String baseConstants) { switch (baseConstants) { case "YES": return YES; case "NO": return NO; default: return NO; } } }
重点是切面类 WebLogAspect
import com.alibaba.fastjson.JSONObject; import com...interfaces.Loggable; import com...common.LogMessage; import com...BaseConstants; import com...enums.LogScopeEnum; import com...service.LogMessageService; import com...utils.HttpUtils; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtMethod; import javassist.bytecode.LocalVariableAttribute; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * 日志记录AOP实现 */ @Aspect @Component public class WebLogAspect { private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); @Autowired private LogMessageService logMessageService; @Autowired private HttpUtils httpUtils; // 开始时间 private long startTime = 0L; // 结束时间 private long endTime = 0L; /** * Controller层切点 */ // @Pointcut("execution(public * *(..))") @Pointcut("execution(* *..controller..*.*(..))") public void controllerAspect() { } /** * 前置通知 用于拦截Controller层记录用户的操作 * * @param joinPoint 切点 */ @Before("controllerAspect()") public void doBeforeInServiceLayer(JoinPoint joinPoint) { } /** * 配置controller环绕通知,使用在方法aspect()上注册的切入点 * * @param point 切点 * @return * @throws Throwable */ @Around("controllerAspect()") public Object doAround(ProceedingJoinPoint point) throws Throwable { // 获取request RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = servletRequestAttributes.getRequest(); // 获取ip地址 String ipAddress = httpUtils.getIpAddress(request); //目标方法实体 Method method = ((MethodSignature) point.getSignature()).getMethod(); boolean hasMethodLogAnno = method.isAnnotationPresent(Loggable.class); //没加注解 直接执行返回结果 /*if (!hasMethodLogAnno) { return point.proceed(); }*/ //日志打印外部开关 String logSwitch = BaseConstants.YES.toString(); //记录日志信息 LogMessage logMessage = new LogMessage(); //方法注解实体 Loggable methodLogAnno = method.getAnnotation(Loggable.class); //处理入参日志 handleRequestLog(point, methodLogAnno, request, logMessage, logSwitch, ipAddress); //执行目标方法内容,获取执行结果 Object result = point.proceed(); //处理接口响应日志 handleResponseLog(logSwitch, logMessage, methodLogAnno, result); return result; } /** * 处理入参日志 * * @param point 切点 * @param methodLogAnnon 日志注解 * @param logMessage 日志信息记录实体 */ private void handleRequestLog(ProceedingJoinPoint point, Loggable methodLogAnnon, HttpServletRequest request, LogMessage logMessage, String logSwitch, String ipAddress) throws Exception { String paramsText = ""; //参数列表 String includeParam = methodLogAnnon.include(); Map<String, Object> methodParamNames = getMethodParamNames( point.getTarget().getClass(), point.getSignature().getName(), includeParam); Map<String, Object> params = getArgsMap( point, methodParamNames); if (params != null) { //序列化参数列表 paramsText = JSONObject.toJSONString(params); } //判断是否输出日志 if (methodLogAnnon.loggable() && methodLogAnnon.scope().contains(LogScopeEnum.BEFORE) && methodLogAnnon.console() && StringUtils.equals(logSwitch, BaseConstants.YES.toString())) { //打印入参日志 logger.info("【{}】 【{}】 【{}】 接口入参成功!, 方法名称:【{}】, 所属模块:【{}】, 请求参数:【{}】", request.getRequestURI(), methodLogAnnon.descp(), ipAddress, point.getSignature().getName(), methodLogAnnon.module(), paramsText); } // 1.构造开始时间 startTime = System.currentTimeMillis(); logMessage.setBeginTime(new Date(startTime)); // 2.接口描述 logMessage.setDescription(methodLogAnnon.descp()); String systemId = ""; if ((null != point.getArgs()) && (point.getArgs().length > 0)) { try { Object[] args = point.getArgs(); String objStr = JSONObject.toJSONString(args[0]); // 如果转换json异常,则为get请求,直接从获取systemId JSONObject jsonObject = JSONObject.parseObject(objStr); systemId = null == jsonObject.get("systemId") ? "" : jsonObject.get("systemId").toString(); } catch (Exception e) { if (null != params && null != params.get("systemId")) { systemId = params.get("systemId").toString(); } } } // 3.构造用户名 logMessage.setUsername(systemId + ":" + ipAddress); // 5.构造调用接口 logMessage.setQueryinterface(request.getRequestURI()); // 5.构造调用方法 logMessage.setMethod(point.getSignature().getName()); // 6.参数 logMessage.setParameter(paramsText); // 7.请求所属模块 logMessage.setModule(methodLogAnnon.module().toString()); } /** * 处理响应日志 * * @param logSwitch 外部日志开关,用于外部动态开启日志打印 * @param logMessage 日志记录信息实体 * @param methodLogAnnon 日志注解实体 * @param result 接口执行结果 */ private void handleResponseLog(String logSwitch, LogMessage logMessage, Loggable methodLogAnnon, Object result) { endTime = System.currentTimeMillis(); //结束时间 logMessage.setEndTime(new Date()); //消耗时间 logMessage.setSpendTime(endTime - startTime); //是否输出日志 if (methodLogAnnon.loggable() && methodLogAnnon.scope().contains(LogScopeEnum.AFTER)) { //判断是否入库 if (methodLogAnnon.db()) { // 入库 try { logMessageService.saveLogMessage(logMessage); } catch (Exception e) { e.printStackTrace(); } } //判断是否输出到控制台 if (methodLogAnnon.console() && StringUtils.equals(logSwitch, BaseConstants.YES.toString())) { //输出到控制台 System.out.println(logMessage); } } } /** * 获取方法入参变量名 * * @param cls 触发的类 * @param methodName 触发的方法名 * @param include 需要打印的变量名 * @return * @throws Exception */ private Map<String, Object> getMethodParamNames(Class cls, String methodName, String include) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(cls)); CtMethod cm = pool.get(cls.getName()).getDeclaredMethod(methodName); LocalVariableAttribute attr = (LocalVariableAttribute) cm .getMethodInfo().getCodeAttribute() .getAttribute(LocalVariableAttribute.tag); if (attr == null) { throw new Exception("attr is null"); } else { Map<String, Object> paramNames = new HashMap<>(); int paramNamesLen = cm.getParameterTypes().length; int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; if (StringUtils.isEmpty(include)) { for (int i = 0; i < paramNamesLen; i++) { paramNames.put(attr.variableName(i + pos), i); } } else { // 若include不为空 for (int i = 0; i < paramNamesLen; i++) { String paramName = attr.variableName(i + pos); if (include.indexOf(paramName) > -1) { paramNames.put(paramName, i); } } } return paramNames; } } /** * 组装入参Map * * @param point 切点 * @param methodParamNames 参数名称集合 * @return */ private Map getArgsMap(ProceedingJoinPoint point, Map<String, Object> methodParamNames) { Object[] args = point.getArgs(); if (null == methodParamNames) { return Collections.EMPTY_MAP; } for (Map.Entry<String, Object> entry : methodParamNames.entrySet()) { int index = Integer.valueOf(String.valueOf(entry.getValue())); if (args != null && args.length > 0) { if (args[index] instanceof ServletRequest || args[index] instanceof ServletResponse || args[index] instanceof MultipartFile) { continue; } else { Object arg = (null == args[index] ? "" : args[index]); methodParamNames.put(entry.getKey(), arg); } } } return methodParamNames; } }
日志实体类LogMessage
import lombok.Data; import java.util.Date; @Data public class LogMessage { private static final long serialVersionUID = 1L; // @Column(name = "id") private Integer id; // id ,后续考虑uuid // @Column(name = "username") private String username; // 用户名 // @Column(name = "queryinterface") private String queryinterface; // 请求接口 // @Column(name = "method") private String method; // 请求方法 // @Column(name = "parameter") private String parameter; // 请求参数 // @Column(name = "description") // 描述信息 private String description; // @Column(name = "module") // 请求所属模块 private String module; // @Column(name = "begin_time") private Date beginTime; // 开始时间 // @Column(name = "end_time") private Date endTime; // 结束时间 // @Column(name = "spend_time") private Long spendTime; // 耗费时间 // @Column(name = "d_date") private Date insertDate; // 操作时间 插入数据的日期 public LogMessage() { this.insertDate = new Date(); } public LogMessage(Integer id, String username, String queryinterface, String method, String parameter, String description, String module, Date beginTime, Date endTime, Long spendTime) { this.id = id; this.username = username; this.queryinterface = queryinterface; this.method = method; this.parameter = parameter; this.description = description; this.module = module; this.beginTime = beginTime; this.endTime = endTime; this.spendTime = spendTime; this.insertDate = new Date(); } }
记录日志的Service类 LogMessageService
import com...common.LogMessage; import com...mapper.WebLogAspectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Date; @Service public class LogMessageService { @Autowired private WebLogAspectMapper webLogAspectMapper; /** * 保存日志记录 * * @param logMessage * @return */ public void saveLogMessage(LogMessage logMessage) { webLogAspectMapper.saveLogMessage(logMessage); } /** * 保存新日志记录 * * @param username 用户名 * @param queryinterface 接口 * @param method 调用的方法名 * @param parameter 参数 * @param description 描述 * @param module 接口所属模块 * @param startTime 开始时间 System.currentTimeMillis(); */ public void saveNewLogMessage(String username, String queryinterface, String method, String parameter, String description, String module, Long startTime) { Long endTime = System.currentTimeMillis(); LogMessage logMessage = new LogMessage(null, username, queryinterface, method, parameter, description, module, new Date(), new Date(endTime), endTime - startTime); webLogAspectMapper.saveLogMessage(logMessage); } }
Mapper类
import com.htsc.thfx.performance.entity.common.LogMessage; public interface WebLogAspectMapper { void saveLogMessage(LogMessage logMessage); }
获取请求ip
import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; @Component public class HttpUtils { private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); /** * 获取IP地址 */ public String getIpAddress(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (Exception e) { logger.error("get native ip by network card failed ==>, msg:{}", e.getMessage()); } if (null != inet && inet.getHostAddress() != null) { ipAddress = inet.getHostAddress(); } } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } return ipAddress; } }

浙公网安备 33010602011771号