异步记录第三方接口调用日志的优雅实现(HttpClient+装饰者模式+异步线程池)

发布时间 2023-12-19 12:50:07作者: ashet

对于第三方接口调用日志这个功能,笔者在工作中曾见过以下两种方式:

Restemplate+装饰者模式+MQ实现

网关监控 + Feign拦截器 + 观察者模式实现

其中观察者模式的实现是我最为佩服的设计,个人认为以上两种实现都显得略过臃肿,应该简化设计,让异步记录的实现更加简洁优雅,因此产生了这样的构思。

为什么选择HttpClient而不是RestTemplate?

其一是因为RestTemplate默认情况下是基于Java原生HTTP的支持,在性能上对比HttpClient略显不足。

其二是RestTemplate在一些版本较老的SpringBoot版本存在较多bug,不够稳定。

其三是HttpClient拥有更广泛的适用性。

异步线程池的实现可参考我的另一篇博客

/**
 * DecoratedHttpClient
 * 特性:异步记录第三方接口调用日志
 */
@Slf4j
public class DecoratedHttpClient {
    
    private final HttpClient httpClient;
    
    /**
     * 默认的HttpClient
     * 跳过SSL证书验证
     */
    public DecoratedHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException{
        this.httpClient = HttpClients.custom()
            .setSSLSocketFactory(
                new SSLConnectionSocketFactory(SSLContextBuilder.create().loadTrustMaterial(TrustAllStrategy.INSTANCE).build()),
                NoopHostnameVerifier.INSTANCE)
            )
            .build()
    }
    
    /**
     * 自定义的HttpClient
     */
    public DecoratedHttpClient(HttpClient httpClient){
        this.httpClient = httpClient;
    }
    
    public String callApi(HttpUriRequest httpUriRequest) throws Exception{
        try {
            HttpResponse httpResponse = httpClient.execute(httpUriRequest);
            String apiResponse = getResponse(httpResponse);
            ThreadPoolSington.getInstance().submitTask(new LogRecordAsyncTask(httpUriRequest, apiResponse, null));
            return apiResponse;
        } catch(IOException e){
            ThreadPoolSington.getInstance().submitTask(new LogRecordAsyncTask(httpUriRequest, null, e));
            throw e;
        }
    }
    
    private String getResponse(HttpResponse httpResponse) throws IOException{
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent()))){
            String line;
            while((line = reader.readLine()) != null){
                stringBuilder.append(line);
            }
        }
        return stringBuilder.toString();
    }

}

 

/**
 * 异步日志任务
 */
@Slf4j
public class LogRecordAsyncTask implents Runnable {
    
    private final HttpUriRequest httpUriRequest;
    private final String apiResponse;
    private final Exception exception;
    
    public LogRecordAsyncTask(HttpUriRequest httpUriRequest, String apiResponse, Exception exception){
        this.httpUriRequest = httpUriRequest;
        this.apiResponse = apiResponse;
        this.exception = exception;
    }
    
    @Override
    public String toString() {
        // TODO 代码均为手敲,顶不住了...   
    }
    
    @Override
    public void run() {
        // TODO 持久化日志
        log.info("第三方接口调用日志 ---> {}", this.toString());
    }
    
}

从代码中可以看到我的设计,记录了HttpUriRequest,第三方接口的原始返回body,异常调用的信息,可以更好的监控第三方接口的调用情况。

在run方法中你甚至还可以拓展实时报警机制,可参考我的另一篇博客