From e91f0a12c20ce98fa29e57558dd103711095be19 Mon Sep 17 00:00:00 2001 From: Anton Mokhovikov Date: Mon, 23 Nov 2020 13:49:47 -0800 Subject: [PATCH] added metric for tagging --- .../cloudformation/AbstractWrapper.java | 11 ++++-- .../amazon/cloudformation/metrics/Metric.java | 2 ++ .../metrics/MetricsPublisher.java | 7 ++-- .../metrics/MetricsPublisherImpl.java | 36 +++++++++++++------ .../proxy/MetricsPublisherProxy.java | 8 +++-- .../amazon/cloudformation/WrapperTest.java | 16 ++++----- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/main/java/software/amazon/cloudformation/AbstractWrapper.java b/src/main/java/software/amazon/cloudformation/AbstractWrapper.java index 61bbfa04..a643b3b3 100644 --- a/src/main/java/software/amazon/cloudformation/AbstractWrapper.java +++ b/src/main/java/software/amazon/cloudformation/AbstractWrapper.java @@ -232,7 +232,8 @@ public void processRequest(final InputStream inputStream, final OutputStream out // A response will be output on all paths, though CloudFormation will // not block on invoking the handlers, but rather listen for callbacks writeResponse(outputStream, handlerResponse, request); - publishExceptionCodeAndCountMetrics(request == null ? null : request.getAction(), handlerResponse.getErrorCode()); + publishExceptionCodeAndCountMetrics(handlerResponse.getStatus(), request == null ? null : request.getAction(), + handlerResponse.getErrorCode(), handlerResponse.getMessage()); } } @@ -496,9 +497,13 @@ private void publishExceptionMetric(final Action action, final Throwable ex, fin /* * null-safe exception metrics delivery */ - private void publishExceptionCodeAndCountMetrics(final Action action, final HandlerErrorCode handlerErrorCode) { + private void publishExceptionCodeAndCountMetrics(final OperationStatus status, + final Action action, + final HandlerErrorCode handlerErrorCode, + final String message) { if (this.metricsPublisherProxy != null) { - this.metricsPublisherProxy.publishExceptionByErrorCodeAndCountBulkMetrics(Instant.now(), action, handlerErrorCode); + this.metricsPublisherProxy.publishExceptionByErrorCodeAndCountBulkMetrics(status, Instant.now(), action, + handlerErrorCode, message); } } diff --git a/src/main/java/software/amazon/cloudformation/metrics/Metric.java b/src/main/java/software/amazon/cloudformation/metrics/Metric.java index 80b4b14a..1bcbb217 100644 --- a/src/main/java/software/amazon/cloudformation/metrics/Metric.java +++ b/src/main/java/software/amazon/cloudformation/metrics/Metric.java @@ -20,8 +20,10 @@ public class Metric { public static final String METRIC_NAME_HANDLER_EXCEPTION = "HandlerException"; public static final String METRIC_NAME_HANDLER_EXCEPTION_BY_ERROR_CODE = "HandlerExceptionByErrorCode"; public static final String METRIC_NAME_HANDLER_EXCEPTION_BY_EXCEPTION_COUNT = "HandlerExceptionByExceptionCount"; + public static final String METRIC_NAME_HANDLER_EXCEPTION_DUE_TO_TAGGING = "HandlerExceptionByTagging"; public static final String METRIC_NAME_HANDLER_DURATION = "HandlerInvocationDuration"; public static final String METRIC_NAME_HANDLER_INVOCATION_COUNT = "HandlerInvocationCount"; + public static final String ACCESS_DENIED_EXCEPTION_MESSAGE = "not authorized"; public static final String DIMENSION_KEY_ACTION_TYPE = "Action"; public static final String DIMENSION_KEY_EXCEPTION_TYPE = "ExceptionType"; diff --git a/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisher.java b/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisher.java index fc2b1322..e76a5c26 100644 --- a/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisher.java +++ b/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisher.java @@ -17,6 +17,7 @@ import java.time.Instant; import software.amazon.cloudformation.Action; import software.amazon.cloudformation.proxy.HandlerErrorCode; +import software.amazon.cloudformation.proxy.OperationStatus; public abstract class MetricsPublisher { @@ -42,9 +43,11 @@ public void publishExceptionMetric(final Instant timestamp, final HandlerErrorCode handlerErrorCode) { } - public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp, + public void publishExceptionByErrorCodeAndCountBulkMetrics(final OperationStatus status, + final Instant timestamp, final Action action, - final HandlerErrorCode handlerErrorCode) { + final HandlerErrorCode handlerErrorCode, + final String message) { } public void publishInvocationMetric(final Instant timestamp, final Action action) { diff --git a/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisherImpl.java b/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisherImpl.java index 8e76929c..4505542a 100644 --- a/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisherImpl.java +++ b/src/main/java/software/amazon/cloudformation/metrics/MetricsPublisherImpl.java @@ -14,10 +14,12 @@ */ package software.amazon.cloudformation.metrics; +import static software.amazon.cloudformation.metrics.Metric.ACCESS_DENIED_EXCEPTION_MESSAGE; import com.google.common.collect.Sets; import java.time.Instant; import java.util.EnumSet; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; import software.amazon.awssdk.services.cloudwatch.model.Dimension; @@ -28,6 +30,7 @@ import software.amazon.cloudformation.injection.CloudWatchProvider; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.Logger; +import software.amazon.cloudformation.proxy.OperationStatus; public class MetricsPublisherImpl extends MetricsPublisher { private final CloudWatchProvider cloudWatchProvider; @@ -69,27 +72,38 @@ public void publishExceptionMetric(final Instant timestamp, } @Override - public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp, + public void publishExceptionByErrorCodeAndCountBulkMetrics(final OperationStatus status, + final Instant timestamp, final Action action, - final HandlerErrorCode handlerErrorCode) { + final HandlerErrorCode handlerErrorCode, + final String message) { Set bulkData = new HashSet<>(); + final String actionName = action == null ? "NO_ACTION" : action.name(); + final String messageLowerCase = message == null ? "" : message.toLowerCase(Locale.ENGLISH); // By Error Code dimensions - EnumSet.allOf(HandlerErrorCode.class).forEach( - errorCode -> bulkData.add(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_ERROR_CODE) - .unit(StandardUnit.COUNT).value(errorCode == handlerErrorCode ? 1.0 : 0.0) - .dimensions(Sets.newHashSet( - Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name()) - .build(), + EnumSet.allOf(HandlerErrorCode.class) + .forEach(errorCode -> bulkData.add(MetricDatum.builder() + .metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_ERROR_CODE).unit(StandardUnit.COUNT) + .value(errorCode == handlerErrorCode ? 1.0 : 0.0) + .dimensions(Sets.newHashSet(Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(actionName).build(), Dimension.builder().name(Metric.DIMENSION_KEY_HANDLER_ERROR_CODE).value(errorCode.name()).build())) .timestamp(timestamp).build())); // By Count dimensions bulkData.add(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_EXCEPTION_COUNT) - .unit(StandardUnit.COUNT).value(handlerErrorCode == null ? 0.0 : 1.0).dimensions(Dimension.builder() - .name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name()).build()) - .timestamp(timestamp).build()); + .unit(StandardUnit.COUNT).value(handlerErrorCode == null ? 0.0 : 1.0) + .dimensions(Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(actionName).build()).timestamp(timestamp) + .build()); + + boolean authorizationError = status == OperationStatus.FAILED + && messageLowerCase.contains(ACCESS_DENIED_EXCEPTION_MESSAGE) && messageLowerCase.contains("tag"); + + bulkData.add(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_DUE_TO_TAGGING) + .unit(StandardUnit.COUNT).value(authorizationError ? 1.0 : 0.0) + .dimensions(Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(actionName).build()).timestamp(timestamp) + .build()); publishBulkMetrics(bulkData.toArray(new MetricDatum[bulkData.size()])); } diff --git a/src/main/java/software/amazon/cloudformation/proxy/MetricsPublisherProxy.java b/src/main/java/software/amazon/cloudformation/proxy/MetricsPublisherProxy.java index 57fa9824..76fc2091 100644 --- a/src/main/java/software/amazon/cloudformation/proxy/MetricsPublisherProxy.java +++ b/src/main/java/software/amazon/cloudformation/proxy/MetricsPublisherProxy.java @@ -35,11 +35,13 @@ public void publishExceptionMetric(final Instant timestamp, .forEach(metricsPublisher -> metricsPublisher.publishExceptionMetric(timestamp, action, e, handlerErrorCode)); } - public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp, + public void publishExceptionByErrorCodeAndCountBulkMetrics(final OperationStatus status, + final Instant timestamp, final Action action, - final HandlerErrorCode handlerErrorCode) { + final HandlerErrorCode handlerErrorCode, + final String message) { metricsPublishers.stream().forEach(metricsPublisher -> metricsPublisher - .publishExceptionByErrorCodeAndCountBulkMetrics(timestamp, action, handlerErrorCode)); + .publishExceptionByErrorCodeAndCountBulkMetrics(status, timestamp, action, handlerErrorCode, message)); } public void publishInvocationMetric(final Instant timestamp, final Action action) { diff --git a/src/test/java/software/amazon/cloudformation/WrapperTest.java b/src/test/java/software/amazon/cloudformation/WrapperTest.java index 941aecd5..30c6abb8 100755 --- a/src/test/java/software/amazon/cloudformation/WrapperTest.java +++ b/src/test/java/software/amazon/cloudformation/WrapperTest.java @@ -147,8 +147,8 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa // validation failure metric should be published for final error handling verify(providerMetricsPublisher).publishExceptionMetric(any(Instant.class), any(), any(TerminalException.class), any(HandlerErrorCode.class)); - verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), any(), - any(HandlerErrorCode.class)); + verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(OperationStatus.class), + any(Instant.class), any(), any(HandlerErrorCode.class), any()); // all metrics should be published even on terminal failure verify(providerMetricsPublisher).publishInvocationMetric(any(Instant.class), eq(action)); @@ -417,8 +417,8 @@ public void invokeHandler_InProgress_returnsInProgress(final String requestDataP } - verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), eq(action), - any()); + verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(), any(Instant.class), eq(action), + any(), any()); // validation failure metric should not be published verifyNoMoreInteractions(providerMetricsPublisher); @@ -459,8 +459,8 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat // all metrics should be published, once for a single invocation verify(providerMetricsPublisher).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher).publishDurationMetric(any(Instant.class), eq(action), anyLong()); - verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), eq(action), - any()); + verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(), any(Instant.class), eq(action), + any(), any()); // validation failure metric should not be published verifyNoMoreInteractions(providerMetricsPublisher); @@ -825,8 +825,8 @@ public void invokeHandler_metricPublisherThrowable_returnsFailureResponse() thro // verify initialiseRuntime was called and initialised dependencies verifyInitialiseRuntime(); - verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), any(Action.class), - any(HandlerErrorCode.class)); + verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(OperationStatus.class), + any(Instant.class), any(Action.class), any(HandlerErrorCode.class), any(String.class)); // no further calls to metrics publisher should occur verifyNoMoreInteractions(providerMetricsPublisher);