All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tosan.client.http.starter.impl.feign.logger.HttpFeignClientLogger Maven / Gradle / Ivy

package com.tosan.client.http.starter.impl.feign.logger;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.util.RawValue;
import com.tosan.tools.mask.starter.dto.JsonReplaceResultDto;
import com.tosan.tools.mask.starter.replace.JsonReplaceHelperDecider;
import feign.Request;
import feign.Response;
import feign.Util;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static feign.Util.UTF_8;

/**
 * @author Ali Alimohammadi
 * @since 8/7/2023
 */
public class HttpFeignClientLogger extends feign.Logger {
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final Logger logger = LoggerFactory.getLogger(HttpFeignClientLogger.class);

    static {
        mapper.enable(SerializationFeature.INDENT_OUTPUT)
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    private final String webServiceName;
    private final JsonReplaceHelperDecider replaceHelperDecider;

    public HttpFeignClientLogger(String webServiceName, JsonReplaceHelperDecider replaceHelperDecider) {
        this.webServiceName = webServiceName;
        this.replaceHelperDecider = replaceHelperDecider;
    }

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        if (logger.isInfoEnabled()) {
            final Map requestData = new LinkedHashMap<>();
            requestData.put("invoke", webServiceName);
            String urlString = request.url();
            boolean hasQueryString = urlString.contains("?");
            if (hasQueryString) {
                String[] splitUrl = urlString.split("[?]");
                String maskedQueryString = splitUrl.length > 1 ? getMaskedQueryString(splitUrl[1]) : "";
                requestData.put("service", getServiceName(methodTag(configKey)));
                requestData.put("url", request.httpMethod() + " " + splitUrl[0] + "?" + maskedQueryString);
            } else {
                requestData.put("service", getServiceName(methodTag(configKey)));
                requestData.put("url", request.httpMethod() + " " + urlString);
            }

            if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
                if (!request.headers().isEmpty()) {
                    requestData.put("headers", getMaskedHeaders(request.headers()));
                }
                if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                    byte[] byteArrayBody = request.body();
                    if (byteArrayBody != null) {
                        String maskedBody = replaceHelperDecider.replace(new String(byteArrayBody, StandardCharsets.UTF_8));
                        requestData.put("body", new RawValue(maskedBody));
                    }
                }
            }
            logger.info(toJson(requestData));
        }
    }

    @Override
    protected void log(String configKey, String format, Object... args) {

    }

    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        if (logger.isInfoEnabled()) {
            final Map responseData = new LinkedHashMap<>();
            responseData.put("invoked", webServiceName);
            int status = response.status();
            responseData.put("service", getServiceName(methodTag(configKey)));
            responseData.put("duration", elapsedTime / 1000.0 + "s");
            responseData.put("status", status);
            if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
                if (!response.headers().isEmpty()) {
                    responseData.put("headers", getMaskedHeaders(response.headers()));
                }
                if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                    if (response.body() != null && !(status == 204 || status == 205)) {
                        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                        if (bodyData != null && bodyData.length > 0) {
                            String securedBody = decodeOrDefault(bodyData, UTF_8, "Binary data");
                            responseData.put("body", new RawValue(securedBody));
                        }
                        response = response.toBuilder().body(bodyData).build();
                    }
                }
            }
            logger.info(toJson(responseData));
        }
        return response;
    }

    @Override
    protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
        if (logger.isInfoEnabled()) {
            Map exceptionData = new LinkedHashMap<>();
            exceptionData.put("invoked", webServiceName);
            exceptionData.put("service", getServiceName(methodTag(configKey)));
            exceptionData.put("duration", elapsedTime / 1000.0 + "s");
            exceptionData.put("exception", ioe.getClass().getSimpleName());
            exceptionData.put("message", ioe.getMessage());
            if (logLevel.ordinal() >= Level.FULL.ordinal() && logger.isDebugEnabled()) {
                exceptionData.put("stackTrace", getStackTrace(ioe));
            }
            logger.warn(toJson(exceptionData));
        }
        return ioe;
    }

    public String decodeOrDefault(byte[] data, Charset charset, String defaultValue) {
        try {
            String bodyString = charset.newDecoder().decode(ByteBuffer.wrap(data)).toString();
            return replaceHelperDecider.replace(bodyString);
        } catch (CharacterCodingException ex) {
            return defaultValue;
        }
    }

    private String getServiceName(String methodTag) {
        return methodTag.substring(1, methodTag.length() - 2);
    }

    private List getStackTrace(Throwable ex) {
        List stackTrace = new ArrayList<>();
        for (StackTraceElement element : ex.getStackTrace()) {
            stackTrace.add(element.toString());
            if (stackTrace.size() > 15) {
                break;
            }
        }
        return stackTrace;
    }

    private String getMaskedQueryString(String queryString) {
        if (StringUtils.isEmpty(queryString)) {
            return queryString;
        }
        StringBuilder result = new StringBuilder();
        String[] queryParams = queryString.split("&");
        for (String queryParam : queryParams) {
            String[] fieldValueSplit = queryParam.split("=");
            if (fieldValueSplit.length == 2) {
                String maskedValue = replaceHelperDecider.replace(fieldValueSplit[0], fieldValueSplit[1]);
                result.append(fieldValueSplit[0]).append("=").append(maskedValue);
            } else {
                result.append(queryParam);
            }
            result.append("&");
        }
        result.deleteCharAt(result.length() - 1);
        return result.toString();
    }

    private Map> getMaskedHeaders(Map> headers) {
        Map> securedHeaders = new HashMap<>();

        for (Map.Entry> entry : headers.entrySet()) {
            String headerName = entry.getKey();
            Collection headerValues = entry.getValue();
            Collection maskedHeaderValues = new ArrayList<>();
            headerValues.forEach(headerValue -> {
                if (headerValue != null && !headerValue.isEmpty()) {
                    JsonReplaceResultDto jsonReplaceResultDto = replaceHelperDecider.checkJsonAndReplace(headerValue);
                    if (!jsonReplaceResultDto.isJson()) {
                        maskedHeaderValues.add(replaceHelperDecider.replace(headerName, headerValue));
                    } else {
                        maskedHeaderValues.add(jsonReplaceResultDto.getReplacedJson());
                    }
                }
            });
            securedHeaders.put(headerName, maskedHeaderValues);
        }
        return securedHeaders;
    }

    private String toJson(Object object) {
        try {
            return mapper.writeValueAsString(object);
        } catch (JsonProcessingException exception) {
            return "error creating json. " + exception.getMessage();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy