首页 > 其他分享 >Spring Boot项目请求日志打印

Spring Boot项目请求日志打印

时间:2023-11-27 15:56:31浏览次数:30  
标签:return String Spring Boot request private import 日志 public

Spring Boot项目请求日志打印

接口请求日志打印效果如图,基本符合中小型项目所需

直接上代码

  • 本代码中使用了hutool的工具包,需要先导入依赖
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>
  • 先来一个json payload处理类
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import org.springframework.http.MediaType;

import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;

@Getter
public class JsonPayload {
    private final String payload;
    private final Map<String, String> headers;

    public JsonPayload(String payload) {
        this.payload = payload;
        this.headers = Collections.emptyMap();
    }

    public JsonPayload(String payload, Map<String, String> headers) {
        this.payload = payload;
        this.headers = headers;
    }

    public static boolean isText(MediaType mediaType) {
        return mediaType.isCompatibleWith(MediaType.TEXT_HTML) || mediaType.isCompatibleWith(MediaType.TEXT_PLAIN);
    }

    public static boolean isJson(MediaType mediaType) {
        return mediaType.isCompatibleWith(MediaType.APPLICATION_JSON);
    }

    public static boolean isText(String contentType) {
        if (StrUtil.isBlank(contentType)) {
            return false;
        }
        try {
            return isText(MediaType.parseMediaType(contentType));
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean isJson(String contentType) {
        if (StrUtil.isBlank(contentType)) {
            return false;
        }
        try {
            return isJson(MediaType.parseMediaType(contentType));
        } catch (Exception e) {
            return false;
        }
    }

    public static String toString(byte[] data, String characterEncoding) {
        if (data.length > 0) {
            Charset charset = characterEncoding == null ? Charset.defaultCharset() : Charset.forName(characterEncoding);
            return new String(data, charset);
        } else {
            return null;
        }
    }
}

  • 再来一个json字符串的处理类
import java.util.Map;

public class JsonStringBuilder {
    private static final char OBJ_START = '{';
    private static final char OBJ_END = '}';
    private static final char FIELD_SPLIT = ',';
    private static final char VALUE_SPLIT = ':';
    private static final String NULL_VALUE = "null";
    public static final char WRAP = '"';
    private static final char ESCAPE_PREFIX = '\\';

    private final static int[] OUTPUT_ESCAPES;

    static {
        int[] table = new int[128];
        for (int i = 0; i < 32; ++i) {
            table[i] = -1;
        }
        table['"'] = '"';
        table['\\'] = '\\';
        table[0x08] = 'b';
        table[0x09] = 't';
        table[0x0C] = 'f';
        table[0x0A] = 'n';
        table[0x0D] = 'r';
        OUTPUT_ESCAPES = table;
    }

    private final StringBuilder stringBuilder;

    public JsonStringBuilder() {
        this(512);
    }

    public JsonStringBuilder(int capacity) {
        this.stringBuilder = new StringBuilder(capacity);
    }

    public JsonStringBuilder startObject() {
        this.stringBuilder.append(OBJ_START);
        return this;
    }

    public JsonStringBuilder endObject() {
        this._removeSplit();
        this.stringBuilder.append(OBJ_END);
        this.stringBuilder.append(FIELD_SPLIT);
        return this;
    }

    public JsonStringBuilder startObject(String name) {
        return _field(name).startObject();
    }

    public JsonStringBuilder field(String name, String value) {
        if (value == null) {
            return nullField(name);
        }
        _field(name);
        this.stringBuilder.append(WRAP);
        for (char c : value.toCharArray()) {
            if (c <= 0x7F && OUTPUT_ESCAPES[c] != 0) {
                this.stringBuilder.append(ESCAPE_PREFIX);
                this.stringBuilder.append(c);
            } else {
                this.stringBuilder.append(c);
            }
        }
        this.stringBuilder.append(WRAP);
        this.stringBuilder.append(FIELD_SPLIT);
        return this;
    }

    public JsonStringBuilder field(String name, Integer value) {
        if (value == null) {
            return nullField(name);
        }
        return fieldRawValue(name, Integer.toString(value));
    }

    public JsonStringBuilder fieldRawValue(String name, String value) {
        if (value == null) {
            return nullField(name);
        }
        _field(name);
        this.stringBuilder.append(value);
        this.stringBuilder.append(FIELD_SPLIT);
        return this;
    }

    public JsonStringBuilder field(String name, Map<String, String> value) {
        if (value == null) {
            return nullField(name);
        }
        startObject(name);
        for (Map.Entry<String, String> entry : value.entrySet()) {
            field(entry.getKey(), entry.getValue());
        }
        endObject();
        return this;
    }


    public JsonStringBuilder nullField(String name) {
        return fieldRawValue(name, NULL_VALUE);
    }

    public String build() {
        this._removeSplit();
        return this.stringBuilder.toString();
    }

    private void _removeSplit() {
        int last = this.stringBuilder.length() - 1;
        if (this.stringBuilder.charAt(last) == FIELD_SPLIT) {
            this.stringBuilder.deleteCharAt(last);
        }
    }

    private JsonStringBuilder _field(String name) {
        this.stringBuilder.append(WRAP);
        this.stringBuilder.append(name);
        this.stringBuilder.append(WRAP);
        this.stringBuilder.append(VALUE_SPLIT);
        return this;
    }


    public static byte[] compress(byte[] data) {
        int offset = 0;
        int length = data.length;
        final byte[] output = new byte[length];
        int counter = 0;
        for (int index = 0; index < length; index++) {
            byte b = data[index];
            if (b == 0x08) {
                continue;
            } else if (b == 0x09) {
                continue;
            } else if (b == 0x0C) {
                continue;
            } else if (b == 0x0A) {
                continue;
            } else if (b == 0x0D) {
                continue;
            } else {
                if (b == 0x22) {
                    counter++;
                }
                if (counter % 2 == 0) {
                    if (b == 0x20) {
                        continue;
                    }
                }
                output[offset] = b;
                offset++;
            }
        }
        if (offset == length) {
            return output;
        } else {
            byte[] dest = new byte[offset];
            System.arraycopy(output, 0, dest, 0, offset);
            return dest;
        }
    }

    public static void escape(String s, StringBuffer sb) {
        if (s == null) return;
        final int len = s.length();
        for (int i = 0; i < len; i++) {
            char ch = s.charAt(i);
            switch (ch) {
                case '"':
                    sb.append("\\\"");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '/':
                    sb.append("\\/");
                    break;
                default:
                    //Reference: http://www.unicode.org/versions/Unicode5.1.0/
                    if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
                        String ss = Integer.toHexString(ch);
                        sb.append("\\u");
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            sb.append('0');
                        }
                        sb.append(ss.toUpperCase());
                    } else {
                        sb.append(ch);
                    }
            }
        }//for
    }
}
  • request请求体处理类
import lombok.Getter;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.ServletInputStream;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

@Getter
public class LogRequest extends JsonPayload {
    private final String method;
    private final String remoteAddr;
    private final String requestURI;
    private final Map<String, String> parameters;

    private static final byte[] EMPTY = new byte[0];

    public LogRequest(ContentCachingRequestWrapper request) {
        super(getPayload(request, request.getCharacterEncoding(), request.getContentType()), getHeaders(request));
        this.method = request.getMethod();
        this.remoteAddr = request.getRemoteAddr();
        this.requestURI = request.getRequestURI();
        this.parameters = getRequestParam(request);
    }

    public static Map<String, String> getHeaders(ContentCachingRequestWrapper request) {
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<>();
        for (Enumeration<String> names = request.getHeaderNames(); names.hasMoreElements(); ) {
            String headerName = names.nextElement();
            singleValueMap.put(headerName, request.getHeader(headerName));
        }
        return singleValueMap;
    }

    private static Map<String, String> getRequestParam(ContentCachingRequestWrapper request) {
        Map<String, String[]> parameters = request.getParameterMap();
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<>(parameters.size());
        parameters.forEach((key, valueList) -> singleValueMap.put(key, valueList == null ? null : valueList[0]));
        return singleValueMap;
    }

    private static String getPayload(ContentCachingRequestWrapper request, String characterEncoding, String contentType) {
        if (!isJson(contentType)) {
            return null;
        }
        return toString(getPayload(request), characterEncoding);
    }

    private static byte[] getPayload(ContentCachingRequestWrapper request) {
        try {
            ServletInputStream is = request.getRequest().getInputStream();
            byte[] rawData;
            if (is.isFinished()) {
                rawData = request.getContentAsByteArray();
            } else {
                rawData = StreamUtils.copyToByteArray(request.getInputStream());
            }
            return JsonStringBuilder.compress(rawData);
        } catch (Exception e) {
            return EMPTY;
        }
    }
}
  • response响应体处理类
import lombok.Getter;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

@Getter
public final class LogResponse extends JsonPayload {
    private final int status;

    public LogResponse(ContentCachingResponseWrapper response) {
        super(getPayload(response, response.getCharacterEncoding(), response.getContentType()), getHeaders(response));
        this.status = response.getStatus();
    }

    public static Map<String, String> getHeaders(ContentCachingResponseWrapper response) {
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<>();
        Collection<String> names = response.getHeaderNames();
        for (String headerName : names) {
            singleValueMap.put(headerName, response.getHeader(headerName));
        }
        return singleValueMap;
    }

    private static String getPayload(ContentCachingResponseWrapper response, String characterEncoding, String contentType) {
        if (!isJson(contentType)) {
            if (isText(contentType)) {
                StringBuffer sb = new StringBuffer();
                sb.append(JsonStringBuilder.WRAP);
                JsonStringBuilder.escape(toString(response.getContentAsByteArray(), characterEncoding), sb);
                sb.append(JsonStringBuilder.WRAP);
                return sb.toString();
            } else {
                return null;
            }
        }
        return toString(response.getContentAsByteArray(), characterEncoding);
    }
}
  • filter拦截器处理类
import cn.hutool.json.JSONUtil;
import com.ximen.cocktailserver.utils.JsonStringBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

@Component
@Slf4j(topic = "request.log")
public final class LogFilter extends OncePerRequestFilter {

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 白名单的路径不做处理
        if (writeList(String.valueOf(request.getRequestURI()))) {
            filterChain.doFilter(request, response);
            return;
        }
        long cost = 0;
        HttpServletRequest requestToUse = request;
        if (!isAsyncDispatch(request) && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request);
        }
        HttpServletResponse responseToUse = response;
        if (!isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
            responseToUse = new ContentCachingResponseWrapper(response);
        }
        try {
            StopWatch watch = new StopWatch();
            watch.start();
            filterChain.doFilter(requestToUse, responseToUse);
            watch.stop();
            cost = watch.getTotalTimeMillis();
        } finally {
            if (!isAsyncStarted(requestToUse)) {
                LogRequest logRequest = new LogRequest((ContentCachingRequestWrapper) requestToUse);
                LogResponse logResponse = new LogResponse((ContentCachingResponseWrapper) responseToUse);
                boolean isJson = JSONUtil.isTypeJSON(logResponse.getPayload());
                // 根据自己的情况,判断什么时候需要打印日志,我是把错误的请求日志才打印出来
                if (logResponse.getStatus() != 200 || (isJson && JSONUtil.parseObj(logResponse.getPayload()).getInt("code", 1001) != 200)) {
                    log.info(logFormatter(logRequest, logResponse, cost));
                }
                ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
                if (wrapper != null) {
                    wrapper.copyBodyToResponse();
                }
            }
        }
    }

    private String logFormatter(LogRequest request, LogResponse response, long spendTime) {
        JsonStringBuilder jsonBuilder = new JsonStringBuilder(2000);
        jsonBuilder.startObject()
                .field("timestamp", LocalDateTime.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
                .field("uri", request.getRequestURI())
                .field("method", request.getMethod())
                .field("refer", request.getRemoteAddr())
                .field("status", response.getStatus())
                .field("time", spendTime + "ms")
                .startObject("request")
                .fieldRawValue("body", request.getPayload())
                .field("param", request.getParameters())
                .field("header", request.getHeaders())
                .endObject()
                .fieldRawValue("response", response.getPayload())
                .endObject();
        return jsonBuilder.build();
    }

    private boolean writeList(String path) {
        return path.equals("/") ||
                path.startsWith("/actuator") ||
                path.startsWith("/plumelog") ||
                path.startsWith("/doc.html") ||
                path.startsWith("/swagger-resources") ||
                path.startsWith("/v2/api-docs") ||
                path.equals("/swagger-ui.html") ||
                path.startsWith("/webjars");
    }

}

白名单这块自己根据情况去加吧,equals就是某个全路径,startsWith就是以什么开始的路径。(这里的路径都指的是除去localhost端口号那些,看代码应该就能知道)

  • 如果日志打印出来的编码有问题,可以设置下yml配置文件
server:
  servlet:
    encoding:
      charset: UTF-8
      force-response: true
  • 一共就这么几个类

标签:return,String,Spring,Boot,request,private,import,日志,public
From: https://www.cnblogs.com/ximensama/p/17859531.html

相关文章

  • Linux重要的日志文件
    1、/var/log/boot.log该文件记录了系统在引导过程中发生的事件,就是Linux系统开机自检过程显示的信息2、/var/log/syslog只记录警告信息,常常是系统出问题的信息,所以更应该关注该文件3、/var/log/wtmp该日志文件永久记录每个用户登录、注销及系统的启动、停机的事件4、/var/ru......
  • 解密Spring Cloud微服务调用:如何轻松获取请求目标方的IP和端口
    公众号「架构成长指南」,专注于生产实践、云原生、分布式系统、大数据技术分享。目的SpringCloud线上微服务实例都是2个起步,如果出问题后,在没有ELK等日志分析平台,如何确定调用到了目标服务的那个实例,以此来排查问题效果可以看到服务有几个实例是上线,并且最终调用了那个实......
  • Spring MVC学习随笔-控制器(Controller)开发详解:接受客户端(Client)请求参数
    学习视频:孙哥说SpringMVC:结合Thymeleaf,重塑你的MVC世界!|前所未有的Web开发探索之旅第三章、SpringMVC控制器开发详解3.1核心要点......
  • 10道不得不会的SpringBoot面试题
    以下是SpringBoot面试题,相信大家都会有种及眼熟又陌生的感觉、看过可能在短暂的面试后又马上忘记了。JavaPub在这里整理这些容易忘记的重点知识及解答,建议收藏,经常温习查阅。来看看你会做几道1.为什么要用springboot?2.springboot有哪些优点?3.springboot核心配置文件是什......
  • springBoot项目启动卡在Root WebApplicationContext: initialization completed... (非
    通过源码启动排查发现,初始化连接池时创建了200次,通过配置文件中将initialSize值改为10,启动时间从1分钟变为了3秒。 ......
  • springboot 在配置文件中对敏感信息加密
    pom文件引入2.1.0版本对应springboot2.1.4release<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.0</version></dependency>配......
  • Oracle 数据库存储过程调用SpringBoot API 接口方法
    数据库存储过程代码CREATEORREPLACEPROCEDUREFSMT.P_GET_HTTP_RES/*描述:存儲過程掉用HTTP接口作者:Janus日期:2023-11-23*/(M_DOC_NOINVARCHAR2,--??M_DOC_TYPEINVARCHAR2,--?据?型M_STANDBYINVARCHAR2,--?用字段M_EM......
  • springboot打印启动信息
    打印启动信息转载自:www.javaman.cn1springBean实例化流程基本流程:1、Spring容器在进行初始化时,会将xml或者annotation配置的bean的信息封装成一个BeanDefinition对象(每一个bean标签或者@bean注解都封装成一个BeanDefinition对象),所有的BeanDefinition存储到一个名为beanDefinitio......
  • 基于springboot的医护人员排班系统-计算机毕业设计源码+LW文档
    选题的意义、研究内容及方法:(后面附主要参考文献)选题意义目的科学合理地安排医护人员工作,提高医护人员排班效率,在满足各病区个性化的基础上保证医护人员的统一管理。方法采用软件生命周期开发方法,自顶向下,逐步细化。对各个科室医护人员的排班信息进行维护,维护好相关信息后进行......
  • 学习Springboot的第五天
    一、运行复杂代码通过第四天的学习,解决了粗心错误,瞬间信心倍增,终于能赶赶进度了,此时我想创造的心按耐不住了,现实是残酷的。。。。打开一个复杂点的新工程之后,没有报jdk的问题,可是又出现另外的插件问题我又搜了一下,又是settings.xml的问题,还是得配置一段代码此处是settings.xm......