I need to log incoming requests. So I've added this class to log incoming requests:
#Slf4j
#Component
public class LoggingFilter implements GlobalFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Set<URI> uris = exchange.getAttributeOrDefault(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, Collections.emptySet());
String originalUri = (uris.isEmpty()) ? "Unknown" : uris.iterator().next().toString();
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
URI routeUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
log.info("Incoming request " + originalUri + " is routed to id: " + route.getId()
+ ", uri:" + routeUri);
return chain.filter(exchange);
}
}
It works perfect with successful request. But when route not found there is no message in log.
Any ideas how I can do that? Thanks!
Related
I'm using test assured to load a reasonable large set of test data into my application under test.
I want to log the request and response details for ONLY the requests that generate an error response (HTTP 4xx or 5xx)
I've tried these snippets but end up with ALL requests logged and only the error responses logged. The problem is that my log files become really large and it's causing issues in Jenkins. I just want to see the errors and the request that caused them.
RequestSpecBuilder build = new RequestSpecBuilder();
build.addFilter(new ErrorLoggingFilter()).log(LogDetail.ALL);
requestSpec = build.build();
RequestSpecBuilder build = new RequestSpecBuilder();
build.log(LogDetail.ALL).addFilter(new ErrorLoggingFilter());
requestSpec = build.build();
You can create your own filter by implementing io.restassured.filter.Filter interface:
public class FailedRequestFilter implements Filter {
private static final Logger logger = Logger.getLogger(FailedRequestFilter.class.getName());
#Override
public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) {
Response response = ctx.next(requestSpec, responseSpec);
if (response.statusCode() >= 400) {
logger.log(Level.INFO, requestSpec.getMethod() + " " + requestSpec.getURI() + " => " +
response.getStatusCode() + " " + response.getStatusLine());
}
return response;
}
}
And then use it in your requests:
RestAssured.given()
.filter(new FailedRequestFilter())
.when()
.get("http://www.example.com");
Using Java 8 lambda expression can also work:
Logger logger = Logger.getLogger("SomeLoggerName");
RestAssured.given()
.filter(
(request, response, ctx) -> {
Response resp = ctx.next(request, response);
if (resp.statusCode() >= 400) {
logger.log(Level.INFO, request.getMethod() + " " + request.getURI() + " => "
+ response.getStatusCode() + " " + response.getStatusLine());
}
return resp;
})
.when()
.get("http://wwww.example.com");
There are 4 interaction models provided in RSocket.
fire and forget
request and response
request stream
request channel
(metadata push)
Spring(and Spring Boot) provides RSocket integration, it is easy to build a RSocket server with the existing messaging infrastructure to hide the original RSocket APIs.
#MessageMapping("hello")
public Mono<Void> hello(Greeting p) {
log.info("received: {} at {}", p, Instant.now());
return Mono.empty();
}
#MessageMapping("greet.{name}")
public Mono<String> greet(#DestinationVariable String name, #Payload Greeting p) {
log.info("received: {}, {} at {}", name, p, Instant.now());
return Mono.just("Hello " + name + ", " + p.getMessage() + " at " + Instant.now());
}
#MessageMapping("greet-stream")
public Flux<String> greetStream(#Payload Greeting p) {
log.info("received: {} at {}", p, Instant.now());
return Flux.interval(Duration.ofSeconds(1))
.map(i -> "Hello #" + i + "," + p.getMessage() + " at " + Instant.now());
}
And in the client side, there is a RescoketRequester provided to shake hands with the server.
#GetMapping("hello")
Mono<Void> hello() {
return this.requester.route("hello").data(new Greeting("Welcome to Rsocket")).send();
}
#GetMapping("name/{name}")
Mono<String> greet(#PathVariable String name) {
return this.requester.route("greet." + name).data(new Greeting("Welcome to Rsocket")).retrieveMono(String.class);
}
#GetMapping(value = "stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<String> greetStream() {
return this.requester.route("greet-stream").data(new Greeting("Welcome to Rsocket"))
.retrieveFlux(String.class)
.doOnNext(msg -> log.info("received messages::" + msg));
}
But how to use requestChannel and metadataPush model in Spring way(using messaging infrastructure)?
The sample codes is on Github. Update: added requestChannel sample.
Update: SETUP and METADATA_PUSH can be handled by #ConnectMapping. And Spring Security RSocket can secure SETUP and REQUEST.
Reference example
For a reference example, let's refer to the client-to-server integration tests and, in particular, to the ServerController class: spring-framework/RSocketClientToServerIntegrationTests.java (line 200) at 6d7bf8050fe710c5253e6032233021d5e025e1d5 · spring-projects/spring-framework · GitHub.
This commit has been mentioned in the release notes:
<…>
RSocket support including response handling via annotated #MessageMapping methods and performing requests via RSocketRequester.
<…>
— Spring Framework 5.2.0.M1 available now.
Channel interaction model
The corresponding code part of the reference example:
#MessageMapping("echo-channel")
Flux<String> echoChannel(Flux<String> payloads) {
return payloads.delayElements(Duration.ofMillis(10)).map(payload -> payload + " async");
}
Metadata push
It seems that, currently, it is not supported by the #MessageMapping annotation.
CommonsRequestLoggingFilter is working fine when logging the incoming request bodies. However, it only logs them after the processing is done. So, when I log the response body, it precedes the request body. I would like to know if there's a way to log the request before it is even processed.
Another thing I would like to mention is that CommonsRequestLoggingFilter actually is aware when the request is received, it logs message like Request received at.... However, that log doesn't contain the request body. After the message is processed, it logs almost exact thing with payload in it.
My configuration is the following for the CommonsRequestLoggingFilter. It has nothing special.
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(true);
loggingFilter.setIncludeQueryString(true);
loggingFilter.setIncludePayload(true);
return loggingFilter;
}
Note. Apparently there's a reason for the behavior mentioned above. It is said that getContentAsByteArray cannot be called until either getInputStream or getReader is called. This method is called inside the logger. I don't know what to do with this. Does it mean there's no way of logging request before?
You can log both before and after interceptor's execute() method like below.
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
private final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
if (log.isDebugEnabled()) {
log.debug("URI : {}", request.getURI());
log.debug("Method : {}", request.getMethod());
log.debug("Headers : {}", request.getHeaders());
log.debug("Request body: {}", new String(body, "UTF-8"));
}
}
private void logResponse(ClientHttpResponse response) throws IOException {
if (log.isDebugEnabled()) {
log.debug("Status code : {}", response.getStatusCode());
log.debug("Status text : {}", response.getStatusText());
log.debug("Headers : {}", response.getHeaders());
log.debug("Response body: {}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
}
}
}
I've been researching this an I've determined that it's probably not best practice to log the request/response bodies.
Starting with Spring Boot 2.0 it's not (and really hasn't officially been) supported.
https://github.com/spring-projects/spring-boot/issues/12953#issuecomment-383830749
I am using Spring Integration Java DSL with a defined IntegrationFlow. I am seeing behavior where the response is missing pieces of data and the correlationId in the aggregator response does not match the value in the response that is received by calling service.
Background:
I have a JMeter performance test running on a server that uses random data and is running at 600 requests per minute. On my laptop, I have a SoapUI performance test running that hits the same server. The SoapUI project sends requests with the same search criteria (we are doing matching) at a rate of 60 requests per minute. The responses should all contain the same result data.
Approximately 0.5% of the time the response is returned with data missing. In these responses, the correlationId of the response that is logged from the aggregator and the correlationId of the response logged from the calling service (logged after the response is returned to the calling service and has already passed through the aggregator) do not match.
Any idea what is wrong? Please see code snippets below.
#Configuration
#EnableAutoConfiguration
#Import(.....AServiceConfig.class)
public class ServiceConfig {
#Bean(name = "inputChannel")
public DirectChannel inputChannel() {
return new DirectChannel();
}
#Bean(name = "outputChannel")
public QueueChannel outputChannel() {
return new QueueChannel();
}
#Bean(name = "transactionLogger")
public ourLogger ourTransactionLogger() {
return OurLoggerFactory.getLogger("ourAppTrx", new ourLoggerConfig(ourTransactionLoggerKey.values()));
}
public IntegrationFlow ourFlow() {
return IntegrationFlows.from(inputChannel())
.split(splitter(ourTransactionLogger()))
.channel(MessageChannels.executor(getExecutor()))
.handle(ourServiceActivator, "service")
.aggregate(t -> t.processor(ourAggregator, AGGREGATE))
.channel(outputChannel())
.get();
}
#Bean(name = "executor")
public Executor getExecutor()
{
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
//snippet from calling service
public InquiryResponse inquire(InquiryRequest request) {
inputChannel.send(MessageBuilder.withPayload(request).build());
Message<?> msgResponse = outputChannel.receive();
InquiryResponse response = (InquiryResponse) msgResponse.getPayload();
TransactionLogger.debug("correlationId + msgResponse.getHeaders().get("correlationId"));
TransactionLogger.debug("InquiryService inquire response = " + response.toString());
return response;
}
//snippet from aggregator
#Aggregator
public <T> InquiryResponse aggregate(List<Message> serviceResponses) {
InquiryResponse response = new InquiryResponse();
serviceResponses.forEach(serviceResponse -> {
Object payload = serviceResponse.getPayload();
if (payload instanceof AMatchResponse) {
response.setA(((AMatchResponse) payload).getA());
} else if (payload instanceof BValueResponse) {
response.setB(((BValueResponse) payload).getB());
} else if (payload instanceof BError) {
response.setB(new B().addBErrorsItem((BError) payload));
} else if (payload instanceof AError) {
response.setA(new A().AError((AError) payload));
} else {
transactionLogger.warn("Unknown message type received. This message will not be aggregated into the response. ||| model=" + payload.getClass().getName());
}
});
transactionLogger.debug("OurAggregator.response = " + response.toString());
return response;
}
How to print response message body as a String within filter method and tried couple of response methods (getEntityOutputStream() / getEntity() / GetContainerResponseWriter() )
public class Test implements ContainerRequestFilter , ContainerResponseFilter) {
#Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
response.
}
}
I use the Jersey logging to print out request / response body
in my ResourceConfig
registerInstances(new LoggingFilter(myLogger, true));
LoggingFilter
public class LoggingFilter implements ContainerRequestFilter, ClientRequestFilter, ContainerResponseFilter,
ClientResponseFilter, WriterInterceptor {
...
you can check out how they do it : https://github.com/jersey/jersey/blob/master/core-common/src/main/java/org/glassfish/jersey/filter/LoggingFilter.java
I had the same problem and this is what I ended up doing. Basically I return the JSON representation of my entity if I could cast it, else I create a custom response message.
I'm sure there will be better ways to do it.
private String createLoggingResponse(ContainerResponseContext resp) {
Object entity = resp.getEntity();
if (BaseModel.class.isInstance(entity)) {
BaseModel model = (BaseModel) entity;
ByteArrayOutputStream out = new ByteArrayOutputStream();
model.toJson(out);
return new String(out.toByteArray());
} else {
MediaType mediaType = resp.getMediaType();
return "[" + entity.toString() + "," + resp.getStatusInfo().getStatusCode() + "," + resp.getStatusInfo().getReasonPhrase() + "," + mediaType.toString() + "]";
}
}