Skip to content

Add dimension, metric and namespace validation #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/Amazon.CloudWatch.EMF/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}
}
}
}
2 changes: 1 addition & 1 deletion src/Amazon.CloudWatch.EMF/Exception/EMFClientException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ public EMFClientException(string message, Exception inner)
{
}
}
}
}
12 changes: 12 additions & 0 deletions src/Amazon.CloudWatch.EMF/Exception/InvalidDimensionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Amazon.CloudWatch.EMF
{
public class InvalidDimensionException : Exception
{
public InvalidDimensionException(string message)
: base(message)
{
}
}
}
12 changes: 12 additions & 0 deletions src/Amazon.CloudWatch.EMF/Exception/InvalidMetricException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Amazon.CloudWatch.EMF
{
public class InvalidMetricException : Exception
{
public InvalidMetricException(string message)
: base(message)
{
}
}
}
12 changes: 12 additions & 0 deletions src/Amazon.CloudWatch.EMF/Exception/InvalidNamespaceException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Amazon.CloudWatch.EMF
{
public class InvalidNamespaceException : Exception
{
public InvalidNamespaceException(string message)
: base(message)
{
}
}
}
2 changes: 2 additions & 0 deletions src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -181,6 +182,7 @@ public MetricsLogger PutMetadata(string key, object value)
/// <returns>the current logger.</returns>
public MetricsLogger SetNamespace(string logNamespace)
{
Validator.ValidateNamespace(logNamespace);
_context.Namespace = logNamespace;
return this;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Amazon.CloudWatch.EMF/Model/DimensionSet.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Amazon.CloudWatch.EMF.Utils;

namespace Amazon.CloudWatch.EMF.Model
{
Expand All @@ -19,6 +20,7 @@ public DimensionSet()
/// <param name="value">the value for the dimension</param>
public DimensionSet(string key, string value)
{
Validator.ValidateDimensionSet(key, value);
Dimensions[key] = value;
}

Expand All @@ -31,6 +33,7 @@ public DimensionSet(string key, string value)
/// <param name="value">the dimension value</param>
public void AddDimension(string key, string value)
{
Validator.ValidateDimensionSet(key, value);
if (Dimensions.Count >= Constants.MAX_DIMENSION_SET_SIZE)
throw new DimensionSetExceededException();

Expand Down
2 changes: 2 additions & 0 deletions src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.CloudWatch.EMF.Utils;

namespace Amazon.CloudWatch.EMF.Model
{
Expand Down Expand Up @@ -102,6 +103,7 @@ public bool HasDefaultDimensions
/// <param name="unit">the units of the metric</param>
public void PutMetric(string key, double value, Unit unit)
{
Validator.ValidateMetric(key, value);
_metricDirective.PutMetric(key, value, unit);
}

Expand Down
87 changes: 87 additions & 0 deletions src/Amazon.CloudWatch.EMF/Utils/Validator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
41 changes: 41 additions & 0 deletions tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<InvalidNamespaceException>(() => _metricsLogger.SetNamespace(namespaceValue));
}

[Fact]
public void SetNamespace_WithNameTooLong_ThrowsInvalidNamespaceException()
{
string namespaceValue = new string('a', Constants.MAX_NAMESPACE_LENGTH + 1);
Assert.Throws<InvalidNamespaceException>(() => _metricsLogger.SetNamespace(namespaceValue));
}

[Fact]
public void TestFlushWithConfiguredServiceName()
{
Expand Down Expand Up @@ -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<InvalidMetricException>(() => _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<InvalidMetricException>(() => _metricsLogger.PutMetric(metricName, 1, Unit.NONE));
}

[Fact]
public void TestPutMetaData()
{
Expand Down
40 changes: 40 additions & 0 deletions tests/Amazon.CloudWatch.EMF.Tests/Model/DimensionSetTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Amazon.CloudWatch.EMF.Model;
using Amazon.CloudWatch.EMF.Utils;
using Xunit;

namespace Amazon.CloudWatch.EMF.Tests.Model
Expand All @@ -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<InvalidDimensionException>(() =>
{
var dimensionSet = new DimensionSet();
dimensionSet.AddDimension(key, value);
});
}

[Fact]
public void AddDimension_WithNameTooLong_ThrowsInvalidDimensionException()
{
Assert.Throws<InvalidDimensionException>(() =>
{
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<InvalidDimensionException>(() =>
{
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()
{
Expand Down