From f80ff199af9e3eea369056c9b9aafcb2f45ec441 Mon Sep 17 00:00:00 2001 From: medevod Date: Thu, 27 Feb 2025 22:18:38 +0100 Subject: [PATCH 1/2] feat: track failed request --- MetricFlow.sln | 9 +- examples/WebApiExample/Program.cs | 86 ++++++++++ .../Properties/launchSettings.json | 41 +++++ examples/WebApiExample/README.md | 156 ++++++++++++++++++ examples/WebApiExample/WebApiExample.csproj | 18 ++ examples/WebApiExample/WebApiExample.http | 6 + .../appsettings.Development.json | 8 + examples/WebApiExample/appsettings.json | 9 + examples/WebApiExample/image.png | Bin 0 -> 60324 bytes .../Abstractions/CounterBase.cs | 9 +- .../Abstractions/ICounter.cs | 2 +- .../Abstractions/IMetricTracker.cs | 2 +- .../Abstractions/MetricTrackerBase.cs | 4 +- .../CounterValues.cs | 3 +- 14 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 examples/WebApiExample/Program.cs create mode 100644 examples/WebApiExample/Properties/launchSettings.json create mode 100644 examples/WebApiExample/README.md create mode 100644 examples/WebApiExample/WebApiExample.csproj create mode 100644 examples/WebApiExample/WebApiExample.http create mode 100644 examples/WebApiExample/appsettings.Development.json create mode 100644 examples/WebApiExample/appsettings.json create mode 100644 examples/WebApiExample/image.png diff --git a/MetricFlow.sln b/MetricFlow.sln index c940a58..8780ec0 100644 --- a/MetricFlow.sln +++ b/MetricFlow.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 @@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetricFlow.Tests", "tests\M EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetKit.MetricFlow.Tracker", "src\DotnetKit.MetricFlow.Tracker\DotnetKit.MetricFlow.Tracker.csproj", "{60112D90-095C-44EA-90E9-846B4EDBF758}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiExample", "examples\WebApiExample\WebApiExample.csproj", "{62617707-AE70-492E-B36D-63D0108EC269}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,6 +34,10 @@ Global {60112D90-095C-44EA-90E9-846B4EDBF758}.Debug|Any CPU.Build.0 = Debug|Any CPU {60112D90-095C-44EA-90E9-846B4EDBF758}.Release|Any CPU.ActiveCfg = Release|Any CPU {60112D90-095C-44EA-90E9-846B4EDBF758}.Release|Any CPU.Build.0 = Release|Any CPU + {62617707-AE70-492E-B36D-63D0108EC269}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62617707-AE70-492E-B36D-63D0108EC269}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62617707-AE70-492E-B36D-63D0108EC269}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62617707-AE70-492E-B36D-63D0108EC269}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -40,6 +46,7 @@ Global {78058871-2DC0-6810-ED9E-588E6E3A1265} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} {3897CD1F-6CA5-41EC-9845-1979426CD7B8} = {01644C81-1609-407B-A964-A15BFA59E822} {60112D90-095C-44EA-90E9-846B4EDBF758} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {62617707-AE70-492E-B36D-63D0108EC269} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7CE4E738-181F-42CA-B63C-365DBD1BC7B9} diff --git a/examples/WebApiExample/Program.cs b/examples/WebApiExample/Program.cs new file mode 100644 index 0000000..703956f --- /dev/null +++ b/examples/WebApiExample/Program.cs @@ -0,0 +1,86 @@ +using DotnetKit.MetricFlow.Tracker; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(sp => new MetricTracker("WebApiExample")); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); + + //add a middleware to track API calls + app.Use(async (context, next) => + { + //skip tracking metrics for the metrics endpoint + if (context.Request.Path == "/metrics") + { + await next(); + return; + } + var tracker = context.RequestServices.GetRequiredService(); + tracker.In(context.Request.Path); + //tracking requests with errors + try + { + await next(); + } + catch (Exception e) + { + tracker.Out(context.Request.Path, new Dictionary() { ["error_message"] = e.Message }, failed: true); + throw; + } + tracker.Out(context.Request.Path); + }); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.MapGet("/throw_exception", () => +{ + throw new Exception("This is an exception"); +}) +.WithName("Exception") +.WithOpenApi(); + +app.MapGet("/metrics", () => +{ + var tracker = app.Services.GetRequiredService(); + return tracker.ToString(); + +}) +.WithName("Metrics") +.WithOpenApi(); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/examples/WebApiExample/Properties/launchSettings.json b/examples/WebApiExample/Properties/launchSettings.json new file mode 100644 index 0000000..9ec0e90 --- /dev/null +++ b/examples/WebApiExample/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64475", + "sslPort": 44327 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5045", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7286;http://localhost:5045", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/WebApiExample/README.md b/examples/WebApiExample/README.md new file mode 100644 index 0000000..c9cc2e1 --- /dev/null +++ b/examples/WebApiExample/README.md @@ -0,0 +1,156 @@ +# Web API Example with MetricFlow + +This example demonstrates how to use the `MetricFlow` library to track and measure metrics in a .NET Web API application. The example includes a `Program` class that sets up the web application, configures middleware to track API calls, and exposes metrics via an endpoint. + +## Goal + +The goal of this example is to showcase how to integrate `MetricFlow` into a .NET Web API application to track various metrics such as request counts and durations for different API endpoints and indicate failed request count. + +This helps in monitoring the performance and usage of the API. + +## Overview of Implementation + +### Key Components + +- **MetricTracker:** Tracks metrics for different API endpoints. +- **Middleware:** Automatically tracks metrics for incoming requests. +- **Endpoints:** Demonstrates how to track metrics for specific API endpoints and expose metrics. + +### Implementation Details + +1. **Service Registration:** + - The `MetricTracker` service is registered as a global service in the dependency injection container. + + ```csharp + builder.Services.AddSingleton(sp => new MetricTracker("WebApiExample")); + ``` + +2. **Middleware Configuration:** + - Middleware is added to track metrics for incoming requests, excluding the `/metrics` endpoint. + - This oveview doesn't track failed requests, please look source code of this example for complete implementation. + + ```csharp + app.Use(async (context, next) => + { + if (context.Request.Path != "/metrics") + { + var tracker = context.RequestServices.GetRequiredService(); + tracker.In(context.Request.Path); + await next(); + tracker.Out(context.Request.Path); + } + else + { + await next(); + } + }); + ``` + +3. **Endpoints:** + - The `/weatherforecast` endpoint simulates a weather forecast API and tracks metrics for the endpoint. + - The `/metrics` endpoint exposes the tracked metrics. + + ```csharp + app.MapGet("/weatherforecast", (MetricTracker tracker) => + { + tracker.In("GetWeatherForecast"); + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + tracker.Out("GetWeatherForecast"); + return forecast; + }) + .WithName("GetWeatherForecast") + .WithOpenApi(); + + app.MapGet("/metrics", (MetricTracker tracker) => + { + return tracker.ToString(); + }) + .WithName("Metrics") + .WithOpenApi(); + ``` + +### Code Example + +```csharp +using System.Diagnostics; +using DotnetKit.MetricFlow.Tracker; +using DotnetKit.MetricFlow.Tracker.Abstractions; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSinglton(sp => new MetricTracker("WebApiExample")); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.Use(async (context, next) => +{ + if (context.Request.Path != "/metrics") + { + var tracker = context.RequestServices.GetRequiredService(); + tracker.In(context.Request.Path); + await next(); + tracker.Out(context.Request.Path); + } + else + { + await next(); + } +}); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", (MetricTracker tracker) => +{ + tracker.In("GetWeatherForecast"); + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + tracker.Out("GetWeatherForecast"); + return forecast; +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.MapGet("/metrics", (MetricTracker tracker) => +{ + return tracker.ToString(); +}) +.WithName("Metrics") +.WithOpenApi(); + +app.Run(); + +record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} +``` + +![alt text](image.png) diff --git a/examples/WebApiExample/WebApiExample.csproj b/examples/WebApiExample/WebApiExample.csproj new file mode 100644 index 0000000..ecb258c --- /dev/null +++ b/examples/WebApiExample/WebApiExample.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/examples/WebApiExample/WebApiExample.http b/examples/WebApiExample/WebApiExample.http new file mode 100644 index 0000000..47fa571 --- /dev/null +++ b/examples/WebApiExample/WebApiExample.http @@ -0,0 +1,6 @@ +@WebApiExample_HostAddress = http://localhost:5045 + +GET {{WebApiExample_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/examples/WebApiExample/appsettings.Development.json b/examples/WebApiExample/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/examples/WebApiExample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/examples/WebApiExample/appsettings.json b/examples/WebApiExample/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/examples/WebApiExample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/WebApiExample/image.png b/examples/WebApiExample/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c6598e2de47bc9c6173661d31cd17cbc875a6a GIT binary patch literal 60324 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
  • Bq(-SWy}51S58KYpc-|N7=SREOI=^dDXld9GQ{ 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⋙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 PEdS8d`@Q;}UGzTznbHme literal 0 HcmV?d00001 diff --git a/src/DotnetKit.MetricFlow.Tracker/Abstractions/CounterBase.cs b/src/DotnetKit.MetricFlow.Tracker/Abstractions/CounterBase.cs index 128d887..a66c421 100644 --- a/src/DotnetKit.MetricFlow.Tracker/Abstractions/CounterBase.cs +++ b/src/DotnetKit.MetricFlow.Tracker/Abstractions/CounterBase.cs @@ -8,6 +8,7 @@ public abstract class CounterBase(string name, Dictionary? metri private DateTime? _startedAt = null; private DateTime? _endedAt = null; private long _inCount = 0; + private long _failedCount = 0; private long _outCount = 0; private long _totalDuration = 0; @@ -21,6 +22,7 @@ public abstract class CounterBase(string name, Dictionary? metri public CounterValues Values => new CounterValues( Interlocked.Read(ref _inCount), Interlocked.Read(ref _outCount), + Interlocked.Read(ref _failedCount), TimeSpan.FromMilliseconds(Interlocked.Read(ref _totalDuration)), TimeSpan.FromMilliseconds(Interlocked.Read(ref _averageDuration)), TimeSpan.FromMilliseconds(Interlocked.Read(ref _minDuration)), @@ -39,9 +41,14 @@ public long Inc() return Interlocked.Increment(ref _inCount); } - public long Dec() + public long Dec(bool? failed = false) { FinalizeState(Stop()); + + if (failed == true) + { + Interlocked.Increment(ref _failedCount); + } return Interlocked.Increment(ref _outCount); } diff --git a/src/DotnetKit.MetricFlow.Tracker/Abstractions/ICounter.cs b/src/DotnetKit.MetricFlow.Tracker/Abstractions/ICounter.cs index defad20..eb60bc9 100644 --- a/src/DotnetKit.MetricFlow.Tracker/Abstractions/ICounter.cs +++ b/src/DotnetKit.MetricFlow.Tracker/Abstractions/ICounter.cs @@ -6,6 +6,6 @@ public interface ICounter DateTime TimeStamp { get; } CounterValues Values { get; } long Inc(); - long Dec(); + long Dec(bool? failed = false); } } \ No newline at end of file diff --git a/src/DotnetKit.MetricFlow.Tracker/Abstractions/IMetricTracker.cs b/src/DotnetKit.MetricFlow.Tracker/Abstractions/IMetricTracker.cs index ffd9d87..5d62886 100644 --- a/src/DotnetKit.MetricFlow.Tracker/Abstractions/IMetricTracker.cs +++ b/src/DotnetKit.MetricFlow.Tracker/Abstractions/IMetricTracker.cs @@ -12,7 +12,7 @@ public interface IMetricTracker IDisposable Track(string counterName, Dictionary? topicTags = null); - long? Out(string counterName, Dictionary? topicTags = null); + long? Out(string counterName, Dictionary? topicTags = null, bool? failed = false); void Clear(); } diff --git a/src/DotnetKit.MetricFlow.Tracker/Abstractions/MetricTrackerBase.cs b/src/DotnetKit.MetricFlow.Tracker/Abstractions/MetricTrackerBase.cs index 5b37c8f..bfcdb96 100644 --- a/src/DotnetKit.MetricFlow.Tracker/Abstractions/MetricTrackerBase.cs +++ b/src/DotnetKit.MetricFlow.Tracker/Abstractions/MetricTrackerBase.cs @@ -27,7 +27,7 @@ public abstract class MetricTrackerBase(string topic, return counter.Inc(); } - public long? Out(string metricName, Dictionary? metricMetadata = null) + public long? Out(string metricName, Dictionary? metricMetadata = null, bool? failed = false) { if (IsSampled(samplingRate, _randomizer)) { @@ -35,7 +35,7 @@ public abstract class MetricTrackerBase(string topic, } var counter = _blockCounters.GetOrAdd(metricName, counterFactory(metricName, metricMetadata)); // Handle metadata as needed - return counter.Dec(); + return counter.Dec(failed); } public IDisposable Track(string metricName, Dictionary? metricMetadata = null) diff --git a/src/DotnetKit.MetricFlow.Tracker/CounterValues.cs b/src/DotnetKit.MetricFlow.Tracker/CounterValues.cs index 9a952db..57e6d61 100644 --- a/src/DotnetKit.MetricFlow.Tracker/CounterValues.cs +++ b/src/DotnetKit.MetricFlow.Tracker/CounterValues.cs @@ -5,6 +5,7 @@ namespace DotnetKit.MetricFlow.Tracker.Abstractions public record CounterValues( long InCount, long OutCount, + long FailedCount, TimeSpan TotalDuration, TimeSpan AverageDuration, TimeSpan MinDuration, @@ -18,7 +19,7 @@ TimeSpan MaxDuration public override string ToString() { var sb = new StringBuilder(); - sb.AppendLine($"Count (in, out): {InCount} / {OutCount}"); + sb.AppendLine($"Count (in, out, failed): {InCount} / {OutCount} / {FailedCount}"); sb.AppendLine($"Avg duration: {AverageDuration.TotalMilliseconds} ms"); sb.AppendLine($"Duration (min, max) : {MinDuration.TotalMilliseconds} ms / {MaxDuration.TotalMilliseconds} ms"); sb.AppendLine($"Total duration: {TotalDuration.TotalMilliseconds} ms"); From 16bae1ccd5ece5ba693548304d282b50fd84bf72 Mon Sep 17 00:00:00 2001 From: medevod Date: Fri, 28 Feb 2025 00:41:08 +0100 Subject: [PATCH 2/2] chore add tests for failed count --- tests/MetricFlow.Tests/MetricTrackerTests.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/MetricFlow.Tests/MetricTrackerTests.cs b/tests/MetricFlow.Tests/MetricTrackerTests.cs index 7c2393c..de69107 100644 --- a/tests/MetricFlow.Tests/MetricTrackerTests.cs +++ b/tests/MetricFlow.Tests/MetricTrackerTests.cs @@ -72,5 +72,25 @@ public async Task MetricTracker_ShouldTrackUsingDisposablePattern() values?.InCount.Should().Be(1); values?.OutCount.Should().Be(1); } + [Fact] + public void MetricTracker_ShouldTrackFailedMetrics() + { + // Arrange + var tracker = new MetricTracker("TestTopic"); + + // Act + tracker.In("TestMetric"); + tracker.Out("TestMetric"); + + tracker.In("TestMetric"); + tracker.Out("TestMetric",failed:true); + // Assert + var values = tracker.GetValues("TestMetric"); + values.Should().NotBeNull(); + values!.InCount.Should().Be(1); + values.OutCount.Should().Be(1); + values.FailedCount.Should().Be(1); + } + } } \ No newline at end of file