diff options
Diffstat (limited to 'jdisc_http_service/src/main/java/com')
5 files changed, 169 insertions, 108 deletions
diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java index 15a38f6eeae..a6b800a9279 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java @@ -2,13 +2,6 @@ package com.yahoo.container.logging; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.slime.SlimeUtils; -import com.yahoo.slime.Type; -import com.yahoo.yolean.Exceptions; - -import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Optional; import java.util.UUID; @@ -69,75 +62,6 @@ public class ConnectionLogEntry { this.sslHandshakeFailureType = builder.sslHandshakeFailureType; } - public String toJson() { - Slime slime = new Slime(); - Cursor cursor = slime.setObject(); - cursor.setString("id", id.toString()); - setTimestamp(cursor, timestamp, "timestamp"); - - setDouble(cursor, durationSeconds, "duration"); - setString(cursor, peerAddress, "peerAddress"); - setInteger(cursor, peerPort, "peerPort"); - setString(cursor, localAddress, "localAddress"); - setInteger(cursor, localPort, "localPort"); - setString(cursor, remoteAddress, "remoteAddress"); - setInteger(cursor, remotePort, "remotePort"); - setLong(cursor, httpBytesReceived, "httpBytesReceived"); - setLong(cursor, httpBytesSent, "httpBytesSent"); - setLong(cursor, requests, "requests"); - setLong(cursor, responses, "responses"); - setString(cursor, sslProtocol, "ssl", "protocol"); - setString(cursor, sslSessionId, "ssl", "sessionId"); - setString(cursor, sslCipherSuite, "ssl", "cipherSuite"); - setString(cursor, sslPeerSubject, "ssl", "peerSubject"); - setTimestamp(cursor, sslPeerNotBefore, "ssl", "peerNotBefore"); - setTimestamp(cursor, sslPeerNotAfter, "ssl", "peerNotAfter"); - setString(cursor, sslSniServerName, "ssl", "sniServerName"); - setString(cursor, sslHandshakeFailureException, "ssl", "handshake-failure", "exception"); - setString(cursor, sslHandshakeFailureMessage, "ssl", "handshake-failure", "message"); - setString(cursor, sslHandshakeFailureType, "ssl", "handshake-failure", "type"); - return new String(Exceptions.uncheck(() -> SlimeUtils.toJsonBytes(slime)), StandardCharsets.UTF_8); - } - - private void setString(Cursor cursor, String value, String... keys) { - if(value != null) { - subCursor(cursor, keys).setString(keys[keys.length - 1], value); - } - } - - private void setLong(Cursor cursor, Long value, String... keys) { - if (value != null) { - subCursor(cursor, keys).setLong(keys[keys.length - 1], value); - } - } - - private void setInteger(Cursor cursor, Integer value, String... keys) { - if (value != null) { - subCursor(cursor, keys).setLong(keys[keys.length - 1], value); - } - } - - private void setTimestamp(Cursor cursor, Instant timestamp, String... keys) { - if (timestamp != null) { - subCursor(cursor, keys).setString(keys[keys.length - 1], timestamp.toString()); - } - } - - private void setDouble(Cursor cursor, Double value, String... keys) { - if (value != null) { - subCursor(cursor, keys).setDouble(keys[keys.length - 1], value); - } - } - - private static Cursor subCursor(Cursor cursor, String... keys) { - Cursor subCursor = cursor; - for (int i = 0; i < keys.length - 1; ++i) { - Cursor field = subCursor.field(keys[i]); - subCursor = field.type() != Type.NIX ? field : subCursor.setObject(keys[i]); - } - return subCursor; - } - public static Builder builder(UUID id, Instant timestamp) { return new Builder(id, timestamp); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/FileConnectionLog.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/FileConnectionLog.java index 62e53a5a514..968ba74b4f2 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/container/logging/FileConnectionLog.java +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/FileConnectionLog.java @@ -5,23 +5,19 @@ package com.yahoo.container.logging; import com.google.inject.Inject; import com.yahoo.component.AbstractComponent; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.logging.Level; import java.util.logging.Logger; /** * @author mortent */ -public class FileConnectionLog extends AbstractComponent implements ConnectionLog, LogWriter<ConnectionLogEntry> { +public class FileConnectionLog extends AbstractComponent implements ConnectionLog { private static final Logger logger = Logger.getLogger(FileConnectionLog.class.getName()); private final ConnectionLogHandler logHandler; @Inject public FileConnectionLog(ConnectionLogConfig config) { - logHandler = new ConnectionLogHandler(config.cluster(), this); + logHandler = new ConnectionLogHandler(config.cluster(), new JsonConnectionLogWriter()); } @Override @@ -34,9 +30,4 @@ public class FileConnectionLog extends AbstractComponent implements ConnectionLo logHandler.shutdown(); } - @Override - // TODO serialize directly to outputstream - public void write(ConnectionLogEntry entry, OutputStream outputStream) throws IOException { - outputStream.write(entry.toJson().getBytes(StandardCharsets.UTF_8)); - } }
\ No newline at end of file diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/FormatUtil.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/FormatUtil.java new file mode 100644 index 00000000000..ee780ad2a83 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/FormatUtil.java @@ -0,0 +1,46 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.logging; + +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; + +/** + * @author bjorncs + */ +class FormatUtil { + + private FormatUtil() {} + + static void writeSecondsField(JsonGenerator generator, String fieldName, Instant instant) throws IOException { + writeSecondsField(generator, fieldName, instant.toEpochMilli()); + } + + static void writeSecondsField(JsonGenerator generator, String fieldName, Duration duration) throws IOException { + writeSecondsField(generator, fieldName, duration.toMillis()); + } + + static void writeSecondsField(JsonGenerator generator, String fieldName, double seconds) throws IOException { + writeSecondsField(generator, fieldName, (long)(seconds * 1000)); + } + + static void writeSecondsField(JsonGenerator generator, String fieldName, long milliseconds) throws IOException { + generator.writeFieldName(fieldName); + generator.writeRawValue(toSecondsString(milliseconds)); + } + + /** @return a string with number of seconds with 3 decimals */ + static String toSecondsString(long milliseconds) { + StringBuilder builder = new StringBuilder().append(milliseconds / 1000L).append('.'); + long decimals = milliseconds % 1000; + if (decimals < 100) { + builder.append('0'); + if (decimals < 10) { + builder.append('0'); + } + } + return builder.append(decimals).toString(); + } +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/JSONFormatter.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JSONFormatter.java index c6d177684ac..441e139bc67 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/container/logging/JSONFormatter.java +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JSONFormatter.java @@ -15,6 +15,8 @@ import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; +import static com.yahoo.container.logging.FormatUtil.writeSecondsField; + /** * Formatting of an {@link AccessLogEntry} in the Vespa JSON access log format. * @@ -45,8 +47,8 @@ public class JSONFormatter implements LogWriter<RequestLogEntry> { String peerAddress = entry.peerAddress().get(); generator.writeStringField("ip", peerAddress); long time = entry.timestamp().get().toEpochMilli(); - writeSeconds(generator, "time", time); - writeSeconds(generator, "duration", entry.duration().get().toMillis()); + FormatUtil.writeSecondsField(generator, "time", time); + FormatUtil.writeSecondsField(generator, "duration", entry.duration().get()); generator.writeNumberField("responsesize", entry.contentSize().orElse(0)); generator.writeNumberField("code", entry.statusCode().orElse(0)); generator.writeStringField("method", entry.httpMethod().orElse("")); @@ -185,23 +187,4 @@ public class JSONFormatter implements LogWriter<RequestLogEntry> { if (rawPath == null) return null; return rawQuery != null ? rawPath + "?" + rawQuery : rawPath; } - - private static void writeSeconds(JsonGenerator generator, String fieldName, long milliseconds) throws IOException { - generator.writeFieldName(fieldName); - generator.writeRawValue(toSecondsString(milliseconds)); - } - - /** @return a string with number of seconds with 3 decimals */ - private static String toSecondsString(long milliseconds) { - StringBuilder builder = new StringBuilder().append(milliseconds / 1000L).append('.'); - long decimals = milliseconds % 1000; - if (decimals < 100) { - builder.append('0'); - if (decimals < 10) { - builder.append('0'); - } - } - return builder.append(decimals).toString(); - } - } diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java new file mode 100644 index 00000000000..394f87c07cc --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java @@ -0,0 +1,117 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.logging; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Instant; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +/** + * @author bjorncs + */ +class JsonConnectionLogWriter implements LogWriter<ConnectionLogEntry> { + + private final JsonFactory jsonFactory = new JsonFactory(new ObjectMapper()); + + @Override + public void write(ConnectionLogEntry record, OutputStream outputStream) throws IOException { + try (JsonGenerator generator = createJsonGenerator(outputStream)) { + generator.writeStartObject(); + generator.writeStringField("id", record.id()); + generator.writeStringField("timestamp", record.timestamp().toString()); + + writeOptionalSeconds(generator, "duration", unwrap(record.durationSeconds())); + writeOptionalString(generator, "peerAddress", unwrap(record.peerAddress())); + writeOptionalInteger(generator, "peerPort", unwrap(record.peerPort())); + writeOptionalString(generator, "localAddress", unwrap(record.localAddress())); + writeOptionalInteger(generator, "localPort", unwrap(record.localPort())); + writeOptionalString(generator, "remoteAddress", unwrap(record.remoteAddress())); + writeOptionalInteger(generator, "remotePort", unwrap(record.remotePort())); + writeOptionalLong(generator, "httpBytesReceived", unwrap(record.httpBytesReceived())); + writeOptionalLong(generator, "httpBytesSent", unwrap(record.httpBytesSent())); + writeOptionalLong(generator, "requests", unwrap(record.requests())); + writeOptionalLong(generator, "responses", unwrap(record.responses())); + + String sslProtocol = unwrap(record.sslProtocol()); + String sslSessionId = unwrap(record.sslSessionId()); + String sslCipherSuite = unwrap(record.sslCipherSuite()); + String sslPeerSubject = unwrap(record.sslPeerSubject()); + Instant sslPeerNotBefore = unwrap(record.sslPeerNotBefore()); + Instant sslPeerNotAfter = unwrap(record.sslPeerNotAfter()); + String sslSniServerName = unwrap(record.sslSniServerName()); + String sslHandshakeFailureException = unwrap(record.sslHandshakeFailureException()); + String sslHandshakeFailureMessage = unwrap(record.sslHandshakeFailureMessage()); + String sslHandshakeFailureType = unwrap(record.sslHandshakeFailureType()); + + if (isAnyValuePresent( + sslProtocol, sslSessionId, sslCipherSuite, sslPeerSubject, sslPeerNotBefore, sslPeerNotAfter, + sslSniServerName, sslHandshakeFailureException, sslHandshakeFailureMessage, sslHandshakeFailureType)) { + generator.writeObjectFieldStart("ssl"); + + writeOptionalString(generator, "protocol", sslProtocol); + writeOptionalString(generator, "sessionId", sslSessionId); + writeOptionalString(generator, "cipherSuite", sslCipherSuite); + writeOptionalString(generator, "peerSubject", sslPeerSubject); + writeOptionalTimestamp(generator, "peerNotBefore", sslPeerNotBefore); + writeOptionalTimestamp(generator, "peerNotAfter", sslPeerNotAfter); + writeOptionalString(generator, "sniServerName", sslSniServerName); + + if (isAnyValuePresent(sslHandshakeFailureException, sslHandshakeFailureMessage, sslHandshakeFailureType)) { + generator.writeObjectFieldStart("handshake-failure"); + writeOptionalString(generator, "exception", sslHandshakeFailureException); + writeOptionalString(generator, "message", sslHandshakeFailureMessage); + writeOptionalString(generator, "type", sslHandshakeFailureType); + generator.writeEndObject(); + } + + generator.writeEndObject(); + } + } + } + + private void writeOptionalString(JsonGenerator generator, String name, String value) throws IOException { + if (value != null) { + generator.writeStringField(name, value); + } + } + + private void writeOptionalInteger(JsonGenerator generator, String name, Integer value) throws IOException { + if (value != null) { + generator.writeNumberField(name, value); + } + } + + private void writeOptionalLong(JsonGenerator generator, String name, Long value) throws IOException { + if (value != null) { + generator.writeNumberField(name, value); + } + } + + private void writeOptionalTimestamp(JsonGenerator generator, String name, Instant value) throws IOException { + if (value != null) { + generator.writeStringField(name, value.toString()); + } + } + + private void writeOptionalSeconds(JsonGenerator generator, String name, Double value) throws IOException { + if (value != null) { + FormatUtil.writeSecondsField(generator, name, value); + } + } + + private static boolean isAnyValuePresent(Object... values) { return Arrays.stream(values).anyMatch(Objects::nonNull); } + private static <T> T unwrap(Optional<T> maybeValue) { return maybeValue.orElse(null); } + + private JsonGenerator createJsonGenerator(OutputStream outputStream) throws IOException { + return jsonFactory.createGenerator(outputStream, JsonEncoding.UTF8) + .configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false) + .configure(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM, false); + } +} |