diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2024-06-05 12:07:27 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-05 12:07:27 +0200 |
commit | 867f21e10e4a538875588056b976543d3f9d72f8 (patch) | |
tree | 38f1de65b9a8bb303749def1624e07b02adbf550 | |
parent | 40dc6e363de9f117f94d9af38b416fb146491339 (diff) | |
parent | f99125732929fb2c90afdc3dda1a269b15d795ee (diff) |
Merge pull request #31438 from vespa-engine/bjorncs/content-in-access-log
Bjorncs/content in access log
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( |