Skip to content
Open
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
79 changes: 75 additions & 4 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,84 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "dotnet: restore",
"type": "shell",
"command": "dotnet",
"args": [
"restore"
],
"problemMatcher": "$msCompile",
"presentation": {
"reveal": "silent",
"panel": "dedicated",
"close": true,
"showReuseMessage": false
}
},
{
"label": "dotnet: build",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore"
],
"problemMatcher": "$msCompile",
"dependsOn": "dotnet: restore",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"close": true,
"showReuseMessage": false
},
"group": "build"
},
{
"label": "dotnet: test",
"type": "shell",
"command": "dotnet",
"args": [
"test",
"--no-build",
"--nologo"
],
"problemMatcher": "$msCompile",
"dependsOn": "dotnet: build",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"close": true,
"showReuseMessage": false
}
},
{
"label": "test",
"type": "shell",
"command": "dotnet test --nologo",
"args": [],
"problemMatcher": [
"$msCompile"
],
"group": "build"
"isBackground": false
},
{
"label": "test",
"type": "shell",
"command": "dotnet test --nologo",
"args": [],
"isBackground": false
},
{
"label": "test",
"type": "shell",
"command": "dotnet test --nologo",
"args": [],
"isBackground": false
},
{
"label": "test",
"type": "shell",
"command": "dotnet test --nologo",
"args": [],
"isBackground": false
}
]
}
179 changes: 178 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Parsing HTTP User Agents with .NET
| NuGet |
|-|
| [![MyCSharp.HttpUserAgentParser](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser) |
| [![MyCSharp.HttpUserAgentParser](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.MemoryCache.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser.MemoryCache)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.MemoryCache)| `dotnet add package MyCSharp.HttpUserAgentParser.MemoryCach.MemoryCache` |
| [![MyCSharp.HttpUserAgentParser](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.MemoryCache.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser.MemoryCache)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.MemoryCache)| `dotnet add package MyCSharp.HttpUserAgentParser.MemoryCache` |
| [![MyCSharp.HttpUserAgentParser.AspNetCore](https://img.shields.io/nuget/v/MyCSharp.HttpUserAgentParser.AspNetCore.svg?logo=nuget&label=MyCSharp.HttpUserAgentParser.AspNetCore)](https://www.nuget.org/packages/MyCSharp.HttpUserAgentParser.AspNetCore) | `dotnet add package MyCSharp.HttpUserAgentParser.AspNetCore` |


Expand Down Expand Up @@ -110,6 +110,183 @@ public void MyMethod(IHttpUserAgentParserAccessor parserAccessor)
}
```

## Telemetry (EventCounters)

Telemetry is **opt-in** and **modular per package**.

- Opt-in: no telemetry overhead unless you explicitly enable it.
- Modular: each package has its own `EventSource` name, so you can monitor only what you use.

### Enable telemetry (Fluent API)

Core parser telemetry:

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
.AddHttpUserAgentParser()
.WithTelemetry();
}
```

MemoryCache telemetry (in addition to core, optional):

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
.AddHttpUserAgentMemoryCachedParser()
.WithTelemetry() // core counters (optional)
.WithMemoryCacheTelemetry();
}
```

ASP.NET Core telemetry (header present/missing):

```csharp
public void ConfigureServices(IServiceCollection services)
{
services
.AddHttpUserAgentMemoryCachedParser()
.AddHttpUserAgentParserAccessor()
.WithAspNetCoreTelemetry();
}
```

### EventSource names and counters

Core (`MyCSharp.HttpUserAgentParser`)

- `parse-requests`
- `parse-duration` (ms)
- `cache-concurrentdictionary-hit`
- `cache-concurrentdictionary-miss`
- `cache-concurrentdictionary-size`

MemoryCache (`MyCSharp.HttpUserAgentParser.MemoryCache`)

- `cache-hit`
- `cache-miss`
- `cache-size`

ASP.NET Core (`MyCSharp.HttpUserAgentParser.AspNetCore`)

- `useragent-present`
- `useragent-missing`

### Monitor counters

Using `dotnet-counters`:

```bash
dotnet-counters monitor --process-id <pid> MyCSharp.HttpUserAgentParser
dotnet-counters monitor --process-id <pid> MyCSharp.HttpUserAgentParser.MemoryCache
dotnet-counters monitor --process-id <pid> MyCSharp.HttpUserAgentParser.AspNetCore
```

### Export to OpenTelemetry

You can collect these EventCounters via OpenTelemetry metrics.

Packages you typically need:

- `OpenTelemetry`
- `OpenTelemetry.Instrumentation.EventCounters`
- an exporter (e.g. `OpenTelemetry.Exporter.OpenTelemetryProtocol`)

Example (minimal):

```csharp
using OpenTelemetry.Metrics;
using MyCSharp.HttpUserAgentParser.Telemetry;
using MyCSharp.HttpUserAgentParser.MemoryCache.Telemetry;
using MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;

builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.AddEventCountersInstrumentation(options =>
{
options.AddEventSources(
HttpUserAgentParserEventSource.EventSourceName,
HttpUserAgentParserMemoryCacheEventSource.EventSourceName,
HttpUserAgentParserAspNetCoreEventSource.EventSourceName);
})
.AddOtlpExporter();
});
```

### Export to Application Insights

Two common approaches:

1) OpenTelemetry → Application Insights (recommended)
- Collect counters with OpenTelemetry (see above)
- Export using an Azure Monitor / Application Insights exporter (API varies by package/version)

2) Custom `EventListener` → `TelemetryClient`
- Attach an `EventListener`
- Parse the `EventCounters` payload
- Forward values as custom metrics

### OpenTelemetry listener (recommended)

You can collect EventCounters as OpenTelemetry metrics.

Typical packages:

- `OpenTelemetry`
- `OpenTelemetry.Instrumentation.EventCounters`
- An exporter, e.g. `OpenTelemetry.Exporter.OpenTelemetryProtocol`

Example:

```csharp
using OpenTelemetry.Metrics;

builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.AddEventCountersInstrumentation(options =>
{
options.AddEventSources(
HttpUserAgentParserEventSource.EventSourceName,
HttpUserAgentParserMemoryCacheEventSource.EventSourceName,
HttpUserAgentParserAspNetCoreEventSource.EventSourceName);
})
.AddOtlpExporter();
});
```

From there you can route metrics to:

- OpenTelemetry Collector
- Prometheus
- Azure Monitor / Application Insights (via an Azure Monitor exporter)

### Application Insights listener (custom)

If you want a direct listener, you can attach an `EventListener` and forward counter values into Application Insights custom metrics.

High-level steps:

1) Enable telemetry via `.WithTelemetry()` / `.WithMemoryCacheTelemetry()` / `.WithAspNetCoreTelemetry()`
2) Register an `EventListener` that enables the corresponding EventSources
3) On `EventCounters` payload, forward values to `TelemetryClient.GetMetric(...).TrackValue(...)`

Notes:

- This is best-effort telemetry.
- Prefer longer polling intervals (e.g. 10s) to reduce noise.

> Notes
>
> - Counters are only emitted when telemetry is enabled and a listener is attached.
> - Values are best-effort and may include cache races.

## Benchmark

```shell
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright © https://myCSharp.de - all rights reserved

using System.Diagnostics.Metrics;
using MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;
using MyCSharp.HttpUserAgentParser.DependencyInjection;

namespace MyCSharp.HttpUserAgentParser.AspNetCore.DependencyInjection;

/// <summary>
/// Fluent extensions to enable telemetry for the AspNetCore package.
/// </summary>
public static class HttpUserAgentParserDependencyInjectionOptionsTelemetryExtensions
{
/// <summary>
/// Enables EventCounter telemetry for the AspNetCore package.
/// </summary>
public static HttpUserAgentParserDependencyInjectionOptions WithAspNetCoreTelemetry(
this HttpUserAgentParserDependencyInjectionOptions options)
{
HttpUserAgentParserAspNetCoreTelemetry.Enable();
return options;
}

/// <summary>
/// Enables native System.Diagnostics.Metrics telemetry for the AspNetCore package.
/// </summary>
public static HttpUserAgentParserDependencyInjectionOptions WithAspNetCoreMeterTelemetry(
this HttpUserAgentParserDependencyInjectionOptions options,
Meter? meter = null)
{
HttpUserAgentParserAspNetCoreTelemetry.EnableMeters(meter);
return options;
}
}
13 changes: 13 additions & 0 deletions src/HttpUserAgentParser.AspNetCore/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;

namespace MyCSharp.HttpUserAgentParser.AspNetCore;

Expand All @@ -16,7 +17,19 @@ public static class HttpContextExtensions
public static string? GetUserAgentString(this HttpContext httpContext)
{
if (httpContext.Request.Headers.TryGetValue("User-Agent", out StringValues value))
{
if (HttpUserAgentParserAspNetCoreTelemetry.IsEnabled)
{
HttpUserAgentParserAspNetCoreTelemetry.UserAgentPresent();
}

return value;
}

if (HttpUserAgentParserAspNetCoreTelemetry.IsEnabled)
{
HttpUserAgentParserAspNetCoreTelemetry.UserAgentMissing();
}

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ namespace MyCSharp.HttpUserAgentParser.AspNetCore;
public class HttpUserAgentParserAccessor(IHttpUserAgentParserProvider httpUserAgentParser)
: IHttpUserAgentParserAccessor
{
/// <summary>
/// The name of the Meter used for metrics.
/// </summary>
public const string MeterName = "MyCSharp.HttpUserAgentParser.AspNetCore";

private readonly IHttpUserAgentParserProvider _httpUserAgentParser = httpUserAgentParser;

/// <summary>
Expand Down
Loading