最近项目中需要将用户请求日志记录下来,打算通过Spring boot的logback将请求日志和一场数据通过发送到rsyslog,再通过rsyslog将日志发送到kafka中,之后就是消费处理就可以了。参考百度部分文章整理实现,个中问题欢迎指正,转载请指明出处
引入springboot logging
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
新建logback-spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!--
说明:
1. 文件的命名和加载顺序有关
logback.xml早于application.yml加载,logback-spring.xml晚于application.yml加载
如果logback配置需要使用application.yml中的属性,需要命名为logback-spring.xml
2. logback使用application.yml中的属性
使用springProperty才可使用application.yml中的值 可以设置默认值
-->
<configuration scan="true" scanPeriod="5 seconds" debug="false">
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<!-- 从yml文件中读取配置-->
<!-- rsyslog主机地址-->
<springProperty scope="context" name="syslogHost" source="logback.rsyslog.host" defaultValue="10.221.2.77"/>
<!-- rsyslog端口号-->
<springProperty scope="context" name="syslogPort" source="logback.rsyslog.port" defaultValue="514"/>
<!-- root日志级别-->
<springProperty scope="context" name="rootLevel" source="logback.level.root" defaultValue="INFO"/>
<!-- 业务代码的日志级别-->
<springProperty scope="context" name="commonLevel" source="logback.level.common" defaultValue="DEBUG"/>
<!-- 彩色日志 -->
<!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--withJansi>true</withJansi-->
<encoder>
<pattern>%boldCyan(%d{yyyy/MM/dd-HH:mm:ss}) [%thread] %highlight(%-5level) %logger- %msg%n %ex</pattern>
</encoder>
</appender>
<!-- pattern配置参考https://www.cnblogs.com/jasenin/p/12692690.html -->
<appender name="sysExceptionlog" class="ch.qos.logback.classic.net.SyslogAppender">
<syslogHost>${syslogHost}</syslogHost>
<port>${syslogPort}</port>
<facility>local2</facility>
<throwableExcluded>true</throwableExcluded>
<suffixPattern>|{"type":"exception","date":"%d{yyyy/MM/dd HH:mm:ss}","level": "%level","class":"%logger","message": "%replace(%message){'\"', '\''}","requestId":"%X{requestId}","stackTrace": "%replace(%ex{short}){'\"', '\''}"}</suffixPattern>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
<level>WARN</level>
</filter>
</appender>
<appender name="sysRequestlog" class="ch.qos.logback.classic.net.SyslogAppender">
<syslogHost>${syslogHost}</syslogHost>
<port>${syslogPort}</port>
<facility>local2</facility>
<throwableExcluded>true</throwableExcluded>
<suffixPattern>|{"type":"request","date":"%d{yyyy/MM/dd HH:mm:ss}","clientIp":"%X{clientIp}","requestId":"%X{requestId}","requestUrl":"%X{requestUrl}","requestMethod":"%X{requestMethod}","requestParam":"%X{requestParam}","requestBody":"%X{requestBody}","responseBody":"%X{responseBody}"}</suffixPattern>
</appender>
<appender name="asyncSysExceptionLog" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="sysExceptionlog" />
</appender>
<!--为了使日志记录对程序的正常运行产生影响,日志记录采用异步方式-->
<appender name="asyncSysRequestLog" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="sysRequestlog" />
</appender>
<root level="${rootLevel}">
<appender-ref ref="console"/>
<appender-ref ref="asyncSysExceptionLog"/>
</root>
<logger name="com.demo" level="${commonLevel}" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="asyncSysExceptionLog"/>
</logger>
<logger name="com.demo.config.filter.RequestLogFilter" level="info" additivity="false">
<appender-ref ref="asyncSysRequestLog"/>
</logger>
</configuration>
自定义filter
- filter的作用是用来拦截用户请求。通过MDC记录用户的请求url、IP、请求参数及相应参数等,方便对异常进行跟踪;
- 获取用户真实IP徐正确配置代理,此处以nginx为例,其他代理请自行百度:
location /demo {
proxy_pass http://localhost/demo;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
自定义wrapper,获取响应值
package com.demo.config.wrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @Title: RequestWrapper
* @Description: 包装HttpServletRequest,目的是让其输入流可重复读
* @Author: andy
* @CreateDate: 2021/7/29 17:50
* @Version: 1.0
*/
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
private static final Logger logger = LoggerFactory.getLogger(HttpServletRequestWrapper.class);
/**
* 用于保存读取body中数据
*/
private byte[] body = null;
public HttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
try {
body = StreamUtils.copyToByteArray(request.getInputStream());
} catch (IOException e) {
logger.error("Wrap requestBody failed");
}
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
}
};
}
}
获取到日志信息
2021-08-20T12:08:40+08:00 DESKTOP-O6HS2C6 |{"type": "request","date":"2021/08/20 12:08:40","clientIp":"124.133.195.42","requestId":"83acb55394f74b40a5294c36a02cb778","requestUrl":"/demo/api/test","requestMethod":"GET","requestParam":"phone=19900002222","requestBody":"","responseBody":"{\"code\":\"S20003\",\"msg\":\"手机号不存在\"}"}
日志信息由 日志产生时间、产生日志的主机名 | 日志类型(request:请求;exception:异常)、自定义时间格式、客户端IP、requestId(用于绑定请求与异常日志,方便跟踪查询)、请求接口、接口方式、请求param、请求body、响应body
评论 (1)