diff --git a/.github/workflows/bootstrap/action.yml b/.github/workflows/bootstrap/action.yml index e47776ef..189ef688 100644 --- a/.github/workflows/bootstrap/action.yml +++ b/.github/workflows/bootstrap/action.yml @@ -38,12 +38,6 @@ runs: uses: actions/setup-dotnet@v5 with: global-json-file: ./global.json - # 6.x is required for the release-notes tool. - # 7.x is required for the dotnet-project-licenses tool. - dotnet-version: | - 6.x - 7.x - 8.x - id: dotnet shell: bash diff --git a/Directory.Packages.props b/Directory.Packages.props index c6af938e..f1cf9075 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,55 +4,50 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - + - - + - + - diff --git a/build/patch-dotnet-auto-install.sh b/build/patch-dotnet-auto-install.sh index 6ba36909..a73ca3bc 100644 --- a/build/patch-dotnet-auto-install.sh +++ b/build/patch-dotnet-auto-install.sh @@ -56,7 +56,7 @@ else fi test -z "$OTEL_DOTNET_AUTO_HOME" && OTEL_DOTNET_AUTO_HOME="$HOME/.otel-dotnet-auto" -test -z "$VERSION" && VERSION="v1.9.0" +test -z "$VERSION" && VERSION="v1.13.0" DOWNLOAD_DIR="${DOWNLOAD_DIR:=${TMPDIR:=$(mktemp -d)}}" diff --git a/build/scripts/Packaging.fs b/build/scripts/Packaging.fs index 8172032d..8d603564 100644 --- a/build/scripts/Packaging.fs +++ b/build/scripts/Packaging.fs @@ -192,7 +192,11 @@ let stageArtifacts (assets:List) = stagedZips |> List.iter (fun (asset, path) -> - injectPluginFiles asset path "netstandard2.1" "net" + // We inject net8.0 as the minimum supported TFM version + // Previously we used netstandard2.1, but this causes issues with adding a handler for the HttpClient + // used for OTLP export as the SDK prefers the `Send` rather than `SendAsync` method which is only available in net8.0+ TFM + // Whe using netstandard2.1 there is no handler code to run so the default user agent is sent. + injectPluginFiles asset path "net8.0" "net" if asset.Name.EndsWith "-windows.zip" then injectPluginFiles asset path "net462" "netfx" @@ -207,7 +211,8 @@ let stageArtifacts (assets:List) = stagedZips let redistribute (arguments:ParseResults) = - exec { run "dotnet" "build" "src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj" "-f" "netstandard2.1" "-c" "release" } + // We build net8.0 as the minimum supported TFM version - See above for details + exec { run "dotnet" "build" "src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj" "-f" "net8.0" "-c" "release" } exec { run "dotnet" "build" "src/Elastic.OpenTelemetry.AutoInstrumentation/Elastic.OpenTelemetry.AutoInstrumentation.csproj" "-f" "net462" "-c" "release" } let assets = downloadArtifacts arguments printfn "" diff --git a/examples/Example.AutoInstrumentation/Dockerfile b/examples/Example.AutoInstrumentation/Dockerfile index 61eb8b65..f9fbb611 100644 --- a/examples/Example.AutoInstrumentation/Dockerfile +++ b/examples/Example.AutoInstrumentation/Dockerfile @@ -1,5 +1,5 @@ -ARG OTEL_VERSION=1.12.0 -FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base +ARG OTEL_VERSION=1.13.0 +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS base ARG TARGETPLATFORM ARG TARGETARCH ARG TARGETVARIANT diff --git a/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj b/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj index b5aa541d..652283c1 100644 --- a/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj +++ b/examples/Example.AutoInstrumentation/Example.AutoInstrumentation.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable Linux diff --git a/examples/Example.AutoInstrumentation/distribution.Dockerfile b/examples/Example.AutoInstrumentation/distribution.Dockerfile index 2ce6c82b..ed285ed7 100644 --- a/examples/Example.AutoInstrumentation/distribution.Dockerfile +++ b/examples/Example.AutoInstrumentation/distribution.Dockerfile @@ -1,5 +1,5 @@ -ARG OTEL_VERSION=1.9.0 -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG OTEL_VERSION=1.13.0 +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG TARGETPLATFORM ARG TARGETARCH ARG TARGETVARIANT @@ -8,27 +8,19 @@ ENV _PROJECT="Example.AutoInstrumentation" ENV _PROJECTPATH="${_PROJECT}/${_PROJECT}.csproj" RUN apt-get update && apt-get install -y unzip - WORKDIR /work - COPY ["examples/${_PROJECTPATH}", "examples/${_PROJECT}/"] RUN dotnet restore -a $TARGETARCH "examples/${_PROJECT}" - COPY .git .git COPY examples/${_PROJECT} examples/${_PROJECT} WORKDIR "/work/examples/${_PROJECT}" RUN dotnet publish "${_PROJECT}.csproj" -c Release -a $TARGETARCH --no-restore -o /app/example - FROM build AS final - COPY ".artifacts/elastic-distribution" /distro/elastic - COPY --from=build /app/example /app/example - ENV OTEL_DOTNET_AUTO_HOME="/app/otel" # Use already downloaded release assets (call ./build.sh redistribute locally if you run this dockerfile manually) RUN DOWNLOAD_DIR="/distro/elastic" sh /distro/elastic/elastic-dotnet-auto-install.sh - ENV OTEL_LOG_LEVEL=debug ENTRYPOINT ["sh", "/app/otel/instrument.sh", "dotnet", "/app/example/Example.AutoInstrumentation.dll"] diff --git a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs index d7a31848..ad280d50 100644 --- a/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs +++ b/src/Elastic.OpenTelemetry.AutoInstrumentation/AutoInstrumentationPlugin.cs @@ -7,7 +7,6 @@ using Elastic.OpenTelemetry.Diagnostics; using Elastic.OpenTelemetry.Exporters; using Elastic.OpenTelemetry.Resources; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OpenTelemetry; using OpenTelemetry.Exporter; @@ -31,6 +30,17 @@ public class AutoInstrumentationPlugin /// public AutoInstrumentationPlugin() => _components = ElasticOpenTelemetry.Bootstrap(SdkActivationMethod.AutoInstrumentation); + /// + /// Configure Resource Builder for Logs, Metrics and Traces + /// + /// to configure + /// Returns for chaining. + public ResourceBuilder ConfigureResource(ResourceBuilder builder) + { + builder.WithElasticDefaultsCore(_components, null, null); + return builder; + } + /// /// To configure tracing SDK before Auto Instrumentation configured SDK. /// @@ -40,10 +50,7 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder try { - builder.ConfigureResource(r => r.WithElasticDefaultsCore(_components, null, null)); - - builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions)); - logger.LogConfiguredOtlpExporterOptions(); + logger.LogInformation("Configuring Elastic Distribution of OpenTelemetry .NET defaults for tracing auto-instrumentation."); ElasticTracerProviderBuilderExtensions.AddActivitySourceWithLogging(builder, logger, "Elastic.Transport", ""); ElasticTracerProviderBuilderExtensions.AddElasticProcessorsCore(builder, null, _components, null); @@ -62,42 +69,43 @@ public TracerProviderBuilder BeforeConfigureTracerProvider(TracerProviderBuilder } /// - /// To configure metrics SDK before Auto Instrumentation configured SDK. + /// Configure traces OTLP exporter options. /// - public MeterProviderBuilder BeforeConfigureMeterProvider(MeterProviderBuilder builder) - { - var logger = _components.Logger; - - try - { - builder.ConfigureResource(r => r.WithElasticDefaultsCore(_components, null, null)); - - builder.ConfigureServices(sc => sc - .Configure(OtlpExporterDefaults.OtlpExporterOptions) - .Configure(o => o.TemporalityPreference = MetricReaderTemporalityPreference.Delta)); - logger.LogConfiguredOtlpExporterOptions(); + /// Otlp options. + public void ConfigureTracesOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "traces"); - logger.LogConfiguredSignalProvider(nameof(Signals.Metrics), nameof(MeterProviderBuilder), ""); - - return builder; - } - catch (Exception ex) - { - logger.LogError(new EventId(521, "AutoInstrumentationTracerFailure"), ex, - "Failed to register EDOT defaults for metrics auto-instrumentation to the MeterProviderBuilder."); - } + /// + /// Configure metrics OTLP exporter options + /// + /// Otlp options + public void ConfigureMetricsOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "metrics"); - return builder; + /// + /// Configure metrics OTLP exporter options + /// + /// Otlp options + public void ConfigureMetricsOptions(MetricReaderOptions options) + { + var logger = _components.Logger; + options.TemporalityPreference = MetricReaderTemporalityPreference.Delta; + logger.LogInformation("Configured Elastic Distribution of OpenTelemetry .NET defaults for logging auto-instrumentation."); } /// - /// To configure logs SDK (the method name is the same as for other logs options). + /// Configure logging OTLP exporter options. /// - public void ConfigureLogsOptions(OpenTelemetryLoggerOptions options) => options.WithElasticDefaults(_components.Logger); + /// Otlp options. + public void ConfigureLogsOptions(OtlpExporterOptions options) => ConfigureOtlpExporter(options, "logs"); /// - /// To configure Resource. + /// To configure logs SDK (the method name is the same as for other logs options). /// - public ResourceBuilder ConfigureResource(ResourceBuilder builder) => - builder.WithElasticDefaultsCore(_components, null, null); + public void ConfigureLogsOptions(OpenTelemetryLoggerOptions options) => options.WithElasticDefaults(_components.Logger); + + private void ConfigureOtlpExporter(OtlpExporterOptions options, string signal) + { + var logger = _components.Logger; + options.ConfigureElasticUserAgent(); + logger.LogConfiguredOtlpExporterOptions(signal); + } } diff --git a/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.sh b/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.sh index 478f34fb..10937afa 100755 --- a/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.sh +++ b/src/Elastic.OpenTelemetry.AutoInstrumentation/_instrument.sh @@ -35,7 +35,7 @@ Attempting to detect the runtime and use the native profiler from corresponding case $(uname -m) in x86_64) ARCHITECTURE="x64" ;; - aarch64) ARCHITECTURE="arm64" ;; + aarch64|arm64) ARCHITECTURE="arm64" ;; esac case "$ARCHITECTURE" in diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs index 152d5693..c636bb5c 100644 --- a/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs +++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/FileLogger.cs @@ -40,7 +40,7 @@ public FileLogger(CompositeElasticOpenTelemetryOptions options) var process = Process.GetCurrentProcess(); // This naming resembles the naming structure for OpenTelemetry log files. - var logFileName = $"edot-dotnet-{process.Id}-{process.ProcessName}-{DateTimeOffset.UtcNow:yyyyMMdd-hhMMssfffZ}.log"; + var logFileName = $"edot-dotnet-{process.Id}-{process.ProcessName}-{DateTimeOffset.UtcNow:yyyyMMdd-HHmmssfffZ}.log"; var logDirectory = options.LogDirectory; LogFilePath = Path.Combine(logDirectory, logFileName); diff --git a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs index 2e8d6ab0..e4ac32af 100644 --- a/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs +++ b/src/Elastic.OpenTelemetry.Core/Diagnostics/LoggerMessages.cs @@ -57,9 +57,9 @@ internal static partial class LoggerMessages "times across all {Target} instances.")] public static partial void LogWithElasticDefaultsCallCount(this ILogger logger, int callCount, string target); - [LoggerMessage(EventId = 12, EventName = "ConfiguredOtlpExporterOptions", Level = LogLevel.Debug, Message = "The `OtlpExporterOptions` have been configured to use the" + + [LoggerMessage(EventId = 12, EventName = "ConfiguredOtlpExporterOptions", Level = LogLevel.Debug, Message = "The `OtlpExporterOptions` for {Signal} have been configured to use the" + "`ElasticUserAgentHandler` to set the EDOT .NET user agent.")] - public static partial void LogConfiguredOtlpExporterOptions(this ILogger logger); + public static partial void LogConfiguredOtlpExporterOptions(this ILogger logger, string signal); diff --git a/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs b/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs index e27e4290..c481e835 100644 --- a/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs +++ b/src/Elastic.OpenTelemetry.Core/Exporters/ElasticUserAgentHandler.cs @@ -5,18 +5,51 @@ #if NETFRAMEWORK using System.Net.Http; #endif +#pragma warning disable IDE0130 // Namespace does not match folder structure + +using Microsoft.Extensions.Logging; namespace Elastic.OpenTelemetry.Exporters; +#pragma warning restore IDE0130 // Namespace does not match folder structure -internal class ElasticUserAgentHandler(string userAgent) : HttpClientHandler +/// +/// Sets a custom User-Agent header for outgoing HTTP requests. +/// Uses DelegatingHandler with SocketsHttpHandler for .NET, and HttpClientHandler for .NET Framework. +/// +internal class ElasticUserAgentHandler +#if NET + : DelegatingHandler +#else + : HttpClientHandler +#endif { - private readonly string _userAgent = userAgent; + private readonly string _userAgent; + +#if NET + public ElasticUserAgentHandler(string userAgent) : base(new SocketsHttpHandler()) => _userAgent = userAgent; +#else + public ElasticUserAgentHandler(string userAgent) => _userAgent = userAgent; +#endif protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + PrepareRequest(request); + return base.SendAsync(request, cancellationToken); + } + +#if NET + // This method is only available in .NET targets and is crucial to override for synchronous calls. + // The upstream SDK prefers synchronous calls in many scenarios and without this override, the User-Agent header would not be set correctly. + protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) + { + PrepareRequest(request); + return base.Send(request, cancellationToken); + } +#endif + + private void PrepareRequest(HttpRequestMessage request) { request.Headers.Remove("User-Agent"); request.Headers.Add("User-Agent", _userAgent); - - return base.SendAsync(request, cancellationToken); } } diff --git a/src/Elastic.OpenTelemetry.Core/Exporters/OtlpExporterDefaults.cs b/src/Elastic.OpenTelemetry.Core/Exporters/OtlpExporterDefaults.cs index 6ca555de..0bc4ace8 100644 --- a/src/Elastic.OpenTelemetry.Core/Exporters/OtlpExporterDefaults.cs +++ b/src/Elastic.OpenTelemetry.Core/Exporters/OtlpExporterDefaults.cs @@ -9,16 +9,24 @@ using System.Net.Http; #endif +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace Elastic.OpenTelemetry.Exporters; +#pragma warning restore IDE0130 // Namespace does not match folder structure internal static class OtlpExporterDefaults { - internal static readonly HttpMessageHandler Handler = new ElasticUserAgentHandler($"elastic-otlp-dotnet/{VersionHelper.InformationalVersion}"); + private static string UserAgent => $"elastic-otlp-dotnet/{VersionHelper.InformationalVersion}"; - public static void OtlpExporterOptions(OtlpExporterOptions options) => + internal static void OtlpExporterOptions(OtlpExporterOptions options) => options.HttpClientFactory = () => { - var client = new HttpClient(Handler); + var client = new HttpClient(new ElasticUserAgentHandler(UserAgent)); return client; }; + + internal static OtlpExporterOptions ConfigureElasticUserAgent(this OtlpExporterOptions options) + { + OtlpExporterOptions(options); + return options; + } } diff --git a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj index 9da6874f..b978e247 100644 --- a/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj +++ b/src/Elastic.OpenTelemetry/Elastic.OpenTelemetry.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs index 5cfae1aa..68825b26 100644 --- a/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/LoggerProviderBuilderExtensions.cs @@ -381,7 +381,7 @@ static void ConfigureBuilder(BuilderContext builderContex if (services is null) { builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions)); - logger.LogConfiguredOtlpExporterOptions(); + logger.LogConfiguredOtlpExporterOptions("logs"); } builder.ConfigureServices(sc => sc.Configure(o => o.WithElasticDefaults(logger))); diff --git a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs index f3919099..02b7d2cf 100644 --- a/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/MeterProviderBuilderExtensions.cs @@ -379,7 +379,7 @@ static void ConfigureBuilder(BuilderContext builderContext if (services is null) { builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions)); - logger.LogConfiguredOtlpExporterOptions(); + logger.LogConfiguredOtlpExporterOptions("metrics"); } builder.ConfigureServices(sc => sc.Configure(o => diff --git a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs index 4a725d91..33f9edae 100644 --- a/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/ServiceCollectionExtensions.cs @@ -316,7 +316,7 @@ internal static IOpenTelemetryBuilder AddElasticOpenTelemetryCore( var logger = DeferredLogger.GetOrCreate(options); services.Configure(OtlpExporterDefaults.OtlpExporterOptions); - logger.LogConfiguredOtlpExporterOptions(); + logger.LogConfiguredOtlpExporterOptions("all signals"); var builder = services.AddOpenTelemetry().WithElasticDefaultsCore(options, builderOptions); diff --git a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs index 5a1b8d38..88bec52f 100644 --- a/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs +++ b/src/Elastic.OpenTelemetry/Extensions/TracerProviderBuilderExtensions.cs @@ -383,7 +383,7 @@ private static void ConfigureBuilder(BuilderContext build if (services is null) { builder.ConfigureServices(sc => sc.Configure(OtlpExporterDefaults.OtlpExporterOptions)); - logger.LogConfiguredOtlpExporterOptions(); + logger.LogConfiguredOtlpExporterOptions("traces"); } #if NET9_0_OR_GREATER