第八章:使用拦截器记录你的SpringBoot的请求日志

请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。请求日志一般都会记录请求参数、请求地址、请求状态(Status Code)、SessionId、请求方法方式(Method)、请求时间、客户端IP地址、请求返回内容、耗时等等。如果你得系统还有其他个性化的配置,也可以完成记录。

新建项目并添加依赖: Web、JPA、MySQL、Druid,并配置druid(见第四章)

application.yml

server:
  port: 9011
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: Sunlu1994
    url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
    type: com.alibaba.druid.pool.DruidDataSource
    max-active: 20 #最大活跃数
    initial-size: 1 #初始化数量
    max-wait: 60000 #最大连接等待时间
    filters: stat #配置监控统计拦截的filters,去掉后监控界面的sql无法统计,wall用于防火墙
    minIdle: 1
    poolPreparedStatements: true  #打开PSCache
    maxOpenPreparedStatements: 20   #指定每个连接的PSCache大小
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #打开mergeSql功能
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

数据库中建表: 

create table t_logger_infos(ali_id int primary key auto_increment,ali_client_ip varchar(20),ali_uri varchar(100),ali_type varchar(20),ali_method varchar(10),ali_param_data longtext,ali_session_id varchar(50),ali_time timestamp,ali_return_time varchar(50),ali_return_data longtext,ali_http_status_code varchar(10),ali_time_consuming int);

创建表的实体类

LoggerEntity.java

@Entity
@Table(name = "t_logger_infos")
public class LoggerEntity implements Serializable {
    @Id
    @GeneratedValue
    @Column(name = "ali_id")
    private Integer id;
    @Column(name = "ali_client_ip")
    private String ip;
    @Column(name = "ali_uri")
    private String uri;
    @Column(name = "ali_type")
    private String type;
    @Column(name = "ali_method")
    private String method;
    @Column(name = "ali_param_data")
    private String paramData;
    @Column(name = "ali_session_id")
    private String sessionId;
    @Column(name = "ali_time")
    private Timestamp time;
    @Column(name = "ali_return_time")
    private String returnTime;
    @Column(name = "ali_return_data")
    private String returnData;
    @Column(name = "ali_http_status_code")
    private String httpStatusCode;
    @Column(name = "ali_time_consuming")
    private Integer timeConsuming;

    @Override
    public String toString() {
        return "LoggerEntity{" +
                "id=" + id +
                ", ip='" + ip + '\'' +
                ", uri='" + uri + '\'' +
                ", type='" + type + '\'' +
                ", method='" + method + '\'' +
                ", paramData='" + paramData + '\'' +
                ", sessionId='" + sessionId + '\'' +
                ", time=" + time +
                ", returnTime='" + returnTime + '\'' +
                ", returnData='" + returnData + '\'' +
                ", httpStatusCode='" + httpStatusCode + '\'' +
                ", timeConsuming=" + timeConsuming +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LoggerEntity that = (LoggerEntity) o;
        return Objects.equals(id, that.id) &&
                Objects.equals(ip, that.ip) &&
                Objects.equals(uri, that.uri) &&
                Objects.equals(type, that.type) &&
                Objects.equals(method, that.method) &&
                Objects.equals(paramData, that.paramData) &&
                Objects.equals(sessionId, that.sessionId) &&
                Objects.equals(time, that.time) &&
                Objects.equals(returnTime, that.returnTime) &&
                Objects.equals(returnData, that.returnData) &&
                Objects.equals(httpStatusCode, that.httpStatusCode) &&
                Objects.equals(timeConsuming, that.timeConsuming);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id, ip, uri, type, method, paramData, sessionId, time, returnTime, returnData, httpStatusCode, timeConsuming);
    }

    public Integer getId() {

        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getParamData() {
        return paramData;
    }

    public void setParamData(String paramData) {
        this.paramData = paramData;
    }

    public String getSessionId() {
        return sessionId;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public Timestamp getTime() {
        return time;
    }

    public void setTime(Timestamp time) {
        this.time = time;
    }

    public String getReturnTime() {
        return returnTime;
    }

    public void setReturnTime(String returnTime) {
        this.returnTime = returnTime;
    }

    public String getReturnData() {
        return returnData;
    }

    public void setReturnData(String returnData) {
        this.returnData = returnData;
    }

    public String getHttpStatusCode() {
        return httpStatusCode;
    }

    public void setHttpStatusCode(String httpStatusCode) {
        this.httpStatusCode = httpStatusCode;
    }

    public Integer getTimeConsuming() {
        return timeConsuming;
    }

    public void setTimeConsuming(Integer timeConsuming) {
        this.timeConsuming = timeConsuming;
    }
}

创建LoggerJPA实现JpaRepository

LoggerJpa.java

@Repository
public interface LoggerJpa extends JpaRepository<LoggerEntity,Integer> {
}

创建日志拦截器 

我们上面的步骤有关请求日志的存储已经编写完成,那么我们接下来需要编写一个请求日志的拦截器,自定义SpringMVC拦截器需要实现HandlerIntercptor接口,并且实现内部的三个方法

LoggerInterceptor.java

//创建拦截器 要实现HandlerInterceptror接口
public class LoggerInterceptor implements HandlerInterceptor {
    //请求开始时间标记
    private static final String LOGGER_SEND_TIME="_send_time";
    //请求日志实体标记
    private static final String LOGGER_ENTITY="_logger_entity";

    //调用请求的时候执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //创建请求实体
        LoggerEntity loggerEntity = new LoggerEntity();
        //获取请求的sessionId
        String sessionId = request.getRequestedSessionId();
        //请求的路径
        String path = request.getRequestURI();
        //获取请求参数信息
        String paramData = JSON.toJSONString(request.getParameterMap(),SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteMapNullValue);
        //设置客户端ip
        loggerEntity.setIp(LoggerUtils.getCliectIp(request));
        //设置请求方法
        loggerEntity.setMethod(request.getMethod());
        //设置请求类型
        loggerEntity.setType(LoggerUtils.getRequestType(request));
        //设置请求参数的json字符串
        loggerEntity.setParamData(paramData);
        //设置请求地址
        loggerEntity.setUri(path);
        //设置sessionId
        loggerEntity.setSessionId(sessionId);
        //设置请求开始时间
        request.setAttribute(LOGGER_SEND_TIME,System.currentTimeMillis());
        //设置请求实体到request中,方便after调用
        request.setAttribute(LOGGER_ENTITY,loggerEntity);
        return true;
    }
    //controller调用之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }
    //viewResolve返回view到前台之前执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //获取请求错误码
        int status = response.getStatus();
        //当前时间
        long currentTime = System.currentTimeMillis();
        //请求开始时间
        long time = Long.parseLong(request.getAttribute(LOGGER_SEND_TIME).toString());
        //获取本次请求日志实体
        LoggerEntity loggerEntity = (LoggerEntity) request.getAttribute(LOGGER_ENTITY);
        //设置请求时间差
        loggerEntity.setTimeConsuming(Integer.valueOf(String.valueOf((currentTime-time))));
        //设置返回时间
        loggerEntity.setReturnTime(String.valueOf(currentTime));
        //设置返回错误码
        loggerEntity.setHttpStatusCode(String.valueOf(status));
        //设置返回值
        loggerEntity.setReturnData(JSON.toJSONString(LoggerUtils.LOGGER_RETURN,SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteMapNullValue));
        //将日志写入到数据库
        LoggerJpa loggerJpa = getDAO(LoggerJpa.class,request);
        loggerJpa.save(loggerEntity);
        System.out.println("写入成功");
    }
    //我们在拦截器内无法通过SpringBean的方式注入LoggerJPA,我只能通过另外一种形式。
    //WebApplicationContextUtils
    //这个工具类可以通过HttpServletRequest请求对象的上下文(ServetCotext)获取Spring管理的Bean
    /*
    创建了一个getDAO的方法,方法需要传入一个实体的类型,以及一个HttpServetRequest请求对象,
    通过WebApplicationContextUtils内部的getRequiredWebApplicationContext方法获取到BeanFactory(实体工厂类)
    ,从而通过工厂实体的getBean方法就可以拿到SpringDataJPA为我们管理的LoggerJPA持久化数据接口实例。
     */
    private <T> T getDAO(Class<T> clazz,HttpServletRequest request)
    {
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        return factory.getBean(clazz);
    }
}

这里需要注意一点,我们在拦截器内无法通过SpringBean的方式注入LoggerJPA,我只能通过另外一种形式。(getDAO方法获取bean)

创建LoggerUtil工具类

LoggerUtils.java

public final class LoggerUtils {

    public static final String LOGGER_RETURN = "_logger_return";

    private LoggerUtils() {}

    /**
     * 获取客户端ip地址
     * @param request
     * @return
     */
    public static String getCliectIp(HttpServletRequest request)
    {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.trim() == "" || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 多个路由时,取第一个非unknown的ip
        final String[] arr = ip.split(",");
        for (final String str : arr) {
            if (!"unknown".equalsIgnoreCase(str)) {
                ip = str;
                break;
            }
        }
        return ip;
    }

    /**
     * 判断是否为ajax请求
     * @param request
     * @return
     */
    public static String getRequestType(HttpServletRequest request) {
        return request.getHeader("X-Requested-With");
    }
}

我们处理日志请求时需要用到FastJson、HttpServet依赖,所以我们修改pom.xml配置文件加入FastJson开源组件以及HttpServlet的maven依赖

<!--HttpServet -->
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax-servlet-api</artifactId>
</dependency>
<!--fastjson -->
<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.15</version>
</dependency>

编写测试控制器在入口类中定义/login

@SpringBootApplication
@RestController
public class Chapter8Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter8Application.class, args);
    }

    //添加druid的sql监控
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    @RequestMapping("/login")
    public JSONObject login(HttpServletRequest request, String name){
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("msg","登陆成功");
        request.setAttribute(LoggerUtils.LOGGER_RETURN,jsonObject);
        return jsonObject;
    }
}

配置拦截器(要继承WebMvcConfigurerAdapter)

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
    }
}

请求地址:http://127.0.0.1:9011/login?name=sun



链接:https://www.jianshu.com/p/890c23a1b3d7
來源:简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值