From 5c7eaaf7733ff46b6ba3f531118098fcb3c79a78 Mon Sep 17 00:00:00 2001 From: Umair Khan Date: Thu, 29 Aug 2024 14:54:47 -0700 Subject: [PATCH] Add new IPC tags from IPC Metrics v2 spec --- .../spectator/ipc/IpcAttemptReason.java | 44 +++++++++ .../netflix/spectator/ipc/IpcLogEntry.java | 46 +++++++++ .../com/netflix/spectator/ipc/IpcMethod.java | 94 +++++++++++++++++++ .../com/netflix/spectator/ipc/IpcMetric.java | 5 + .../com/netflix/spectator/ipc/IpcTagKey.java | 28 +++++- .../spectator/ipc/IpcLogEntryTest.java | 51 ++++++++++ 6 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcAttemptReason.java create mode 100644 spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMethod.java diff --git a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcAttemptReason.java b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcAttemptReason.java new file mode 100644 index 000000000..296817d35 --- /dev/null +++ b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcAttemptReason.java @@ -0,0 +1,44 @@ +/** + * Copyright 2024 Netflix, Inc. + * + * 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 + * + * http://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 com.netflix.spectator.ipc; + +import com.netflix.spectator.api.Tag; + +public enum IpcAttemptReason implements Tag { + + /** + * Represents the initial attempt for the request. + */ + initial, + + /** + * Represents a retry attempt for the request. + */ + retry, + + /** + * Represents a hedge attempt for the request. + */ + hedge; + + @Override public String key() { + return IpcTagKey.attemptFinal.key(); + } + + @Override public String value() { + return name(); + } +} diff --git a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcLogEntry.java b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcLogEntry.java index 2f843b6e6..e90af1db9 100644 --- a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcLogEntry.java +++ b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcLogEntry.java @@ -56,14 +56,17 @@ public final class IpcLogEntry { private String protocol; private IpcStatus status; + private String statusCode; private String statusDetail; private Throwable exception; private IpcAttempt attempt; + private String attemptReason; private IpcAttemptFinal attemptFinal; private String vip; private String endpoint; + private String method; private String clientRegion; private String clientZone; @@ -217,6 +220,14 @@ public IpcLogEntry withStatus(IpcStatus status) { return this; } + /** + * Set the implementation specific status code for the request. + */ + public IpcLogEntry withStatusCode(String statusCode) { + this.statusCode = statusCode; + return this; + } + /** * Set the detailed implementation specific status for the request. In most cases it * is preferable to use {@link #withException(Throwable)} or {@link #withHttpStatus(int)} @@ -282,6 +293,22 @@ public IpcLogEntry withAttempt(int attempt) { return withAttempt(IpcAttempt.forAttemptNumber(attempt)); } + /** + * Set the reason for the attempt for the request. + * See {@link IpcAttemptReason} for possible values. + */ + public IpcLogEntry withAttemptReason(IpcAttemptReason attemptReason) { + return withAttemptReason(attemptReason.value()); + } + + /** + * Set the reason for the attempt for the request. + */ + public IpcLogEntry withAttemptReason(String attemptReason) { + this.attemptReason = attemptReason; + return this; + } + /** * Set whether or not this is the final attempt for the request. */ @@ -307,6 +334,22 @@ public IpcLogEntry withEndpoint(String endpoint) { return this; } + /** + * Set the method used for this request. + * See {@link IpcMethod} for possible values. + */ + public IpcLogEntry withMethod(IpcMethod method) { + return withMethod(method.value()); + } + + /** + * Set the method used for this request. + */ + public IpcLogEntry withMethod(String method) { + this.method = method; + return this; + } + /** * Set the client region for the request. In the case of the server side this will be * automatically filled in if the {@link NetflixHeader#Zone} is specified on the client @@ -899,6 +942,7 @@ public String toString() { .addField("protocol", protocol) .addField("uri", uri) .addField("path", path) + .addField("method", method) .addField("endpoint", endpoint) .addField("vip", vip) .addField("clientRegion", clientRegion) @@ -916,9 +960,11 @@ public String toString() { .addField("remoteAddress", remoteAddress) .addField("remotePort", remotePort) .addField("attempt", attempt) + .addField("attemptReason", attemptReason) .addField("attemptFinal", attemptFinal) .addField("result", result) .addField("status", status) + .addField("statusCode", statusCode) .addField("statusDetail", statusDetail) .addField("exceptionClass", getExceptionClass()) .addField("exceptionMessage", getExceptionMessage()) diff --git a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMethod.java b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMethod.java new file mode 100644 index 000000000..a43613cbc --- /dev/null +++ b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMethod.java @@ -0,0 +1,94 @@ +/** + * Copyright 2024 Netflix, Inc. + * + * 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 + * + * http://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 com.netflix.spectator.ipc; + +import com.netflix.spectator.api.Tag; + +public enum IpcMethod implements Tag { + + /** + * Represents a unary gRPC method. + */ + unary, + + /** + * Represents a client streaming gRPC method. + */ + client_streaming, + + /** + * Represents a server streaming gRPC method. + */ + server_streaming, + + /** + * Represents a bidirectional streaming gRPC method. + */ + bidi_streaming, + + /** + * Represents an HTTP GET request. + */ + get, + + /** + * Represents an HTTP POST request. + */ + post, + + /** + * Represents an HTTP PUT request. + */ + put, + + /** + * Represents an HTTP PATCH request. + */ + patch, + + /** + * Represents an HTTP DELETE request. + */ + delete, + + /** + * Represents an HTTP OPTIONS request. + */ + options, + + /** + * Represents a GraphQL query. + */ + query, + + /** + * Represents a GraphQL mutation. + */ + mutation, + + /** + * Represents a GraphQL subscription. + */ + subscription; + + @Override public String key() { + return IpcTagKey.method.key(); + } + + @Override public String value() { + return name(); + } +} diff --git a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMetric.java b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMetric.java index a3c1ee98f..1de35180d 100644 --- a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMetric.java +++ b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcMetric.java @@ -49,7 +49,9 @@ public enum IpcMetric { IpcTagKey.status ), EnumSet.of( + IpcTagKey.attemptReason, IpcTagKey.endpoint, + IpcTagKey.method, IpcTagKey.failureInjected, IpcTagKey.httpMethod, IpcTagKey.httpStatus, @@ -58,6 +60,7 @@ public enum IpcMetric { IpcTagKey.serverApp, IpcTagKey.serverCluster, IpcTagKey.serverAsg, + IpcTagKey.statusCode, IpcTagKey.statusDetail, IpcTagKey.vip ) @@ -75,6 +78,7 @@ public enum IpcMetric { ), EnumSet.of( IpcTagKey.endpoint, + IpcTagKey.method, IpcTagKey.clientApp, IpcTagKey.clientCluster, IpcTagKey.clientAsg, @@ -83,6 +87,7 @@ public enum IpcMetric { IpcTagKey.httpStatus, IpcTagKey.id, IpcTagKey.protocol, + IpcTagKey.statusCode, IpcTagKey.statusDetail, IpcTagKey.vip ) diff --git a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcTagKey.java b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcTagKey.java index 08d2280fa..bbfbcd186 100644 --- a/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcTagKey.java +++ b/spectator-ext-ipc/src/main/java/com/netflix/spectator/ipc/IpcTagKey.java @@ -32,6 +32,12 @@ public enum IpcTagKey { */ result("ipc.result"), + /** + * Indicates where the result was ultimately sourced from such as cache, direct, + * proxy, fallback, etc. + */ + source("ipc.source"), + /** * Dimension indicating a high level status for the request. These values are the same * for all implementations to make it easier to query across services. See {@link IpcStatus} @@ -39,10 +45,18 @@ public enum IpcTagKey { */ status("ipc.status"), + /** + * Dimension indicating the transport-specific code that aligns to the IPC status. + * The values for this are implementation specific. For example with HTTP, + * the status code value would be used here. + */ + statusCode("ipc.status.code"), + /** * Optional dimension indicating a more detailed status. The values for this are - * implementation specific. For example with HTTP, the status code would be a likely - * value used here. + * implementation specific. For example, the {@link #status} may be + * {@code connection_error} and {@code statusDetail} would be {@code no_servers}, + * {@code connect_timeout}, {@code ssl_handshake_failure}, etc. */ statusDetail("ipc.status.detail"), @@ -72,6 +86,11 @@ public enum IpcTagKey { */ attempt("ipc.attempt"), + /** + * Indicates the reason for the attempt. See {@link IpcAttemptReason} for possible values. + */ + attemptReason("ipc.attempt.reason"), + /** * Indicates if this is the final attempt for the request. For example, if the client * is configured to allow 1 retry, then the second attempt would be final. Acceptable @@ -103,6 +122,11 @@ public enum IpcTagKey { */ protocol("ipc.protocol"), + /** + * Method used to make the IPC request. See {@link IpcMethod} for possible values. + */ + method("ipc.method"), + /** * Region where the client is located. * diff --git a/spectator-ext-ipc/src/test/java/com/netflix/spectator/ipc/IpcLogEntryTest.java b/spectator-ext-ipc/src/test/java/com/netflix/spectator/ipc/IpcLogEntryTest.java index 526e05356..9267df758 100644 --- a/spectator-ext-ipc/src/test/java/com/netflix/spectator/ipc/IpcLogEntryTest.java +++ b/spectator-ext-ipc/src/test/java/com/netflix/spectator/ipc/IpcLogEntryTest.java @@ -146,6 +146,16 @@ public void status() { Assertions.assertEquals(expected, actual); } + @Test + public void statusCode() { + String expected = "deadline_exceeded"; + String actual = (String) entry + .withStatusCode(expected) + .convert(this::toMap) + .get("statusCode"); + Assertions.assertEquals(expected, actual); + } + @Test public void statusDetail() { String expected = "connection_failed"; @@ -188,6 +198,26 @@ public void attempt() { Assertions.assertEquals(expected, actual); } + @Test + public void attemptReason() { + String expected = IpcAttemptReason.retry.value(); + String actual = (String) entry + .withAttemptReason(IpcAttemptReason.retry) + .convert(this::toMap) + .get("attemptReason"); + Assertions.assertEquals(expected, actual); + } + + @Test + public void customAttemptReason() { + String expected = "unknown"; + String actual = (String) entry + .withAttemptReason(expected) + .convert(this::toMap) + .get("attemptReason"); + Assertions.assertEquals(expected, actual); + } + @Test public void attemptFinal() { String expected = IpcAttemptFinal.is_true.value(); @@ -239,6 +269,27 @@ public void endpointViaUri404() { Assertions.assertEquals("unknown", actual); } + @Test + public void method() { + String expected = IpcMethod.get.value(); + String actual = (String) entry + .withMethod(IpcMethod.get) + .convert(this::toMap) + .get("method"); + Assertions.assertEquals(expected, actual); + } + + @Test + public void customMethod() { + String expected = "websocket"; + String actual = (String) entry + .withMethod(expected) + .convert(this::toMap) + .get("method"); + Assertions.assertEquals(expected, actual); + } + + @Test public void clientNode() { String expected = "i-12345";