Skip to content

Add support to clear custom dimensions #38

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 7 commits into from
Sep 16, 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
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,122 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
"Time": 189
}
```

## API

### MetricsLogger

The `MetricsLogger` is the interface you will use to publish embedded metrics.

- MetricsLogger **PutMetric**(string key, double value, Unit unit)
- MetricsLogger **PutMetric**(string key, double value)

Adds a new metric to the current logger context. Multiple metrics using the same key will be appended to an array of values. The Embedded Metric Format supports a maxumum of 100 metrics per key.

Metrics must meet CloudWatch Metrics requirements, otherwise a `InvalidMetricException` will be thrown. See [MetricDatum](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) for valid values.

Example:

```c#
metrics.PutMetric("ProcessingLatency", 101, Unit.MILLISECONDS);
```

- MetricsLogger **PutProperty**(string key, object value)

Adds or updates the value for a given property on this context. This value is not submitted to CloudWatch Metrics but is searchable by CloudWatch Logs Insights. This is useful for contextual and potentially high-cardinality data that is not appropriate for CloudWatch Metrics dimensions.

Example:
```c#
metrics.PutProperty("AccountId", "123456789");
metrics.PutProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");

Dictionary<string, object> payLoad = new Dictionary<string, object>
{
{ "sampleTime", 123456789 },
{ "temperature", 273.0 },
{ "pressure", 101.3 }
};
metrics.PutProperty("Payload", payLoad);
```

- MetricsLogger **PutDimensions**(DimensionSet dimensions)

Adds a new set of dimensions that will be associated with all metric values.

**WARNING**: Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values).
If the cardinality of a particular value is expected to be high, you should consider
using `setProperty` instead.

Dimensions must meet CloudWatch Dimensions requirements, otherwise a `InvalidDimensionException` will be thrown. See [Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values.

Example:

```c#
DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
metrics.PutDimensions(dimensionSet);
```

- MetricsLogger **SetDimensions**(params DimensionSet[] dimensionSets)
- MetricsLogger **SetDimensions**(bool useDefault, params DimensionSet[] dimensionSets)

Explicitly override all dimensions. This will remove the default dimensions unless `useDefault` is set to true.

**WARNING**:Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values).
If the cardinality of a particular value is expected to be high, you should consider
using `setProperty` instead.

Dimensions must meet CloudWatch Dimensions requirements, otherwise a `InvalidDimensionException` will be thrown. See [Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values.

Examples:

```c#
DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
metrics.SetDimensions(true, dimensionSet); // Will preserve default dimensions
```

```c#
DimensionSet dimensionSet = new DimensionSet();
dimensionSet.AddDimension("Service", "Aggregator");
dimensionSet.AddDimension("Region", "us-west-2");
metrics.SetDimensions(dimensionSet); // Will remove default dimensions
```

- MetricsLogger **ResetDimensions**(bool useDefault)

Explicitly clear all custom dimensions. Set `useDefault` to `true` to keep using the default dimensions.

- MetricsLogger **SetNamespace**(string logNamespace)

Sets the CloudWatch [namespace](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace) that extracted metrics should be published to. If not set, a default value of aws-embedded-metrics will be used.
Namespaces must meet CloudWatch Namespace requirements, otherwise a `InvalidNamespaceException` will be thrown. See [Namespace](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace) for valid values.

Example:

```c#
SetNamespace("MyApplication")
```

- **Flush**()

Flushes the current MetricsContext to the configured sink and resets all properties and metric values. The namespace and default dimensions will be preserved across flushes. Custom dimensions are preserved by default, but this behavior can be changed by setting `flushPreserveDimensions = false` on the metrics logger.

Examples:

```c#
flush(); // default dimensions and custom dimensions will be preserved after each flush()
```

```c#
logger.setFlushPreserveDimensions = false;
flush(); // only default dimensions will be preserved after each flush()
```

```c#
setFlushPreserveDimensions(false);
resetDimensions(false); // default dimensions are disabled; no dimensions will be preserved after each flush()
flush();
```
27 changes: 26 additions & 1 deletion src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Amazon.CloudWatch.EMF.Logger
{
public class MetricsLogger : IMetricsLogger, IDisposable
{
public bool FlushPreserveDimensions = true;
private readonly ILogger _logger;
private readonly IEnvironment _environment;
private readonly IEnvironmentProvider _environmentProvider;
Expand Down Expand Up @@ -86,7 +87,7 @@ public void Flush()
_logger.LogDebug("Sending data to sink. {}", _environment.Sink.GetType().Name);

_environment.Sink.Accept(_context);
_context = _context.CreateCopyWithContext();
_context = _context.CreateCopyWithContext(this.FlushPreserveDimensions);
}

/// <summary>
Expand Down Expand Up @@ -133,6 +134,30 @@ public MetricsLogger SetDimensions(params DimensionSet[] dimensionSets)
return this;
}

/// <summary>
/// Overwrites all dimensions on this MetricsLogger instance; also overriding default dimensions unless useDefault is set to true
/// </summary>
/// <param name="useDefault">whether to use the default dimensions</param>
/// <param name="dimensionSets">the dimensionSets to set</param>
/// <returns>the current logger</returns>
/// <seealso cref="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Dimension"/>
public MetricsLogger SetDimensions(bool useDefault, params DimensionSet[] dimensionSets)
{
_context.SetDimensions(useDefault, dimensionSets);
return this;
}

/// <summary>
/// Resets all dimensions on this MetricsLogger instance; also resetting default dimensions unless useDefault is set to true
/// </summary>
/// <param name="useDefault">whether to use the default dimensions</param>
/// <returns>the current logger</returns>
public MetricsLogger ResetDimensions(bool useDefault)
{
_context.ResetDimensions(useDefault);
return this;
}

/// <summary>
/// Puts a metric value.
/// This value will be emitted to CloudWatch Metrics asynchronously and does
Expand Down
29 changes: 26 additions & 3 deletions src/Amazon.CloudWatch.EMF/Model/MetricDirective.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,40 @@ internal void SetDimensions(List<DimensionSet> dimensionSets)
CustomDimensionSets = dimensionSets;
}

/// <summary>
/// Overrides all existing dimensions, optionally suppressing any default dimensions.
/// </summary>
/// <param name="useDefault">whether to use the default dimensions</param>
/// <param name="dimensionSets">the dimension sets to use in lieu of all existing custom and default dimensions</param>
internal void SetDimensions(bool useDefault, List<DimensionSet> dimensionSets)
{
_shouldUseDefaultDimensionSet = useDefault;
CustomDimensionSets = dimensionSets;
}

/// <summary>
/// Resets all dimensions to the default dimensions.
/// </summary>
/// <param name="useDefault">whether to keep the default dimensions</param>
internal void ResetDimensions(bool useDefault)
{
_shouldUseDefaultDimensionSet = useDefault;
CustomDimensionSets = new List<DimensionSet>();
}

internal List<DimensionSet> GetAllDimensionSets()
{
if (!_shouldUseDefaultDimensionSet)
{
return CustomDimensionSets;
}

CustomDimensionSets.ForEach(ds => DefaultDimensionSet.AddRange(ds));
var dimensions = new DimensionSet();

dimensions.AddRange(this.DefaultDimensionSet);
CustomDimensionSets.ForEach(ds => dimensions.AddRange(ds));

var dimensions = new List<DimensionSet>() { DefaultDimensionSet };
return dimensions;
return new List<DimensionSet>() { dimensions };
}

internal MetricDirective DeepCloneWithNewMetrics(List<MetricDefinition> metrics)
Expand Down
24 changes: 22 additions & 2 deletions src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ public MetricsContext(
/// <summary>
/// Creates a new MetricsContext with the same namespace, properties,
/// and dimensions as this one but empty metrics-directive collection.
/// Custom dimensions are preserved by default unless preservedDimension is set to false
/// </summary>
/// <returns></returns>
public MetricsContext CreateCopyWithContext()
public MetricsContext CreateCopyWithContext(bool preserveDimensions = true)
{
return new MetricsContext(
_metricDirective.Namespace,
_rootNode.GetProperties(),
_metricDirective.CustomDimensionSets,
preserveDimensions ? _metricDirective.CustomDimensionSets : new List<DimensionSet>(),
_metricDirective.DefaultDimensionSet);
}

Expand Down Expand Up @@ -189,6 +190,25 @@ public void SetDimensions(params DimensionSet[] dimensionSets)
_metricDirective.SetDimensions(dimensionSets.ToList());
}

/// <summary>
/// Update the dimensions to the specified list; optionally overriding default dimensions
/// </summary>
/// <param name="useDefault">whether to use default dimensions or not.</param>
/// <param name="dimensionSets">the dimensionSets to use instead of all existing dimensions and default dimensions.</param>
public void SetDimensions(bool useDefault, params DimensionSet[] dimensionSets)
{
_metricDirective.SetDimensions(useDefault, dimensionSets.ToList());
}

/// <summary>
/// Reset all dimensions
/// </summary>
/// <param name="useDefault">whether to keep default dimensions or not.</param>
public void ResetDimensions(bool useDefault)
{
_metricDirective.ResetDimensions(useDefault);
}

/// <summary>
/// Adds the specified key-value pair to the metadata.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions src/Amazon.CloudWatch.EMF/Utils/Validator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ namespace Amazon.CloudWatch.EMF.Utils
{
public class Validator
{
/// <summary>
/// Validates dimension set.
/// </summary>
/// <param name="dimensionName">Dimension name</param>
/// <param name="dimensionValue">Dimension value</param>
/// <exception cref="InvalidDimensionException">Thrown when dimension name or value is invalid</exception>
internal static void ValidateDimensionSet(in string dimensionName, in string dimensionValue)
{
if (dimensionName == null || dimensionName.Trim().Length == 0)
Expand Down Expand Up @@ -43,6 +49,12 @@ internal static void ValidateDimensionSet(in string dimensionName, in string dim
}
}

/// <summary>
/// Validates metric name.
/// </summary>
/// <param name="name">Metric name</param>
/// <param name="value">Metric value</param>
/// <exception cref="InvalidMetricException">Thrown when metric name or value is invalid</exception>
internal static void ValidateMetric(in string name, in double value)
{
if (name == null || name.Trim().Length == 0)
Expand All @@ -61,6 +73,11 @@ internal static void ValidateMetric(in string name, in double value)
}
}

/// <summary>
/// Validates namespace.
/// </summary>
/// <param name="@namespace">Namespace</param>
/// <exception cref="InvalidNamespaceException">Thrown when namespace is invalid</exception>
internal static void ValidateNamespace(in string @namespace)
{
if (@namespace == null || @namespace.Trim().Length == 0)
Expand All @@ -79,6 +96,11 @@ internal static void ValidateNamespace(in string @namespace)
}
}

/// <summary>
/// Checks if given string is only ASCII.
/// </summary>
/// <param name="str">String to check</param>
/// <returns>True if string is only ASCII, false otherwise</returns>
private static bool IsAscii(in string str)
{
return Encoding.UTF8.GetByteCount(str) == str.Length;
Expand Down
Loading