From 8a36f265c9309fb7289dc321bc37169a9beaef3b Mon Sep 17 00:00:00 2001 From: Christophe Chevalier Date: Thu, 1 Aug 2024 15:52:54 +0200 Subject: [PATCH] Core: add AddBetterHttpIntrumentation(..) extension methods for OpenTelemetry Tracing and Metrics - enable the default trace and metrics sources needed to monitor HTTP requests --- .../Doxense.Networking.Http.csproj | 2 +- .../Networking/Http/BetterHttpClient.cs | 1 + .../Http/BetterHttpClientContext.cs | 23 ++++---- .../Http/BetterHttpInstrumentation.cs | 42 ++++++++++++++ .../Http/BetterHttpOpenTelemetryExtensions.cs | 55 +++++++++++++++++++ 5 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 Doxense.Networking.Http/Networking/Http/BetterHttpInstrumentation.cs create mode 100644 Doxense.Networking.Http/Networking/Http/BetterHttpOpenTelemetryExtensions.cs diff --git a/Doxense.Networking.Http/Doxense.Networking.Http.csproj b/Doxense.Networking.Http/Doxense.Networking.Http.csproj index a36387703..c20211bb4 100644 --- a/Doxense.Networking.Http/Doxense.Networking.Http.csproj +++ b/Doxense.Networking.Http/Doxense.Networking.Http.csproj @@ -4,7 +4,7 @@ Library net8.0 true - 11.0 + 12.0 enable true false diff --git a/Doxense.Networking.Http/Networking/Http/BetterHttpClient.cs b/Doxense.Networking.Http/Networking/Http/BetterHttpClient.cs index 8eb356931..483a4e845 100644 --- a/Doxense.Networking.Http/Networking/Http/BetterHttpClient.cs +++ b/Doxense.Networking.Http/Networking/Http/BetterHttpClient.cs @@ -510,4 +510,5 @@ private void Dispose(bool disposing) #endregion } + } diff --git a/Doxense.Networking.Http/Networking/Http/BetterHttpClientContext.cs b/Doxense.Networking.Http/Networking/Http/BetterHttpClientContext.cs index da26a5b35..86179d119 100644 --- a/Doxense.Networking.Http/Networking/Http/BetterHttpClientContext.cs +++ b/Doxense.Networking.Http/Networking/Http/BetterHttpClientContext.cs @@ -45,7 +45,6 @@ namespace Doxense.Networking.Http public class BetterHttpClientContext { - private static readonly ActivitySource ActivitySource = new("Doxense.Networking.Http"); /// Instance of the client executing this request public required BetterHttpClient Client { get; init; } @@ -164,14 +163,14 @@ public bool IsLikelyJson() public async Task ReadAsJsonAsync(CrystalJsonSettings? settings = null) { this.Cancellation.ThrowIfCancellationRequested(); - using var activity = ActivitySource.StartActivity("JSON Parse"); + using var activity = BetterHttpInstrumentation.ActivitySource.StartActivity("JSON Parse"); try { //BUGBUG: PERF: tant qu'on n'a pas de read async json, on est obligé de buffer dans un MemoryStream!! using (var ms = DefaultPool.GetStream()) { - await this.CopyToAsync(ms); + await this.CopyToAsync(ms).ConfigureAwait(false); return CrystalJson.Parse(ms.ToSlice(), settings); } } @@ -186,14 +185,14 @@ public async Task ReadAsJsonAsync(CrystalJsonSettings? settings = nul public async Task ReadAsJsonObjectAsync(CrystalJsonSettings? settings = null) { this.Cancellation.ThrowIfCancellationRequested(); - using var activity = ActivitySource.StartActivity("JSON Parse"); + using var activity = BetterHttpInstrumentation.ActivitySource.StartActivity("JSON Parse"); try { //BUGBUG: PERF: tant qu'on n'a pas de read async json, on est obligé de buffer dans un MemoryStream!! using (var ms = DefaultPool.GetStream()) { - await CopyToAsync(ms); + await CopyToAsync(ms).ConfigureAwait(false); activity?.SetTag("json.length", ms.Length); return CrystalJson.Parse(ms.ToSlice(), settings).AsObjectOrDefault(); } @@ -209,14 +208,14 @@ public async Task ReadAsJsonAsync(CrystalJsonSettings? settings = nul public async Task ReadAsJsonArrayAsync(CrystalJsonSettings? settings = null) { this.Cancellation.ThrowIfCancellationRequested(); - using var activity = ActivitySource.StartActivity("JSON Parse"); + using var activity = BetterHttpInstrumentation.ActivitySource.StartActivity("JSON Parse"); try { //BUGBUG: PERF: tant qu'on n'a pas de read async json, on est obligé de buffer dans un MemoryStream!! using (var ms = DefaultPool.GetStream()) { - await CopyToAsync(ms); + await CopyToAsync(ms).ConfigureAwait(false); return CrystalJson.Parse(ms.ToSlice(), settings).AsArrayOrDefault(); } } @@ -231,14 +230,14 @@ public async Task ReadAsJsonAsync(CrystalJsonSettings? settings = nul public async Task ReadAsJsonAsync(CrystalJsonSettings? settings = null, ICrystalJsonTypeResolver? resolver = null) { this.Cancellation.ThrowIfCancellationRequested(); - using var activity = ActivitySource.StartActivity("JSON Parse"); + using var activity = BetterHttpInstrumentation.ActivitySource.StartActivity("JSON Parse"); try { //BUGBUG: PERF: tant qu'on n'a pas de read async json, on est obligé de buffer dans un MemoryStream!! using (var ms = DefaultPool.GetStream()) { - await CopyToAsync(ms); + await CopyToAsync(ms).ConfigureAwait(false); return CrystalJson.Deserialize(ms.ToSlice(), default, settings, resolver); } } @@ -268,14 +267,14 @@ public bool IsLikelyXml() public async Task ReadAsXmlAsync(LoadOptions options = LoadOptions.None) { this.Cancellation.ThrowIfCancellationRequested(); - using var activity = ActivitySource.StartActivity("XML Parse"); + using var activity = BetterHttpInstrumentation.ActivitySource.StartActivity("XML Parse"); try { - var stream = await this.Response.Content.ReadAsStreamAsync(this.Cancellation); + var stream = await this.Response.Content.ReadAsStreamAsync(this.Cancellation).ConfigureAwait(false); //note: do NOT dispose this stream here! - return await XDocument.LoadAsync(stream, options, this.Cancellation); + return await XDocument.LoadAsync(stream, options, this.Cancellation).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Doxense.Networking.Http/Networking/Http/BetterHttpInstrumentation.cs b/Doxense.Networking.Http/Networking/Http/BetterHttpInstrumentation.cs new file mode 100644 index 000000000..62839ef09 --- /dev/null +++ b/Doxense.Networking.Http/Networking/Http/BetterHttpInstrumentation.cs @@ -0,0 +1,42 @@ +#region Copyright (c) 2023-2024 SnowBank SAS, (c) 2005-2023 Doxense SAS +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of SnowBank nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL SNOWBANK SAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +namespace Doxense.Networking.Http +{ + using System.Diagnostics; + + internal static class BetterHttpInstrumentation + { + + public const string ActivityName = "Doxense.Networking.Http"; + public const string ActivityVersion = "0.1.0"; //TODO: what version should we use? + + public static readonly ActivitySource ActivitySource = new(ActivityName, ActivityVersion); + + //TODO: add Meters here! + } + +} diff --git a/Doxense.Networking.Http/Networking/Http/BetterHttpOpenTelemetryExtensions.cs b/Doxense.Networking.Http/Networking/Http/BetterHttpOpenTelemetryExtensions.cs new file mode 100644 index 000000000..1ac10d365 --- /dev/null +++ b/Doxense.Networking.Http/Networking/Http/BetterHttpOpenTelemetryExtensions.cs @@ -0,0 +1,55 @@ +#region Copyright (c) 2023-2024 SnowBank SAS, (c) 2005-2023 Doxense SAS +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of SnowBank nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL SNOWBANK SAS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +namespace OpenTelemetry.Trace +{ + using Doxense.Networking.Http; + using OpenTelemetry.Metrics; + + public static class BetterHttpOpenTelemetryExtensions + { + + /// Enable instrumentation for all "core" features of the SDK + public static TracerProviderBuilder AddBetterHttpIntrumentation(this TracerProviderBuilder builder) + { + builder.AddSource([ + BetterHttpInstrumentation.ActivityName, + "SnowBank.Sdk.Serialization.Json", + "Doxense.Serialization.Json", //LEGACY: will be removed at some point + ]); + return builder; + } + + /// Enable instrumentation for all "core" features of the SDK + public static MeterProviderBuilder AddBetterHttpInstrumentation(this MeterProviderBuilder builder) + { + //TODO: add counters! + return builder; + } + + } + +}