美文网首页一些收藏Spring Cloudspringboot
统一记录请求响应日志(包括外部调用的请求响应)

统一记录请求响应日志(包括外部调用的请求响应)

作者: 后来丶_a24d | 来源:发表于2020-04-21 13:58 被阅读0次

目录

  • 功能
  • 结构说明
  • 结果说明
  • 调用关系
  • 代码

功能

  • 统一记录请求响应日志,如果项目没有外部调用,只是单纯的被调用,那么其实使用aop也能达到记录请求响应的需求。但是如果需要记录调用外部接口的请求和响应,就需要别的处理方式.
  • 以下代码均是线程安全,才有线程封闭的方式保证线程安全

结构说明

整体结构.png

结果说明

  • 注意: response结构可以是{status: 200, {code: "...", msg:"..."} }
// 模拟的第一次请求调用记录的结构展示
// apiMethodName方法名称
key: apiMethodName, value: TestService
// sourceRequest请求
key: sourceRequest, value: {"id":"1","msg":"这是个测试的请求"}
// remoteInvokeCollection 远程调用集合
key: remoteInvokeCollection, value: === testRemoteInvoke ===
{"msg":"这是远程测试调用结果, request id为 1"}
// 时间
key: timeSpan, value: 116
// sourceResponse表示响应
key: sourceResponse, value: {"msg":"这是对id为: 1 请求的响应"}
————————————————————————————
// 模拟的第二次请求调用记录的结构展示
key: apiMethodName, value: TestService
key: sourceRequest, value: {"id":"2","msg":"这是个测试的请求2"}
key: remoteInvokeCollection, value: === testRemoteInvoke ===
{"msg":"这是远程测试调用结果, request id为 2"}

key: timeSpan, value: 0
key: sourceResponse, value: {"msg":"这是对id为: 2 请求的响应"}

调用关系

  • 首先调用unify.req.resp.test.service包下面的service方法,接着调用unify.req.resp.test.biz对应的方法,远程方法调用使用unify.req.resp.test.deal包模拟。unify.req.resp.test.checker包是效验参数用的。unify.req.resp.test.log包是记录到es的数据结构。

代码

Test类, 入口
import unify.req.resp.test.service.TestRequest;
import unify.req.resp.test.service.TestService;

public class Test {

    public static void main(String[] args) throws Exception {
        // 这里模拟调用过程,为了简单测试,并没有使用spring 而是直接new
        TestRequest testRequest = new TestRequest();
        testRequest.setId("1");
        testRequest.setMsg("这是个测试的请求");

        TestRequest testRequest2 = new TestRequest();
        testRequest2.setId("2");
        testRequest2.setMsg("这是个测试的请求2");

        //模拟调用
        TestService testService = new TestService();
        testService.service(testRequest);
        System.out.println("————————————————————————————");
        TestService testService2 = new TestService();
        testService2.service(testRequest2);
    }

}
service
  • package unify.req.resp.test.service;
public abstract class BaseService<Req, Resp> {

    /**
     * 通过统一处理请求响应,以对所有请求响应进行分析,记录
     *
     * @param request 请求
     * @return 响应
     */
    public final Resp service(Req request) {
        // 记录时间消耗作用
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Resp response = null;
        //通过此方法可以获取到响应具体类型
        /*Class<Resp> respClass = (Class<Resp>)
                ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];*/

        try {
            // 对req进行判空检查 使用Check类 抛异常形式
            ObjectChecker.NULL.check(request);
            // 后续子类继承对req请求参数进行判断
            validateRequest(request);
            //将request方面ThreadLocal以便后续使用
            addReqToThreadLocalContext(request);

            // 请求真正处理部分
            response = getBiz().process(request);
        } catch (BusinessException e) {
            // 对业务异常进行处理 比如打印
            System.out.println("有业务异常发生");
        } catch (Throwable cause) {
            // 对通用异常进行处理 比如打印
            System.out.println("有通用异常发生");
        } finally {
            // 如果response为空,则添加默认处理
            defaultResponse(response);

            // 将req, resp,调用链写入es之类的. 此处Gson可做额外处理,这里不做拓展
            Gson gson = new Gson();
            ThreadSafeLogRecord.setReqRespLog(gson.toJson(request), gson.toJson(response), this.getClass().getSimpleName(), stopWatch);
            ThreadSafeLogRecord.write();

            // 清理写入ThreadLocal
            ThreadLocalContext.clear();
        }
        return response;
    }


    private void defaultResponse(Resp response) {
        if (response == null) {
            // 对response进行处理
        }
    }


    protected abstract void validateRequest(Req request) throws BusinessException;

    private void addReqToThreadLocalContext(Req request) {
        @SuppressWarnings("unchecked")
        Class<Req> requestClass = (Class<Req>) request.getClass();
        ThreadLocalContext.add(requestClass, request);
    }

    protected abstract Biz<Req, Resp> getBiz();
}

public class TestRequest {
    private String id;
    private String msg;

    public String getId() {
        return id;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

public class TestResponse {
    private String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

public class TestService extends BaseService<TestRequest, TestResponse> {

    @Override
    protected void validateRequest(TestRequest request) throws BusinessException {

    }
    
    @Override
    protected Biz<TestRequest, TestResponse> getBiz() {
        // 这里没有用spring依赖注入,项目里面可以改,这边只是为了简单测试
        return new TestBiz();
    }
}

biz
  • package unify.req.resp.test.biz;
public interface Biz<Req, Resp> {
    Resp process(Req request) throws BusinessException;
}

public abstract class AbstractBiz<Req, Resp> implements Biz<Req, Resp> {

    @Override
    public Resp process(Req request) throws BusinessException {
        try {
            Resp response = this.exe(request);
            try {
                this.log(request, response);
            } catch (Throwable cause) {
                // 打印错误日志
            }
            return response;
        } catch (Exception e) {
            // 打印错误日志 并往上抛出
            throw new BusinessException();
        }
    }

    protected abstract Resp exe(Req request) throws Exception;

    protected abstract void log(Req request, Resp response) throws Exception;

}

public class TestBiz extends AbstractBiz<TestRequest, TestResponse> {

    @Override
    protected TestResponse exe(TestRequest request) throws Exception {
        TestResponse testResponse = new TestResponse();
        testResponse.setMsg(String.format("这是对id为: %s 请求的响应", request.getId()));

        // 当需要调用别系统请求接口时 也会记录, 模拟远程调用
        TestDeal testDeal = new TestDeal();
        testDeal.deal(request);

        return testResponse;
    }

    @Override
    protected void log(TestRequest request, TestResponse response) throws Exception {

    }
}
deal远程调用模拟
  • package unify.req.resp.test.deal;
public interface Deal<T, K> {
    K deal(T request) throws Exception;
}

public abstract class AbstractDeal<T, K> implements Deal<T, K> {

    @Override
    public final K deal(T request) throws Exception {
        // 远程调用

        // 这里response应该是远程调用的结果,这里模拟处理
        K response = processResponse(request);

        // 添加日志 gson 应该抽出来作为单例更合适,这里简单使用
        Gson gson = new Gson();
        ThreadSafeLogRecord.setRemoteInvokeLog(gson.toJson(response));
        return response;
    }

    protected abstract K processResponse(T request);
}

public class TestDeal extends AbstractDeal<TestRequest, TestResponse> {
    @Override
    protected TestResponse processResponse(TestRequest request) {
        TestResponse testResponse = new TestResponse();
        testResponse.setMsg(String.format("这是远程测试调用结果, request id为 %s", request.getId()));
        return testResponse;
    }
}
log
  • package unify.req.resp.test.log;
public interface KeyValue<T> {
    String getKey();

    T getValue();
}

public class DomainValue<T> implements KeyValue<T> {

    public DomainValue(String field) {
        this.field = field;
        this.value = null;
    }


    private String field;
    private T value;

    @Override
    public String getKey() {
        return field;
    }

    @Override
    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class DomainAppendedValue implements KeyValue<String>{

    public DomainAppendedValue(String field) {
        this.field = field;
        this.values = new StringBuffer();
    }

    private String field;
    private StringBuffer values;


    @Override
    public String getKey() {
        return field;
    }

    @Override
    public String getValue() {
        return values.toString();
    }

    public void append(Object value) {
        this.values.append(value);
    }

}

public final class ThreadLocalContext {
    private static final int CONTEXT_DEFAULT_SIZE = 1 << 5;

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<Map<String, Object>>() {
        @Override
        protected Map<String, Object> initialValue() {
            return new ConcurrentHashMap<>(CONTEXT_DEFAULT_SIZE);
        }
    };

    public static <T> void add(Class<T> clazz, T value) {
        add(clazz.getName(), value);
    }

    public static void add(String key, Object value) {
        if (CONTEXT.get().containsKey(key)) {
            throw new RuntimeException(String.format("conflict on context=[%s] when executor add method", key));
        }
        CONTEXT.get().put(key, value);
    }

    public static Object get(String key) {
        return CONTEXT.get().get(key);
    }

    public static void clear() {
        CONTEXT.get().clear();
    }
}

/**
 * 记录到es展示结果 resKey resValue resKey1 resValue1
 * 使用ThreadLocal封装 key: req_resp_log value: {resKey : resKey resValue; resKey1 resKey 1resValue1}
 */
public class ThreadSafeLogRecord {
    private static final String REQ_RESP_LOG = "req_resp_log";

    public static void addDomainAppendedValue(String key, String value) {
        KeyValue keyValue = get(key);
        if (keyValue == null) {
            keyValue = new DomainAppendedValue(key);
            add(key, keyValue);
        }
        DomainAppendedValue domainAppendedValue = (DomainAppendedValue) keyValue;
        domainAppendedValue.append(value);
    }

    public static <T> void addDomainValue(String key, T value) {
        KeyValue keyValue = get(key);
        if (keyValue == null) {
            keyValue = new DomainValue<T>(key);
            add(key, keyValue);
        }
        DomainValue<T> fieldValue = (DomainValue<T>) keyValue;
        fieldValue.setValue(value);
    }

    private static KeyValue<?> get(String key) {
        if (StringUtils.isEmpty(key)) {
            return null;
        }
        return getLogs().get(key);
    }

    private static Map<String, KeyValue<?>> getLogs() {
        Object value = ThreadLocalContext.get(REQ_RESP_LOG);
        if (value == null) {
            value = new HashMap<>();
            ThreadLocalContext.add(REQ_RESP_LOG, value);
        }
        return (Map<String, KeyValue<?>>) value;
    }

    private static void add(String key, KeyValue<?> keyValue) {
        if (!StringUtils.isEmpty(key) && keyValue != null) {
            getLogs().put(key, keyValue);
        }
    }

    public static void setReqRespLog(String request, String response, String simpleName, StopWatch stopWatch) {
        // req resp 可以 加 Gzip处理
        addDomainAppendedValue("sourceRequest", request);
        addDomainAppendedValue("sourceResponse", response);
        addDomainValue("timeSpan", stopWatch.getTime());
        addDomainValue("apiMethodName", simpleName);
    }

    public static void write() {
        Map<String, KeyValue<?>> map = getLogs();
        // 这里本该记录到es 但是这里只做演示输出
        for (KeyValue<?> keyValue : map.values()) {
            System.out.println("key: " + keyValue.getKey() + ", value: " + keyValue.getValue());
        }
    }

    public static void setRemoteInvokeLog(String req) {
        setRemoteInvokeRequest(String.format("=== %s ===", "testRemoteInvoke"));
        setRemoteInvokeRequest("\r\n");
        setRemoteInvokeRequest(req);
        setRemoteInvokeRequest("\r\n");
    }

    private static void setRemoteInvokeRequest(String request) {
        addDomainAppendedValue("remoteInvokeCollection", request);
    }
}
exception
  • package unify.req.resp.test.exception;
public class BusinessException extends Exception {
    // 这里简化业务异常处理类,一般需要记录 异常code, 异常msg。
}
参数校验
  • package unify.req.resp.test.checker;
public interface Checker<T> {
    void check(T value) throws BusinessException;
}

public enum ObjectChecker implements Checker<Object> {
    NULL {
        @Override
        public void check(Object value) throws BusinessException {
            if (value == null) {
                throw new BusinessException();
            }
        }
    }
}

相关文章

  • 统一记录请求响应日志(包括外部调用的请求响应)

    目录 功能 结构说明 结果说明 调用关系 代码 功能 统一记录请求响应日志,如果项目没有外部调用,只是单纯的被调用...

  • 网络教程

    http协议包括 请求协议:请求行、请求头、请求体 响应协议:响应行、响应头、响应体 servlet

  • HTTP与TCP协议的区别

    (一) HTTP是应用层协议,负责对请求和响应数据的封装,包括请求方法、请求头和请求正文,以及状态行,响应头和响应...

  • Vapor文档学习廿八: HTTP -Response

    在接收的请求后通常要返回Response作为响应。我们做外部请求时也要接收响应对象。 Status http请求状...

  • 服务API

    概述 首先服务的调用有统一的请求和响应参数格式。 系统服务

  • 请求、响应

    网址:协议 主机 路径 端口(包括请求数据) 锚点 请求 / 响应行 请求 / 响应首部(头) body(请求一般...

  • 【ajax】jqery ajax 请求与响应数据拦截修改

    使用 jq 统一拦截所有请求和响应,修改请求参数或者响应体返回值

  • 如何低侵入的记录调用日志

    前言 前阵子朋友他老大叫他实现这么一个功能,就是低侵入的记录接口每次的请求响应日志,然后并统计每次请求调用的成功、...

  • 网络请求相关

    HTTP 超文本传输协议 请求/响应报文 连接建立流程 HTTP的特点 1. 请求/响应报文 请求报文请求报文包括...

  • Spring Boot 接口访问日志

    参考文档 需求 框架需要记录每一个HTTP请求的信息,包括请求路径、请求参数、响应状态、返回参数、请求耗时等信息 ...

网友评论

    本文标题:统一记录请求响应日志(包括外部调用的请求响应)

    本文链接:https://www.haomeiwen.com/subject/tfeuihtx.html