springboot-23-aspectj日志记录及threadlocal内存泄漏

对于请求参数的处理和响应, 如果在代码中体现日志会显得很繁琐, 普遍的解决方案是使用spring的切面方案去解决.

这儿使用的是springboot的切面: http://www.cnblogs.com/wenbronk/p/6848984.html

最开始的aspectj切面解决:

package com.iwhere.easy.travel.aspect;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;

@Aspect
@Component
public class ControllerAspect {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());  

    private String name = "easy-travel-server";

    @Pointcut("execution(public * com.wenbronk.controller.*.*(..))")
    public void controllerLog(){}

    @Pointcut("execution(public * com.wenbronk.service.*.*(..))")
    public void serviceLog(){}

    private ThreadLocal<Long> startTime = new ThreadLocal<>();

    private ThreadLocal<String> requestId = new ThreadLocal<>();

    private ThreadLocal<String> interfaceName = new ThreadLocal<>();

    private ThreadLocal<String> param = new ThreadLocal<>();

    private SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd‘T‘HH:mm:ss.SSS");

    @Before("controllerLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 设置请求开始时间
        startTime.set(System.currentTimeMillis());
        Date stTimeDate = new Date(startTime.get());
        String dateStr = dataFormat.format(stTimeDate);
        // 设置请求标识
        String requestIdStr = UUID.randomUUID().toString();
        requestId.set(requestIdStr);
        // 提取全部参数  paramJson
        Enumeration<String> paramNames = request.getParameterNames();
        JSONObject paramJson = new JSONObject();
        while(paramNames.hasMoreElements()){
            String paramName = paramNames.nextElement();
            paramJson.put(paramName, request.getParameter(paramName));
        }

        // 提取接口标识(url中截取)
        String requestUrl = request.getRequestURL().toString();
        int start = requestUrl.lastIndexOf("/")+1;
        String interfaceNameStr = null;
        if (requestUrl.contains("?")){
            interfaceNameStr = requestUrl.substring(start, requestUrl.indexOf("?"));
        } else {
            interfaceNameStr = requestUrl.substring(start);
        }
        param.set(paramJson.toJSONString());
        interfaceName.set(interfaceNameStr);
        // 将requst的唯一标识放置在request中,在其他环节可以穿起来
        request.setAttribute("requestId", requestId.get());
    }

    @AfterReturning(returning="rvt",pointcut="controllerLog()")
    public void doAfterReturning(JoinPoint joinPoint,Object rvt) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("finished" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " "
                 + request.getRequestURL().toString() + " " + param.get()
                 + (System.currentTimeMillis() - startTime.get())
                 + " " + rvt.toString());
    }

    @AfterThrowing(throwing="ex", pointcut="controllerLog()")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable ex) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
         // 发生地点
         int lineNum = 0;
         String className = null;
         String methodName = null;
         StackTraceElement[] st = ex.getStackTrace();
         for (StackTraceElement stackTraceElement : st) {
             lineNum = stackTraceElement.getLineNumber();
             className = stackTraceElement.getClassName();
             methodName = stackTraceElement.getMethodName();
            System.out.println("[类:" + className + "]调用"
            + methodName + "时在第" + lineNum
            + "行代码处发生异常!异常类型:" + ex.getClass().getName());
            break;
         }
         String exceptionMessage = "[类:" + className + "]调用"+ methodName + "时在第" + lineNum + "行代码处发生异常!异常类型:" + ex.getClass().getName();
        logger.info("exception" + " " + name + " " + interfaceName.get() + " " + requestId.get() + " "
                 + request.getRequestURL().toString() + " " + param.get()
                 + " " + exceptionMessage);
    }
}

可见这个里面有一个before和after, 然后还有一个异常处理的方法

偶然间看到这个博客

http://blog.csdn.net/lhqj1992/article/details/52451136
https://my.oschina.net/xpbug/blog/113444
https://segmentfault.com/a/1190000000537475

由于此项目采用的是线程池, 所以可能存在内存一直上涨, 一直到线程池max之后达到一个稳定态, 也就发生了我们认为的内存泄漏

之后改成这个方法:

package com.iwhere.scrapy.aspect;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSON;

/**
 * 日志切面类
 * @author wenbronk
 * @Date 上午9:21:59
 */

@Aspect   //定义一个切面
@Configuration
public class LogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
//    private static final SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd‘T‘HH:mm:ss.SSS");

    // 定义切点 Pointcut
    @Pointcut("execution(* com.iwhere.scrapy.controller.*Controller.*(..))")
    public void excudeService() {
    }

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Long startTime = System.currentTimeMillis();

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();
        LOGGER.info("请求开始, url: {}, method: {}, uri: {}, params: {}", url, method, uri, queryString);

        // result的值就是被拦截方法的返回值
        Object result = pjp.proceed();
        LOGGER.info("请求结束,url: {}, response: {}, time: {}s", url, JSON.toJSONString(result), (System.currentTimeMillis() - startTime) / 1000);
        return result;
    }

}

然后还需要加入一个全局异常处理框架:

http://www.cnblogs.com/wenbronk/p/6850785.html

具体效果等待进一步测试

时间: 2024-10-03 05:31:02

springboot-23-aspectj日志记录及threadlocal内存泄漏的相关文章

深入分析 ThreadLocal 内存泄漏问题

前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用 ThreadLocal,就可能会导致内存泄漏.下面,我们将围绕三个方面来分析 ThreadLocal 内存泄漏的问题 ThreadLocal 实现原理 ThreadLocal为什么会内存泄漏 ThreadLocal 最佳实践 ThreadLocal 实现原理 ThreadLocalThreadLocal的实现是这样的:每个Th

捉虫记录:解决内存泄漏问题

LinJM   2014_05_23 解决内存泄漏问题 在VS2010的Debug模式下面,点击运行,然后退出,之后会在输出框里面出现内存泄漏信息(如下图所示). Analysis:主要是new了之后没有delete相应的变量,所以,很明显就是要在不使用时delete掉这个变量.不过,有个问题,如下图所示: 我代码修改位置如下所示: 我把红下划线部分注释掉就不会出现上面那个问题,后来讨论分析才发现pBim现在分配给了pAdjustmentLyInfo,二者现在指向同一个内存空间,当我delete

java多线程--------深入分析 ThreadLocal 内存泄漏问题

前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能会导致内存泄漏.下面,我们将围绕三个方面来分析ThreadLocal 内存泄漏的问题 ThreadLocal 实现原理 ThreadLocal为什么会内存泄漏 ThreadLocal 最佳实践 ThreadLocal 实现原理 ThreadLocal的实现是这样的:每个Thread 维护一个 Thr

18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题

1. 造成内存泄漏的原因? threadLocal是为了解决对象不能被多线程共享访问的问题,通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这样每个线程使用自己的对象实例,彼此不会影响达到隔离的作用,从而就解决了对象在被共享访问带来线程安全问题.如果将同步机制和threadLocal做一个横向比较的话,同步机制就是通过控制线程访问共享对象的顺序,而threadLocal就是为每一个线程分配一个该对象,各用各的互不影响.打个比方说,现在有1

ThreadLocal内存泄漏真因探究(转)

出处: 链接:https://www.jianshu.com/p/a1cd61fa22da ThreadLocal原理回顾 ThreadLocal的原理:每个Thread内部维护着一个ThreadLocalMap,它是一个Map.这个映射表的Key是一个弱引用,其实就是ThreadLocal本身,Value是真正存的线程变量Object. 也就是说ThreadLocal本身并不真正存储线程的变量值,它只是一个工具,用来维护Thread内部的Map,帮助存和取.注意上图的虚线,它代表一个弱引用类型

Java并发编程之ThreadLocal内存泄漏探究

使用 ThreadLocal 不当可能会导致内存泄露,是什么原因导致的内存泄漏呢? 我们首先看一个例子,代码如下: /** * Created by cong on 2018/7/14. */ public class ThreadLocalOutOfMemoryTest { static class LocalVariable { private Long[] a = new Long[1024*1024]; } // (1) final static ThreadPoolExecutor p

内存中OLTP(Hekaton)里的事务日志记录

在今天的文章里,我想详细讨论下内存中OLTP里的事务日志如何写入事务日志.我们都知道,对于你的内存优化表(Memory Optimized Tables),内存中OLTP提供你2个持久性(durability)选项: SCHEMA_ONLY SCHEMA_AND_DATA 今天我不想更多讨论SCHEMA_ONLY,因为使用这个选项,在事务日志里没有发生任何日志(SQL Server 重启后你的数据会丢失).今天我们会专门讲解下SCHEMA_AND_DATA选项的持久性. SCHEMA_AND_D

了解 JavaScript 应用程序中的内存泄漏

简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理即可实现,但却忽略了它可能在程序中带来重大的问题.不当清理的对象可能会存在比预期要长得多的时间.这些对象继续响应事件和消耗资源.它们可强制浏览器从一个虚拟磁盘驱动器分配内存页,这显著影响了计算机的速度(在极端的情形中,会导致浏览器崩溃). 内存泄漏指任何对象在您不再拥有或需要它之后仍然存在.在最近几

Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector

那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简称vld). vld工具是VC++环境下一款小巧易用.免费开源的内存泄漏检测工具,vld可以显示导致内存泄漏的完整内存分配调用堆栈.vld的检测报告能够对每个内存泄漏点提供完整的堆栈跟踪,并且包含其源文件及行号信息. 安装过程是,先在到地址http://vld.codeplex.com/下载vld安