Skip to content

Commit 3d1abc6

Browse files
committed
feat(telemetry): add telemetry support for user agent parsing
1 parent 5e3841f commit 3d1abc6

20 files changed

+732
-13
lines changed

.vscode/tasks.json

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,51 @@
11
{
2-
"version": "2.0.0",
3-
"tasks": [
4-
{
5-
"label": "test",
6-
"type": "shell",
7-
"command": "dotnet test --nologo",
8-
"args": [],
9-
"problemMatcher": [
10-
"$msCompile"
11-
],
12-
"group": "build"
13-
}
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "dotnet: restore",
6+
"type": "shell",
7+
"command": "dotnet",
8+
"args": ["restore"],
9+
"problemMatcher": "$msCompile",
10+
"presentation": {
11+
"reveal": "silent",
12+
"panel": "dedicated",
13+
"close": true,
14+
"showReuseMessage": false
15+
}
16+
},
17+
{
18+
"label": "dotnet: build",
19+
"type": "shell",
20+
"command": "dotnet",
21+
"args": ["build", "--no-restore"],
22+
"problemMatcher": "$msCompile",
23+
"dependsOn": "dotnet: restore",
24+
"presentation": {
25+
"reveal": "always",
26+
"panel": "dedicated",
27+
"close": true,
28+
"showReuseMessage": false
29+
},
30+
"group": "build"
31+
},
32+
{
33+
"label": "dotnet: test",
34+
"type": "shell",
35+
"command": "dotnet",
36+
"args": [
37+
"test",
38+
"--no-build",
39+
"--nologo"
40+
],
41+
"problemMatcher": "$msCompile",
42+
"dependsOn": "dotnet: build",
43+
"presentation": {
44+
"reveal": "always",
45+
"panel": "dedicated",
46+
"close": true,
47+
"showReuseMessage": false
48+
}
49+
}
50+
]
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;
4+
using MyCSharp.HttpUserAgentParser.DependencyInjection;
5+
6+
namespace MyCSharp.HttpUserAgentParser.AspNetCore.DependencyInjection;
7+
8+
/// <summary>
9+
/// Fluent extensions to enable telemetry for the AspNetCore package.
10+
/// </summary>
11+
public static class HttpUserAgentParserDependencyInjectionOptionsTelemetryExtensions
12+
{
13+
/// <summary>
14+
/// Enables EventCounter telemetry for the AspNetCore package.
15+
/// </summary>
16+
public static HttpUserAgentParserDependencyInjectionOptions WithAspNetCoreTelemetry(
17+
this HttpUserAgentParserDependencyInjectionOptions options)
18+
{
19+
HttpUserAgentParserAspNetCoreTelemetry.Enable();
20+
return options;
21+
}
22+
}

src/HttpUserAgentParser.AspNetCore/HttpContextExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using Microsoft.AspNetCore.Http;
44
using Microsoft.Extensions.Primitives;
5+
using MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;
56

67
namespace MyCSharp.HttpUserAgentParser.AspNetCore;
78

@@ -16,7 +17,15 @@ public static class HttpContextExtensions
1617
public static string? GetUserAgentString(this HttpContext httpContext)
1718
{
1819
if (httpContext.Request.Headers.TryGetValue("User-Agent", out StringValues value))
20+
{
21+
if (HttpUserAgentParserAspNetCoreTelemetry.AreCountersEnabled)
22+
HttpUserAgentParserAspNetCoreEventSource.Log.UserAgentPresent();
23+
1924
return value;
25+
}
26+
27+
if (HttpUserAgentParserAspNetCoreTelemetry.AreCountersEnabled)
28+
HttpUserAgentParserAspNetCoreEventSource.Log.UserAgentMissing();
2029

2130
return null;
2231
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Diagnostics.Tracing;
5+
6+
namespace MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;
7+
8+
/// <summary>
9+
/// EventSource for EventCounters emitted by MyCSharp.HttpUserAgentParser.AspNetCore.
10+
/// </summary>
11+
[EventSource(Name = "MyCSharp.HttpUserAgentParser.AspNetCore")]
12+
[ExcludeFromCodeCoverage]
13+
internal sealed class HttpUserAgentParserAspNetCoreEventSource : EventSource
14+
{
15+
public static readonly HttpUserAgentParserAspNetCoreEventSource Log = new();
16+
17+
private readonly IncrementingEventCounter _userAgentPresent;
18+
private readonly IncrementingEventCounter _userAgentMissing;
19+
20+
private HttpUserAgentParserAspNetCoreEventSource()
21+
{
22+
_userAgentPresent = new IncrementingEventCounter("useragent-present", this)
23+
{
24+
DisplayName = "User-Agent header present",
25+
DisplayUnits = "calls",
26+
};
27+
28+
_userAgentMissing = new IncrementingEventCounter("useragent-missing", this)
29+
{
30+
DisplayName = "User-Agent header missing",
31+
DisplayUnits = "calls",
32+
};
33+
}
34+
35+
[NonEvent]
36+
public void UserAgentPresent()
37+
{
38+
if (!IsEnabled()) return;
39+
_userAgentPresent?.Increment();
40+
}
41+
42+
[NonEvent]
43+
public void UserAgentMissing()
44+
{
45+
if (!IsEnabled()) return;
46+
_userAgentMissing?.Increment();
47+
}
48+
49+
protected override void Dispose(bool disposing)
50+
{
51+
if (disposing)
52+
{
53+
_userAgentPresent?.Dispose();
54+
_userAgentMissing?.Dispose();
55+
}
56+
57+
base.Dispose(disposing);
58+
}
59+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
namespace MyCSharp.HttpUserAgentParser.AspNetCore.Telemetry;
6+
7+
/// <summary>
8+
/// Opt-in switch for AspNetCore package telemetry.
9+
/// </summary>
10+
[ExcludeFromCodeCoverage]
11+
internal static class HttpUserAgentParserAspNetCoreTelemetry
12+
{
13+
private static volatile bool s_enabled;
14+
15+
public static bool IsEnabled => s_enabled;
16+
17+
public static bool AreCountersEnabled => s_enabled && HttpUserAgentParserAspNetCoreEventSource.Log.IsEnabled();
18+
19+
public static void Enable() => s_enabled = true;
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using MyCSharp.HttpUserAgentParser.DependencyInjection;
4+
using MyCSharp.HttpUserAgentParser.MemoryCache.Telemetry;
5+
6+
namespace MyCSharp.HttpUserAgentParser.MemoryCache.DependencyInjection;
7+
8+
/// <summary>
9+
/// Fluent extensions to enable telemetry for the MemoryCache package.
10+
/// </summary>
11+
public static class HttpUserAgentParserDependencyInjectionOptionsTelemetryExtensions
12+
{
13+
/// <summary>
14+
/// Enables EventCounter telemetry for the MemoryCache provider.
15+
/// </summary>
16+
public static HttpUserAgentParserDependencyInjectionOptions WithMemoryCacheTelemetry(
17+
this HttpUserAgentParserDependencyInjectionOptions options)
18+
{
19+
HttpUserAgentParserMemoryCacheTelemetry.Enable();
20+
return options;
21+
}
22+
}

src/HttpUserAgentParser.MemoryCache/HttpUserAgentParserMemoryCachedProvider.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright © https://myCSharp.de - all rights reserved
22

3+
using System.Diagnostics.CodeAnalysis;
34
using Microsoft.Extensions.Caching.Memory;
45
using MyCSharp.HttpUserAgentParser.Providers;
6+
using MyCSharp.HttpUserAgentParser.MemoryCache.Telemetry;
57

68
namespace MyCSharp.HttpUserAgentParser.MemoryCache;
79

@@ -21,6 +23,36 @@ public HttpUserAgentInformation Parse(string userAgent)
2123
{
2224
CacheKey key = GetKey(userAgent);
2325

26+
if (!HttpUserAgentParserMemoryCacheTelemetry.IsEnabled)
27+
return ParseWithoutTelemetry(key);
28+
29+
bool countersEnabled = HttpUserAgentParserMemoryCacheTelemetry.AreCountersEnabled;
30+
if (countersEnabled && _memoryCache.TryGetValue(key, out HttpUserAgentInformation cached))
31+
{
32+
HttpUserAgentParserMemoryCacheEventSource.Log.CacheHit();
33+
return cached;
34+
}
35+
36+
return _memoryCache.GetOrCreate(key, static entry =>
37+
{
38+
CacheKey key = (entry.Key as CacheKey)!;
39+
entry.SlidingExpiration = key.Options.CacheEntryOptions.SlidingExpiration;
40+
entry.SetSize(1);
41+
42+
// Miss path. Note: Like other cache implementations, races can happen; counters are best-effort.
43+
if (HttpUserAgentParserMemoryCacheTelemetry.AreCountersEnabled)
44+
HttpUserAgentParserMemoryCacheEventSource.Log.CacheMiss();
45+
46+
HttpUserAgentParserMemoryCacheEventSource.Log.CacheSizeIncrement();
47+
entry.RegisterPostEvictionCallback(static (_, _, _, _) => HttpUserAgentParserMemoryCacheEventSource.Log.CacheSizeDecrement());
48+
49+
return HttpUserAgentParser.Parse(key.UserAgent);
50+
});
51+
}
52+
53+
[ExcludeFromCodeCoverage]
54+
private HttpUserAgentInformation ParseWithoutTelemetry(CacheKey key)
55+
{
2456
return _memoryCache.GetOrCreate(key, static entry =>
2557
{
2658
CacheKey key = (entry.Key as CacheKey)!;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Diagnostics.Tracing;
5+
6+
namespace MyCSharp.HttpUserAgentParser.MemoryCache.Telemetry;
7+
8+
/// <summary>
9+
/// EventSource for EventCounters emitted by MyCSharp.HttpUserAgentParser.MemoryCache.
10+
/// </summary>
11+
[EventSource(Name = "MyCSharp.HttpUserAgentParser.MemoryCache")]
12+
[ExcludeFromCodeCoverage]
13+
internal sealed class HttpUserAgentParserMemoryCacheEventSource : EventSource
14+
{
15+
public static readonly HttpUserAgentParserMemoryCacheEventSource Log = new();
16+
17+
private readonly IncrementingEventCounter? _cacheHit;
18+
private readonly IncrementingEventCounter _cacheMiss;
19+
private readonly PollingCounter _cacheSize;
20+
21+
private static long s_cacheSize;
22+
23+
private HttpUserAgentParserMemoryCacheEventSource()
24+
{
25+
_cacheHit = new IncrementingEventCounter("cache-hit", this)
26+
{
27+
DisplayName = "MemoryCache cache hit",
28+
DisplayUnits = "calls",
29+
};
30+
31+
_cacheMiss = new IncrementingEventCounter("cache-miss", this)
32+
{
33+
DisplayName = "MemoryCache cache miss",
34+
DisplayUnits = "calls",
35+
};
36+
37+
_cacheSize = new PollingCounter("cache-size", this, static () => Volatile.Read(ref s_cacheSize))
38+
{
39+
DisplayName = "MemoryCache cache size",
40+
DisplayUnits = "entries",
41+
};
42+
}
43+
44+
[NonEvent]
45+
public void CacheHit()
46+
{
47+
if (!IsEnabled()) return;
48+
_cacheHit?.Increment();
49+
}
50+
51+
[NonEvent]
52+
public void CacheMiss()
53+
{
54+
if (!IsEnabled()) return;
55+
_cacheMiss?.Increment();
56+
}
57+
58+
[NonEvent]
59+
public void CacheSizeIncrement() => Interlocked.Increment(ref s_cacheSize);
60+
61+
[NonEvent]
62+
public void CacheSizeDecrement() => Interlocked.Decrement(ref s_cacheSize);
63+
64+
protected override void Dispose(bool disposing)
65+
{
66+
if (disposing)
67+
{
68+
_cacheHit?.Dispose();
69+
_cacheMiss?.Dispose();
70+
_cacheSize?.Dispose();
71+
}
72+
73+
base.Dispose(disposing);
74+
}
75+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using System.Diagnostics.CodeAnalysis;
4+
5+
namespace MyCSharp.HttpUserAgentParser.MemoryCache.Telemetry;
6+
7+
/// <summary>
8+
/// Opt-in switch for MemoryCache package telemetry.
9+
/// </summary>
10+
[ExcludeFromCodeCoverage]
11+
internal static class HttpUserAgentParserMemoryCacheTelemetry
12+
{
13+
private static volatile bool s_enabled;
14+
15+
public static bool IsEnabled => s_enabled;
16+
17+
public static bool AreCountersEnabled => s_enabled && HttpUserAgentParserMemoryCacheEventSource.Log.IsEnabled();
18+
19+
public static void Enable() => s_enabled = true;
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using MyCSharp.HttpUserAgentParser.Telemetry;
4+
5+
namespace MyCSharp.HttpUserAgentParser.DependencyInjection;
6+
7+
/// <summary>
8+
/// Fluent extensions to enable telemetry.
9+
/// </summary>
10+
public static class HttpUserAgentParserDependencyInjectionOptionsTelemetryExtensions
11+
{
12+
/// <summary>
13+
/// Enables core EventCounter telemetry for the parser.
14+
/// This is opt-in to keep the default path free of telemetry overhead.
15+
/// </summary>
16+
public static HttpUserAgentParserDependencyInjectionOptions WithTelemetry(
17+
this HttpUserAgentParserDependencyInjectionOptions options)
18+
{
19+
HttpUserAgentParserTelemetry.Enable();
20+
return options;
21+
}
22+
}

0 commit comments

Comments
 (0)