Skip to content

Commit df44f05

Browse files
author
Mark Kuhn
committed
add support to clear custom dimensions
1 parent 7b7d2c1 commit df44f05

File tree

5 files changed

+289
-6
lines changed

5 files changed

+289
-6
lines changed

README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,121 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
150150
"Time": 189
151151
}
152152
```
153+
154+
## API
155+
156+
### MetricsLogger
157+
158+
The `MetricsLogger` is the interface you will use to publish embedded metrics.
159+
160+
- MetricsLogger **PutMetric**(string key, double value, Unit unit)
161+
- MetricsLogger **PutMetric**(string key, double value)
162+
163+
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.
164+
165+
Units must meet CloudWatch Metrics unit requirements, if not it will default to None. See [MetricDatum](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html) for valid values.
166+
167+
Example:
168+
169+
```c#
170+
metrics.PutMetric("ProcessingLatency", 101, Unit.MILLISECONDS);
171+
```
172+
173+
- MetricsLogger **PutProperty**(string key, object value)
174+
175+
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.
176+
177+
Example:
178+
```c#
179+
metrics.PutProperty("AccountId", "123456789");
180+
metrics.PutProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
181+
182+
Dictionary<string, object> payLoad = new Dictionary<string, object>
183+
{
184+
{ "sampleTime", 123456789 },
185+
{ "temperature", 273.0 },
186+
{ "pressure", 101.3 }
187+
};
188+
metrics.PutProperty("Payload", payLoad);
189+
```
190+
191+
- MetricsLogger **PutDimensions**(DimensionSet dimensions)
192+
193+
Adds a new set of dimensions that will be associated with all metric values.
194+
195+
**WARNING**: Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values).
196+
If the cardinality of a particular value is expected to be high, you should consider
197+
using `setProperty` instead.
198+
199+
Dimensions must meet CloudWatch Dimensions requirements. See [Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values.
200+
201+
Example:
202+
203+
```c#
204+
DimensionSet dimensionSet = new DimensionSet();
205+
dimensionSet.AddDimension("Service", "Aggregator");
206+
dimensionSet.AddDimension("Region", "us-west-2");
207+
metrics.PutDimensions(dimensionSet);
208+
```
209+
210+
- MetricsLogger **SetDimensions**(params DimensionSet[] dimensionSets)
211+
- MetricsLogger **SetDimensions**(bool useDefault, params DimensionSet[] dimensionSets)
212+
213+
Explicitly override all dimensions. This will remove the default dimensions unless `useDefault` is set to true.
214+
215+
**WARNING**:Each dimension set will result in a new CloudWatch metric (even dimension sets with the same values).
216+
If the cardinality of a particular value is expected to be high, you should consider
217+
using `setProperty` instead.
218+
219+
Dimensions must meet CloudWatch Dimensions requirements. See [Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html) for valid values.
220+
221+
Examples:
222+
223+
```c#
224+
DimensionSet dimensionSet = new DimensionSet();
225+
dimensionSet.AddDimension("Service", "Aggregator");
226+
dimensionSet.AddDimension("Region", "us-west-2");
227+
metrics.SetDimensions(true, dimensionSet); // Will preserve default dimensions
228+
```
229+
230+
```c#
231+
DimensionSet dimensionSet = new DimensionSet();
232+
dimensionSet.AddDimension("Service", "Aggregator");
233+
dimensionSet.AddDimension("Region", "us-west-2");
234+
metrics.SetDimensions(dimensionSet); // Will remove default dimensions
235+
```
236+
237+
- MetricsLogger **ResetDimensions**(bool useDefault)
238+
239+
Explicitly clear all custom dimensions. Set `useDefault` to `true` to keep using the default dimensions.
240+
241+
- MetricsLogger **SetNamespace**(string logNamespace)
242+
243+
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.
244+
245+
Example:
246+
247+
```c#
248+
SetNamespace("MyApplication")
249+
```
250+
251+
- **Flush**()
252+
253+
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.
254+
255+
Examples:
256+
257+
```c#
258+
flush(); // default dimensions and custom dimensions will be preserved after each flush()
259+
```
260+
261+
```c#
262+
logger.setFlushPreserveDimensions = false;
263+
flush(); // only default dimensions will be preserved after each flush()
264+
```
265+
266+
```c#
267+
setFlushPreserveDimensions(false);
268+
resetDimensions(false); // default dimensions are disabled; no dimensions will be preserved after each flush()
269+
flush();
270+
```

src/Amazon.CloudWatch.EMF/Logger/MetricsLogger.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Amazon.CloudWatch.EMF.Logger
1111
{
1212
public class MetricsLogger : IMetricsLogger, IDisposable
1313
{
14+
public bool FlushPreserveDimensions = true;
1415
private readonly ILogger _logger;
1516
private readonly IEnvironment _environment;
1617
private readonly IEnvironmentProvider _environmentProvider;
@@ -86,7 +87,7 @@ public void Flush()
8687
_logger.LogDebug("Sending data to sink. {}", _environment.Sink.GetType().Name);
8788

8889
_environment.Sink.Accept(_context);
89-
_context = _context.CreateCopyWithContext();
90+
_context = _context.CreateCopyWithContext(this.FlushPreserveDimensions);
9091
}
9192

9293
/// <summary>
@@ -133,6 +134,30 @@ public MetricsLogger SetDimensions(params DimensionSet[] dimensionSets)
133134
return this;
134135
}
135136

137+
/// <summary>
138+
/// Overwrites all dimensions on this MetricsLogger instance; also overriding default dimensions unless useDefault is set to true
139+
/// </summary>
140+
/// <param name="useDefault">whether to use the default dimensions</param>
141+
/// <param name="dimensionSets">the dimensionSets to set</param>
142+
/// <returns>the current logger</returns>
143+
/// <seealso cref="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Dimension"/>
144+
public MetricsLogger SetDimensions(bool useDefault, params DimensionSet[] dimensionSets)
145+
{
146+
_context.SetDimensions(useDefault, dimensionSets);
147+
return this;
148+
}
149+
150+
/// <summary>
151+
/// Resets all dimensions on this MetricsLogger instance; also resetting default dimensions unless useDefault is set to true
152+
/// </summary>
153+
/// <param name="useDefault">whether to use the default dimensions</param>
154+
/// <returns>the current logger</returns>
155+
public MetricsLogger ResetDimensions(bool useDefault)
156+
{
157+
_context.ResetDimensions(useDefault);
158+
return this;
159+
}
160+
136161
/// <summary>
137162
/// Puts a metric value.
138163
/// This value will be emitted to CloudWatch Metrics asynchronously and does

src/Amazon.CloudWatch.EMF/Model/MetricDirective.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,40 @@ internal void SetDimensions(List<DimensionSet> dimensionSets)
109109
CustomDimensionSets = dimensionSets;
110110
}
111111

112+
/// <summary>
113+
/// Overrides all existing dimensions, optionally suppressing any default dimensions.
114+
/// </summary>
115+
/// <param name="useDefault">whether to use the default dimensions</param>
116+
/// <param name="dimensionSets">the dimension sets to use in lieu of all existing custom and default dimensions</param>
117+
internal void SetDimensions(bool useDefault, List<DimensionSet> dimensionSets)
118+
{
119+
_shouldUseDefaultDimensionSet = useDefault;
120+
CustomDimensionSets = dimensionSets;
121+
}
122+
123+
/// <summary>
124+
/// Resets all dimensions to the default dimensions.
125+
/// </summary>
126+
/// <param name="useDefault">whether to keep the default dimensions</param>
127+
internal void ResetDimensions(bool useDefault)
128+
{
129+
_shouldUseDefaultDimensionSet = useDefault;
130+
CustomDimensionSets = new List<DimensionSet>();
131+
}
132+
112133
internal List<DimensionSet> GetAllDimensionSets()
113134
{
114135
if (!_shouldUseDefaultDimensionSet)
115136
{
116137
return CustomDimensionSets;
117138
}
118139

119-
CustomDimensionSets.ForEach(ds => DefaultDimensionSet.AddRange(ds));
140+
var dimensions = new DimensionSet();
141+
142+
dimensions.AddRange(this.DefaultDimensionSet);
143+
CustomDimensionSets.ForEach(ds => dimensions.AddRange(ds));
120144

121-
var dimensions = new List<DimensionSet>() { DefaultDimensionSet };
122-
return dimensions;
145+
return new List<DimensionSet>() { dimensions };
123146
}
124147

125148
internal MetricDirective DeepCloneWithNewMetrics(List<MetricDefinition> metrics)

src/Amazon.CloudWatch.EMF/Model/MetricsContext.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ public MetricsContext(
5252
/// <summary>
5353
/// Creates a new MetricsContext with the same namespace, properties,
5454
/// and dimensions as this one but empty metrics-directive collection.
55+
/// Custom dimensions are preserved by default unless preservedDimension is set to false
5556
/// </summary>
5657
/// <returns></returns>
57-
public MetricsContext CreateCopyWithContext()
58+
public MetricsContext CreateCopyWithContext(bool preserveDimensions = true)
5859
{
5960
return new MetricsContext(
6061
_metricDirective.Namespace,
6162
_rootNode.GetProperties(),
62-
_metricDirective.CustomDimensionSets,
63+
preserveDimensions ? _metricDirective.CustomDimensionSets : new List<DimensionSet>(),
6364
_metricDirective.DefaultDimensionSet);
6465
}
6566

@@ -189,6 +190,25 @@ public void SetDimensions(params DimensionSet[] dimensionSets)
189190
_metricDirective.SetDimensions(dimensionSets.ToList());
190191
}
191192

193+
/// <summary>
194+
/// Update the dimensions to the specified list; optionally overriding default dimensions
195+
/// </summary>
196+
/// <param name="useDefault">whether to use default dimensions or not.</param>
197+
/// <param name="dimensionSets">the dimensionSets to use instead of all existing dimensions and default dimensions.</param>
198+
public void SetDimensions(bool useDefault, params DimensionSet[] dimensionSets)
199+
{
200+
_metricDirective.SetDimensions(useDefault, dimensionSets.ToList());
201+
}
202+
203+
/// <summary>
204+
/// Reset all dimensions
205+
/// </summary>
206+
/// <param name="useDefault">whether to keep default dimensions or not.</param>
207+
public void ResetDimensions(bool useDefault)
208+
{
209+
_metricDirective.ResetDimensions(useDefault);
210+
}
211+
192212
/// <summary>
193213
/// Adds the specified key-value pair to the metadata.
194214
/// </summary>

tests/Amazon.CloudWatch.EMF.Tests/Logger/MetricsLoggerTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,50 @@ public void TestOverridePreviousDimensions()
9696
Assert.Equal(dimensionValue, _sink.MetricsContext.GetAllDimensionSets()[0].GetDimensionValue(dimensionName));
9797
}
9898

99+
[Fact]
100+
public void SetDimensions_WithUseDefault_PreservesDefaultDimensions()
101+
{
102+
string dimensionName = "dim";
103+
string dimensionValue = "dimValue";
104+
string defaultDimName = "defaultDim";
105+
string defaultDimValue = "defaultDimValue";
106+
107+
MetricsContext metricsContext = new MetricsContext();
108+
metricsContext.DefaultDimensions.AddDimension(defaultDimName, defaultDimValue);
109+
_metricsLogger = new MetricsLogger(_environment, metricsContext, _logger);
110+
111+
_metricsLogger.PutDimensions(new DimensionSet("foo", "bar"));
112+
_metricsLogger.SetDimensions(true, new DimensionSet(dimensionName, dimensionValue));
113+
_metricsLogger.Flush();
114+
115+
Assert.Single(_sink.MetricsContext.GetAllDimensionSets());
116+
Assert.Equal(2, _sink.MetricsContext.GetAllDimensionSets()[0].DimensionKeys.Count);
117+
ExpectDimension(dimensionName, dimensionValue);
118+
ExpectDimension(defaultDimName, defaultDimValue);
119+
}
120+
121+
[Fact]
122+
public void SetDimensions_WithoutUseDefault_DoesNotPreserveDefaultDimensions()
123+
{
124+
string dimensionName = "dim";
125+
string dimensionValue = "dimValue";
126+
string defaultDimName = "defaultDim";
127+
string defaultDimValue = "defaultDimValue";
128+
129+
MetricsContext metricsContext = new MetricsContext();
130+
metricsContext.DefaultDimensions.AddDimension(defaultDimName, defaultDimValue);
131+
_metricsLogger = new MetricsLogger(_environment, metricsContext, _logger);
132+
133+
_metricsLogger.PutDimensions(new DimensionSet("foo", "bar"));
134+
_metricsLogger.SetDimensions(false, new DimensionSet(dimensionName, dimensionValue));
135+
_metricsLogger.Flush();
136+
137+
Assert.Single(_sink.MetricsContext.GetAllDimensionSets());
138+
Assert.Single(_sink.MetricsContext.GetAllDimensionSets()[0].DimensionKeys);
139+
ExpectDimension(dimensionName, dimensionValue);
140+
ExpectDimension(defaultDimName, null);
141+
}
142+
99143
[Fact]
100144
public void TestPutDuplicateDimensions()
101145
{
@@ -140,6 +184,32 @@ public void TestSetPutDuplicateDimensions()
140184
Assert.Equal(dimensionValue4, _sink.MetricsContext.GetAllDimensionSets()[1].GetDimensionValue(dimensionName3));
141185
}
142186

187+
[Theory]
188+
[InlineData(true, 2)]
189+
[InlineData(false, 1)]
190+
public void ResetDimensions_ClearsDimensions(bool useDefault, int expectedDimensionSets)
191+
{
192+
string dimensionName1 = "dim";
193+
string dimensionValue1 = "dimValue";
194+
string dimensionName2 = "dim2";
195+
string dimensionValue2 = "dimValue2";
196+
197+
MetricsContext metricsContext = new MetricsContext();
198+
metricsContext.DefaultDimensions.AddDimension("foo", "bar");
199+
_metricsLogger = new MetricsLogger(_environment, metricsContext, _logger);
200+
201+
_metricsLogger.PutDimensions(new DimensionSet(dimensionName1, dimensionValue1));
202+
_metricsLogger.ResetDimensions(useDefault);
203+
_metricsLogger.PutDimensions(new DimensionSet(dimensionName2, dimensionValue2));
204+
_metricsLogger.Flush();
205+
206+
Assert.Single(_sink.MetricsContext.GetAllDimensionSets());
207+
Assert.Equal(expectedDimensionSets, _sink.MetricsContext.GetAllDimensionSets()[0].DimensionKeys.Count);
208+
Assert.Null(_sink.MetricsContext.GetAllDimensionSets()[0].GetDimensionValue(dimensionName1));
209+
ExpectDimension(dimensionName2, dimensionValue2);
210+
ExpectDimension(dimensionName1, null);
211+
}
212+
143213
[Fact]
144214
public void TestSetNameSpace()
145215
{
@@ -202,6 +272,33 @@ public void TestFlushWithDefaultDimensionDefined()
202272
ExpectDimension("LogGroup", null);
203273
}
204274

275+
[Fact]
276+
public void SetFlushPreserveDimensions_ToFalse_DoesNotPreserveDimensions()
277+
{
278+
string dimensionName = "dim";
279+
string dimensionValue = "val";
280+
string defaultDim = "defaultDim";
281+
string defaultDimValue = "defaultDimValue";
282+
283+
MetricsContext metricsContext = new MetricsContext();
284+
metricsContext.DefaultDimensions.AddDimension(defaultDim, defaultDimValue);
285+
MetricsLogger _metricsLogger1 = new MetricsLogger(_environment, metricsContext, _logger);
286+
287+
_metricsLogger1.PutDimensions(new DimensionSet(dimensionName, dimensionValue));
288+
_metricsLogger1.FlushPreserveDimensions = false;
289+
_metricsLogger1.Flush();
290+
291+
Assert.Equal(2, _sink.MetricsContext.GetAllDimensionSets()[0].DimensionKeys.Count);
292+
ExpectDimension(dimensionName, dimensionValue);
293+
ExpectDimension(defaultDim, defaultDimValue);
294+
295+
_metricsLogger1.Flush();
296+
297+
Assert.Single(_sink.MetricsContext.GetAllDimensionSets()[0].DimensionKeys);
298+
ExpectDimension(dimensionName, null);
299+
ExpectDimension(defaultDim, defaultDimValue);
300+
}
301+
205302
[Fact]
206303
public void TestPutMetricWithNoUnit()
207304
{

0 commit comments

Comments
 (0)