美文网首页一些收藏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();
                }
            }
        }
    }
    

    相关文章

      网友评论

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

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