Skip to content

Commit 4d6e961

Browse files
committed
feat(metrics): update metrics to version 2.0.0, enhance cold start tracking, and improve documentation
2 parents 75ac8ef + 807bead commit 4d6e961

33 files changed

+929
-507
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ jobs:
3737
run: dotnet build --configuration Release --no-restore /tl
3838

3939
- name: Test & Code Coverage
40-
run: dotnet test --no-restore --filter "Category!=E2E" --collect:"XPlat Code Coverage" --results-directory ./codecov --verbosity quiet
40+
run: dotnet test --no-restore --filter "Category!=E2E" --collect:"XPlat Code Coverage" --results-directory ./codecov --verbosity normal
4141

4242
- name: Test Examples
43-
run: dotnet test ../examples/ --verbosity quiet
43+
run: dotnet test ../examples/ --verbosity normal
4444

4545
- name: Codecov
4646
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # 5.3.1

.github/workflows/label_pr_on_title.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ on:
66
types:
77
- completed
88

9-
permissions:
10-
contents: read
11-
129
jobs:
1310
get_pr_details:
1411
permissions:
15-
id-token: write
1612
contents: read
13+
id-token: write
14+
pull-requests: read
1715
# Guardrails to only ever run if PR recording workflow was indeed
1816
# run in a PR event and ran successfully
1917
if: ${{ github.event.workflow_run.conclusion == 'success' }}
@@ -27,6 +25,7 @@ jobs:
2725
permissions:
2826
contents: read
2927
id-token: write
28+
pull-requests: write
3029
needs: get_pr_details
3130
runs-on: ubuntu-latest
3231
steps:

.github/workflows/on_label_added.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ permissions:
1212
jobs:
1313
get_pr_details:
1414
permissions:
15+
contents: read
1516
id-token: write
1617
if: ${{ github.event.workflow_run.conclusion == 'success' }}
1718
uses: ./.github/workflows/reusable_export_pr_details.yml

.github/workflows/on_merged_pr.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
permissions:
2626
contents: read
2727
id-token: write
28+
issues: write
29+
pull-requests: write
2830
needs: get_pr_details
2931
runs-on: ubuntu-latest
3032
if: needs.get_pr_details.outputs.prIsMerged == 'true'

.github/workflows/on_opened_pr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
get_pr_details:
1414
permissions:
1515
id-token: write
16+
contents: read
1617
if: ${{ github.event.workflow_run.conclusion == 'success' }}
1718
uses: ./.github/workflows/reusable_export_pr_details.yml
1819
with:

.github/workflows/reusable_export_pr_details.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
export_pr_details:
4444
permissions:
4545
id-token: write
46+
contents: read
4647
# see https://github.com/aws-powertools/powertools-lambda-python/issues/1349
4748
if: inputs.workflow_origin == 'aws-powertools/powertools-lambda-dotnet'
4849
runs-on: ubuntu-latest

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Build](https://github.com/aws-powertools/powertools-lambda-dotnet/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/aws-powertools/powertools-lambda-dotnet/actions/workflows/build.yml)
55
[![codecov.io](https://codecov.io/github/aws-powertools/powertools-lambda-dotnet/branch/develop/graphs/badge.svg)](https://app.codecov.io/gh/aws-powertools/powertools-lambda-dotnet)
66
[![dotnet support](https://img.shields.io/static/v1?label=dotnet&message=%20NET6.0|NET8.0&color=blue?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
7-
[![NuGet Downloads](https://img.shields.io/nuget/dt/AWS.Lambda.Powertools.Logging.svg)](https://www.nuget.org/packages?q=AWS.Lambda.Powertools)
7+
[![NuGet Downloads](https://img.shields.io/nuget/dt/AWS.Lambda.Powertools.Logging.svg)](https://www.nuget.org/packages?q=AWS.Lambda.Powertools) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/aws-powertools/powertools-lambda-dotnet/badge)](https://scorecard.dev/viewer/?uri=github.com/aws-powertools/powertools-lambda-dotnet)
88
[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET?style=flat-square)](https://discord.gg/B8zZKbbyET)
99

1010
Powertools for AWS Lambda (.NET) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda-dotnet/#features).

docs/core/metrics-v2.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ These metrics can be visualized through [Amazon CloudWatch Console](https://aws.
1616
* Ahead-of-Time compilation to native code support [AOT](https://docs.aws.amazon.com/lambda/latest/dg/dotnet-native-aot.html) from version 1.7.0
1717
* Support for AspNetCore middleware and filters to capture metrics for HTTP requests
1818

19+
## Breaking changes from V1
20+
21+
* **`Dimensions`** outputs as an array of arrays instead of an array of objects. Example: `Dimensions: [["service", "Environment"]]` instead of `Dimensions: ["service", "Environment"]`
22+
* **`FunctionName`** is not added as default dimension and only to cold start metric.
23+
* **`Default Dimensions`** can now be included in Cold Start metrics, this is a potential breaking change if you were relying on the absence of default dimensions in Cold Start metrics when searching.
24+
1925
<br />
2026

2127
<figure>
@@ -435,7 +441,7 @@ During metrics validation, if no metrics are provided then a warning will be log
435441
!!! tip "Metric validation"
436442
If metrics are provided, and any of the following criteria are not met, **`SchemaValidationException`** will be raised:
437443

438-
* Maximum of 9 dimensions
444+
* Maximum of 30 dimensions
439445
* Namespace is set
440446
* Metric units must be [supported by CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)
441447

@@ -613,7 +619,7 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use **`PushSing
613619
...
614620
```
615621

616-
By default it will skip all previously defined dimensions including default dimensions. Use default_dimensions keyword argument if you want to reuse default dimensions or specify custom dimensions from a dictionary.
622+
By default it will skip all previously defined dimensions including default dimensions. Use `dimensions` argument if you want to reuse default dimensions or specify custom dimensions from a dictionary.
617623

618624
- `Metrics.DefaultDimensions`: Reuse default dimensions when using static Metrics
619625
- `Options.DefaultDimensions`: Reuse default dimensions when using Builder or Configure patterns
@@ -634,7 +640,7 @@ By default it will skip all previously defined dimensions including default dime
634640
unit: MetricUnit.Count,
635641
nameSpace: "ExampleApplication",
636642
service: "Booking",
637-
defaultDimensions: new Dictionary<string, string>
643+
dimensions: new Dictionary<string, string>
638644
{
639645
{"FunctionContext", "$LATEST"}
640646
});
@@ -654,7 +660,7 @@ By default it will skip all previously defined dimensions including default dime
654660
{
655661
{ "Default", "SingleMetric" }
656662
});
657-
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, defaultDimensions: Metrics.DefaultDimensions );
663+
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions );
658664
...
659665
```
660666
=== "Default Dimensions Options / Builder patterns"
@@ -677,7 +683,7 @@ By default it will skip all previously defined dimensions including default dime
677683

678684
public void HandlerSingleMetricDimensions()
679685
{
680-
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, defaultDimensions: _metrics.Options.DefaultDimensions);
686+
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions);
681687
}
682688
...
683689
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
18+
namespace AWS.Lambda.Powertools.Common;
19+
20+
/// <inheritdoc />
21+
public class ConsoleWrapper : IConsoleWrapper
22+
{
23+
/// <inheritdoc />
24+
public void WriteLine(string message) => Console.WriteLine(message);
25+
/// <inheritdoc />
26+
public void Debug(string message) => System.Diagnostics.Debug.WriteLine(message);
27+
/// <inheritdoc />
28+
public void Error(string message) => Console.Error.WriteLine(message);
29+
/// <inheritdoc />
30+
public string ReadLine() => Console.ReadLine();
31+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
namespace AWS.Lambda.Powertools.Common;
17+
18+
/// <summary>
19+
/// Wrapper for console operations to facilitate testing by abstracting system console interactions.
20+
/// </summary>
21+
public interface IConsoleWrapper
22+
{
23+
/// <summary>
24+
/// Writes the specified message followed by a line terminator to the standard output stream.
25+
/// </summary>
26+
/// <param name="message">The message to write.</param>
27+
void WriteLine(string message);
28+
29+
/// <summary>
30+
/// Writes a debug message to the trace listeners in the Debug.Listeners collection.
31+
/// </summary>
32+
/// <param name="message">The debug message to write.</param>
33+
void Debug(string message);
34+
35+
/// <summary>
36+
/// Writes the specified error message followed by a line terminator to the standard error stream.
37+
/// </summary>
38+
/// <param name="message">The error message to write.</param>
39+
void Error(string message);
40+
41+
/// <summary>
42+
/// Reads the next line of characters from the standard input stream.
43+
/// </summary>
44+
/// <returns>The next line of characters from the input stream, or null if no more lines are available.</returns>
45+
string ReadLine();
46+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using Amazon.Lambda.Core;
17+
using Microsoft.AspNetCore.Http;
18+
19+
namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http;
20+
21+
22+
/// <summary>
23+
/// Tracks and manages cold start metrics for Lambda functions in ASP.NET Core applications.
24+
/// </summary>
25+
/// <remarks>
26+
/// This class is responsible for detecting and recording the first invocation (cold start) of a Lambda function.
27+
/// It ensures thread-safe tracking of cold starts and proper metric capture using the provided IMetrics implementation.
28+
/// </remarks>
29+
internal class ColdStartTracker : IDisposable
30+
{
31+
private readonly IMetrics _metrics;
32+
private static bool _coldStart = true;
33+
private static readonly object _lock = new();
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="ColdStartTracker"/> class.
37+
/// </summary>
38+
/// <param name="metrics">The metrics implementation to use for capturing cold start metrics.</param>
39+
public ColdStartTracker(IMetrics metrics)
40+
{
41+
_metrics = metrics;
42+
}
43+
44+
/// <summary>
45+
/// Tracks the cold start of the Lambda function.
46+
/// </summary>
47+
/// <param name="context">The current HTTP context.</param>
48+
internal void TrackColdStart(HttpContext context)
49+
{
50+
if (!_coldStart) return;
51+
52+
lock (_lock)
53+
{
54+
if (!_coldStart) return;
55+
_metrics.CaptureColdStartMetric(context.Items["LambdaContext"] as ILambdaContext);
56+
_coldStart = false;
57+
}
58+
}
59+
60+
/// <summary>
61+
/// Resets the cold start tracking state.
62+
/// </summary>
63+
internal static void ResetColdStart()
64+
{
65+
lock (_lock)
66+
{
67+
_coldStart = true;
68+
}
69+
}
70+
71+
/// <inheritdoc />
72+
public void Dispose()
73+
{
74+
ResetColdStart();
75+
}
76+
}

libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,22 @@ namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http;
2020
/// <summary>
2121
/// Represents a filter that captures and records metrics for HTTP endpoints.
2222
/// </summary>
23-
public class MetricsFilter : IEndpointFilter
23+
/// <remarks>
24+
/// This filter is responsible for tracking cold starts and capturing metrics during HTTP request processing.
25+
/// It integrates with the ASP.NET Core endpoint routing system to inject metrics collection at the endpoint level.
26+
/// </remarks>
27+
/// <inheritdoc cref="IEndpointFilter"/>
28+
/// <inheritdoc cref="IDisposable"/>
29+
public class MetricsFilter : IEndpointFilter, IDisposable
2430
{
25-
private readonly MetricsHelper _metricsHelper;
31+
private readonly ColdStartTracker _coldStartTracker;
2632

2733
/// <summary>
2834
/// Initializes a new instance of the <see cref="MetricsFilter"/> class.
2935
/// </summary>
30-
/// <param name="metrics">The metrics instance to use for recording metrics.</param>
3136
public MetricsFilter(IMetrics metrics)
3237
{
33-
_metricsHelper = new MetricsHelper(metrics);
38+
_coldStartTracker = new ColdStartTracker(metrics);
3439
}
3540

3641
/// <summary>
@@ -41,17 +46,23 @@ public MetricsFilter(IMetrics metrics)
4146
/// <returns>A task that represents the asynchronous operation, containing the result of the endpoint invocation.</returns>
4247
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
4348
{
44-
var result = await next(context);
45-
4649
try
4750
{
48-
await _metricsHelper.CaptureColdStartMetrics(context.HttpContext);
49-
return result;
51+
_coldStartTracker.TrackColdStart(context.HttpContext);
5052
}
5153
catch
5254
{
5355
// ignored
54-
return result;
5556
}
57+
58+
return await next(context);
59+
}
60+
61+
/// <summary>
62+
/// Disposes of the resources used by the filter.
63+
/// </summary>
64+
public void Dispose()
65+
{
66+
_coldStartTracker.Dispose();
5667
}
5768
}

0 commit comments

Comments
 (0)