aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
diff options
context:
space:
mode:
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java')
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java167
1 files changed, 167 insertions, 0 deletions
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
new file mode 100644
index 00000000000..4de5e5e5387
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/AccessLogRequestLog.java
@@ -0,0 +1,167 @@
+// Copyright 2017 Yahoo Holdings. 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.Objects;
+import com.yahoo.container.logging.AccessLog;
+import com.yahoo.container.logging.AccessLogEntry;
+import com.yahoo.container.logging.RequestLog;
+import com.yahoo.container.logging.RequestLogEntry;
+import com.yahoo.jdisc.http.ServerConfig;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+import javax.servlet.http.HttpServletRequest;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.yahoo.jdisc.http.server.jetty.HttpServletRequestUtils.getConnectorLocalPort;
+
+/**
+ * This class is a bridge between Jetty's {@link org.eclipse.jetty.server.handler.RequestLogHandler}
+ * and our own configurable access logging in different formats provided by {@link AccessLog}.
+ *
+ * @author Oyvind Bakksjo
+ * @author bjorncs
+ */
+class AccessLogRequestLog extends AbstractLifeCycle implements org.eclipse.jetty.server.RequestLog {
+
+ private static final Logger logger = Logger.getLogger(AccessLogRequestLog.class.getName());
+
+ // HTTP headers that are logged as extra key-value-pairs in access log entries
+ private static final List<String> LOGGED_REQUEST_HEADERS = List.of("Vespa-Client-Version");
+
+ private final RequestLog requestLog;
+ private final List<String> remoteAddressHeaders;
+ private final List<String> remotePortHeaders;
+
+ AccessLogRequestLog(RequestLog requestLog, ServerConfig.AccessLog config) {
+ this.requestLog = requestLog;
+ this.remoteAddressHeaders = config.remoteAddressHeaders();
+ this.remotePortHeaders = config.remotePortHeaders();
+ }
+
+ @Override
+ public void log(Request request, Response response) {
+ try {
+ RequestLogEntry.Builder builder = new RequestLogEntry.Builder();
+
+ String peerAddress = request.getRemoteAddr();
+ int peerPort = request.getRemotePort();
+ long startTime = request.getTimeStamp();
+ long endTime = System.currentTimeMillis();
+ builder.peerAddress(peerAddress)
+ .peerPort(peerPort)
+ .localPort(getLocalPort(request))
+ .timestamp(Instant.ofEpochMilli(startTime))
+ .duration(Duration.ofMillis(Math.max(0, endTime - startTime)))
+ .contentSize(response.getHttpChannel().getBytesWritten())
+ .statusCode(response.getCommittedMetaData().getStatus());
+
+ addNonNullValue(builder, request.getMethod(), RequestLogEntry.Builder::httpMethod);
+ addNonNullValue(builder, request.getRequestURI(), RequestLogEntry.Builder::rawPath);
+ addNonNullValue(builder, request.getProtocol(), RequestLogEntry.Builder::httpVersion);
+ addNonNullValue(builder, request.getScheme(), RequestLogEntry.Builder::scheme);
+ addNonNullValue(builder, request.getHeader("User-Agent"), RequestLogEntry.Builder::userAgent);
+ addNonNullValue(builder, request.getHeader("Host"), RequestLogEntry.Builder::hostString);
+ addNonNullValue(builder, request.getHeader("Referer"), RequestLogEntry.Builder::referer);
+ addNonNullValue(builder, request.getQueryString(), RequestLogEntry.Builder::rawQuery);
+
+ Principal principal = (Principal) request.getAttribute(ServletRequest.JDISC_REQUEST_PRINCIPAL);
+ addNonNullValue(builder, principal, RequestLogEntry.Builder::userPrincipal);
+
+ String requestFilterId = (String) request.getAttribute(ServletRequest.JDISC_REQUEST_CHAIN);
+ addNonNullValue(builder, requestFilterId, (b, chain) -> b.addExtraAttribute("request-chain", chain));
+
+ String responseFilterId = (String) request.getAttribute(ServletRequest.JDISC_RESPONSE_CHAIN);
+ addNonNullValue(builder, responseFilterId, (b, chain) -> b.addExtraAttribute("response-chain", chain));
+
+ UUID connectionId = (UUID) request.getAttribute(JettyConnectionLogger.CONNECTION_ID_REQUEST_ATTRIBUTE);
+ addNonNullValue(builder, connectionId, (b, uuid) -> b.connectionId(uuid.toString()));
+
+ String remoteAddress = getRemoteAddress(request);
+ if (!Objects.equal(remoteAddress, peerAddress)) {
+ builder.remoteAddress(remoteAddress);
+ }
+ int remotePort = getRemotePort(request);
+ if (remotePort != peerPort) {
+ builder.remotePort(remotePort);
+ }
+ LOGGED_REQUEST_HEADERS.forEach(header -> {
+ String value = request.getHeader(header);
+ if (value != null) {
+ builder.addExtraAttribute(header, value);
+ }
+ });
+ X509Certificate[] clientCert = (X509Certificate[]) request.getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT);
+ if (clientCert != null && clientCert.length > 0) {
+ builder.sslPrincipal(clientCert[0].getSubjectX500Principal());
+ }
+
+ AccessLogEntry accessLogEntry = (AccessLogEntry) request.getAttribute(JDiscHttpServlet.ATTRIBUTE_NAME_ACCESS_LOG_ENTRY);
+ if (accessLogEntry != null) {
+ var extraAttributes = accessLogEntry.getKeyValues();
+ if (extraAttributes != null) {
+ extraAttributes.forEach(builder::addExtraAttributes);
+ }
+ addNonNullValue(builder, accessLogEntry.getHitCounts(), RequestLogEntry.Builder::hitCounts);
+ addNonNullValue(builder, accessLogEntry.getTrace(), RequestLogEntry.Builder::traceNode);
+ }
+
+ requestLog.log(builder.build());
+ } catch (Exception e) {
+ // Catching any exceptions here as it is unclear how Jetty handles exceptions from a RequestLog.
+ logger.log(Level.SEVERE, "Failed to log access log entry: " + e.getMessage(), e);
+ }
+ }
+
+ private String getRemoteAddress(HttpServletRequest request) {
+ for (String header : remoteAddressHeaders) {
+ String value = request.getHeader(header);
+ if (value != null) return value;
+ }
+ return request.getRemoteAddr();
+ }
+
+ private int getRemotePort(HttpServletRequest request) {
+ for (String header : remotePortHeaders) {
+ String value = request.getHeader(header);
+ if (value != null) {
+ OptionalInt maybePort = parsePort(value);
+ if (maybePort.isPresent()) return maybePort.getAsInt();
+ }
+ }
+ return request.getRemotePort();
+ }
+
+ private static int getLocalPort(Request request) {
+ int connectorLocalPort = getConnectorLocalPort(request);
+ if (connectorLocalPort <= 0) return request.getLocalPort(); // If connector is already closed
+ return connectorLocalPort;
+ }
+
+ private static OptionalInt parsePort(String port) {
+ try {
+ return OptionalInt.of(Integer.parseInt(port));
+ } catch (IllegalArgumentException e) {
+ return OptionalInt.empty();
+ }
+ }
+
+ private static <T> void addNonNullValue(
+ RequestLogEntry.Builder builder, T value, BiConsumer<RequestLogEntry.Builder, T> setter) {
+ if (value != null) {
+ setter.accept(builder, value);
+ }
+ }
+
+}