Spring WebClient로 요청 본문 로깅하기
스프링 웹클라이언트에 대하여
Spring은 Reactive로 가고 있습니다. Spring One 2020 컨퍼런스에서 소개된 강연을 한 번만 보면 Java 및 Spring의 Reactive Web 및 함수형 프로그래밍 패러다임이 최전선에 있다는 것을 알 수 있습니다.
비차단 방식으로 작업을 수행할 때의 흥미로운 문제 중 하나는 로깅과 같은 간단한 작업이 때때로 조금 더 복잡해질 수 있다는 것입니다. 데이터를 언제 사용할 수 있는지 정확히 모르기 때문에 Spring의 RestTemplate과 같은 방식으로 데이터를 로그에 던질 수는 없습니다. (RestTemplate 이라고 하면 deprecation 대상인 것 같습니다! https://docs.spring.io/spring-framework/docs/current/javadoc-api/index.html?org/springframework/web/client/RestTemplate.html )
문서에 따르면, 특히 차단 및 비차단 방법을 활용할 수 있는 기능을 제공하기 때문에 오늘날 아웃바운드 API 호출에 org.springframework.web.reactive.client.WebClient
를 사용해야 합니다.
이제 Spring WebClient를 사용해 본 사람이라면 요청 내용이나 응답 내용을 검색하는 것이 때때로 약간 어려울 수 있다는 사실을 증명할 수 있습니다. 특히 특정 형식을 찾고 있는 경우에 그렇습니다.
답변이 없는 수십 개의 스택 오버플로 게시물이 있으며 동일한 응답을 가지고 있습니다. 주제에 대한 Baeldung의 기사를 확인하세요. https://www.baeldung.com/spring-log-webclient-calls .
이제 Baeldung은 내가 셀 수 있는 것보다 더 많은 시간을 사이드 프로젝트에서 그리고 전문적으로 저장했습니다. 그러나 이 기사에서는 기본적인 구현 예 이상을 보여주지 않습니다. 누락된 것은 예제 출력과 기사에서 언급되지 않은 주의 사항을 공유하는 것입니다.
그래서 더 이상 고민하지 않고 여기에 예제, 주석 및 출력과 함께 Spring Webclient에서 요청 및 응답 로깅(HTTP 본문 포함)을 수행하는 가장 좋은 방법(제 생각에는)을 살펴보겠습니다.
Netty 로깅은 Baeldung의 게시물에 포함되어 있지만 Jetty HTTP 클라이언트만큼 세분화되지는 않습니다. 첫 번째 단계는 기본 HTTP 클라이언트에 대한 액세스를 제공하는 필수 종속성을 추가하는 것입니다.
코드
# Gradle
implementation group: 'org.eclipse.jetty', name: 'jetty-reactive-httpclient', version: '1.1.4'
# Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-reactive-httpclient</artifactId>
<version>1.1.4</version>
</dependency>
필요한 클래스에 액세스할 수 있게 되면 빌드해야 하는 두 가지 구성 요소가 있습니다.
첫 번째는 강화 방법입니다. 이 메서드는 요청을 받고 다시 요청을 제공하여 우리가 관심 있는 모든 부분을 가로채고 기록할 수 있도록 합니다.
다음은 향상 방법의 예이며 결과는 다음과 같습니다.
// org.eclipse.jetty.client.api.Request
private Request enhance(Request inboundRequest) {
StringBuilder log = new StringBuilder();
// Request Logging
inboundRequest.onRequestBegin(request ->
log.append("Request: \n")
.append("URI: ")
.append(request.getURI())
.append("\n")
.append("Method: ")
.append(request.getMethod()));
inboundRequest.onRequestHeaders(request -> {
log.append("\nHeaders:\n");
for (HttpField header : request.getHeaders()) {
log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
}
});
inboundRequest.onRequestContent((request, content) ->
log.append("Body: \n\t")
.append(content.toString()));
log.append("\n");
// Response Logging
inboundRequest.onResponseBegin(response ->
log.append("Response:\n")
.append("Status: ")
.append(response.getStatus())
.append("\n"));
inboundRequest.onResponseHeaders(response -> {
log.append("Headers:\n");
for (HttpField header : response.getHeaders()) {
log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
}
});
inboundRequest.onResponseContent(((response, content) -> {
var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
log.append("Response Body:\n" + bufferAsString);
}));
// Add actual log invocation
logger.info("HTTP ->\n");
inboundRequest.onRequestSuccess(request -> logger.info(log.toString()));
inboundRequest.onResponseSuccess(response -> logger.info(log.toString()));
// Return original request
return inboundRequest;
}
요청 개체는 기록하려는 데이터에 도달하고 잡을 수 있는 많은 후크를 제공합니다. 인터페이스 문서는 여기 -> https://www.eclipse.org/jetty/javadoc/9.4.8.v20171121/org/eclipse/jetty/client/api/Request.html
WebClient를 호출하는 동안 강화 메서드를 실행하기 위해 자체 HttpClient를 만들고 기본 JettyClientHttpConnector 대신 사용할 것입니다. 다음은 WebClient를 제공하는 예제 빈입니다.
@Bean
public WebClient jettyHttpClient() {
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
@Override
public Request newRequest(URI uri) {
Request request = super.newRequest(uri);
return enhance(request);
}
};
return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient)).build();
}
출력
이제 기본 HttpClient로 시드한 WebClient를 사용하여 다음 출력을 얻습니다.
2020-10-08 15:00:00.000 INFO 2100 --- [ @cafebabe-37]
c.s.l.examples.JettyWebClient :
Request:
URI: http://httpbin.org/get
Method: GET
Headers:
Accept-Encoding : gzip
User-Agent : Jetty/9.4.31.v20200723
Accept : */*
Host : httpbin.org
Response:
Status: 200
Headers:
Date : Thu, 08 Oct 2020 20:24:17 GMT
Content-Type : application/json
Content-Length : 297
Connection : keep-alive
Server : gunicorn/19.9.0
Access-Control-Allow-Origin : *
Access-Control-Allow-Credentials : true
Response Body:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "Jetty/9.4.31.v20200723",
"X-Amzn-Trace-Id": "Root=1-5f7f7571- 157328ac70a3bd900bc1c8bc"
},
"origin": "12.345.678.91",
"url": "http://httpbin.org/get"
}
Netty 접근 방식보다 이 접근 방식을 사용하는 이점 중 하나는 이것이 표시되기 위해 로그를 변경할 필요가 없다는 것입니다. 또한 필요한 경우 각 요청의 데이터를 저장할 수 있습니다.
이 게시물의 목표는 요청 및 응답 데이터를 가장 세부적으로 제어할 수 있는 구현을 이해하는 것이었습니다.
위의 코드는 다음 저장소에 있는 전체 작동 애플리케이션에서 사용할 수 있습니다. https://github.com/StevenPG/logging-with-webclient
Reference
이 문제에 관하여(Spring WebClient로 요청 본문 로깅하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/stevenpg/logging-with-spring-webclient-2j6o
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
# Gradle
implementation group: 'org.eclipse.jetty', name: 'jetty-reactive-httpclient', version: '1.1.4'
# Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-reactive-httpclient</artifactId>
<version>1.1.4</version>
</dependency>
필요한 클래스에 액세스할 수 있게 되면 빌드해야 하는 두 가지 구성 요소가 있습니다.
첫 번째는 강화 방법입니다. 이 메서드는 요청을 받고 다시 요청을 제공하여 우리가 관심 있는 모든 부분을 가로채고 기록할 수 있도록 합니다.
다음은 향상 방법의 예이며 결과는 다음과 같습니다.
// org.eclipse.jetty.client.api.Request
private Request enhance(Request inboundRequest) {
StringBuilder log = new StringBuilder();
// Request Logging
inboundRequest.onRequestBegin(request ->
log.append("Request: \n")
.append("URI: ")
.append(request.getURI())
.append("\n")
.append("Method: ")
.append(request.getMethod()));
inboundRequest.onRequestHeaders(request -> {
log.append("\nHeaders:\n");
for (HttpField header : request.getHeaders()) {
log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
}
});
inboundRequest.onRequestContent((request, content) ->
log.append("Body: \n\t")
.append(content.toString()));
log.append("\n");
// Response Logging
inboundRequest.onResponseBegin(response ->
log.append("Response:\n")
.append("Status: ")
.append(response.getStatus())
.append("\n"));
inboundRequest.onResponseHeaders(response -> {
log.append("Headers:\n");
for (HttpField header : response.getHeaders()) {
log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
}
});
inboundRequest.onResponseContent(((response, content) -> {
var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
log.append("Response Body:\n" + bufferAsString);
}));
// Add actual log invocation
logger.info("HTTP ->\n");
inboundRequest.onRequestSuccess(request -> logger.info(log.toString()));
inboundRequest.onResponseSuccess(response -> logger.info(log.toString()));
// Return original request
return inboundRequest;
}
요청 개체는 기록하려는 데이터에 도달하고 잡을 수 있는 많은 후크를 제공합니다. 인터페이스 문서는 여기 -> https://www.eclipse.org/jetty/javadoc/9.4.8.v20171121/org/eclipse/jetty/client/api/Request.html
WebClient를 호출하는 동안 강화 메서드를 실행하기 위해 자체 HttpClient를 만들고 기본 JettyClientHttpConnector 대신 사용할 것입니다. 다음은 WebClient를 제공하는 예제 빈입니다.
@Bean
public WebClient jettyHttpClient() {
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
@Override
public Request newRequest(URI uri) {
Request request = super.newRequest(uri);
return enhance(request);
}
};
return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient)).build();
}
출력
이제 기본 HttpClient로 시드한 WebClient를 사용하여 다음 출력을 얻습니다.
2020-10-08 15:00:00.000 INFO 2100 --- [ @cafebabe-37]
c.s.l.examples.JettyWebClient :
Request:
URI: http://httpbin.org/get
Method: GET
Headers:
Accept-Encoding : gzip
User-Agent : Jetty/9.4.31.v20200723
Accept : */*
Host : httpbin.org
Response:
Status: 200
Headers:
Date : Thu, 08 Oct 2020 20:24:17 GMT
Content-Type : application/json
Content-Length : 297
Connection : keep-alive
Server : gunicorn/19.9.0
Access-Control-Allow-Origin : *
Access-Control-Allow-Credentials : true
Response Body:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "Jetty/9.4.31.v20200723",
"X-Amzn-Trace-Id": "Root=1-5f7f7571- 157328ac70a3bd900bc1c8bc"
},
"origin": "12.345.678.91",
"url": "http://httpbin.org/get"
}
Netty 접근 방식보다 이 접근 방식을 사용하는 이점 중 하나는 이것이 표시되기 위해 로그를 변경할 필요가 없다는 것입니다. 또한 필요한 경우 각 요청의 데이터를 저장할 수 있습니다.
이 게시물의 목표는 요청 및 응답 데이터를 가장 세부적으로 제어할 수 있는 구현을 이해하는 것이었습니다.
위의 코드는 다음 저장소에 있는 전체 작동 애플리케이션에서 사용할 수 있습니다. https://github.com/StevenPG/logging-with-webclient
Reference
이 문제에 관하여(Spring WebClient로 요청 본문 로깅하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/stevenpg/logging-with-spring-webclient-2j6o텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)