背景

之前的文章中,对日志记录做过简单的介绍。日常开发中,我们常用的日志有两种,一种是业务日志,该类型主要用于记录系统中某些业务的变化或属性的改变,比如业务流转过程中记录状态的变化或对象属性的变化,此类型主要用于查询业务轨迹使用;另一种是系统日志,主要记录方法的调用信息,比如方法名称,参数以及调用者的名称、ip、调用时间等信息。本文结合项目中的实际情况,介绍下如何使用Spring的aop原理实现系统级日志管理。

核心代码

1.声明系统日志切面类并交给Spring容器管理

代码如下:

@Aspect
@Component
public class SysLogAspect {

    @Autowired
    private SysLogService sysLogService;

    @Pointcut("execution(* com.xx.xx.*.service.*.*(..))")
    public void logPointCut() { 

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行方法
        Object result = point.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        // 保存日志
        saveSysLog(point, time);
        return result;
    }

    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {

        String userName = BaseController.getUsername();//BaseController为项目中的基类,主要存储用户登录后的一些基本信息,该类可有可无,根据自己的实际情况而定

        //判断是否SysLogService的调用操作,SysLogService本身不再记录日志
        if(joinPoint.getTarget() instanceof SysLogService) { //SysLogService,自己声明的系统日志接口
            return ;
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        SysLog sysLog = new SysLog();

        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        // 请求的参数
        Object[] args = joinPoint.getArgs();
        try{
            String params = JSONObject.toJSONString(args[0]);
            sysLog.setParams(params);
        } catch (Exception e){

        }

        // 获取request
        HttpServletRequest request = HttpUtils.getHttpServletRequest();
        // 设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));

        // 用户名
        sysLog.setUserName(userName);

        // 执行时长(毫秒)
        sysLog.setTime(time);

        // 保存系统日志
        sysLogService.save(sysLog);
    }

}

复制代码

2.声明ip工具类

代码如下:

public class IPUtils {

    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);

    /**
     * 获取IP地址
     * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
     * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }

        return ip;
    }

}

复制代码

3.声明HttpUtils工具类

代码如下:

public class HttpUtils {

    private static final Log log = LogFactory.getLog(HttpUtils.class);

    /**
     * 获取HttpServletRequest对象
     * @return
     */
    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 输出信息到浏览器
     * @param response
     * @throws IOException
     */
    public static void print(HttpServletResponse response, int code, String msg) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        HttpResult result = HttpResult.error(code, msg);
        String json = JSONObject.toJSONString(result);
        response.getWriter().print(json);
        response.getWriter().flush();
        response.getWriter().close();
    }

    public static String sendpost(String serverUrl, String data) {

        StringBuilder responseBuilder = null;
        BufferedReader reader = null;
        OutputStreamWriter wr = null;

        String re=null;
        try {
            URL url = new URL(serverUrl);
            URLConnection e = url.openConnection();
            e.setDoOutput(true);
            e.setConnectTimeout(5000);
            wr = new OutputStreamWriter(e.getOutputStream());
            wr.write(data);
            wr.flush();
            reader = new BufferedReader(new InputStreamReader(e.getInputStream()));
            responseBuilder = new StringBuilder();
            String line = null;

            while((line = reader.readLine()) != null) {
                responseBuilder.append(line).append("\n");
            }
            re=responseBuilder.toString();
            log.debug("http请求返回结果:"+re);
        } catch (IOException e1) {
            log.error("", e1);
        } finally {
            if(wr != null) {
                try {
                    wr.close();
                } catch (IOException e2) {
                    log.error("close error", e2);
                }
            }

            if(reader != null) {
                try {
                    reader.close();
                } catch (IOException e3) {
                    log.error("close error", e3);
                }
            }

        }

        return re;
    }
}

复制代码

4.测试结果

image

备注:由于系统被频繁使用,每个接口被调用时都会生成一条数据,所以系统日志表中的数据量会特别大,要定期备份数据并清除一些旧数据,否则表中的数据会越来越多影响性能。