Skip to content

Commit

Permalink
feat(metrics): update metrics to version 2.0.0, enhance cold start tr…
Browse files Browse the repository at this point in the history
…acking, and improve documentation
  • Loading branch information
hjgraca committed Feb 27, 2025
2 parents 75ac8ef + 807bead commit 4d6e961
Show file tree
Hide file tree
Showing 33 changed files with 929 additions and 507 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ jobs:
run: dotnet build --configuration Release --no-restore /tl

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

- name: Test Examples
run: dotnet test ../examples/ --verbosity quiet
run: dotnet test ../examples/ --verbosity normal

- name: Codecov
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # 5.3.1
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/label_pr_on_title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ on:
types:
- completed

permissions:
contents: read

jobs:
get_pr_details:
permissions:
id-token: write
contents: read
id-token: write
pull-requests: read
# Guardrails to only ever run if PR recording workflow was indeed
# run in a PR event and ran successfully
if: ${{ github.event.workflow_run.conclusion == 'success' }}
Expand All @@ -27,6 +25,7 @@ jobs:
permissions:
contents: read
id-token: write
pull-requests: write
needs: get_pr_details
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/on_label_added.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
jobs:
get_pr_details:
permissions:
contents: read
id-token: write
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/workflows/reusable_export_pr_details.yml
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/on_merged_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
needs: get_pr_details
runs-on: ubuntu-latest
if: needs.get_pr_details.outputs.prIsMerged == 'true'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/on_opened_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
get_pr_details:
permissions:
id-token: write
contents: read
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/workflows/reusable_export_pr_details.yml
with:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/reusable_export_pr_details.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
export_pr_details:
permissions:
id-token: write
contents: read
# see https://github.com/aws-powertools/powertools-lambda-python/issues/1349
if: inputs.workflow_origin == 'aws-powertools/powertools-lambda-dotnet'
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![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)
[![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)
[![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)
[![NuGet Downloads](https://img.shields.io/nuget/dt/AWS.Lambda.Powertools.Logging.svg)](https://www.nuget.org/packages?q=AWS.Lambda.Powertools)
[![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)
[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET?style=flat-square)](https://discord.gg/B8zZKbbyET)

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).
Expand Down
16 changes: 11 additions & 5 deletions docs/core/metrics-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ These metrics can be visualized through [Amazon CloudWatch Console](https://aws.
* 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
* Support for AspNetCore middleware and filters to capture metrics for HTTP requests

## Breaking changes from V1

* **`Dimensions`** outputs as an array of arrays instead of an array of objects. Example: `Dimensions: [["service", "Environment"]]` instead of `Dimensions: ["service", "Environment"]`
* **`FunctionName`** is not added as default dimension and only to cold start metric.
* **`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.

<br />

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

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

Expand Down Expand Up @@ -613,7 +619,7 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use **`PushSing
...
```

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.
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.

- `Metrics.DefaultDimensions`: Reuse default dimensions when using static Metrics
- `Options.DefaultDimensions`: Reuse default dimensions when using Builder or Configure patterns
Expand All @@ -634,7 +640,7 @@ By default it will skip all previously defined dimensions including default dime
unit: MetricUnit.Count,
nameSpace: "ExampleApplication",
service: "Booking",
defaultDimensions: new Dictionary<string, string>
dimensions: new Dictionary<string, string>
{
{"FunctionContext", "$LATEST"}
});
Expand All @@ -654,7 +660,7 @@ By default it will skip all previously defined dimensions including default dime
{
{ "Default", "SingleMetric" }
});
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, defaultDimensions: Metrics.DefaultDimensions );
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions );
...
```
=== "Default Dimensions Options / Builder patterns"
Expand All @@ -677,7 +683,7 @@ By default it will skip all previously defined dimensions including default dime

public void HandlerSingleMetricDimensions()
{
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, defaultDimensions: _metrics.Options.DefaultDimensions);
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions);
}
...
```
Expand Down
31 changes: 31 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using System;

namespace AWS.Lambda.Powertools.Common;

/// <inheritdoc />
public class ConsoleWrapper : IConsoleWrapper
{
/// <inheritdoc />
public void WriteLine(string message) => Console.WriteLine(message);
/// <inheritdoc />
public void Debug(string message) => System.Diagnostics.Debug.WriteLine(message);
/// <inheritdoc />
public void Error(string message) => Console.Error.WriteLine(message);
/// <inheritdoc />
public string ReadLine() => Console.ReadLine();
}
46 changes: 46 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

namespace AWS.Lambda.Powertools.Common;

/// <summary>
/// Wrapper for console operations to facilitate testing by abstracting system console interactions.
/// </summary>
public interface IConsoleWrapper
{
/// <summary>
/// Writes the specified message followed by a line terminator to the standard output stream.
/// </summary>
/// <param name="message">The message to write.</param>
void WriteLine(string message);

/// <summary>
/// Writes a debug message to the trace listeners in the Debug.Listeners collection.
/// </summary>
/// <param name="message">The debug message to write.</param>
void Debug(string message);

/// <summary>
/// Writes the specified error message followed by a line terminator to the standard error stream.
/// </summary>
/// <param name="message">The error message to write.</param>
void Error(string message);

/// <summary>
/// Reads the next line of characters from the standard input stream.
/// </summary>
/// <returns>The next line of characters from the input stream, or null if no more lines are available.</returns>
string ReadLine();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using Amazon.Lambda.Core;
using Microsoft.AspNetCore.Http;

namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http;


/// <summary>
/// Tracks and manages cold start metrics for Lambda functions in ASP.NET Core applications.
/// </summary>
/// <remarks>
/// This class is responsible for detecting and recording the first invocation (cold start) of a Lambda function.
/// It ensures thread-safe tracking of cold starts and proper metric capture using the provided IMetrics implementation.
/// </remarks>
internal class ColdStartTracker : IDisposable
{
private readonly IMetrics _metrics;
private static bool _coldStart = true;
private static readonly object _lock = new();

/// <summary>
/// Initializes a new instance of the <see cref="ColdStartTracker"/> class.
/// </summary>
/// <param name="metrics">The metrics implementation to use for capturing cold start metrics.</param>
public ColdStartTracker(IMetrics metrics)
{
_metrics = metrics;
}

/// <summary>
/// Tracks the cold start of the Lambda function.
/// </summary>
/// <param name="context">The current HTTP context.</param>
internal void TrackColdStart(HttpContext context)
{
if (!_coldStart) return;

lock (_lock)
{
if (!_coldStart) return;
_metrics.CaptureColdStartMetric(context.Items["LambdaContext"] as ILambdaContext);
_coldStart = false;
}
}

/// <summary>
/// Resets the cold start tracking state.
/// </summary>
internal static void ResetColdStart()
{
lock (_lock)
{
_coldStart = true;
}
}

/// <inheritdoc />
public void Dispose()
{
ResetColdStart();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http;
/// <summary>
/// Represents a filter that captures and records metrics for HTTP endpoints.
/// </summary>
public class MetricsFilter : IEndpointFilter
/// <remarks>
/// This filter is responsible for tracking cold starts and capturing metrics during HTTP request processing.
/// It integrates with the ASP.NET Core endpoint routing system to inject metrics collection at the endpoint level.
/// </remarks>
/// <inheritdoc cref="IEndpointFilter"/>
/// <inheritdoc cref="IDisposable"/>
public class MetricsFilter : IEndpointFilter, IDisposable
{
private readonly MetricsHelper _metricsHelper;
private readonly ColdStartTracker _coldStartTracker;

/// <summary>
/// Initializes a new instance of the <see cref="MetricsFilter"/> class.
/// </summary>
/// <param name="metrics">The metrics instance to use for recording metrics.</param>
public MetricsFilter(IMetrics metrics)
{
_metricsHelper = new MetricsHelper(metrics);
_coldStartTracker = new ColdStartTracker(metrics);
}

/// <summary>
Expand All @@ -41,17 +46,23 @@ public MetricsFilter(IMetrics metrics)
/// <returns>A task that represents the asynchronous operation, containing the result of the endpoint invocation.</returns>
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var result = await next(context);

try
{
await _metricsHelper.CaptureColdStartMetrics(context.HttpContext);
return result;
_coldStartTracker.TrackColdStart(context.HttpContext);
}
catch
{
// ignored
return result;
}

return await next(context);
}

/// <summary>
/// Disposes of the resources used by the filter.
/// </summary>
public void Dispose()
{
_coldStartTracker.Dispose();
}
}
Loading

0 comments on commit 4d6e961

Please sign in to comment.