diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 896a2889f..0efe5dfa4 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -154,6 +154,9 @@ jobs: icacls C:\Windows\Temp /grant "IUSR:(OI)(CI)F" /T icacls "c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files" /grant "IUSR:(OI)(CI)F" /T + # The test filter here is intentional. + # The IIS tests are slow and quite flaky so we only run one of them in CI, as a smoke test + # to verify that the IIS integration works. - name: Run tests run: | set ELASTIC_APM_TESTS_FULL_FRAMEWORK_ENABLED=true diff --git a/Directory.Packages.props b/Directory.Packages.props index 9529af6cf..11c49a6c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -124,7 +124,7 @@ - + diff --git a/build/scripts/Build.fs b/build/scripts/Build.fs index 4baf887d8..8d978c5b4 100644 --- a/build/scripts/Build.fs +++ b/build/scripts/Build.fs @@ -19,22 +19,14 @@ open Scripts.TestEnvironment open Tooling module Build = - - let private oldDiagnosticSourceVersion = SemVer.parse "4.6.0" - let private diagnosticSourceVersion6 = SemVer.parse "6.0.0" - - let mutable private currentDiagnosticSourceVersion = None + let private oldDiagnosticSourceVersion = SemVer.parse "6.0.0" + let mutable private currentDiagnosticSourceVersion = None + let private aspNetFullFramework = Paths.IntegrationsProjFile "Elastic.Apm.AspNetFullFramework" let private allSrcProjects = !! "src/**/*.csproj" - let private fullFrameworkProjects = [ - aspNetFullFramework - Paths.TestProjFile "Elastic.Apm.AspNetFullFramework.Tests" - Paths.SampleProjFile "AspNetFullFrameworkSampleApp" - ] - /// Gets all the Target Frameworks from a project file let private getAllTargetFrameworks (p: string) = let doc = XElement.Load p @@ -83,21 +75,19 @@ module Build = DisableInternalBinLog = true NoLogo = true }) projectOrSln - + /// Gets the current version of System.Diagnostics.DiagnosticSource referenced by Elastic.Apm let getCurrentApmDiagnosticSourceVersion = match currentDiagnosticSourceVersion with | Some v -> v | None -> - let xml = XDocument.Load("Directory.Packages.props") - let package = xml.XPathSelectElement("//PackageVersion[@Include='System.Diagnostics.DiagnosticSource']") + let xml = XDocument.Load "Directory.Packages.props" + let package = xml.XPathSelectElement "//PackageVersion[@Include='System.Diagnostics.DiagnosticSource']" let version = package.Attribute("Version").Value let version = SemVer.parse version currentDiagnosticSourceVersion <- Some(version) version - - let private majorVersions = Dictionary() - + /// Publishes specific projects with specific DiagnosticSource versions let private publishProjectsWithDiagnosticSourceVersion projects version = projects @@ -110,8 +100,8 @@ module Build = |> (fun p -> sprintf "%s_%i.0.0/%s" p version.Major framework) |> Paths.BuildOutput |> Path.GetFullPath - - printfn "Publishing %s %s with System.Diagnostics.DiagnosticSource %O..." proj framework version + + printfn "Publishing %s %s with System.Diagnostics.DiagnosticSource %O to %s..." proj framework version output DotNet.Exec ["publish" ; proj sprintf "\"/p:DiagnosticSourceVersion=%O\"" version "-c"; "Release" @@ -121,21 +111,15 @@ module Build = "--nologo"; "--force"] ) ) - - /// Publishes ElasticApmStartupHook against a 4.x and 6.x version of System.Diagnostics.DiagnosticSource + /// Publishes ElasticApmStartupHook against an older version of System.Diagnostics.DiagnosticSource let private publishElasticApmStartupHookWithDiagnosticSourceVersion () = let projects = !! (Paths.SrcProjFile "Elastic.Apm") - ++ (Paths.StartupHookProjFile "Elastic.Apm.StartupHook.Loader") + ++ Paths.StartupHookProjFile "Elastic.Apm.StartupHook.Loader" publishProjectsWithDiagnosticSourceVersion projects oldDiagnosticSourceVersion - - let elasticApmProj = - !! (Paths.SrcProjFile "Elastic.Apm") - - publishProjectsWithDiagnosticSourceVersion elasticApmProj diagnosticSourceVersion6 - + /// Runs dotnet build on all .NET core projects in the solution. /// When running on Windows and not CI, also runs MSBuild Build on .NET Framework let Build () = @@ -226,8 +210,6 @@ module Build = DotNet.Exec ["publish" ; proj; "-c"; "Release"; "-f"; framework; "-v"; "q"; "--nologo"; $"--property:PublishDir=%s{output}"] ) ) - - publishElasticApmStartupHookWithDiagnosticSourceVersion() /// Packages projects into nuget packages let Pack () = @@ -258,7 +240,9 @@ module Build = |> Seq.iter (fun file -> file.CopyTo(Path.combine destination.FullName file.Name, true) |> ignore) /// Creates versioned ElasticApmAgent.zip file - let AgentZip () = + let AgentZip () = + publishElasticApmStartupHookWithDiagnosticSourceVersion() + let name = "ElasticApmAgent" let currentAssemblyVersion = Versioning.CurrentVersion.FileVersion let versionedName = @@ -267,33 +251,26 @@ module Build = let agentDir = Paths.BuildOutput name |> DirectoryInfo agentDir.Create() - // copy startup hook to root of agent directory + // Copy startup hook to root of agent directory !! (Paths.BuildOutput "ElasticApmAgentStartupHook/netstandard2.0") |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs agentDir) - // assemblies compiled against "current" version of System.Diagnostics.DiagnosticSource - !! (Paths.BuildOutput "Elastic.Apm.StartupHook.Loader/netstandard2.0") - ++ (Paths.BuildOutput "Elastic.Apm/netstandard2.0") + // Copy assemblies compiled against latest target and "current" version of System.Diagnostics.DiagnosticSource + !! (Paths.BuildOutput "Elastic.Apm.StartupHook.Loader/netstandard2.1") + ++ Paths.BuildOutput "Elastic.Apm/net8.0" |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" getCurrentApmDiagnosticSourceVersion.Major))) - - // assemblies compiled against older version of System.Diagnostics.DiagnosticSource - !! (Paths.BuildOutput (sprintf "Elastic.Apm.StartupHook.Loader_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) - ++ (Paths.BuildOutput (sprintf "Elastic.Apm_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) + + // Copy assemblies compiled against older version of System.Diagnostics.DiagnosticSource + !! (Paths.BuildOutput (sprintf "Elastic.Apm.StartupHook.Loader_%i.0.0/netstandard2.1" oldDiagnosticSourceVersion.Major)) + ++ Paths.BuildOutput (sprintf "Elastic.Apm_%i.0.0/netstandard2.1" oldDiagnosticSourceVersion.Major) |> Seq.filter Path.isDirectory |> Seq.map DirectoryInfo |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" oldDiagnosticSourceVersion.Major))) - - // assemblies compiled against 6.0 version of System.Diagnostics.DiagnosticSource - !! (Paths.BuildOutput (sprintf "Elastic.Apm.StartupHook.Loader_%i.0.0/netstandard2.0" oldDiagnosticSourceVersion.Major)) - ++ (Paths.BuildOutput (sprintf "Elastic.Apm_%i.0.0/net8.0" diagnosticSourceVersion6.Major)) - |> Seq.filter Path.isDirectory - |> Seq.map DirectoryInfo - |> Seq.iter (copyDllsAndPdbs (agentDir.CreateSubdirectory(sprintf "%i.0.0" diagnosticSourceVersion6.Major))) - + // include version in the zip file name ZipFile.CreateFromDirectory(agentDir.FullName, Paths.BuildOutput versionedName + ".zip") diff --git a/global.json b/global.json index 42f2b9487..bb2001960 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.404", + "version": "9.0.303", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/sample/ApiSamples/Program.cs b/sample/ApiSamples/Program.cs index ef51c3f08..fe8fd13ef 100644 --- a/sample/ApiSamples/Program.cs +++ b/sample/ApiSamples/Program.cs @@ -314,11 +314,9 @@ public static void SampleSpanWithCustomContextFillAll() // ReSharper restore ArrangeMethodOrOperatorBody #pragma warning restore IDE0022 -#if NET8_0_OR_GREATER /// /// Test for https://github.com/elastic/apm-agent-dotnet/issues/884 /// private IAsyncEnumerable TestCompilation() => throw new Exception(); -#endif } } diff --git a/src/Elastic.Apm/AgentComponents.cs b/src/Elastic.Apm/AgentComponents.cs index 63fcd0d63..65526201e 100644 --- a/src/Elastic.Apm/AgentComponents.cs +++ b/src/Elastic.Apm/AgentComponents.cs @@ -15,12 +15,12 @@ using Elastic.Apm.Logging; using Elastic.Apm.Metrics; using Elastic.Apm.Metrics.MetricsProvider; +using Elastic.Apm.Model; using Elastic.Apm.Report; using Elastic.Apm.ServerInfo; -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 using Elastic.Apm.OpenTelemetry; -using Elastic.Apm.Model; #endif #if NETFRAMEWORK @@ -62,9 +62,9 @@ internal AgentComponents( ApmServerInfo = apmServerInfo ?? new ApmServerInfo(); HttpTraceConfiguration = new HttpTraceConfiguration(); -#if NET // Initialize early because ServerInfoCallback requires it and might execute // before EnsureElasticActivityStarted runs +#if NET || NETSTANDARD2_1 ElasticActivityListener = new ElasticActivityListener(this, HttpTraceConfiguration); // Ensure we have a listener so that transaction activities are created when the OTel bridge is disabled @@ -73,6 +73,7 @@ internal AgentComponents( ActivitySource.AddActivityListener(Transaction.Listener); } #endif + var systemInfoHelper = new SystemInfoHelper(Logger); var system = systemInfoHelper.GetSystemInfo(Configuration.HostName, hostNameDetector); @@ -124,11 +125,18 @@ internal AgentComponents( private void EnsureElasticActivityListenerStarted() { -#if !NET + if (!Configuration.OpenTelemetryBridgeEnabled) + return; + +#if NETFRAMEWORK + Logger.Info() + ?.Log( + "OpenTelemetry (Activity) bridge is not supported on .NET Framework - bridge won't be enabled. Current Server version: {version}", + ApmServerInfo.Version?.ToString() ?? "unknown"); return; -#else - if (!Configuration.OpenTelemetryBridgeEnabled) return; +#endif +#if NET || NETSTANDARD2_1 // If the server version is not known yet, we enable the listener - and then the callback will do the version check once we have the version if (ApmServerInfo.Version == null || ApmServerInfo.Version == new ElasticVersion(0, 0, 0, null)) ElasticActivityListener?.Start(TracerInternal); @@ -151,10 +159,8 @@ private void EnsureElasticActivityListenerStarted() private void ServerInfoCallback(bool success, IApmServerInfo serverInfo) { -#if !NET8_0_OR_GREATER - return; -#else - if (!Configuration.OpenTelemetryBridgeEnabled) return; + if (!Configuration.OpenTelemetryBridgeEnabled) + return; if (success) { @@ -170,7 +176,9 @@ private void ServerInfoCallback(bool success, IApmServerInfo serverInfo) ?.Log( "OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer - bridge won't be enabled. Current Server version: {version}", serverInfo.Version.ToString()); - ElasticActivityListener?.Dispose(); +#if NET || NETSTANDARD2_1 + ElasticActivityListener?.Dispose(); +#endif } } else @@ -180,7 +188,6 @@ private void ServerInfoCallback(bool success, IApmServerInfo serverInfo) "Unable to read server version - OpenTelemetry (Activity) bridge is only supported with APM Server 7.16.0 or newer. " + "The bridge remains active, but due to unknown server version it may not work as expected."); } -#endif } #pragma warning disable IDE0022 private static IApmLogger DefaultLogger(IApmLogger logger, IConfigurationReader configurationReader) @@ -243,7 +250,7 @@ internal static IApmLogger GetGlobalLogger(IApmLogger fallbackLogger, LogLevel a return fallbackLogger; } -#if NET +#if NET || NETSTANDARD2_1 private ElasticActivityListener ElasticActivityListener { get; } #endif @@ -286,8 +293,9 @@ public void Dispose() if (PayloadSender is IDisposable disposablePayloadSender) disposablePayloadSender.Dispose(); + CentralConfigurationFetcher?.Dispose(); -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 ElasticActivityListener?.Dispose(); #endif } diff --git a/src/Elastic.Apm/Api/IExecutionSegment.cs b/src/Elastic.Apm/Api/IExecutionSegment.cs index 5e4a6e3e9..f76f5320e 100644 --- a/src/Elastic.Apm/Api/IExecutionSegment.cs +++ b/src/Elastic.Apm/Api/IExecutionSegment.cs @@ -56,7 +56,7 @@ public interface IExecutionSegment /// /// The outcome of the IExecutionSegment: success, failure, or unknown. /// - public Outcome Outcome { get; set; } + Outcome Outcome { get; set; } /// /// Distributed tracing data for this segment as the distributed tracing caller. @@ -297,7 +297,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// /// /// - public void SetLabel(string key, string value); + void SetLabel(string key, string value); /// /// Labels are used to add indexed information to transactions, spans, and errors. Indexed means the data is searchable and @@ -308,7 +308,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// with underscores. /// /// The value of the label - public void SetLabel(string key, bool value); + void SetLabel(string key, bool value); /// /// @@ -319,7 +319,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// /// /// - public void SetLabel(string key, double value); + void SetLabel(string key, double value); /// /// @@ -330,7 +330,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// /// /// - public void SetLabel(string key, int value); + void SetLabel(string key, int value); /// /// @@ -341,7 +341,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// /// /// - public void SetLabel(string key, long value); + void SetLabel(string key, long value); /// /// @@ -352,7 +352,7 @@ Task CaptureSpan(string name, string type, Func> func, stri /// /// /// - public void SetLabel(string key, decimal value); + void SetLabel(string key, decimal value); /// /// Start and return a new custom span as a child of this execution segment. diff --git a/src/Elastic.Apm/Api/ISpan.cs b/src/Elastic.Apm/Api/ISpan.cs index 6c42f2bab..69fd0bb50 100644 --- a/src/Elastic.Apm/Api/ISpan.cs +++ b/src/Elastic.Apm/Api/ISpan.cs @@ -26,7 +26,7 @@ public interface ISpan : IExecutionSegment /// /// Indicates that this span is an exit span. /// - public bool IsExitSpan { get; } + bool IsExitSpan { get; } /// /// The stack trace which was captured for the given span. diff --git a/src/Elastic.Apm/Api/ITransaction.cs b/src/Elastic.Apm/Api/ITransaction.cs index 14cd5b61e..7613c5539 100644 --- a/src/Elastic.Apm/Api/ITransaction.cs +++ b/src/Elastic.Apm/Api/ITransaction.cs @@ -24,7 +24,7 @@ public interface ITransaction : IExecutionSegment /// /// Contains data related to FaaS (Function as a Service) events. /// - public Faas FaaS { get; set; } + Faas FaaS { get; set; } /// /// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user. @@ -46,7 +46,7 @@ public interface ITransaction : IExecutionSegment /// A snapshot of configuration from when the transaction started. A snapshot contains values /// from initial configuration combined with dynamic values from central configuration, if enabled. /// - public IConfiguration Configuration { get; } + IConfiguration Configuration { get; } /// /// A string describing the result of the transaction. diff --git a/src/Elastic.Apm/BackendComm/BackendCommUtils.cs b/src/Elastic.Apm/BackendComm/BackendCommUtils.cs index 9e2168e81..c68590bfc 100644 --- a/src/Elastic.Apm/BackendComm/BackendCommUtils.cs +++ b/src/Elastic.Apm/BackendComm/BackendCommUtils.cs @@ -126,7 +126,7 @@ private static void ConfigServicePoint(Uri serverUrlBase, IApmLogger logger) => private static HttpClientHandler CreateHttpClientHandler(IConfiguration configuration, IApmLogger logger) { -#if NET462 +#if NET462 // SSL options are not available in .NET Framework 4.6.2 try { var systemNetHttpVersion = typeof(HttpClientHandler).Assembly.GetName().Version; @@ -136,9 +136,7 @@ private static HttpClientHandler CreateHttpClientHandler(IConfiguration configur { logger.Error()?.LogException(ex, "Could not determine the assembly version of System.Net.Http."); } -#endif - -#if !NET462 +#else Func serverCertificateCustomValidationCallback = null; if (!configuration.VerifyServerCert) @@ -212,24 +210,14 @@ private static HttpClientHandler CreateHttpClientHandler(IConfiguration configur UseDefaultCredentials = configuration.UseWindowsCredentials }; - // Due to the potential for binding issues (e.g.https://github.com/dotnet/runtime/issues/29314) - // and runtime exceptions on .NET Framework versions <4.7.2, we don't attempt to set the certificate - // validation callback or SSL protocols on net462, which should also apply to .NET Framework <4.7.2 runtimes - // which resolve to that target. -#if NET8_0_OR_GREATER - // The defaults are good out of the box in .NET 8 and upwards, so we don't need to set anything. - httpClientHandler.SslProtocols = SslProtocols.None; -#elif NET3_0_OR_GREATER +#if NETSTANDARD || NET472 httpClientHandler.SslProtocols |= SslProtocols.Tls12; - httpClientHandler.SslProtocols |= SslProtocols.Tls13; logger.Info()?.Log("CreateHttpClientHandler - SslProtocols: {SslProtocols}", ServicePointManager.SecurityProtocol); -#elif !NET462 - httpClientHandler.SslProtocols |= SslProtocols.Tls12; - logger.Info()?.Log("CreateHttpClientHandler - SslProtocols: {SslProtocols}", httpClientHandler.SslProtocols); #else ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; logger.Info()?.Log("CreateHttpClientHandler - SslProtocols: {SslProtocols}", ServicePointManager.SecurityProtocol); #endif + #if !NET462 httpClientHandler.ServerCertificateCustomValidationCallback = serverCertificateCustomValidationCallback; logger.Info()?.Log("CreateHttpClientHandler - Setting ServerCertificateCustomValidationCallback"); diff --git a/src/Elastic.Apm/Config/ConfigurationLogger.cs b/src/Elastic.Apm/Config/ConfigurationLogger.cs index d4ebca4e0..6b002327e 100644 --- a/src/Elastic.Apm/Config/ConfigurationLogger.cs +++ b/src/Elastic.Apm/Config/ConfigurationLogger.cs @@ -79,6 +79,8 @@ public static void PrintAgentLogPreamble(IApmLogger logger, IConfigurationReader info.Log("Matched TFM: {TargetFrameworkMoniker}", "net8.0"); #elif NETSTANDARD2_0 info.Log("Matched TFM: {TargetFrameworkMoniker}", "netstandard2.0"); +#elif NETSTANDARD2_1 + info.Log("Matched TFM: {TargetFrameworkMoniker}", "netstandard2.1"); #elif NET472 info.Log("Matched TFM: {TargetFrameworkMoniker}", "net472"); #elif NET462 diff --git a/src/Elastic.Apm/Elastic.Apm.csproj b/src/Elastic.Apm/Elastic.Apm.csproj index c23a8041e..1d4696793 100644 --- a/src/Elastic.Apm/Elastic.Apm.csproj +++ b/src/Elastic.Apm/Elastic.Apm.csproj @@ -1,13 +1,17 @@ - + - netstandard2.0;net462;net472;net8.0 + netstandard2.0;netstandard2.1;net462;net472;net8.0 true Elastic.Apm Elastic.Apm Elastic.Apm - Elastic APM .NET Agent base package. This package provides core functionality for transmitting of all Elastic APM types and is a dependent package for all other Elastic APM package. Additionally this package contains the public Agent API that allows you to manually capture transactions and spans. Please install the platform specific package for the best experience. See: https://github.com/elastic/apm-agent-dotnet/tree/main/docs + Elastic APM .NET Agent base package. This package provides core functionality for transmitting + of all Elastic APM types and is a dependent package for all other Elastic APM package. + Additionally this package contains the public Agent API that allows you to manually capture + transactions and spans. Please install the platform specific package for the best experience. + See: https://github.com/elastic/apm-agent-dotnet/tree/main/docs apm, monitoring, elastic, elasticapm, analytics, sdk @@ -75,9 +79,23 @@ - + + + + + + + diff --git a/src/Elastic.Apm/Filters/CookieHeaderRedactionFilter.cs b/src/Elastic.Apm/Filters/CookieHeaderRedactionFilter.cs index b8574c057..eeb3c1601 100644 --- a/src/Elastic.Apm/Filters/CookieHeaderRedactionFilter.cs +++ b/src/Elastic.Apm/Filters/CookieHeaderRedactionFilter.cs @@ -9,7 +9,7 @@ using Elastic.Apm.Config; using Elastic.Apm.Helpers; using Elastic.Apm.Model; -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 using System.Buffers; #endif @@ -45,7 +45,7 @@ internal static void HandleCookieHeader(Dictionary headers, IRea // e.g. Cookies | cookies | COOKIES const int maxKeys = 4; -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 var matchedKeys = ArrayPool.Shared.Rent(maxKeys); var matchedValues = ArrayPool.Shared.Rent(maxKeys); #else @@ -81,7 +81,7 @@ internal static void HandleCookieHeader(Dictionary headers, IRea } } -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 ArrayPool.Shared.Return(matchedKeys); ArrayPool.Shared.Return(matchedValues); #endif diff --git a/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 39fd468cd..7e9b49fef 100644 --- a/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -711,10 +711,6 @@ protected virtual JsonProperty CreatePropertyFromConstructorParameter(JsonProper private Func GetDefaultCreator(Type createdType) => JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(createdType); -#if NET35 - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = - "System.Runtime.Serialization.DataContractAttribute.#get_IsReference()")] -#endif private void InitializeContract(JsonContract contract) { var containerAttribute = JsonTypeReflector.GetCachedAttribute(contract.NonNullableUnderlyingType); @@ -1258,8 +1254,8 @@ protected virtual IValueProvider CreateMemberValueProvider(MemberInfo member) // warning - this method use to cause errors with Intellitrace. Retest in VS Ultimate after changes IValueProvider valueProvider; -#if !(PORTABLE40 || PORTABLE || DOTNET || NET472 || NETSTANDARD2_0 || NET5_0_OR_GREATER) - if (DynamicCodeGeneration) +#if !(DOTNET || NET472 || NETSTANDARD || NET) + if (DynamicCodeGeneration) { valueProvider = new DynamicValueProvider(member); } @@ -1267,10 +1263,8 @@ protected virtual IValueProvider CreateMemberValueProvider(MemberInfo member) { valueProvider = new ReflectionValueProvider(member); } -#elif !(PORTABLE40) - valueProvider = new ExpressionValueProvider(member); #else - valueProvider = new ReflectionValueProvider(member); + valueProvider = new ExpressionValueProvider(member); #endif return valueProvider; diff --git a/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 59c5d832c..8e4a020bb 100644 --- a/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/src/Elastic.Apm/Libraries/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -468,7 +468,7 @@ public static ReflectionDelegateFactory ReflectionDelegateFactory { get { -#if !(PORTABLE40 || PORTABLE || DOTNET || NET472 || NETSTANDARD2_0 || NET5_0_OR_GREATER) +#if !(DOTNET || NET472 || NETSTANDARD || NET) if (DynamicCodeGeneration) { return DynamicReflectionDelegateFactory.Instance; diff --git a/src/Elastic.Apm/Logging/IApmLoggingExtensions.cs b/src/Elastic.Apm/Logging/IApmLoggingExtensions.cs index 9aa11559a..83c79f852 100644 --- a/src/Elastic.Apm/Logging/IApmLoggingExtensions.cs +++ b/src/Elastic.Apm/Logging/IApmLoggingExtensions.cs @@ -103,7 +103,7 @@ private static void DoLog(this IApmLogger logger, LogLevel level, string message } } -#if !NET8_0_OR_GREATER +#if !NET || NETSTANDARD2_1 private static readonly object _lock = new(); #endif @@ -113,7 +113,7 @@ private static LogValuesFormatter GetOrAddFormatter(string message, IReadOnlyCol return formatter; formatter = new LogValuesFormatter(message, args); -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 Formatters.AddOrUpdate(message, formatter); return formatter; #else diff --git a/src/Elastic.Apm/Logging/ScopedLogger.cs b/src/Elastic.Apm/Logging/ScopedLogger.cs index 1ad8d35ed..a1855491e 100644 --- a/src/Elastic.Apm/Logging/ScopedLogger.cs +++ b/src/Elastic.Apm/Logging/ScopedLogger.cs @@ -16,7 +16,7 @@ internal class ScopedLogger : IApmLogger public IApmLogger Logger { get; } -#if !NET8_0_OR_GREATER +#if !NET || NETSTANDARD2_1 private readonly object _lock = new(); #endif @@ -30,7 +30,7 @@ internal LogValuesFormatter GetOrAddFormatter(string message, IReadOnlyCollectio return formatter; formatter = new LogValuesFormatter($"{{{{{{Scope}}}}}} {message}", args, Scope); -#if NET8_0_OR_GREATER +#if NET || NETSTANDARD2_1 Formatters.AddOrUpdate(message, formatter); return formatter; #else diff --git a/src/Elastic.Apm/Metrics/Linux/GlobalMemoryStatus.cs b/src/Elastic.Apm/Metrics/Linux/GlobalMemoryStatus.cs index 025a097c6..bfa806afd 100644 --- a/src/Elastic.Apm/Metrics/Linux/GlobalMemoryStatus.cs +++ b/src/Elastic.Apm/Metrics/Linux/GlobalMemoryStatus.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; using Elastic.Apm.Logging; -#if NET8_0_OR_GREATER +#if NET using System.Buffers; using System.Buffers.Text; #endif @@ -19,7 +19,7 @@ internal static class GlobalMemoryStatus { public const string ProcMemInfo = "/proc/meminfo"; -#if NET8_0_OR_GREATER +#if NET private static readonly FileStreamOptions Options = new() { BufferSize = 0, Mode = FileMode.Open, Access = FileAccess.Read }; private static readonly byte Space = (byte)' '; @@ -57,7 +57,7 @@ internal static (long totalMemory, long availableMemory) GetTotalAndAvailableSys } try { -#if NET8_0_OR_GREATER +#if NET using var fs = new FileStream(memInfoPath, Options); var buffer = ArrayPool.Shared.Rent(8192); // Should easily be large enough for max meminfo file. diff --git a/src/Elastic.Apm/Metrics/MetricsProvider/CgroupMetricsProvider.cs b/src/Elastic.Apm/Metrics/MetricsProvider/CgroupMetricsProvider.cs index d1f64a488..8e8c3e5c9 100644 --- a/src/Elastic.Apm/Metrics/MetricsProvider/CgroupMetricsProvider.cs +++ b/src/Elastic.Apm/Metrics/MetricsProvider/CgroupMetricsProvider.cs @@ -12,7 +12,7 @@ using Elastic.Apm.Helpers; using Elastic.Apm.Logging; -#if NET8_0_OR_GREATER +#if NET using System.Buffers.Text; using System.Runtime.CompilerServices; using System.Text; @@ -46,7 +46,7 @@ internal class CgroupMetricsProvider : IMetricsProvider internal static readonly Regex Cgroup2MountPoint = new("^\\d+? \\d+? .+? .+? (.*?) .*cgroup2.*cgroup.*"); internal static readonly Regex MemoryCgroup = new("^\\d+:memory:.*"); -#if NET8_0_OR_GREATER +#if NET private static readonly FileStreamOptions Options = new() { BufferSize = 0, Mode = FileMode.Open, Access = FileAccess.Read }; #endif @@ -316,7 +316,7 @@ private MetricSample GetMemoryMemLimitBytes() return new MetricSample(SystemProcessCgroupMemoryMemLimitBytes, totalMemory); } -#if NET8_0_OR_GREATER // Optimised code for newer runtimes +#if NET // Optimised code for newer (supported) runtimes return GetLongValueFromFile(_cGroupFiles.MaxMemoryFile, SystemProcessCgroupMemoryMemLimitBytes); #else using var reader = new StreamReader(_cGroupFiles.MaxMemoryFile); @@ -339,7 +339,7 @@ private MetricSample GetMemoryMemUsageBytes() { try { -#if NET8_0_OR_GREATER // Optimised code for newer runtimes +#if NET // Optimised code for newer (supported) runtimes return GetLongValueFromFile(_cGroupFiles.UsedMemoryFile, SystemProcessCgroupMemoryMemUsageBytes); #else using var reader = new StreamReader(_cGroupFiles.UsedMemoryFile); @@ -356,7 +356,7 @@ private MetricSample GetMemoryMemUsageBytes() return null; } -#if NET8_0_OR_GREATER +#if NET [MethodImpl(MethodImplOptions.AggressiveInlining)] private MetricSample GetLongValueFromFile(string path, string sampleName) { @@ -373,11 +373,6 @@ private MetricSample GetLongValueFromFile(string path, string sampleName) } #endif - private const byte Newline = (byte)'\n'; - private const byte Space = (byte)' '; - private static ReadOnlySpan _totalInactiveFile => "total_inactive_file"u8; - private static ReadOnlySpan _inactiveFile => "inactive_file"u8; - public bool IsEnabled(IReadOnlyList disabledMetrics) => IsSystemProcessCgroupMemoryMemLimitBytesEnabled(disabledMetrics) || IsSystemProcessCgroupMemoryMemUsageBytesEnabled(disabledMetrics); diff --git a/src/Elastic.Apm/Model/Transaction.cs b/src/Elastic.Apm/Model/Transaction.cs index 7bf8f229b..26046a4ef 100644 --- a/src/Elastic.Apm/Model/Transaction.cs +++ b/src/Elastic.Apm/Model/Transaction.cs @@ -26,7 +26,7 @@ internal class Transaction : ITransaction { internal static readonly string ApmTransactionActivityName = "ElasticApm.Transaction"; -#if NET +#if NET || NETSTANDARD2_1 internal static readonly ActivitySource ElasticApmActivitySource = new("Elastic.Apm"); // This simply ensures our transaction activity is always created. diff --git a/src/Elastic.Apm/OpenTelemetry/ElasticActivityListener.cs b/src/Elastic.Apm/OpenTelemetry/ElasticActivityListener.cs index 89fe3e57d..c0081933d 100644 --- a/src/Elastic.Apm/OpenTelemetry/ElasticActivityListener.cs +++ b/src/Elastic.Apm/OpenTelemetry/ElasticActivityListener.cs @@ -3,8 +3,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -#if NET8_0_OR_GREATER - +#if NET || NETSTANDARD2_1 using System; using System.Collections.Generic; using System.Diagnostics; @@ -104,7 +103,7 @@ internal void Start(Tracer tracerInternal) return; } - if (KnownListeners.SkippedActivityNamesSet.Contains(activity.DisplayName)) + if (KnownListeners.SkippedActivityNamesSet.Contains(activity.OperationName)) { _logger?.Trace()?.Log("ActivityStarted: name:{DisplayName} id:{ActivityId} traceId:{TraceId} skipped because it matched " + "a skipped activity name defined in KnownListeners.", activity.DisplayName, activity.Id, activity.TraceId); @@ -135,21 +134,23 @@ private bool CreateTransactionForActivity(Activity activity, long timestamp, Lis transaction = _tracer.StartTransactionInternal(activity.DisplayName, "unknown", timestamp, true, activity.SpanId.ToString(), - distributedTracingData: dt, links: spanLinks, current: activity); + distributedTracingData: dt, links: spanLinks.Count > 0 ? spanLinks : null, current: activity); } else if (activity.ParentId == null) { transaction = _tracer.StartTransactionInternal(activity.DisplayName, "unknown", timestamp, true, activity.SpanId.ToString(), - activity.TraceId.ToString(), links: spanLinks, current: activity); + activity.TraceId.ToString(), links: spanLinks.Count > 0 ? spanLinks : null, current: activity); } - if (transaction == null) return false; + if (transaction == null) + return false; transaction.Otel = new OTel { SpanKind = activity.Kind.ToString() }; if (activity.Id != null) _activeTransactions.AddOrUpdate(activity, transaction); + return true; } @@ -159,15 +160,16 @@ private void CreateSpanForActivity(Activity activity, long timestamp, List 0 ? spanLinks : null, current: activity); } else { newSpan = (_tracer.CurrentSpan as Span)?.StartSpanInternal(activity.DisplayName, "unknown", - timestamp: timestamp, id: activity.SpanId.ToString(), links: spanLinks, current: activity); + timestamp: timestamp, id: activity.SpanId.ToString(), links: spanLinks.Count > 0 ? spanLinks : null, current: activity); } - if (newSpan == null) return; + if (newSpan == null) + return; newSpan.Otel = new OTel { SpanKind = activity.Kind.ToString() }; @@ -194,10 +196,11 @@ private void CreateSpanForActivity(Activity activity, long timestamp, List(); - foreach (var (key, value) in activity.TagObjects) + otel.Attributes ??= []; + foreach (var tagObject in activity.TagObjects) { - if (i >= 128) break; + if (i >= 128) + break; - if (value is string s) - otel.Attributes[key] = s.Truncate(10_000); + if (tagObject.Value is string s) + otel.Attributes[tagObject.Key] = s.Truncate(10_000); else - otel.Attributes[key] = value; + otel.Attributes[tagObject.Key] = tagObject.Value; i++; } } @@ -264,7 +270,8 @@ private static void UpdateSpan(Activity activity, Span span) // By default we set unknown outcome span.Outcome = Outcome.Unknown; -#if NET8_0_OR_GREATER + +#if NET // Not available in netstandard2.1 switch (activity.Status) { case ActivityStatusCode.Unset: @@ -278,6 +285,7 @@ private static void UpdateSpan(Activity activity, Span span) break; } #endif + span.End(); } @@ -417,7 +425,11 @@ private static bool TryGetStringValue(Activity activity, string key, out string { value = null; +#if NET var attribute = activity.GetTagItem(key); +#else + var attribute = activity.TagObjects.FirstOrDefault(kvp => kvp.Key == key).Value; +#endif if (attribute is string stringValue) { diff --git a/src/Elastic.Apm/Report/PayloadSenderV2.cs b/src/Elastic.Apm/Report/PayloadSenderV2.cs index d9c9624fa..945e9f930 100644 --- a/src/Elastic.Apm/Report/PayloadSenderV2.cs +++ b/src/Elastic.Apm/Report/PayloadSenderV2.cs @@ -398,7 +398,7 @@ private void ProcessQueueItems(object[] queueItems) { content.Headers.ContentType = MediaTypeHeaderValue; -#if NET8_0_OR_GREATER +#if NET HttpResponseMessage response; try { @@ -424,7 +424,7 @@ private void ProcessQueueItems(object[] queueItems) var message = "Unknown 400 Bad Request"; if (response?.Content != null) { -#if NET8_0_OR_GREATER +#if NET var intakeResponse = _payloadItemSerializer.Deserialize(response.Content.ReadAsStream()); #else var intakeResponse = _payloadItemSerializer.Deserialize(response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()); diff --git a/src/Elastic.Apm/ServerInfo/IApmServerInfo.cs b/src/Elastic.Apm/ServerInfo/IApmServerInfo.cs index bce8decac..f5dc3d24b 100644 --- a/src/Elastic.Apm/ServerInfo/IApmServerInfo.cs +++ b/src/Elastic.Apm/ServerInfo/IApmServerInfo.cs @@ -16,6 +16,6 @@ internal interface IApmServerInfo /// The agent should not depend on the APM server version and if the version is not (yet) available the agent should /// default to a reasonable behaviour. /// - public ElasticVersion Version { get; set; } + ElasticVersion Version { get; set; } } } diff --git a/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs b/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs index 3cc8e7465..ff64d6b96 100644 --- a/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs +++ b/src/integrations/Elastic.Apm.AspNetCore/ApplicationBuilderExtensions.cs @@ -93,7 +93,7 @@ params IDiagnosticsSubscriber[] subscribers } private static string GetEnvironmentName(this IServiceProvider serviceProvider) => -#if NET8_0_OR_GREATER +#if NET (serviceProvider.GetService(typeof(IWebHostEnvironment)) as IWebHostEnvironment)?.EnvironmentName; #else #pragma warning disable CS0246 diff --git a/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs b/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs index afb85db01..6d782fb77 100644 --- a/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs +++ b/src/integrations/Elastic.Apm.Extensions.Hosting/ServiceCollectionExtensions.cs @@ -106,7 +106,7 @@ public static IServiceCollection AddElasticApm(this IServiceCollection services, } private static string GetDefaultEnvironmentName(IServiceProvider serviceProvider) => -#if NET8_0_OR_GREATER +#if NET (serviceProvider.GetService(typeof(IHostEnvironment)) as IHostEnvironment)?.EnvironmentName; // This is preferred since 3.0 #else #pragma warning disable CS0246 diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeException.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeException.cs index a7f7821a4..10e48f742 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeException.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeException.cs @@ -22,6 +22,6 @@ public interface IConsumeException /// /// Gets the consume result associated with the consume request /// - public IConsumeResult ConsumerRecord { get; } + IConsumeResult ConsumerRecord { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeResult.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeResult.cs index 1099595f0..1f1bda087 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeResult.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IConsumeResult.cs @@ -17,27 +17,27 @@ public interface IConsumeResult /// /// Gets the topic /// - public string Topic { get; } + string Topic { get; } /// /// Gets the partition /// - public Partition Partition { get; } + Partition Partition { get; } /// /// Gets the offset /// - public Offset Offset { get; } + Offset Offset { get; } /// /// Gets the Kafka record /// - public IMessage Message { get; } + IMessage Message { get; } /// /// Gets a value indicating whether gets whether the message is a partition EOF /// // ReSharper disable once InconsistentNaming - public bool IsPartitionEOF { get; } + bool IsPartitionEOF { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryReport.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryReport.cs index 13517f889..e1e8b209d 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryReport.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryReport.cs @@ -22,6 +22,6 @@ public interface IDeliveryReport : IDeliveryResult /// /// Gets the Error associated with the delivery report /// - public IError Error { get; } + IError Error { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryResult.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryResult.cs index 2a42e80e3..dc8a169b0 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryResult.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IDeliveryResult.cs @@ -22,11 +22,11 @@ public interface IDeliveryResult /// /// Gets the Kafka partition. /// - public Partition Partition { get; } + Partition Partition { get; } /// /// Gets the Kafka offset /// - public Offset Offset { get; } + Offset Offset { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IError.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IError.cs index 341d302bf..a18ee72ac 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IError.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IError.cs @@ -22,12 +22,12 @@ public interface IError /// /// Gets a value indicating whether the error is really an error /// - public bool IsError { get; } + bool IsError { get; } /// /// Gets the string representation of the error /// /// The string representation of the error - public string ToString(); + string ToString(); } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IHeaders.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IHeaders.cs index 7a7ab9c0a..1bcb938d5 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IHeaders.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IHeaders.cs @@ -24,13 +24,13 @@ public interface IHeaders /// /// The header's key value /// The value of the header. May be null. Format strings as UTF8 - public void Add(string key, byte[] val); + void Add(string key, byte[] val); /// /// Removes all headers for the given key. /// /// The key to remove all headers for - public void Remove(string key); + void Remove(string key); /// /// Try to get the value of the latest header with the specified key. @@ -47,6 +47,6 @@ public interface IHeaders /// true if the a value with the specified key was present in /// the collection, false otherwise. /// - public bool TryGetLastBytes(string key, out byte[] lastHeader); + bool TryGetLastBytes(string key, out byte[] lastHeader); } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IMessage.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IMessage.cs index fd0086811..be8e91a20 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IMessage.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IMessage.cs @@ -22,16 +22,16 @@ public interface IMessage /// /// Gets the value of the message /// - public object Value { get; } + object Value { get; } /// /// Gets the timestamp that the message was produced /// - public ITimestamp Timestamp { get; } + ITimestamp Timestamp { get; } /// /// Gets or sets the headers for the record /// - public IHeaders Headers { get; set; } + IHeaders Headers { get; set; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IProduceException.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IProduceException.cs index 8d93c7770..4bd82b5d5 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IProduceException.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/IProduceException.cs @@ -22,6 +22,6 @@ public interface IProduceException /// /// Gets the delivery result associated with the produce request /// - public IDeliveryResult DeliveryResult { get; } + IDeliveryResult DeliveryResult { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITimestamp.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITimestamp.cs index 8191856a7..06730dad2 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITimestamp.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITimestamp.cs @@ -24,11 +24,11 @@ public interface ITimestamp /// /// Gets the timestamp type /// - public int Type { get; } + int Type { get; } /// /// Gets the UTC DateTime for the timestamp /// - public DateTime UtcDateTime { get; } + DateTime UtcDateTime { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITopicPartition.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITopicPartition.cs index db1f14a8e..269a801ef 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITopicPartition.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITopicPartition.cs @@ -22,11 +22,11 @@ public interface ITopicPartition /// /// Gets the Kafka topic name. /// - public string Topic { get; } + string Topic { get; } /// /// Gets the Kafka partition. /// - public Partition Partition { get; } + Partition Partition { get; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITypedDeliveryHandlerShimAction.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITypedDeliveryHandlerShimAction.cs index 294188d1f..a4b86ea71 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITypedDeliveryHandlerShimAction.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/Kafka/ITypedDeliveryHandlerShimAction.cs @@ -24,6 +24,6 @@ public interface ITypedDeliveryHandlerShimAction /// Sets the delivery report handler /// [DuckField] - public object Handler { set; } + object Handler { set; } } } diff --git a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/RabbitMq/IBasicGetResult.cs b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/RabbitMq/IBasicGetResult.cs index 990d032f8..2e1421681 100644 --- a/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/RabbitMq/IBasicGetResult.cs +++ b/src/profiler/Elastic.Apm.Profiler.Managed/Integrations/RabbitMq/IBasicGetResult.cs @@ -37,6 +37,6 @@ public interface IBasicGetResult /// /// Retrieve the routing key with which this message was published. /// - public string RoutingKey { get; } + string RoutingKey { get; } } } diff --git a/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj b/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj index c04e0e3d4..59676bf36 100644 --- a/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj +++ b/src/startuphook/Elastic.Apm.StartupHook.Loader/Elastic.Apm.StartupHook.Loader.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 false diff --git a/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs b/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs index 0e3d5677d..40276126b 100644 --- a/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs +++ b/src/startuphook/Elastic.Apm.StartupHook.Loader/Loader.cs @@ -4,8 +4,6 @@ // See the LICENSE file in the project root for more information using System; -using System.IO; -using System.Reflection; using Elastic.Apm.AspNetCore.DiagnosticListener; using Elastic.Apm.DiagnosticSource; using Elastic.Apm.Elasticsearch; diff --git a/src/startuphook/ElasticApmAgentStartupHook/StartupHook.cs b/src/startuphook/ElasticApmAgentStartupHook/StartupHook.cs index 8fcf9d4ed..fd2ceac57 100644 --- a/src/startuphook/ElasticApmAgentStartupHook/StartupHook.cs +++ b/src/startuphook/ElasticApmAgentStartupHook/StartupHook.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text.RegularExpressions; using ElasticApmStartupHook; @@ -17,32 +18,41 @@ internal class StartupHook private const string LoaderDll = "Elastic.Apm.StartupHook.Loader.dll"; private const string LoaderTypeName = "Elastic.Apm.StartupHook.Loader.Loader"; private const string LoaderTypeMethod = "Initialize"; + private const string SystemDiagnosticsDiagnosticsource = "System.Diagnostics.DiagnosticSource"; private static readonly byte[] SystemDiagnosticsDiagnosticSourcePublicKeyToken = { 204, 123, 19, 255, 205, 45, 221, 81 }; - private static readonly Regex VersionRegex = new Regex( + + private static readonly Regex VersionRegex = new( @"^(?\d+)(\.(?\d+))?(\.(?\d+))?(\-(?
[0-9A-Za-z]+))?$",
 		RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
 
-	private static StartupHookLogger _logger;
+	private static StartupHookLogger Logger;
 
 	/// 
 	/// The Initialize method called by DOTNET_STARTUP_HOOKS
 	/// 
 	public static void Initialize()
 	{
+		Logger = StartupHookLogger.Create();
+
+		if (!IsNet8OrHigher())
+		{
+			Logger.WriteLine("The .NET runtime version is lower than .NET 8. The Elastic APM .NET Agent startup " +
+				"hook is only supported on .NET 8 or higher. Some functionality may not work as expected.");
+		}
+
 		var startupHookEnvVar = Environment.GetEnvironmentVariable("DOTNET_STARTUP_HOOKS");
 		var startupHookDirectory = Path.GetDirectoryName(startupHookEnvVar);
 
 		if (string.IsNullOrEmpty(startupHookEnvVar) || !File.Exists(startupHookEnvVar))
 			return;
 
-		_logger = StartupHookLogger.Create();
-		_logger.WriteLine($"Check if {SystemDiagnosticsDiagnosticsource} is loaded");
-
 		var assemblies = AppDomain.CurrentDomain.GetAssemblies();
 
-		_logger.WriteLine($"Assemblies loaded:{Environment.NewLine}{string.Join(Environment.NewLine, assemblies.Select(a => a.GetName()))}");
+		Logger.WriteLine($"Assemblies loaded:{Environment.NewLine}{string.Join(Environment.NewLine, assemblies.Select(a => a.GetName()))}");
+
+		Logger.WriteLine($"Check if {SystemDiagnosticsDiagnosticsource} is loaded");
 
 		var diagnosticSourceAssemblies = assemblies
 			.Where(a => a.GetName().Name.Equals(SystemDiagnosticsDiagnosticsource, StringComparison.Ordinal))
@@ -52,16 +62,17 @@ public static void Initialize()
 		switch (diagnosticSourceAssemblies.Count)
 		{
 			case 0:
-				_logger.WriteLine($"No {SystemDiagnosticsDiagnosticsource} loaded. Load using AssemblyLoadContext.Default");
-
+				Logger.WriteLine($"No {SystemDiagnosticsDiagnosticsource} loaded. Loading using AssemblyLoadContext.Default");
 				diagnosticSourceAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(SystemDiagnosticsDiagnosticsource));
 				break;
 			case 1:
 				diagnosticSourceAssembly = diagnosticSourceAssemblies[0];
+				Logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} is loaded (Version: {diagnosticSourceAssembly.GetName().Version}). ");
 				break;
 			default:
-				_logger.WriteLine($"Found {diagnosticSourceAssemblies.Count} {SystemDiagnosticsDiagnosticsource} assemblies loaded in the app domain");
+				Logger.WriteLine($"Found {diagnosticSourceAssemblies.Count} {SystemDiagnosticsDiagnosticsource} assemblies loaded in the app domain");
 				diagnosticSourceAssembly = diagnosticSourceAssemblies.First();
+				Logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} is loaded (Version: {diagnosticSourceAssembly.GetName().Version}). ");
 				break;
 		}
 
@@ -77,6 +88,9 @@ public static void Initialize()
 				.First();
 
 			loaderDirectory = Path.Combine(startupHookDirectory, highestAvailableAgent);
+
+			Logger.WriteLine($"Loading {LoaderDll} using AssemblyLoadContext.Default from {loaderDirectory}");
+
 			loaderAssembly = AssemblyLoadContext.Default
 				.LoadFromAssemblyPath(Path.Combine(loaderDirectory, LoaderDll));
 		}
@@ -86,37 +100,74 @@ public static void Initialize()
 			var diagnosticSourcePublicKeyToken = diagnosticSourceAssemblyName.GetPublicKeyToken();
 			if (!diagnosticSourcePublicKeyToken.SequenceEqual(SystemDiagnosticsDiagnosticSourcePublicKeyToken))
 			{
-				_logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} public key token "
+				Logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} public key token "
 					+ $"{PublicKeyTokenBytesToString(diagnosticSourcePublicKeyToken)} did not match expected "
 					+ $"public key token {PublicKeyTokenBytesToString(SystemDiagnosticsDiagnosticSourcePublicKeyToken)}");
 				return;
 			}
 
 			var diagnosticSourceVersion = diagnosticSourceAssemblyName.Version;
-			_logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} {diagnosticSourceVersion} loaded");
-			// .NET 7 still uses LoaderDll version 6.
-			var majorVersion = Math.Min(6, diagnosticSourceVersion.Major);
-			loaderDirectory = Path.Combine(startupHookDirectory, $"{majorVersion}.0.0");
+			Logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} {diagnosticSourceVersion} loaded");
+
+			if (diagnosticSourceVersion.Major < 6)
+			{
+				Logger.WriteLine($"{SystemDiagnosticsDiagnosticsource} version {diagnosticSourceVersion} is not supported. 6.0.0 or higher is required. Agent not loaded.");
+				return;
+			}
+			else if (diagnosticSourceVersion.Major == 6 || diagnosticSourceVersion.Major == 7)
+			{
+				loaderDirectory = Path.Combine(startupHookDirectory, "6.0.0");
+			}
+			else
+			{
+				loaderDirectory = Path.Combine(startupHookDirectory, "8.0.0");
+			}
+
 			if (Directory.Exists(loaderDirectory))
 			{
+				Logger.WriteLine($"Loading {LoaderDll} using AssemblyLoadContext.Default from {loaderDirectory}");
+
 				loaderAssembly = AssemblyLoadContext.Default
 					.LoadFromAssemblyPath(Path.Combine(loaderDirectory, LoaderDll));
 			}
 			else
 			{
-				_logger.WriteLine(
+				Logger.WriteLine(
 					$"No compatible agent for {SystemDiagnosticsDiagnosticsource} {diagnosticSourceVersion}. Agent not loaded");
 			}
 		}
 
 		if (loaderAssembly is null)
 		{
-			_logger.WriteLine($"No {LoaderDll} assembly loaded. Agent not loaded");
+			Logger.WriteLine($"No {LoaderDll} assembly loaded. Agent not loaded");
 			return;
 		}
 
 		LoadAssembliesFromLoaderDirectory(loaderDirectory);
 		InvokerLoaderMethod(loaderAssembly);
+
+		static bool IsNet8OrHigher()
+		{
+			var desc = RuntimeInformation.FrameworkDescription;
+			if (desc.StartsWith(".NET", StringComparison.OrdinalIgnoreCase))
+			{
+				var parts = desc.Split(' ');
+				if (parts.Length >= 2 && Version.TryParse(parts[1], out var version))
+				{
+					return version.Major >= 8;
+				}
+			}
+			return false;
+		}
+
+		static string PublicKeyTokenBytesToString(byte[] publicKeyToken)
+		{
+			var token = string.Empty;
+			for (var i = 0; i < publicKeyToken.Length; i++)
+				token += $"{publicKeyToken[i]:x2}";
+
+			return token;
+		}
 	}
 
 	/// 
@@ -150,7 +201,7 @@ private static void LoadAssembliesFromLoaderDirectory(string loaderDirectory)
 				}
 				catch (Exception e)
 				{
-					_logger.WriteLine(e.ToString());
+					Logger.WriteLine(e.ToString());
 				}
 			}
 
@@ -158,43 +209,31 @@ private static void LoadAssembliesFromLoaderDirectory(string loaderDirectory)
 		};
 	}
 
-	/// 
-	/// Converts public key token bytes into a string
-	/// 
-	private static string PublicKeyTokenBytesToString(byte[] publicKeyToken)
-	{
-		var token = string.Empty;
-		for (var i = 0; i < publicKeyToken.Length; i++)
-			token += $"{publicKeyToken[i]:x2}";
-
-		return token;
-	}
-
 	/// 
 	/// Invokes the Elastic.Apm.StartupHook.Loader.Loader.Initialize() method from a specific assembly
 	/// 
 	/// The loader assembly
 	private static void InvokerLoaderMethod(Assembly loaderAssembly)
 	{
-		_logger.WriteLine($"Get {LoaderTypeName} type");
+		Logger.WriteLine($"Get {LoaderTypeName} type");
 		var loaderType = loaderAssembly.GetType(LoaderTypeName);
 
 		if (loaderType is null)
 		{
-			_logger.WriteLine($"{LoaderTypeName} type is null");
+			Logger.WriteLine($"{LoaderTypeName} type is null");
 			return;
 		}
 
-		_logger.WriteLine($"Get {LoaderTypeName}.{LoaderTypeMethod} method");
+		Logger.WriteLine($"Get {LoaderTypeName}.{LoaderTypeMethod} method");
 		var initializeMethod = loaderType.GetMethod(LoaderTypeMethod, BindingFlags.Public | BindingFlags.Static);
 
 		if (initializeMethod is null)
 		{
-			_logger.WriteLine($"{LoaderTypeName}.{LoaderTypeMethod} method is null");
+			Logger.WriteLine($"{LoaderTypeName}.{LoaderTypeMethod} method is null");
 			return;
 		}
 
-		_logger.WriteLine($"Invoke {LoaderTypeName}.{LoaderTypeMethod} method");
+		Logger.WriteLine($"Invoke {LoaderTypeName}.{LoaderTypeMethod} method");
 		initializeMethod.Invoke(null, null);
 	}
 }
diff --git a/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs b/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs
index 69ea6fd0a..0fbcb5497 100644
--- a/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs
+++ b/src/startuphook/ElasticApmAgentStartupHook/StartupHookLogger.cs
@@ -51,7 +51,7 @@ public void WriteLine(string message)
 				var log = $"[{DateTime.Now:u}] {message}";
 				Console.Out.WriteLine(log);
 				Console.Out.Flush();
-				File.AppendAllLines(_logPath, new[] { log });
+				File.AppendAllLines(_logPath, [log]);
 			}
 			catch
 			{
diff --git a/test/Elastic.Apm.Tests.Utilities/XUnit/FactRequiresMvcTestingFix.cs b/test/Elastic.Apm.Tests.Utilities/XUnit/FactRequiresMvcTestingFix.cs
index 0974a18af..8f654f35b 100644
--- a/test/Elastic.Apm.Tests.Utilities/XUnit/FactRequiresMvcTestingFix.cs
+++ b/test/Elastic.Apm.Tests.Utilities/XUnit/FactRequiresMvcTestingFix.cs
@@ -14,7 +14,7 @@ public FactRequiresMvcTestingFix()
 	{
 		if (Environment.Version.Major < 7)
 			return;
-		Skip = $"This test is disabled on .NET 7 until https://github.com/dotnet/aspnetcore/issues/45233";
+		Skip = $"This test is disabled on .NET until https://github.com/dotnet/aspnetcore/issues/45233";
 	}
 }
 
@@ -24,6 +24,6 @@ public TheoryRequiresMvcTestingFix()
 	{
 		if (Environment.Version.Major < 7)
 			return;
-		Skip = $"This test is disabled on .NET 7 until https://github.com/dotnet/aspnetcore/issues/45233";
+		Skip = $"This test is disabled on .NET until https://github.com/dotnet/aspnetcore/issues/45233";
 	}
 }
diff --git a/test/Elastic.Apm.Tests/HelpersTests/TimeUtilsTests.cs b/test/Elastic.Apm.Tests/HelpersTests/TimeUtilsTests.cs
index c131d3dae..29f071cc0 100644
--- a/test/Elastic.Apm.Tests/HelpersTests/TimeUtilsTests.cs
+++ b/test/Elastic.Apm.Tests/HelpersTests/TimeUtilsTests.cs
@@ -27,7 +27,7 @@ public class TimeUtilsTests
 			{ 5 * 24 * 60 * 60 * 1_000_000L, new DateTime(1970, 1, 6, 0, 0, 0, DateTimeKind.Utc) }
 		};
 
-#if NETCOREAPP
+#if NET
 		[Fact]
 		public void TimeUtilsUnixEpochDateTimeEqualsDateTimeUnixEpoch() => TimeUtils.UnixEpochDateTime.Should().Be(DateTime.UnixEpoch);
 #endif
diff --git a/test/Elastic.Apm.Tests/LoggerTests.cs b/test/Elastic.Apm.Tests/LoggerTests.cs
index 1be429dd6..038114050 100644
--- a/test/Elastic.Apm.Tests/LoggerTests.cs
+++ b/test/Elastic.Apm.Tests/LoggerTests.cs
@@ -156,7 +156,7 @@ public void EnsureFormattersAreShared()
 			var scopedLogger = consoleLogger.Scoped("MyTestScope");
 			for (var i = 0; i < 10; i++)
 				scopedLogger.Warning()?.Log("This is a test log from the test StructuredLogTemplateWith1MissingArgument, args: {arg1}", i);
-#if NET8_0_OR_GREATER
+#if NET
 			var cachedFormatterCount = scopedLogger.Formatters.Count();
 			cachedFormatterCount.Should().Be(1);
 
diff --git a/test/Elastic.Apm.Tests/MetricsTests.cs b/test/Elastic.Apm.Tests/MetricsTests.cs
index 60f0664a0..0ed862de2 100644
--- a/test/Elastic.Apm.Tests/MetricsTests.cs
+++ b/test/Elastic.Apm.Tests/MetricsTests.cs
@@ -306,7 +306,7 @@ public void CollectGcMetrics()
 			var logger = new TestLogger(LogLevel.Trace);
 			using (var gcMetricsProvider = new GcMetricsProvider(logger, new List()))
 			{
-#if NETCOREAPP2_2_OR_GREATER
+#if NET
 				//EventSource Microsoft-Windows-DotNETRuntime is only 2.2+, no gc metrics on 2.1
 				//repeat the allocation multiple times and make sure at least 1 GetSamples() call returns value
 
diff --git a/test/Elastic.Apm.Tests/ServerCertificateTests.cs b/test/Elastic.Apm.Tests/ServerCertificateTests.cs
index f1d20ee92..cb5383458 100644
--- a/test/Elastic.Apm.Tests/ServerCertificateTests.cs
+++ b/test/Elastic.Apm.Tests/ServerCertificateTests.cs
@@ -5,7 +5,7 @@
 
 // depends on Mock APM server project TargetFramework
 
-#if NET5_0_OR_GREATER
+#if NET
 
 using System;
 using System.IO;
@@ -70,6 +70,11 @@ public void ServerCert_Should_Allow_Https_To_Apm_Server()
 
 			_server.ReceivedData.Transactions.Should().HaveCount(1);
 			var transaction = _server.ReceivedData.Transactions.First();
+
+			transaction.Context.Should().NotBeNull();
+			transaction.Context.Labels.Should().NotBeNull();
+			transaction.Context.Labels.MergedDictionary.Should().NotBeNull();
+
 			transaction.Context.Labels.MergedDictionary.Should().ContainKey("self_signed_cert");
 		}
 
diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs
index 137eeb935..72ddabd35 100644
--- a/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs
+++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/AspNetCoreBasicTests.cs
@@ -89,11 +89,8 @@ public async Task HomeSimplePageTransactionTest()
 
 			var aspNetCoreVersion = Assembly.Load("Microsoft.AspNetCore").GetName().Version.ToString();
 			agent.Service.Framework.Version.Should().Be(aspNetCoreVersion);
-#if NET8_0
+
 			agent.Service.Runtime.Name.Should().Be(Runtime.DotNetName + " 8");
-#else
-			agent.Service.Runtime.Name.Should().Be(Runtime.DotNetCoreName);
-#endif
 			agent.Service.Runtime.Version.Should().StartWith(Directory.GetParent(typeof(object).Assembly.Location).Name);
 
 			var transaction = payloadSender.FirstTransaction;
@@ -118,13 +115,7 @@ public async Task HomeSimplePageTransactionTest()
 			}
 
 			//test transaction.context.request
-#if NET5_0_OR_GREATER
-			transaction.Context.Request.HttpVersion.Should().Be("1.1");
-#elif NETCOREAPP3_0 || NETCOREAPP3_1
 			transaction.Context.Request.HttpVersion.Should().Be("2");
-#else
-			transaction.Context.Request.HttpVersion.Should().Be("2.0");
-#endif
 			transaction.Context.Request.Method.Should().Be("GET");
 
 			//test transaction.context.request.url
@@ -237,14 +228,7 @@ public async Task HomeSimplePagePostTransactionTest()
 			var aspNetCoreVersion = Assembly.Load("Microsoft.AspNetCore").GetName().Version.ToString(2);
 			agent.Service.Framework.Version.Should().StartWith(aspNetCoreVersion);
 
-#if NET6_0
-			agent.Service.Runtime.Name.Should().Be(Runtime.DotNetName + " 6");
-#elif NET7_0
-			agent.Service.Runtime.Name.Should().Be(Runtime.DotNetName + " 7");
-#else
 			agent.Service.Runtime.Name.Should().Be(Runtime.DotNetCoreName);
-#endif
-
 			agent.Service.Runtime.Version.Should().StartWith(Directory.GetParent(typeof(object).Assembly.Location).Name);
 
 			payloadSender.Transactions.Should().ContainSingle();
@@ -272,13 +256,7 @@ public async Task HomeSimplePagePostTransactionTest()
 			}
 
 			//test transaction.context.request
-#if NET5_0_OR_GREATER
-			transaction.Context.Request.HttpVersion.Should().Be("1.1");
-#elif NETCOREAPP3_0 || NETCOREAPP3_1
 			transaction.Context.Request.HttpVersion.Should().Be("2");
-#else
-			transaction.Context.Request.HttpVersion.Should().Be("2.0");
-#endif
 			transaction.Context.Request.Method.Should().Be("POST");
 
 			//test transaction.context.request.url
diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/Helper.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/Helper.cs
index 8fed78d03..1218ab961 100644
--- a/test/integrations/Elastic.Apm.AspNetCore.Tests/Helper.cs
+++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/Helper.cs
@@ -2,9 +2,9 @@
 // Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
 // See the LICENSE file in the project root for more information
 
+using System;
 using System.Net.Http;
 using System.Reflection;
-using Elastic.Apm.AspNetCore.DiagnosticListener;
 using Elastic.Apm.DiagnosticSource;
 using Elastic.Apm.EntityFrameworkCore;
 using Microsoft.AspNetCore.Builder;
@@ -27,9 +27,7 @@ WebApplicationFactory factory
 #pragma warning disable IDE0022 // Use expression body for methods
 				client = GetClient(agent, factory);
 #pragma warning restore IDE0022 // Use expression body for methods
-#if NETCOREAPP3_0 || NETCOREAPP3_1
 				client.DefaultRequestVersion = new Version(2, 0);
-#endif
 			}
 			else
 				client = GetClientWithoutExceptionPage(agent, factory);
diff --git a/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs b/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs
index 7cd8c4aab..496cb6fbb 100644
--- a/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs
+++ b/test/integrations/Elastic.Apm.AspNetCore.Tests/TransactionIgnoreUrlsTest.cs
@@ -7,9 +7,7 @@
 using System.Net.Http;
 using System.Threading.Tasks;
 using Elastic.Apm.Extensions.Hosting;
-using Elastic.Apm.Logging;
 using Elastic.Apm.Tests.Utilities;
-using Elastic.Apm.Tests.Utilities.XUnit;
 using FluentAssertions;
 using Microsoft.AspNetCore.Mvc.Testing;
 using SampleAspNetCoreApp;
@@ -53,9 +51,7 @@ private void Setup()
 #pragma warning disable IDE0022 // Use expression body for methods
 			_client = Helper.GetClient(_agent, _factory);
 #pragma warning restore IDE0022 // Use expression body for methods
-#if NETCOREAPP3_0 || NETCOREAPP3_1
 			_client.DefaultRequestVersion = new Version(2, 0);
-#endif
 		}
 
 		/// 
diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/Continuations/ValueTaskContinuationGeneratorTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/Continuations/ValueTaskContinuationGeneratorTests.cs
index 69679eddf..eae3f98f6 100644
--- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/Continuations/ValueTaskContinuationGeneratorTests.cs
+++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/Continuations/ValueTaskContinuationGeneratorTests.cs
@@ -7,7 +7,7 @@
 // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
 // 
 
-#if NET8_0_OR_GREATER
+#if NET
 using System;
 using System.Threading;
 using System.Threading.Tasks;
diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/DuckIgnoreTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/DuckIgnoreTests.cs
index 621f9132a..9e850003e 100644
--- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/DuckIgnoreTests.cs
+++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/DuckIgnoreTests.cs
@@ -25,7 +25,7 @@ public void NonPublicStructCopyTest()
 			Assert.Equal(ValuesDuckType.Third.ToString(), ((IGetValue)copy).GetValueProp);
 		}
 
-#if NET8_0_OR_GREATER
+#if NET
 		[Fact]
 		public void NonPublicStructInterfaceProxyTest()
 		{
@@ -69,17 +69,17 @@ public struct CopyStruct : IGetValue
 			public string GetValue() => Value.ToString();
 		}
 
-#if NETCOREAPP3_0_OR_GREATER
+#if NET
 		// Interface with a default implementation
 		public interface IPrivateStruct
 		{
 			ValuesDuckType Value { get; }
 
 			[DuckIgnore]
-			public string GetValueProp => Value.ToString();
+			string GetValueProp => Value.ToString();
 
 			[DuckIgnore]
-			public string GetValue() => Value.ToString();
+			string GetValue() => Value.ToString();
 		}
 #endif
 
diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/ExceptionsTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/ExceptionsTests.cs
index c4a6b656f..97b386549 100644
--- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/ExceptionsTests.cs
+++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/ExceptionsTests.cs
@@ -318,18 +318,18 @@ public void TargetMethodNotFoundException()
 
 		public interface ITargetMethodNotFoundException
 		{
-			public void AddTypo(string key, string value);
+			void AddTypo(string key, string value);
 		}
 
 		public interface ITargetMethodNotFound2Exception
 		{
-			public void AddGeneric(object value);
+			void AddGeneric(object value);
 		}
 
 		public interface ITargetMethodNotFound3Exception
 		{
 			[Duck(GenericParameterTypeNames = new string[] { "P1", "P2" })]
-			public void AddGeneric(object value);
+			void AddGeneric(object value);
 		}
 
 		internal class TargetMethodNotFoundExceptionClass
@@ -355,7 +355,7 @@ public void ProxyMethodParameterIsMissingException()
 		public interface IProxyMethodParameterIsMissingException
 		{
 			[Duck(ParameterTypeNames = new string[] { "System.String", "System.String" })]
-			public void Add(string key);
+			void Add(string key);
 		}
 
 		internal class ProxyMethodParameterIsMissingExceptionClass
@@ -384,13 +384,13 @@ public void ProxyAndTargetMethodParameterSignatureMismatchException()
 		public interface IProxyAndTargetMethodParameterSignatureMismatchException
 		{
 			[Duck(ParameterTypeNames = new string[] { "System.String", "System.String" })]
-			public void Add(string key, ref string value);
+			void Add(string key, ref string value);
 		}
 
 		public interface IProxyAndTargetMethodParameterSignatureMismatch2Exception
 		{
 			[Duck(ParameterTypeNames = new string[] { "System.String", "System.String" })]
-			public void Add(string key, out string value);
+			void Add(string key, out string value);
 		}
 
 		internal class ProxyAndTargetMethodParameterSignatureMismatchExceptionClass
@@ -438,9 +438,9 @@ public void TargetMethodAmbiguousMatchException()
 
 		public interface ITargetMethodAmbiguousMatchException
 		{
-			public void Add(string key, object value);
+			void Add(string key, object value);
 
-			public void Add(string key, string value);
+			void Add(string key, string value);
 		}
 
 		internal class TargetMethodAmbiguousMatchExceptionClass
diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/Methods/ProxiesDefinitions/IDictioDuckType.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/Methods/ProxiesDefinitions/IDictioDuckType.cs
index 18773212b..207975adc 100644
--- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/Methods/ProxiesDefinitions/IDictioDuckType.cs
+++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/Methods/ProxiesDefinitions/IDictioDuckType.cs
@@ -15,13 +15,13 @@ namespace Elastic.Apm.Profiler.Managed.Tests.DuckTyping.Methods.ProxiesDefinitio
 {
 	public interface IDictioDuckType
 	{
-		public ICollection Keys { get; }
+		ICollection Keys { get; }
 
-		public ICollection Values { get; }
+		ICollection Values { get; }
 
 		int Count { get; }
 
-		public string this[string key] { get; set; }
+		string this[string key] { get; set; }
 
 		void Add(string key, string value);
 
diff --git a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/StructTests.cs b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/StructTests.cs
index 2be73e11a..07f277996 100644
--- a/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/StructTests.cs
+++ b/test/profiler/Elastic.Apm.Profiler.Managed.Tests/DuckTyping/StructTests.cs
@@ -102,7 +102,7 @@ public interface IPrivateDuckChainingTarget
 		public interface IPrivateTarget
 		{
 			[Duck(Kind = DuckKind.Field)]
-			public string Name { get; }
+			string Name { get; }
 		}
 
 		private class PrivateDuckChainingTarget
diff --git a/test/startuphook/Elastic.Apm.StartupHook.Sample/Elastic.Apm.StartupHook.Sample.csproj b/test/startuphook/Elastic.Apm.StartupHook.Sample/Elastic.Apm.StartupHook.Sample.csproj
index ccd7e8ece..9d6373c0f 100644
--- a/test/startuphook/Elastic.Apm.StartupHook.Sample/Elastic.Apm.StartupHook.Sample.csproj
+++ b/test/startuphook/Elastic.Apm.StartupHook.Sample/Elastic.Apm.StartupHook.Sample.csproj
@@ -1,7 +1,7 @@
 
 
   
-    net8.0
+    net8.0;net9.0
     false
   
 
diff --git a/test/startuphook/Elastic.Apm.StartupHook.Sample/Startup.cs b/test/startuphook/Elastic.Apm.StartupHook.Sample/Startup.cs
index 251396d9a..df083d930 100644
--- a/test/startuphook/Elastic.Apm.StartupHook.Sample/Startup.cs
+++ b/test/startuphook/Elastic.Apm.StartupHook.Sample/Startup.cs
@@ -13,7 +13,7 @@ public class Startup
 
 		// This method gets called by the runtime. Use this method to add services to the container.
 		public void ConfigureServices(IServiceCollection services) =>
-#if NET5_0_OR_GREATER
+#if NET
 			services.AddControllersWithViews();
 #else
 			services.AddMvc();
diff --git a/test/startuphook/Elastic.Apm.StartupHook.Tests/DotnetProject.cs b/test/startuphook/Elastic.Apm.StartupHook.Tests/DotnetProject.cs
index 8e0062aa2..fce6be31f 100644
--- a/test/startuphook/Elastic.Apm.StartupHook.Tests/DotnetProject.cs
+++ b/test/startuphook/Elastic.Apm.StartupHook.Tests/DotnetProject.cs
@@ -5,13 +5,10 @@
 
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 using System.IO.Compression;
 using System.Linq;
-using System.Xml.Linq;
 using ProcNet;
-using ProcNet.Std;
 using Xunit.Abstractions;
 
 namespace Elastic.Apm.StartupHook.Tests
@@ -168,7 +165,7 @@ public static DotnetProject Create(ITestOutputHelper output, string name, string
 				"new",
 				"globaljson",
 				"--sdk-version",
-				"8.0.404", // Fixing this specific version, for now
+				"9.0.303", // Fixing this specific version, for now
 				"--roll-forward",
 				"LatestPatch"
 			])
diff --git a/test/startuphook/Elastic.Apm.StartupHook.Tests/Elastic.Apm.StartupHook.Tests.csproj b/test/startuphook/Elastic.Apm.StartupHook.Tests/Elastic.Apm.StartupHook.Tests.csproj
index 809140a35..5c31fc309 100644
--- a/test/startuphook/Elastic.Apm.StartupHook.Tests/Elastic.Apm.StartupHook.Tests.csproj
+++ b/test/startuphook/Elastic.Apm.StartupHook.Tests/Elastic.Apm.StartupHook.Tests.csproj
@@ -1,13 +1,13 @@
 
 
-    
-      net8.0
-    
+  
+    net8.0
+  
+
+  
+    
+  
 
-    
-      
-    
-  
   
     
     
diff --git a/test/startuphook/Elastic.Apm.StartupHook.Tests/StartupHookTests.cs b/test/startuphook/Elastic.Apm.StartupHook.Tests/StartupHookTests.cs
index 8810d7bee..dd8be841b 100644
--- a/test/startuphook/Elastic.Apm.StartupHook.Tests/StartupHookTests.cs
+++ b/test/startuphook/Elastic.Apm.StartupHook.Tests/StartupHookTests.cs
@@ -31,10 +31,21 @@ public partial class StartupHookTests(ITestOutputHelper output)
 		private static IEnumerable<(string TargetFramework, string RuntimeName, string Version, string ShortVersion)> GetDotNetFrameworkVersionInfos()
 		{
 			yield return ("net8.0", ".NET 8", "8.0.0.0", "80");
+			yield return ("net9.0", ".NET 9", "9.0.0.0", "90");
 		}
 
+		private static readonly Dictionary CommonEnvVars = new()
+		{
+			[CloudProvider.ToEnvironmentVariable()] = "none",
+			[CentralConfig.ToEnvironmentVariable()] = "false",
+			// We disable the OpenTelemetry bridge here to avoid additional transactions
+			// that may be created. In .NET 9 there are some additional activities for
+			// HttpConnection that result in additional spans and transactions.
+			["ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED"] = "false"
+		};
+
 		public static IEnumerable DotNetFrameworkVersionInfos()
-			=> GetDotNetFrameworkVersionInfos().Select(i => new[] { i.TargetFramework, i.RuntimeName, i.Version });
+				=> GetDotNetFrameworkVersionInfos().Select(i => new[] { i.TargetFramework, i.RuntimeName, i.Version });
 
 		public static IEnumerable DotNetFrameworks()
 			=> DotNetFrameworkVersionInfos().Select(o => o[0..1]);
@@ -75,10 +86,9 @@ public async Task Auto_Instrument_With_StartupHook_Should_Capture_Transaction(st
 
 			using (var sampleApp = new SampleApplication())
 			{
-				var environmentVariables = new Dictionary
+				var environmentVariables = new Dictionary(CommonEnvVars)
 				{
-					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}",
-					[CloudProvider.ToEnvironmentVariable()] = "none"
+					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}"
 				};
 
 				var uri = sampleApp.Start(targetFramework, environmentVariables);
@@ -118,10 +128,9 @@ public async Task Auto_Instrument_With_StartupHook_Should_Capture_Error(string t
 
 			using (var sampleApp = new SampleApplication())
 			{
-				var environmentVariables = new Dictionary
+				var environmentVariables = new Dictionary(CommonEnvVars)
 				{
-					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}",
-					[CloudProvider.ToEnvironmentVariable()] = "none"
+					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}"
 				};
 
 				var uri = sampleApp.Start(targetFramework, environmentVariables);
@@ -168,10 +177,9 @@ public async Task Auto_Instrument_With_StartupHook_Should_Capture_Metadata(
 
 			using (var sampleApp = new SampleApplication())
 			{
-				var environmentVariables = new Dictionary
+				var environmentVariables = new Dictionary(CommonEnvVars)
 				{
-					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}",
-					[CloudProvider.ToEnvironmentVariable()] = "none"
+					[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}"
 				};
 
 				var uri = sampleApp.Start(targetFramework, environmentVariables);
@@ -215,10 +223,9 @@ public async Task Auto_Instrument_With_StartupHook(string template, string name,
 
 			using var project = DotnetProject.Create(_output, name, template, targetFramework, "--no-https");
 
-			var environmentVariables = new Dictionary
+			var environmentVariables = new Dictionary(CommonEnvVars)
 			{
-				[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}",
-				[CloudProvider.ToEnvironmentVariable()] = "none"
+				[ServerUrl.ToEnvironmentVariable()] = $"http://localhost:{port}"
 			};
 
 			using var process = project.CreateProcess(SolutionPaths.AgentZip, environmentVariables);