aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2024-06-05 12:07:27 +0200
committerGitHub <noreply@github.com>2024-06-05 12:07:27 +0200
commit867f21e10e4a538875588056b976543d3f9d72f8 (patch)
tree38f1de65b9a8bb303749def1624e07b02adbf550
parent40dc6e363de9f117f94d9af38b416fb146491339 (diff)
parentf99125732929fb2c90afdc3dda1a269b15d795ee (diff)
Merge pull request #31438 from vespa-engine/bjorncs/content-in-access-log
Bjorncs/content in access log
-rw-r--r--config-model-api/abi-spec.json3
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java12
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java4
-rw-r--r--container-core/abi-spec.json9
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java13
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/JSONFormatter.java12
-rw-r--r--container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java6
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java1
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java102
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java3
-rw-r--r--container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def3
-rw-r--r--container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java17
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java6
14 files changed, 172 insertions, 21 deletions
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index 1aaaf64852a..5833662ff66 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -1388,7 +1388,8 @@
"public java.util.Optional cloudAccount()",
"public boolean allowUserFilters()",
"public java.time.Duration endpointConnectionTtl()",
- "public java.util.List dataplaneTokens()"
+ "public java.util.List dataplaneTokens()",
+ "public java.util.List requestPrefixForLoggingContent()"
],
"fields" : [ ]
},
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 89041ce8242..b4c101600da 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -174,6 +174,8 @@ public interface ModelContext {
default List<DataplaneToken> dataplaneTokens() { return List.of(); }
+ default List<String> requestPrefixForLoggingContent() { return List.of(); }
+
}
@Retention(RetentionPolicy.RUNTIME)
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
index 5f824950ecd..7062370537e 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
@@ -28,6 +28,7 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
private final List<String> remoteAddressHeaders;
private final List<String> remotePortHeaders;
private final Set<String> knownServerNames;
+ private final Set<String> requestPrefixForLoggingContent;
public static Builder builder(String name, int listenPort) { return new Builder(name, listenPort); }
@@ -40,6 +41,12 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
this.remoteAddressHeaders = List.copyOf(builder.remoteAddressHeaders);
this.remotePortHeaders = List.copyOf(builder.remotePortHeaders);
this.knownServerNames = Collections.unmodifiableSet(new TreeSet<>(builder.knownServerNames));
+ builder.requestPrefixForLoggingContent.forEach(prefix -> {
+ var regex = "^.*:[0|1](\\.\\d+)?$";
+ if (!prefix.matches(regex))
+ throw new IllegalArgumentException("Invalid prefix '%s, must match regex '%s'".formatted(prefix, regex));
+ });
+ this.requestPrefixForLoggingContent = Collections.unmodifiableSet(new TreeSet<>(builder.requestPrefixForLoggingContent));
}
private static SslProvider createSslProvider(Builder builder) {
@@ -73,7 +80,8 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
.maxConnectionLife(endpointConnectionTtl != null ? endpointConnectionTtl.toSeconds() : 0)
.accessLog(new ConnectorConfig.AccessLog.Builder()
.remoteAddressHeaders(remoteAddressHeaders)
- .remotePortHeaders(remotePortHeaders))
+ .remotePortHeaders(remotePortHeaders)
+ .contentPathPrefixes(requestPrefixForLoggingContent))
.serverName.known(knownServerNames);
}
@@ -93,6 +101,7 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
String tlsCaCertificatesPath;
boolean tokenEndpoint;
Set<String> knownServerNames = Set.of();
+ Set<String> requestPrefixForLoggingContent = Set.of();
private Builder(String name, int port) { this.name = name; this.port = port; }
public Builder clientAuth(SslClientAuth auth) { clientAuth = auth; return this; }
@@ -106,6 +115,7 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
public Builder remoteAddressHeader(String header) { this.remoteAddressHeaders.add(header); return this; }
public Builder remotePortHeader(String header) { this.remotePortHeaders.add(header); return this; }
public Builder knownServerNames(Set<String> knownServerNames) { this.knownServerNames = Set.copyOf(knownServerNames); return this; }
+ public Builder requestPrefixForLoggingContent(Collection<String> v) { this.requestPrefixForLoggingContent = Set.copyOf(v); return this; }
public HostedSslConnectorFactory build() { return new HostedSslConnectorFactory(this); }
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
index 4983b36bee1..d3f5407b0f9 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java
@@ -607,7 +607,8 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
var builder = HostedSslConnectorFactory.builder(serverName, getMtlsDataplanePort(state))
.proxyProtocol(state.zone().cloud().useProxyProtocol())
.tlsCiphersOverride(state.getProperties().tlsCiphersOverride())
- .endpointConnectionTtl(state.getProperties().endpointConnectionTtl());
+ .endpointConnectionTtl(state.getProperties().endpointConnectionTtl())
+ .requestPrefixForLoggingContent(state.getProperties().requestPrefixForLoggingContent());
var endpointCert = state.endpointCertificateSecrets().orElse(null);
if (endpointCert != null) {
builder.endpointCertificate(endpointCert);
@@ -670,6 +671,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.remotePortHeader("X-Forwarded-Port")
.clientAuth(SslClientAuth.NEED)
.knownServerNames(tokenEndpoints)
+ .requestPrefixForLoggingContent(state.getProperties().requestPrefixForLoggingContent())
.build();
server.addConnector(connector);
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index e1ffda5649c..bf9a177c28e 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -1047,11 +1047,14 @@
"public com.yahoo.jdisc.http.ConnectorConfig$AccessLog$Builder remoteAddressHeaders(java.util.Collection)",
"public com.yahoo.jdisc.http.ConnectorConfig$AccessLog$Builder remotePortHeaders(java.lang.String)",
"public com.yahoo.jdisc.http.ConnectorConfig$AccessLog$Builder remotePortHeaders(java.util.Collection)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$AccessLog$Builder contentPathPrefixes(java.lang.String)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$AccessLog$Builder contentPathPrefixes(java.util.Collection)",
"public com.yahoo.jdisc.http.ConnectorConfig$AccessLog build()"
],
"fields" : [
"public java.util.List remoteAddressHeaders",
- "public java.util.List remotePortHeaders"
+ "public java.util.List remotePortHeaders",
+ "public java.util.List contentPathPrefixes"
]
},
"com.yahoo.jdisc.http.ConnectorConfig$AccessLog" : {
@@ -1066,7 +1069,9 @@
"public java.util.List remoteAddressHeaders()",
"public java.lang.String remoteAddressHeaders(int)",
"public java.util.List remotePortHeaders()",
- "public java.lang.String remotePortHeaders(int)"
+ "public java.lang.String remotePortHeaders(int)",
+ "public java.util.List contentPathPrefixes()",
+ "public java.lang.String contentPathPrefixes(int)"
],
"fields" : [ ]
},
diff --git a/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java b/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
index e4af680726a..0eb4485936d 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/AccessLogEntry.java
@@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@@ -31,11 +32,23 @@ import static java.util.stream.Collectors.toMap;
*/
public class AccessLogEntry {
+ public record Content(String type, long length, byte[] body) {}
+
private final Object monitor = new Object();
private HitCounts hitCounts;
private TraceNode traceNode;
private ListMap<String,String> keyValues=null;
+ private Content content;
+
+ public void setContent(Content entity) {
+ synchronized (monitor) {
+ requireNull(this.content);
+ this.content = entity;
+ }
+ }
+
+ public Optional<Content> getContent() { synchronized (monitor) { return Optional.ofNullable(content); } }
public void setHitCounts(final HitCounts hitCounts) {
synchronized (monitor) {
diff --git a/container-core/src/main/java/com/yahoo/container/logging/JSONFormatter.java b/container-core/src/main/java/com/yahoo/container/logging/JSONFormatter.java
index f1e865cd596..1f42f8ded97 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/JSONFormatter.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/JSONFormatter.java
@@ -1,15 +1,16 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.logging;
-import com.yahoo.json.Jackson;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
+import com.yahoo.json.Jackson;
import com.yahoo.yolean.trace.TraceNode;
import java.io.IOException;
import java.io.OutputStream;
import java.security.Principal;
+import java.util.Base64;
import java.util.Collection;
import java.util.Objects;
import java.util.logging.Level;
@@ -102,6 +103,15 @@ public class JSONFormatter implements LogWriter<RequestLogEntry> {
trace.accept(new TraceRenderer(generator, timestamp));
}
+ var content = entry.content().orElse(null);
+ if (content != null) {
+ generator.writeObjectFieldStart("content");
+ generator.writeStringField("type", content.type());
+ generator.writeNumberField("length", content.length());
+ generator.writeStringField("body", Base64.getEncoder().encodeToString(content.body()));
+ generator.writeEndObject();
+ }
+
// Only add search sub block of this is a search request
if (isSearchRequest(entry)) {
HitCounts hitCounts = entry.hitCounts().get();
diff --git a/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java b/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
index 0092d67b0fa..a5f82a8b637 100644
--- a/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
+++ b/container-core/src/main/java/com/yahoo/container/logging/RequestLogEntry.java
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.logging;
+import com.yahoo.container.logging.AccessLogEntry.Content;
import com.yahoo.yolean.trace.TraceNode;
import java.security.Principal;
@@ -50,6 +51,7 @@ public class RequestLogEntry {
private final Principal sslPrincipal;
private final HitCounts hitCounts;
private final TraceNode traceNode;
+ private final Content content;
private final SortedMap<String, Collection<String>> extraAttributes;
private RequestLogEntry(Builder builder) {
@@ -76,6 +78,7 @@ public class RequestLogEntry {
this.sslPrincipal = builder.sslPrincipal;
this.hitCounts = builder.hitCounts;
this.traceNode = builder.traceNode;
+ this.content = builder.content;
this.extraAttributes = copyExtraAttributes(builder.extraAttributes);
}
@@ -102,6 +105,7 @@ public class RequestLogEntry {
public Optional<Principal> sslPrincipal() { return Optional.ofNullable(sslPrincipal); }
public Optional<HitCounts> hitCounts() { return Optional.ofNullable(hitCounts); }
public Optional<TraceNode> traceNode() { return Optional.ofNullable(traceNode); }
+ public Optional<Content> content() { return Optional.ofNullable(content); }
public SortedSet<String> extraAttributeKeys() { return Collections.unmodifiableSortedSet((SortedSet<String>)extraAttributes.keySet()); }
public Collection<String> extraAttributeValues(String key) { return Collections.unmodifiableCollection(extraAttributes.get(key)); }
@@ -145,6 +149,7 @@ public class RequestLogEntry {
private Principal userPrincipal;
private HitCounts hitCounts;
private TraceNode traceNode;
+ private Content content;
private Principal sslPrincipal;
private final Map<String, Collection<String>> extraAttributes = new HashMap<>();
@@ -171,6 +176,7 @@ public class RequestLogEntry {
public Builder sslPrincipal(Principal sslPrincipal) { this.sslPrincipal = requireNonNull(sslPrincipal); return this; }
public Builder hitCounts(HitCounts hitCounts) { this.hitCounts = requireNonNull(hitCounts); return this; }
public Builder traceNode(TraceNode traceNode) { this.traceNode = requireNonNull(traceNode); return this; }
+ public Builder content(Content content) { this.content = requireNonNull(content); return this; }
public Builder addExtraAttribute(String key, String value) {
this.extraAttributes.computeIfAbsent(requireNonNull(key), __ -> new ArrayList<>()).add(requireNonNull(value));
return this;
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
index af1179fadba..6c071a162d1 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
@@ -117,6 +117,7 @@ class AccessLogRequestLog extends AbstractLifeCycle implements org.eclipse.jetty
}
addNonNullValue(builder, accessLogEntry.getHitCounts(), RequestLogEntry.Builder::hitCounts);
addNonNullValue(builder, accessLogEntry.getTrace(), RequestLogEntry.Builder::traceNode);
+ accessLogEntry.getContent().ifPresent(builder::content);
}
http2StreamId(request).ifPresent(streamId -> builder.addExtraAttribute("http2-stream-id", Integer.toString(streamId)));
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java
index 2c9cffa6786..3b1db55defd 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLoggingRequestHandler.java
@@ -1,18 +1,34 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.server.jetty;
-import com.google.common.base.Preconditions;
import com.yahoo.container.logging.AccessLogEntry;
import com.yahoo.jdisc.Request;
import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.jdisc.handler.DelegatedRequestHandler;
import com.yahoo.jdisc.handler.RequestHandler;
import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpHeaders;
import com.yahoo.jdisc.http.HttpRequest;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static com.yahoo.jdisc.http.server.jetty.RequestUtils.getConnector;
/**
* A wrapper RequestHandler that enables access logging. By wrapping the request handler, we are able to wrap the
@@ -23,6 +39,7 @@ import java.util.Optional;
* Does not otherwise interfere with the request processing of the delegate request handler.
*
* @author bakksjo
+ * @author bjorncs
*/
public class AccessLoggingRequestHandler extends AbstractRequestHandler implements DelegatedRequestHandler {
public static final String CONTEXT_KEY_ACCESS_LOG_ENTRY
@@ -38,27 +55,94 @@ public class AccessLoggingRequestHandler extends AbstractRequestHandler implemen
(AccessLogEntry) requestContextMap.get(CONTEXT_KEY_ACCESS_LOG_ENTRY));
}
- private final RequestHandler delegate;
+ private final org.eclipse.jetty.server.Request jettyRequest;
+ private final RequestHandler delegateRequestHandler;
private final AccessLogEntry accessLogEntry;
+ private final List<String> pathPrefixes;
+ private final List<Double> samplingRate;
+ private final Random rng = new Random();
public AccessLoggingRequestHandler(
- final RequestHandler delegateRequestHandler,
- final AccessLogEntry accessLogEntry) {
- this.delegate = delegateRequestHandler;
+ org.eclipse.jetty.server.Request jettyRequest,
+ RequestHandler delegateRequestHandler,
+ AccessLogEntry accessLogEntry) {
+ this.jettyRequest = jettyRequest;
+ this.delegateRequestHandler = delegateRequestHandler;
this.accessLogEntry = accessLogEntry;
+ var contentPathPrefixes = getConnector(jettyRequest).connectorConfig().accessLog().contentPathPrefixes();
+ this.pathPrefixes = contentPathPrefixes.stream()
+ .map(s -> {
+ var separatorIndex = s.lastIndexOf(':');
+ return s.substring(0, separatorIndex == -1 ? s.length() : separatorIndex);
+ })
+ .toList();
+ this.samplingRate = contentPathPrefixes.stream()
+ .map(s -> {
+ return Double.parseDouble(s.substring(s.lastIndexOf(':') + 1));
+ })
+ .toList();
}
@Override
public ContentChannel handleRequest(final Request request, final ResponseHandler handler) {
- Preconditions.checkArgument(request instanceof HttpRequest, "Expected HttpRequest, got " + request);
final HttpRequest httpRequest = (HttpRequest) request;
httpRequest.context().put(CONTEXT_KEY_ACCESS_LOG_ENTRY, accessLogEntry);
- return delegate.handleRequest(request, handler);
+ var methodsWithEntity = List.of(HttpRequest.Method.POST, HttpRequest.Method.PUT, HttpRequest.Method.PATCH);
+ var originalContentChannel = delegateRequestHandler.handleRequest(request, handler);
+ var uriPath = request.getUri().getPath();
+ if (methodsWithEntity.contains(httpRequest.getMethod())) {
+ for (int i = 0; i < pathPrefixes.size(); i++) {
+ if (uriPath.startsWith(pathPrefixes.get(i))) {
+ if (samplingRate.get(i) > rng.nextDouble()) {
+ return new ContentLoggingContentChannel(originalContentChannel);
+ }
+ }
+ }
+ }
+ return originalContentChannel;
}
-
@Override
public RequestHandler getDelegate() {
- return delegate;
+ return delegateRequestHandler;
+ }
+
+ private class ContentLoggingContentChannel implements ContentChannel {
+ private static final int CONTENT_LOGGING_MAX_SIZE = 16 * 1024 * 1024;
+
+ final AtomicLong length = new AtomicLong();
+ final ByteArrayOutputStream accumulatedRequestContent;
+ final ContentChannel originalContentChannel;
+
+ public ContentLoggingContentChannel(ContentChannel originalContentChannel) {
+ this.originalContentChannel = originalContentChannel;
+ var contentLength = jettyRequest.getContentLength();
+ this.accumulatedRequestContent = new ByteArrayOutputStream(contentLength == -1 ? 128 : contentLength);
+ }
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ length.addAndGet(buf.remaining());
+ var bytesToLog = Math.min(buf.remaining(), CONTENT_LOGGING_MAX_SIZE - accumulatedRequestContent.size());
+ if (bytesToLog > 0) accumulatedRequestContent.write(buf.array(), buf.arrayOffset() + buf.position(), bytesToLog);
+ if (originalContentChannel != null) originalContentChannel.write(buf, handler);
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ var bytes = accumulatedRequestContent.toByteArray();
+ accessLogEntry.setContent(new AccessLogEntry.Content(
+ Objects.requireNonNullElse(jettyRequest.getHeader(HttpHeaders.Names.CONTENT_TYPE), ""),
+ length.get(),
+ bytes));
+ accumulatedRequestContent.reset();
+ length.set(0);
+ if (originalContentChannel != null) originalContentChannel.close(handler);
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ if (originalContentChannel != null) originalContentChannel.onError(error);
+ }
}
}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
index 0b021ec8bcd..9d873f75516 100644
--- a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
@@ -214,7 +214,8 @@ class HttpRequestDispatch {
new FilteringRequestHandler(context.filterResolver(), (Request)servletRequest),
servletRequest, context.removeRawPostBodyForWwwUrlEncodedPost());
- return new AccessLoggingRequestHandler(requestHandler, accessLogEntry);
+ return new AccessLoggingRequestHandler(
+ (Request) servletRequest, requestHandler, accessLogEntry);
}
private static RequestHandler wrapHandlerIfFormPost(RequestHandler requestHandler,
diff --git a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
index 2906f75a1f5..b11081cad92 100644
--- a/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
+++ b/container-core/src/main/resources/configdefinitions/jdisc.http.jdisc.http.connector.def
@@ -154,3 +154,6 @@ accessLog.remoteAddressHeaders[] string
# HTTP request headers that contain remote port
accessLog.remotePortHeaders[] string
+
+# Path prefixes for which content should be logged
+accessLog.contentPathPrefixes[] string
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 73dfb85519c..c7a140b142e 100644
--- a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -82,6 +82,7 @@ import static com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.ResponseValidat
import static com.yahoo.jdisc.http.server.jetty.Utils.createHttp2Client;
import static com.yahoo.jdisc.http.server.jetty.Utils.createSslTestDriver;
import static com.yahoo.jdisc.http.server.jetty.Utils.generatePrivateKeyAndCertificate;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
@@ -742,12 +743,18 @@ public class HttpServerTest {
JettyTestDriver driver = JettyTestDriver.newConfiguredInstance(
new EchoRequestHandler(),
new ServerConfig.Builder(),
- new ConnectorConfig.Builder(),
+ new ConnectorConfig.Builder().accessLog(
+ new ConnectorConfig.AccessLog.Builder()
+ .contentPathPrefixes("/status:1")
+ .contentPathPrefixes("/state/v1:0.001")),
binder -> binder.bind(RequestLog.class).toInstance(requestLogMock));
driver.client().newPost("/status.html").setContent("abcdef").execute().expectStatusCode(is(OK));
RequestLogEntry entry = requestLogMock.poll(Duration.ofSeconds(5));
assertEquals(200, entry.statusCode().getAsInt());
assertEquals(6, entry.requestSize().getAsLong());
+ assertEquals("text/plain; charset=UTF-8", entry.content().get().type());
+ assertEquals(6, entry.content().get().length());
+ assertEquals("abcdef", new String(entry.content().get().body(), UTF_8));
assertTrue(driver.close());
}
@@ -894,7 +901,7 @@ public class HttpServerTest {
final HttpRequest httpRequest = (HttpRequest)request;
final String connectedAt = String.valueOf(httpRequest.getConnectedAt(TimeUnit.MILLISECONDS));
final ContentChannel ch = handler.handleResponse(new Response(OK));
- ch.write(ByteBuffer.wrap(connectedAt.getBytes(StandardCharsets.UTF_8)), null);
+ ch.write(ByteBuffer.wrap(connectedAt.getBytes(UTF_8)), null);
ch.close(null);
return null;
}
@@ -924,7 +931,7 @@ public class HttpServerTest {
List<Cookie> cookies = new ArrayList<>(((HttpRequest)request).decodeCookieHeader());
cookies.sort(new CookieComparator());
final ContentChannel out = ResponseDispatch.newInstance(Response.Status.OK).connect(handler);
- out.write(StandardCharsets.UTF_8.encode(cookies.toString()), null);
+ out.write(UTF_8.encode(cookies.toString()), null);
out.close(null);
return null;
}
@@ -938,7 +945,7 @@ public class HttpServerTest {
public ContentChannel handleRequest(Request request, ResponseHandler handler) {
Map<String, List<String>> parameters = new TreeMap<>(((HttpRequest)request).parameters());
ContentChannel responseContentChannel = ResponseDispatch.newInstance(Response.Status.OK).connect(handler);
- responseContentChannel.write(ByteBuffer.wrap(parameters.toString().getBytes(StandardCharsets.UTF_8)),
+ responseContentChannel.write(ByteBuffer.wrap(parameters.toString().getBytes(UTF_8)),
NULL_COMPLETION_HANDLER);
// Have the request content written back to the response.
@@ -1012,7 +1019,7 @@ public class HttpServerTest {
@Override
public ContentChannel handleRequest(Request req, ResponseHandler handler) {
final ContentChannel ch = handler.handleResponse(new Response(OK));
- ch.write(ByteBuffer.wrap(req.getUri().toString().getBytes(StandardCharsets.UTF_8)), null);
+ ch.write(ByteBuffer.wrap(req.getUri().toString().getBytes(UTF_8)), null);
ch.close(null);
return null;
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
index 52fb3854324..fb807d186eb 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
@@ -455,6 +455,12 @@ public class PermanentFlags {
HOSTNAME
);
+ public static final UnboundListFlag<String> LOG_REQUEST_CONTENT = defineListFlag(
+ "log-request-content", List.of(), String.class,
+ "Include request content in access log for paths starting with any of these prefixes",
+ "Takes effect on next redeployment",
+ INSTANCE_ID);
+
private PermanentFlags() {}
private static UnboundBooleanFlag defineFeatureFlag(