Skip to content

Commit

Permalink
[plugin-rest-api] Add extended logging for HTTP requests and responses
Browse files Browse the repository at this point in the history
  • Loading branch information
web-flow committed Nov 10, 2023
1 parent ed72cbb commit b00fe66
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 18 deletions.
6 changes: 6 additions & 0 deletions docs/modules/plugins/pages/plugin-rest-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ include::partial$plugin-installation.adoc[]
|`<empty>`
|The property family to set HTTP headers for all outgoing requests, e.g. rest-api.http.header.my-sample-header=my-sample-value

|`rest-api.http.extended-logging`
a|`true`
`false`
|`false`
|Enable logging of HTTP request/response headers and bodies (applied to the following content types only: `text/plain`, `text/html`, `text/xml`, `application/json`, `application/xml`)

|===

See xref:ROOT:tests-configuration.adoc#_http_configuration[HTTP configuration] for more fine-grained control over the HTTP interactions.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.http;

import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON;
import static org.apache.hc.core5.http.ContentType.APPLICATION_XML;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpEntityContainer;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LoggingEventBuilder;
import org.vividus.http.client.HttpResponse;
import org.vividus.http.handler.HttpResponseHandler;

public class ExtendedHttpLoggingInterceptor implements HttpRequestInterceptor, HttpResponseHandler
{
private static final String NEW_LINE = System.lineSeparator();
private static final String HEADERS_FORMAT = String.format("%nHeaders:%n{}");
private static final String BODY_FORMAT = String.format("%nBody:%n{}");

private static final Set<String> LOGGED_CONTENT_TYPES = Set.of(APPLICATION_JSON.getMimeType(),
APPLICATION_XML.getMimeType());

private final Logger logger;

private final boolean extendedLogging;

public ExtendedHttpLoggingInterceptor(Class<?> loggerType, boolean extendedLogging)
{
this.logger = LoggerFactory.getLogger(loggerType);
this.extendedLogging = extendedLogging;
}

@Override
public void process(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws IOException
{
String argBrackets = " {}";
LoggingEventBuilder loggingEventBuilder = logger.atInfo();
StringBuilder loggerFormat = new StringBuilder("Request:");

ProtocolVersion protocolVersion = request.getVersion();
if (protocolVersion != null)
{
loggingEventBuilder = loggingEventBuilder.addArgument(protocolVersion);
loggerFormat.append(argBrackets);
}
loggingEventBuilder = loggingEventBuilder.addArgument(request);
loggerFormat.append(argBrackets);

if (extendedLogging)
{
String headersAsString = Stream.of(request.getHeaders()).map(Object::toString)
.collect(Collectors.joining(NEW_LINE));
loggingEventBuilder = loggingEventBuilder.addArgument(headersAsString);
loggerFormat.append(HEADERS_FORMAT);

if (request instanceof HttpEntityContainer httpEntityContainer)
{
HttpEntity entity = httpEntityContainer.getEntity();
if (entity instanceof StringEntity stringEntity)
{
String textBody = new String(stringEntity.getContent().readAllBytes(), StandardCharsets.UTF_8);
loggingEventBuilder = loggingEventBuilder.addArgument(textBody);
loggerFormat.append(BODY_FORMAT);
}
else if (entity != null)
{
int bodySizeInBytes = entity.getContent().available();
loggingEventBuilder = loggingEventBuilder.addArgument(bodySizeInBytes);
loggerFormat.append(NEW_LINE).append("Body: not text data. Size {} bytes.");
}
}
}
loggingEventBuilder.log(loggerFormat.toString());
}

@Override
public void handle(HttpResponse httpResponse)
{
LoggingEventBuilder loggingEventBuilder = logger.atInfo().addArgument(httpResponse.getStatusCode())
.addArgument(httpResponse.getFrom());
StringBuilder loggerFormat = new StringBuilder("Response: status code {}, {}");

if (extendedLogging)
{
Header[] headers = httpResponse.getResponseHeaders();
String headersAsString = Stream.of(headers).map(Object::toString).collect(Collectors.joining(NEW_LINE));
loggingEventBuilder = loggingEventBuilder.addArgument(headersAsString);
loggerFormat.append(HEADERS_FORMAT);

String mimeType = MimeTypeUtils.getMimeTypeFromHeaders(headers)
.orElseGet(ContentType.DEFAULT_TEXT::getMimeType);
if (httpResponse.getResponseBody() != null
&& mimeType.startsWith("text/") || LOGGED_CONTENT_TYPES.contains(mimeType))
{
loggingEventBuilder = loggingEventBuilder.addArgument(httpResponse.getResponseBodyAsString());
loggerFormat.append(BODY_FORMAT);
}
}
loggingEventBuilder.log(loggerFormat.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.http;

import java.util.Optional;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.message.MessageSupport;

public final class MimeTypeUtils
{
private MimeTypeUtils()
{
}

public static Optional<String> getMimeTypeFromHeaders(Header... headers)
{
return Stream.of(headers)
.filter(h -> HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(h.getName())
&& StringUtils.isNotBlank(h.getValue()))
.findFirst()
.map(MessageSupport::parse)
.map(elements -> elements[0])
.map(HeaderElement::getName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,14 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpEntityContainer;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -63,7 +58,7 @@ public void process(HttpRequest request, EntityDetails entityDetails, HttpContex
HttpEntity entity = httpEntityContainer.getEntity();
if (entity != null)
{
mimeType = getMimeType(request.getHeaders())
mimeType = MimeTypeUtils.getMimeTypeFromHeaders(request.getHeaders())
.orElseGet(() ->
Optional.ofNullable(ContentType.parseLenient(entity.getContentType()))
.orElse(ContentType.DEFAULT_TEXT).getMimeType()
Expand All @@ -88,7 +83,8 @@ public void handle(HttpResponse response) throws IOException
{
Header[] headers = response.getResponseHeaders();
String attachmentTitle = String.format("Response: %s %s", response.getMethod(), response.getFrom());
String mimeType = getMimeType(headers).orElseGet(ContentType.DEFAULT_TEXT::getMimeType);
String mimeType = MimeTypeUtils.getMimeTypeFromHeaders(headers)
.orElseGet(ContentType.DEFAULT_TEXT::getMimeType);
attachApiMessage(attachmentTitle, headers, response.getResponseBody(), mimeType, response.getStatusCode());
}

Expand All @@ -102,15 +98,4 @@ private void attachApiMessage(String title, Header[] headers, byte[] body, Strin

attachmentPublisher.publishAttachment("/org/vividus/http/attachment/api-message.ftl", dataMap, title);
}

private Optional<String> getMimeType(Header... headers)
{
return Stream.of(headers)
.filter(h -> HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(h.getName())
&& StringUtils.isNotBlank(h.getValue()))
.findFirst()
.map(MessageSupport::parse)
.map(elements -> elements[0])
.map(HeaderElement::getName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Loggers>
<Logger name="org.vividus.http.HttpRequestExecutor" level="INFO" additivity="false">
<AppenderRef ref="console" />
<AppenderRef ref="file" />
</Logger>
</Loggers>
</Configuration>
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Default API endpoint
rest-api.http.endpoint=
rest-api.http.cookie-store-level=global
rest-api.http.extended-logging=false
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<constructor-arg value="rest-api.http.header." />
</bean>
</property>
<property name="firstRequestInterceptor" ref="extendedHttpLoggingInterceptor"/>
<property name="lastRequestInterceptor" ref="publishingAttachmentInterceptor" />
<property name="lastResponseInterceptor">
<bean class="org.vividus.http.SavingConnectionDetailsHttpResponseInterceptor">
Expand All @@ -69,6 +70,7 @@
</property>
<property name="httpResponseHandlers">
<list>
<ref bean="extendedHttpLoggingInterceptor" />
<ref bean="publishingAttachmentInterceptor" />
</list>
</property>
Expand All @@ -80,6 +82,11 @@
<constructor-arg ref="softAssert" />
</bean>

<bean id="extendedHttpLoggingInterceptor" class="org.vividus.http.ExtendedHttpLoggingInterceptor">
<constructor-arg value="org.vividus.http.HttpRequestExecutor" type="java.lang.Class" />
<constructor-arg value="${rest-api.http.extended-logging}" />
</bean>

<bean id="publishingAttachmentInterceptor" class="org.vividus.http.PublishingAttachmentInterceptor" />

<bean id="httpCookieSteps" class="org.vividus.steps.api.HttpCookieSteps"/>
Expand Down
Loading

0 comments on commit b00fe66

Please sign in to comment.