diff --git a/src/Amazon.CloudWatch.EMF/Constants.cs b/src/Amazon.CloudWatch.EMF/Constants.cs
index 1d50e2f..807471a 100644
--- a/src/Amazon.CloudWatch.EMF/Constants.cs
+++ b/src/Amazon.CloudWatch.EMF/Constants.cs
@@ -2,12 +2,17 @@ namespace Amazon.CloudWatch.EMF
{
public class Constants
{
+ public const int MAX_DIMENSION_SET_SIZE = 30;
+ public const int MAX_DIMENSION_NAME_LENGTH = 250;
+ public const int MAX_DIMENSION_VALUE_LENGTH = 1024;
+ public const int MAX_METRIC_NAME_LENGTH = 1024;
+ public const int MAX_NAMESPACE_LENGTH = 256;
+ public const string VALID_NAMESPACE_REGEX = "^[a-zA-Z0-9._#:/-]+$";
+
public const int DEFAULT_AGENT_PORT = 25888;
public const string UNKNOWN = "Unknown";
- public const int MAX_DIMENSION_SET_SIZE = 30;
-
public const int MAX_METRICS_PER_EVENT = 100;
public const string DEFAULT_NAMESPACE = "aws-embedded-metrics";
diff --git a/src/Amazon.CloudWatch.EMF/Exception/DimensionSetExceededException.cs b/src/Amazon.CloudWatch.EMF/Exception/DimensionSetExceededException.cs
index 4b89fcf..86e774a 100644
--- a/src/Amazon.CloudWatch.EMF/Exception/DimensionSetExceededException.cs
+++ b/src/Amazon.CloudWatch.EMF/Exception/DimensionSetExceededException.cs
@@ -9,15 +9,5 @@ public DimensionSetExceededException()
". Account for default dimensions if not using SetDimensions.")
{
}
-
- public DimensionSetExceededException(string message)
- : base(message)
- {
- }
-
- public DimensionSetExceededException(string message, Exception inner)
- : base(message, inner)
- {
- }
}
-}
\ No newline at end of file
+}
diff --git a/src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs b/src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs
index 8f9bc8c..a4fc9eb 100644
--- a/src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs
+++ b/src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs
@@ -18,4 +18,4 @@ public EMFClientException(string message, Exception inner)
{
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Amazon.CloudWatch.EMF/Exception/InvalidDimensionException.cs b/src/Amazon.CloudWatch.EMF/Exception/InvalidDimensionException.cs
new file mode 100644
index 0000000..e62467d
--- /dev/null
+++ b/src/Amazon.CloudWatch.EMF/Exception/InvalidDimensionException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Amazon.CloudWatch.EMF
+{
+ public class InvalidDimensionException : Exception
+ {
+ public InvalidDimensionException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/src/Amazon.CloudWatch.EMF/Exception/InvalidMetricException.cs b/src/Amazon.CloudWatch.EMF/Exception/InvalidMetricException.cs
new file mode 100644
index 0000000..da4fa75
--- /dev/null
+++ b/src/Amazon.CloudWatch.EMF/Exception/InvalidMetricException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Amazon.CloudWatch.EMF
+{
+ public class InvalidMetricException : Exception
+ {
+ public InvalidMetricException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/src/Amazon.CloudWatch.EMF/Exception/InvalidNamespaceException.cs b/src/Amazon.CloudWatch.EMF/Exception/InvalidNamespaceException.cs
new file mode 100644
index 0000000..d90a001
--- /dev/null
+++ b/src/Amazon.CloudWatch.EMF/Exception/InvalidNamespaceException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Amazon.CloudWatch.EMF
+{
+ public class InvalidNamespaceException : Exception
+ {
+ public InvalidNamespaceException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs b/src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs
index f021f7b..fa4263b 100644
--- a/src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs
+++ b/src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs
@@ -3,6 +3,7 @@
using Amazon.CloudWatch.EMF.Config;
using Amazon.CloudWatch.EMF.Environment;
using Amazon.CloudWatch.EMF.Model;
+using Amazon.CloudWatch.EMF.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -181,6 +182,7 @@ public MetricsLogger PutMetadata(string key, object value)
/// the current logger.
public MetricsLogger SetNamespace(string logNamespace)
{
+ Validator.ValidateNamespace(logNamespace);
_context.Namespace = logNamespace;
return this;
}
diff --git a/src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs b/src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs
index a5c2021..bcfd79d 100644
--- a/src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs
+++ b/src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using Amazon.CloudWatch.EMF.Utils;
namespace Amazon.CloudWatch.EMF.Model
{
@@ -19,6 +20,7 @@ public DimensionSet()
/// the value for the dimension
public DimensionSet(string key, string value)
{
+ Validator.ValidateDimensionSet(key, value);
Dimensions[key] = value;
}
@@ -31,6 +33,7 @@ public DimensionSet(string key, string value)
/// the dimension value
public void AddDimension(string key, string value)
{
+ Validator.ValidateDimensionSet(key, value);
if (Dimensions.Count >= Constants.MAX_DIMENSION_SET_SIZE)
throw new DimensionSetExceededException();
diff --git a/src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs b/src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs
index 44ab4a3..112799b 100644
--- a/src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs
+++ b/src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Amazon.CloudWatch.EMF.Utils;
namespace Amazon.CloudWatch.EMF.Model
{
@@ -102,6 +103,7 @@ public bool HasDefaultDimensions
/// the units of the metric
public void PutMetric(string key, double value, Unit unit)
{
+ Validator.ValidateMetric(key, value);
_metricDirective.PutMetric(key, value, unit);
}
diff --git a/src/Amazon.CloudWatch.EMF/Utils/Validator.cs b/src/Amazon.CloudWatch.EMF/Utils/Validator.cs
new file mode 100644
index 0000000..bd9f665
--- /dev/null
+++ b/src/Amazon.CloudWatch.EMF/Utils/Validator.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Text;
+
+namespace Amazon.CloudWatch.EMF.Utils
+{
+ public class Validator
+ {
+ internal static void ValidateDimensionSet(in string dimensionName, in string dimensionValue)
+ {
+ if (dimensionName == null || dimensionName.Trim().Length == 0)
+ {
+ throw new InvalidDimensionException("Dimension name must include at least one non-whitespace character");
+ }
+
+ if (dimensionValue == null || dimensionValue.Trim().Length == 0)
+ {
+ throw new InvalidDimensionException("Dimension value must include at least one non-whitespace character");
+ }
+
+ if (dimensionName.Length > Constants.MAX_DIMENSION_NAME_LENGTH)
+ {
+ throw new InvalidDimensionException($"Dimension name cannot be longer than {Constants.MAX_DIMENSION_NAME_LENGTH} characters: {dimensionName}");
+ }
+
+ if (dimensionValue.Length > Constants.MAX_DIMENSION_VALUE_LENGTH)
+ {
+ throw new InvalidDimensionException($"Dimension value cannot be longer than {Constants.MAX_DIMENSION_VALUE_LENGTH} characters: {dimensionValue}");
+ }
+
+ if (!IsAscii(dimensionName))
+ {
+ throw new InvalidDimensionException($"Dimension name contains invalid characters: {dimensionName}");
+ }
+
+ if (!IsAscii(dimensionValue))
+ {
+ throw new InvalidDimensionException($"Dimension value contains invalid characters: {dimensionValue}");
+ }
+
+ if (dimensionName.StartsWith(":"))
+ {
+ throw new InvalidDimensionException("Dimension name cannot start with ':'");
+ }
+ }
+
+ internal static void ValidateMetric(in string name, in double value)
+ {
+ if (name == null || name.Trim().Length == 0)
+ {
+ throw new InvalidMetricException($"Metric name {name} must include at least one non-whitespace character");
+ }
+
+ if (name.Length > Constants.MAX_METRIC_NAME_LENGTH)
+ {
+ throw new InvalidMetricException($"Metric name {name} cannot be longer than {Constants.MAX_METRIC_NAME_LENGTH} characters");
+ }
+
+ if (!Double.IsFinite(value))
+ {
+ throw new InvalidMetricException($"Metric value {value} must be a finite number");
+ }
+ }
+
+ internal static void ValidateNamespace(in string @namespace)
+ {
+ if (@namespace == null || @namespace.Trim().Length == 0)
+ {
+ throw new InvalidNamespaceException($"Namespace {@namespace} must include at least one non-whitespace character");
+ }
+
+ if (@namespace.Length > Constants.MAX_NAMESPACE_LENGTH)
+ {
+ throw new InvalidNamespaceException($"Namespace {@namespace} cannot be longer than {Constants.MAX_NAMESPACE_LENGTH} characters");
+ }
+
+ if (!System.Text.RegularExpressions.Regex.IsMatch(@namespace, Constants.VALID_NAMESPACE_REGEX))
+ {
+ throw new InvalidNamespaceException($"Namespace {@namespace} contains invalid characters");
+ }
+ }
+
+ private static bool IsAscii(in string str)
+ {
+ return Encoding.UTF8.GetByteCount(str) == str.Length;
+ }
+ }
+}
diff --git a/tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs b/tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs
index dd1efc0..e9936a9 100644
--- a/tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs
+++ b/tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs
@@ -31,6 +31,9 @@ public MetricsLoggerTests()
_sink = new MockSink();
_environment.Sink.Returns(_sink);
+ _environment.LogGroupName.Returns("LogGroup");
+ _environment.Name.Returns("Environment");
+ _environment.Type.Returns("Type");
_environmentProvider.ResolveEnvironment().Returns(_environment);
_metricsLogger = new MetricsLogger(_environmentProvider, _logger);
@@ -146,6 +149,25 @@ public void TestSetNameSpace()
Assert.Equal(namespaceValue, _sink.MetricsContext.Namespace);
}
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData("namespace ")]
+ [InlineData("ǹẚḿⱸṥṕấćē")]
+ [InlineData("name$pace")]
+ public void SetNamespace_WithInvalidNamespace_ThrowsInvalidNamespaceException(string namespaceValue)
+ {
+ Assert.Throws(() => _metricsLogger.SetNamespace(namespaceValue));
+ }
+
+ [Fact]
+ public void SetNamespace_WithNameTooLong_ThrowsInvalidNamespaceException()
+ {
+ string namespaceValue = new string('a', Constants.MAX_NAMESPACE_LENGTH + 1);
+ Assert.Throws(() => _metricsLogger.SetNamespace(namespaceValue));
+ }
+
[Fact]
public void TestFlushWithConfiguredServiceName()
{
@@ -216,6 +238,25 @@ public void TestPutMetricWithUnit()
Assert.Equal(Unit.MILLISECONDS, metricDefinition.Unit);
}
+ [Theory]
+ [InlineData(null, 1)]
+ [InlineData("", 1)]
+ [InlineData(" ", 1)]
+ [InlineData("metric", Double.PositiveInfinity)]
+ [InlineData("metric", Double.NegativeInfinity)]
+ [InlineData("metric", Double.NaN)]
+ public void PutMetric_WithInvalidMetric_ThrowsInvalidMetricException(string metricName, double metricValue)
+ {
+ Assert.Throws(() => _metricsLogger.PutMetric(metricName, metricValue, Unit.NONE));
+ }
+
+ [Fact]
+ public void PutMetric_WithNameTooLong_ThrowsInvalidMetricException()
+ {
+ string metricName = new string('a', Constants.MAX_METRIC_NAME_LENGTH + 1);
+ Assert.Throws(() => _metricsLogger.PutMetric(metricName, 1, Unit.NONE));
+ }
+
[Fact]
public void TestPutMetaData()
{
diff --git a/tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs b/tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs
index 7da20b7..e8255bf 100644
--- a/tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs
+++ b/tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs
@@ -1,4 +1,5 @@
using Amazon.CloudWatch.EMF.Model;
+using Amazon.CloudWatch.EMF.Utils;
using Xunit;
namespace Amazon.CloudWatch.EMF.Tests.Model
@@ -25,6 +26,45 @@ public void AddDimension_30_Dimensions()
Assert.Equal(dimensionSetSize, dimensionSet.DimensionKeys.Count);
}
+ [Theory]
+ [InlineData(null, "value")]
+ [InlineData(" ", "value")]
+ [InlineData("ďïɱ", "value")]
+ [InlineData("dim", null)]
+ [InlineData("dim", " ")]
+ [InlineData("dim", "ⱱẵĺ")]
+ [InlineData(":dim", "val")]
+ public void AddDimension_WithInvalidValues_ThrowsInvalidDimensionException(string key, string value)
+ {
+ Assert.Throws(() =>
+ {
+ var dimensionSet = new DimensionSet();
+ dimensionSet.AddDimension(key, value);
+ });
+ }
+
+ [Fact]
+ public void AddDimension_WithNameTooLong_ThrowsInvalidDimensionException()
+ {
+ Assert.Throws(() =>
+ {
+ var dimensionSet = new DimensionSet();
+ string dimensionName = new string('a', Constants.MAX_DIMENSION_NAME_LENGTH + 1);
+ dimensionSet.AddDimension(dimensionName, "value");
+ });
+ }
+
+ [Fact]
+ public void AddDimension_WithValueTooLong_ThrowsInvalidDimensionException()
+ {
+ Assert.Throws(() =>
+ {
+ var dimensionSet = new DimensionSet();
+ string dimensionValue = new string('a', Constants.MAX_DIMENSION_VALUE_LENGTH + 1);
+ dimensionSet.AddDimension("name", dimensionValue);
+ });
+ }
+
[Fact]
public void AddDimension_Limit_Exceeded_Error()
{