java反射机制 ios jtable vue前端 sql server 视频教程 多商户商城模板 jquery清除子元素 jquery移除子元素 float占几个字节 java通用版qq浏览器下载 mysql设置自增初始值 matlab插值函数 python实例 pythonassert函数 python指数函数 python入门指南 java编程基础 java运行环境配置 java的正则表达式 java语言代码大全 java平台 java特性 php语言入门 flash实例教程 js删除数组指定元素 相关软件 冬青鼠 免费家谱制作软件 cf透视辅助 mssql 银头鲑鱼 疯狂java 刷新页面 java获取时间戳 php取整 挑战程序设计竞赛 cf小号 deallocate 安卓游戏辅助 拍照姿势的摆法女
当前位置: 首页 > 学习教程  > 编程语言

记录Http请求日志(埋点)-过滤器方式

2020/11/4 15:00:38 文章标签:

一、需求 1、需求概述 内部管理系统,用于统计用户的使用情况,使用习惯。 2、分析 由于是内部系统,用商业级埋点有点浪费。可以借助ELK日志分析系统,为HTTP API接口增加统一请求日志。 3、统一请求日志要记录以下信息&#xf…

一、需求

1、需求概述

内部管理系统,用于统计用户的使用情况,使用习惯。

2、分析

由于是内部系统,用商业级埋点有点浪费。可以借助ELK日志分析系统,为HTTP API接口增加统一请求日志。

3、统一请求日志要记录以下信息:
  • 请求信息:请求路径、请求参数、请求时间、响应状态
  • 用户信息:用户id、操作系统、浏览器版本
  • 应用信息:接口耗时、响应结果(API统一格式的返回结果)
二、选用AOP or Filter

1、AOP拦截所有方法,可以拦截指定Controller;

2、Filter拦截所有请求,拦截位置比Handler靠前,比较适合用于行为日志记录

以终为始,不管黑猫白猫,能抓住老鼠就是好猫。这两种方式均能满足需求。本章选用Filter方式实现。

3、选用个Filter

  • OncePerRequestFilter(SpringMVC)

抽象类OncePerRequestFilter继承自GenericFilterBean,它保留了GenericFilterBean中的所有方法并对之进行了扩展,在oncePerRequestFilter中的主要方法是doFilter。

  • AbstractRequestLoggingFilter(SpringMVC)

AbstractRequestLoggingFilter是对OncePerRequestFilter的扩展,它除了遗传了其父类及祖先类的所有功能外,还在doFilterInternal中决定了在过滤之前和之后执行的事件,它的子类关注的是beforeRequest和afterRequest。

因为埋点需求需要记录response信息,选用基于OncePerRequestFilter实现,并参考HttpTraceLogFilter一些思路和写法

三、实现代码

1、日志配置

logback-spring.xml

<!-- 行为埋点 -->
    <appender name="EVENT_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_HOME}/event.log</File>
        <append>true</append>
        <!-- ThresholdFilter:临界值过滤器,过滤掉 TRACE 和 DEBUG 级别的日志 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>%msg%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 每天生成一个日志文件,保存30天的日志文件
            - 如果隔一段时间没有输出日志,前面过期的日志不会被删除,只有再重新打印日志的时候,会触发删除过期日志的操作。
            -->
            <fileNamePattern>${LOG_HOME}/event.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <maxFileSize>100MB</maxFileSize>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
    </appender>
<!-- 省略其他 -->
<logger name="event_log" level="info" additivity="false">
        <appender-ref ref="EVENT_LOG"/>
    </logger>

pom

<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.3.2</version>
		</dependency>

2、日志格式封装(model)

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;

/**
 *	行为埋点
 * @author Ice sun
 * @date 2020/10/29 18:01
 */
@Data
public class EventLog {

	/**
	 * 用户id
	 */
	private String userId;
	/**
	 * 用户邮箱
	 */
	private String email;
	/**
	 * 机构名称
	 */
	private String orgName;
	/**
	 * 部门名称
	 */
	private String deptName;
	/**
	 * 请求路径
	 */
	private String url;
	/**
	 * 请求 IP
	 */
	private String ip;
	/**
	 * 请求参数
	 */
	private String params;
	/**
	 * 接口用时(毫秒)
	 */
	private Long useTime;
	/**
	 * 浏览器类型
	 */
	private String browser;
	/**
	 * 操作系统类型
	 */
	private String operatingSystem;
	/**
	 * 请求响应状态,示例:200 302等
	 */
	private Integer status;
	/**
	 * 统一接口响应状态代码 1 成功 其他见码表
	 */
	private String code;
	/**
	 * 统一接口响应状态描述
	 */
	private String msg;
	/**
	 * 统一接口响应状态结果
	 */
	private String data;
	/**
	 * 记录时间
	 */
	@JsonSerialize(using = LocalDateTimeSerializer.class)
	@DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
	private LocalDateTime createTime;
}

3、过滤器(Filter)

/**
 *	埋点Filter
 * @author Ice sun
 * @date 2020/11/2 19:27
 */
public class HttpEventLogFilter extends OncePerRequestFilter {

	private final Logger log = LoggerFactory.getLogger("event_log");
	/**
	 * 预留日志总开关,通过配置开启或关闭
	 */
	private boolean logEnabed = true;
	/**
	 * 排除上传文件类请求
	 */
	private static final String IGNORE_CONTENT_TYPE = "multipart/form-data";

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (!(request instanceof ContentCachingRequestWrapper)) {
			request = new ContentCachingRequestWrapper(request);
		}

		if (!(response instanceof ContentCachingResponseWrapper)) {
			response = new ContentCachingResponseWrapper(response);
		}

		int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
		LocalDateTime start = LocalDateTime.now();
		try {
			filterChain.doFilter(request, response);
			status = response.getStatus();
		}catch (Exception e){
			log.error("TraceLogFilter",e);
		} finally {
			if(logEnabed&&!Objects.equals(IGNORE_CONTENT_TYPE, request.getContentType())) {
				ObjectMapper mapper = new ObjectMapper();

				LocalDateTime end = LocalDateTime.now();
				Duration duration = Duration.between(start, end);
				Long useTime = duration.toMillis();
				HttpSession session = request.getSession(false);

				EventLog eventLog = new EventLog();
				String userId = (String) session.getAttribute("userId");
				String email = (String) session.getAttribute("email");
				//请求参数
				StringBuffer requestParams = new StringBuffer();
				if("POST".equals(request.getMethod())){
					String params = getRequestBody(request);
					requestParams.append(params);
				}else {
					String queryString = request.getQueryString();
					requestParams.append(queryString);
				}
				//响应结果,如果没有统一返回格式,此处需要改写
				String result = getResponseBody(response);
				ResultVO resultVO = new ResultVO();//API统一响应封装类
				resultVO = mapper.readValue(result,ResultVO.class);

				String ip = getRealIP(request);
				String header = request.getHeader("User-Agent");
				//hutools工具类
				UserAgent ua = UserAgentUtil.parse(header);

				eventLog.setUserId(userId);
				eventLog.setEmail(email);
				eventLog.setParams(requestParams.toString());
				eventLog.setUrl(request.getRequestURI());
				eventLog.setIp(ip);
				eventLog.setBrowser(ua == null ? "" : ua.getBrowser() == null ? "" : ua.getBrowser().toString());
				eventLog.setOperatingSystem(ua == null ? "" : ua.getPlatform() == null ? "" : ua.getPlatform().toString());
				eventLog.setStatus(status);
				eventLog.setUseTime(useTime);
				eventLog.setCode(resultVO.getCode());
				eventLog.setData(resultVO.getDate());
				eventLog.setMsg(resultVO.getMsg());
				eventLog.setCreateTime(start);

				log.info(mapper.writeValueAsString(eventLog));
			}
		}

	}

	/**
	 * 获取请求的真实IP
	 * @param request
	 * @return
	 */
	public static String getRealIP(HttpServletRequest request) {
		String ip = request.getHeader("X-Forwarded-For");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			//多次反向代理后会有多个ip值,第一个ip才是真实ip
			int index = ip.indexOf(",");
			if (index != -1) {
				return ip.substring(0, index);
			} else {
				return ip;
			}
		}
		ip = request.getHeader("X-Real-IP");
		if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
			return ip;
		}
		return request.getRemoteAddr();
	}

	private String getRequestBody(HttpServletRequest request) {
		String requestBody = "";
		ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
		if (wrapper != null) {
			try {
				requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
			} catch (IOException e) {
				// NOOP
			}
		}
		return requestBody;
	}

	private String getResponseBody(HttpServletResponse response) {
		String responseBody = "";
		ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
		if (wrapper != null) {
			try {
				responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
			} catch (IOException e) {
				// NOOP
			}
		}
		return responseBody;
	}

}

4、SpringBoot,Application类注册Filter

   /**
	 * 记录http请求日志
	 * @return
	 */
	@Bean
	public FilterRegistrationBean httpEventFilter() {
		FilterRegistrationBean registration = new FilterRegistrationBean(new HttpEventLogFilter());
		registration.addUrlPatterns("/api/*");
		return registration;
	}
四、总结

实际需求拆解出来的思考过程和关键代码实现。上述代码未考虑是用户否登录情况,即用户信息日志,加上session值的判断更加完整。

AOP实现地址:待补充

参考链接:https://www.sofastack.tech/projects/

SOFATracer 是蚂蚁金服开发的基于 OpenTracing 规范 的分布式链路跟踪系统,其核心理念就是通过一个全局的 TraceId 将分布在各个服务节点上的同一次请求串联起来。通过统一的 TraceId 将调用链路中的各种网络调用情况以日志的方式记录下来同时也提供远程汇报到 Zipkin 进行展示的能力,以此达到透视化网络调用的目的。


本文链接: http://www.dtmao.cc/news_show_350302.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?