FeignClient CustomLogger 구현

SeungHyuk Shin·2023년 2월 16일
0

class FeignCustomLogger() : Logger() {

    private val log = LoggerFactory.getLogger(this::class.java)

    override fun log(configKey: String?, format: String?, vararg args: Any?) {
        val traceId = getTraceId()
        
		// 로깅
    }

    override fun logRequest(configKey: String?, logLevel: Level?, request: Request) {
    
        // 요청 로깅 구현 ...
    }

    override fun logAndRebufferResponse(
        configKey: String,
        logLevel: Level,
        response: Response,
        elapsedTime: Long,
    ): Response {
    
        // 응답 로깅 구현 ...
        
        return response
    }

    private fun extractHost(url: String): Pair<String, String> {
        return try {
            val urlObj = URL(url)
            Pair(urlObj.host, urlObj.path + if (urlObj.query != null) "?" + urlObj.query else "")
        } catch (e: MalformedURLException) {
            val match = Regex("(?<=//)[^/]+(/.*)?").find(url ?: "")
            Pair(match?.value?.substringBefore("/") ?: "", match?.value?.substringAfter("/") ?: "")
        }
    }

    private fun getTraceId(): String {
        return RequestContextHolder.currentRequestAttributes().getAttribute("traceId", 0).toString()
    }

    private fun readBody(body: ByteArray): String {
        val inputStream = ByteArrayInputStream(body)
        val reader = InputStreamReader(inputStream, Charset.forName("UTF-8"))
        return reader.readText()
    }
}

다음과 같이 FeignClient용 CustomLogger를 구현했는데 java.io.IOException: stream is closed 오류가 발생했고 원인은 FeginClient 메시지 디코더였다.

  @Bean
    fun defaultFeignDecoder(): Decoder {
        val jacksonConverter: HttpMessageConverter<*> = MappingJackson2HttpMessageConverter(JsonUtils.objectMapper)
        val objectFactory: ObjectFactory<HttpMessageConverters> = ObjectFactory<HttpMessageConverters> {
            HttpMessageConverters(
                jacksonConverter,
            )
        }

        return object : ResponseEntityDecoder(SpringDecoder(objectFactory)) {
            override fun decode(response: Response?, type: Type?): Any? {
                if (response?.status() == 404 || response?.body() == null) {
                    return null
                }
                return super.decode(response, type)
            }
        }
    }

CustomLogger에서 Response Body Stream을 이미 열어 확인을 한 후에 ResponseEntityDecoder에 요청을 보내는데 ResponseEntityDecoder 입장에서는 이미 스트림이 닫혀있어 오류가 발생한 것이였고, 따라서 Logging 시에는 Response body를 직접 열지 않고 Response 전체를 로깅하는것으로 변경하였다.

참고용으로 FeignClient 라이브러리 ResponseHandler 클래스의 코드를 첨부한다.

  void handleResponse(CompletableFuture<Object> resultFuture,
                      String configKey,
                      Response response,
                      Type returnType,
                      long elapsedTime) {
    // copied fairly liberally from SynchronousMethodHandler
    boolean shouldClose = true;

    try {
      if (logLevel != Level.NONE) {
        response = logger.logAndRebufferResponse(configKey, logLevel, response,
            elapsedTime);
      }
      if (Response.class == returnType) {
        if (response.body() == null) {
          resultFuture.complete(response);
        } else if (response.body().length() == null
            || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
          shouldClose = false;
          resultFuture.complete(response);
        } else {
          // Ensure the response body is disconnected
          final byte[] bodyData = Util.toByteArray(response.body().asInputStream());
          resultFuture.complete(response.toBuilder().body(bodyData).build());
        }
      } else if (response.status() >= 200 && response.status() < 300) {
        if (isVoidType(returnType)) {
          resultFuture.complete(null);
        } else {
          final Object result = decode(response, returnType);
          shouldClose = closeAfterDecode;
          resultFuture.complete(result);
        }
      } else if (decode404 && response.status() == 404 && !isVoidType(returnType)) {
        final Object result = decode(response, returnType);
        shouldClose = closeAfterDecode;
        resultFuture.complete(result);
      } else {
        resultFuture.completeExceptionally(errorDecoder.decode(configKey, response));
      }
    } catch (final IOException e) {
      if (logLevel != Level.NONE) {
        logger.logIOException(configKey, logLevel, e, elapsedTime);
      }
      resultFuture.completeExceptionally(errorReading(response.request(), response, e));
    } catch (final Exception e) {
      resultFuture.completeExceptionally(e);
    } finally {
      if (shouldClose) {
        ensureClosed(response.body());
      }
    }

  }

수정) 2023/04/07 : Stream is Closed 오류가 간헐적으로 발생중..

0개의 댓글