diff --git a/.cspell/other.txt b/.cspell/other.txt index a0b3134eed..f53aa5cee6 100644 --- a/.cspell/other.txt +++ b/.cspell/other.txt @@ -44,6 +44,7 @@ NETRUNTIME Npgsql NSERVICEBUS omnisharp +OPAMP OPENTRACING OPERATINGSYSTEM ORACLEMDA @@ -58,9 +59,9 @@ protos RABBITMQ Serilog spdlog -srcs SQLCLIENT sqlserver +srcs STACKEXCHANGEREDIS TMPDIR tracesexporter diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b839fdda..e86146b23a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h ### Added +- Experimental support for OpAMP (by default the client is disabled). + ### Changed #### Dependency updates diff --git a/docs/config.md b/docs/config.md index 5c66d05509..7dfee45324 100644 --- a/docs/config.md +++ b/docs/config.md @@ -203,10 +203,10 @@ due to lack of stable semantic convention. **Status**: [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md). -| ID | Instrumented library | Supported versions | Instrumentation type | Status | -|-----------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| ID | Instrumented library | Supported versions | Instrumentation type | Status | +|-----------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------| | `ILOGGER` | [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) **Not supported on .NET Framework** | ≥9.0.0 | bytecode or source \[1\] | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | -| `LOG4NET` | [log4net](https://www.nuget.org/packages/log4net) \[2\] | ≥2.0.13 && < 4.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | +| `LOG4NET` | [log4net](https://www.nuget.org/packages/log4net) \[2\] | ≥2.0.13 && < 4.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | \[1\]: For ASP.NET Core applications, the `LoggingBuilder` instrumentation can be enabled without using the .NET CLR Profiler by setting @@ -499,3 +499,11 @@ instead. |----------------------------------|-------------------------------------------------------------------------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| | `OTEL_DOTNET_AUTO_LOG_DIRECTORY` | Directory of the .NET Tracer logs. | *See the previous note on default paths* | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | | `OTEL_LOG_LEVEL` | SDK log level. (supported values: `none`,`error`,`warn`,`info`,`debug`) | `info` | [Stable](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | + +## OpAMP Client + +| Environment variable | Description | Default value | Status | +|------------------------------------------|--------------------------------------------|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| `OTEL_DOTNET_AUTO_OPAMP_ENABLED` | Enables OpAMP client. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | +| `OTEL_DOTNET_AUTO_OPAMP_SERVER_URL` | OpAMP server url. | `https://localhost:4318/v1/opamp` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | +| `OTEL_DOTNET_AUTO_OPAMP_CONNECTION_TYPE` | OpAMP connection type (http or websocket). | `http` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index ab162c9387..48971cb167 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -24,6 +24,7 @@ + @@ -46,6 +47,7 @@ + @@ -60,12 +62,18 @@ + + + + + + diff --git a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h index 937da8851f..993c3e66ab 100644 --- a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h +++ b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h @@ -18,6 +18,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() const USHORT auto_major = atoi(AUTO_MAJOR); assembly_version_redirect_map_.insert({ + { L"Google.Protobuf", {3, 31, 1, 0} }, { L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 8} }, { L"Microsoft.Extensions.Configuration", {9, 0, 0, 8} }, { L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 8} }, @@ -50,6 +51,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Instrumentation.Runtime", {1, 12, 0, 496} }, { L"OpenTelemetry.Instrumentation.SqlClient", {1, 12, 0, 667} }, { L"OpenTelemetry.Instrumentation.Wcf", {1, 12, 0, 500} }, + { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} }, { L"OpenTelemetry.Resources.Azure", {1, 12, 0, 501} }, { L"OpenTelemetry.Resources.Host", {1, 12, 0, 503} }, { L"OpenTelemetry.Resources.OperatingSystem", {1, 12, 0, 504} }, @@ -61,6 +63,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"System.Buffers", {4, 0, 5, 0} }, { L"System.Collections", {4, 0, 11, 0} }, { L"System.Collections.Concurrent", {4, 0, 11, 0} }, + { L"System.Collections.Immutable", {8, 0, 0, 0} }, { L"System.Collections.NonGeneric", {4, 0, 3, 0} }, { L"System.Collections.Specialized", {4, 0, 3, 0} }, { L"System.ComponentModel", {4, 0, 1, 0} }, diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs index 6ace88c28c..419f98f49b 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs @@ -36,6 +36,21 @@ internal partial class ConfigurationKeys /// public const string SetupSdk = "OTEL_DOTNET_AUTO_SETUP_SDK"; + /// + /// Configuration key for enabling OpAmp client. + /// + public const string OpAmpEnabled = "OTEL_DOTNET_AUTO_OPAMP_ENABLED"; + + /// + /// Configuration key for OpAmp server url. + /// + public const string OpAmpServerUrl = "OTEL_DOTNET_AUTO_OPAMP_SERVER_URL"; + + /// + /// Configuration key for OpAmp server connection type. + /// + public const string OpAmpConnectionType = "OTEL_DOTNET_AUTO_OPAMP_CONNECTION_TYPE"; + /// /// Configuration key for enabling all instrumentations. /// diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs index 3f54323096..976926a2a9 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs @@ -27,6 +27,11 @@ internal class GeneralSettings : Settings /// public bool ProfilerEnabled { get; private set; } + /// + /// Gets a value indicating whether the OpAmp client is enabled. + /// + public bool OpAmpClientEnabled { get; private set; } + protected override void OnLoadEnvVar(Configuration configuration) { var providerPlugins = configuration.GetString(ConfigurationKeys.ProviderPlugins); @@ -42,5 +47,6 @@ protected override void OnLoadEnvVar(Configuration configuration) SetupSdk = configuration.GetBool(ConfigurationKeys.SetupSdk) ?? true; ProfilerEnabled = configuration.GetString(ConfigurationKeys.ProfilingEnabled) == "1"; + OpAmpClientEnabled = configuration.GetBool(ConfigurationKeys.OpAmpEnabled) ?? false; } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs index b75bb583e1..52f6247b9f 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs @@ -13,6 +13,7 @@ using OpenTelemetry.AutoInstrumentation.Loading; using OpenTelemetry.AutoInstrumentation.Logging; using OpenTelemetry.AutoInstrumentation.Plugins; +using OpenTelemetry.AutoInstrumentation.Util; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -207,6 +208,13 @@ public static void Initialize() { OpenTracingHelper.EnableOpenTracing(_tracerProvider); } + + if (GeneralSettings.Value.OpAmpClientEnabled) + { + var resources = ResourceHelper.AggregateResources(_tracerProvider, _meterProvider, LoggerProvider); + + OpAmpHelper.EnableOpAmpClient(resources); + } } #if NET @@ -525,6 +533,8 @@ private static void OnExit(object? sender, EventArgs e) try { + OpAmpHelper.StopOpAmpClientIfRunning(); + #if NET LazyInstrumentationLoader?.Dispose(); _sampleExporter?.Dispose(); diff --git a/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs b/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs new file mode 100644 index 0000000000..04c527d80f --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs @@ -0,0 +1,160 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using OpenTelemetry.AutoInstrumentation.Configurations; +using OpenTelemetry.AutoInstrumentation.Logging; +using OpenTelemetry.OpAmp.Client; +using OpenTelemetry.OpAmp.Client.Settings; +using OpenTelemetry.Resources; + +namespace OpenTelemetry.AutoInstrumentation; + +internal static class OpAmpHelper +{ + private static readonly CancellationTokenSource _cts = new(); + private static readonly IOtelLogger Logger = OtelLogging.GetLogger(); + + private static OpAmpClient? _client; + private static Task? _clientRunningTask; + + public static bool IsRunning { get; private set; } + + public static void EnableOpAmpClient(Resource resources) + { + try + { + _client = new OpAmpClient(settings => ConfigureClient(settings, resources)); + + _clientRunningTask = Task.Run(async () => + { + try + { + await _client.StartAsync(_cts.Token); + + IsRunning = true; + } + catch (Exception ex) + { + Logger.Warning(ex, "OpAmp client stopped unexpectedly."); + + IsRunning = false; + } + }); + } + catch (Exception ex) + { + Logger.Error(ex, "An error occurred while initializing the OpAmp client."); + } + } + + public static void StopOpAmpClientIfRunning() + { + if (!IsRunning) + { + return; + } + + try + { + _client?.StopAsync().GetAwaiter().GetResult(); + + if (_clientRunningTask != null) + { + _cts.Cancel(); + _clientRunningTask.GetAwaiter().GetResult(); + } + + _client?.Dispose(); + } + catch (Exception ex) + { + Logger.Error(ex, "An error occurred while stopping the OpAmp client."); + } + finally + { + IsRunning = false; + } + } + + private static void ConfigureClient(OpAmpClientSettings settings, Resource resources) + { + // Configure connection type + var connectionType = GetConnectionType(); + if (connectionType.HasValue) + { + settings.ConnectionType = connectionType.Value; + } + + // Configure server URL + var serverUrl = GetServerUrl(); + if (serverUrl != null) + { + settings.ServerUrl = serverUrl; + } + + // Configure resource attributes for identification + foreach (var resourceAttribute in resources.Attributes) + { + if (resourceAttribute.Value == null) + { + continue; + } + + var value = resourceAttribute.Value.ToString(); + if (!string.IsNullOrWhiteSpace(value)) + { + if (resourceAttribute.Key == "service.name") + { + settings.Identification.AddIdentifyingAttribute(resourceAttribute.Key, value); + } + else + { + settings.Identification.AddNonIdentifyingAttribute(resourceAttribute.Key, value); + } + } + } + + settings.Identification.AddNonIdentifyingAttribute("opamp.version", GetOpAmpVersion()); + } + + private static Uri? GetServerUrl() + { + var url = Environment.GetEnvironmentVariable(ConfigurationKeys.OpAmpServerUrl); + + if (string.IsNullOrWhiteSpace(url)) + { + // indicates that the default value should be used + return null; + } + + return new Uri(url); + } + + private static ConnectionType? GetConnectionType() + { + var type = Environment.GetEnvironmentVariable(ConfigurationKeys.OpAmpConnectionType); + + if (string.IsNullOrWhiteSpace(type)) + { + // indicates that the default value should be used + return null; + } + + return type.ToLower() switch + { + "websocket" => ConnectionType.WebSocket, + "http" => ConnectionType.Http, + _ => throw new InvalidOperationException($"{ConfigurationKeys.OpAmpConnectionType} environment variable has an invalid value. Valid values are 'websocket' and 'http'."), + }; + } + + private static string GetOpAmpVersion() + { + var assembly = typeof(OpAmpClient).Assembly; + + return assembly + .GetCustomAttribute()? + .InformationalVersion?.Split(['+'], 2)[0] ?? "unknown"; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj index 11b865a9c1..a897ea8d76 100644 --- a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj +++ b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj @@ -36,6 +36,7 @@ + @@ -60,6 +61,7 @@ + @@ -74,12 +76,18 @@ + + + + + + diff --git a/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs new file mode 100644 index 0000000000..5238b22973 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Resources; + +namespace OpenTelemetry.AutoInstrumentation.Util; + +internal static class ResourceHelper +{ + public static Resource AggregateResources(params BaseProvider?[] providers) + { + var resource = Resource.Empty; + + foreach (var provider in providers) + { + if (provider == null) + { + continue; + } + + try + { + var providerResource = provider.GetResource(); + if (providerResource != null) + { + resource = resource.Merge(providerResource); + } + } + catch (Exception) + { + // intentionally empty + } + } + + return resource; + } +} diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt index 7c7c26e312..6f2a5e5c4f 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt @@ -4,6 +4,7 @@ /LICENSE, /instrument.sh, /linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so, + /net/Google.Protobuf.dll, /net/Microsoft.Extensions.Diagnostics.Abstractions.dll, /net/OpenTelemetry.Api.ProviderBuilderExtensions.dll, /net/OpenTelemetry.Api.dll, @@ -30,6 +31,7 @@ /net/OpenTelemetry.Instrumentation.SqlClient.dll, /net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll, /net/OpenTelemetry.Instrumentation.Wcf.dll, + /net/OpenTelemetry.OpAmp.Client.dll, /net/OpenTelemetry.Resources.Azure.dll, /net/OpenTelemetry.Resources.Container.dll, /net/OpenTelemetry.Resources.Host.dll, diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt index db81591ff2..5d9e727ad6 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt @@ -4,6 +4,7 @@ /LICENSE, /instrument.sh, /linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so, + /net/Google.Protobuf.dll, /net/Microsoft.Extensions.Diagnostics.Abstractions.dll, /net/OpenTelemetry.Api.ProviderBuilderExtensions.dll, /net/OpenTelemetry.Api.dll, @@ -30,6 +31,7 @@ /net/OpenTelemetry.Instrumentation.SqlClient.dll, /net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll, /net/OpenTelemetry.Instrumentation.Wcf.dll, + /net/OpenTelemetry.OpAmp.Client.dll, /net/OpenTelemetry.Resources.Azure.dll, /net/OpenTelemetry.Resources.Container.dll, /net/OpenTelemetry.Resources.Host.dll, diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt index 93d33bbaaa..f6e5df6aaa 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt @@ -4,6 +4,7 @@ /LICENSE, /instrument.sh, /linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so, + /net/Google.Protobuf.dll, /net/Microsoft.Extensions.Diagnostics.Abstractions.dll, /net/OpenTelemetry.Api.ProviderBuilderExtensions.dll, /net/OpenTelemetry.Api.dll, @@ -30,6 +31,7 @@ /net/OpenTelemetry.Instrumentation.SqlClient.dll, /net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll, /net/OpenTelemetry.Instrumentation.Wcf.dll, + /net/OpenTelemetry.OpAmp.Client.dll, /net/OpenTelemetry.Resources.Azure.dll, /net/OpenTelemetry.Resources.Container.dll, /net/OpenTelemetry.Resources.Host.dll, diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt index 4a6ef4451c..c1ef51a9d7 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt @@ -4,6 +4,7 @@ /LICENSE, /instrument.sh, /linux-x64/OpenTelemetry.AutoInstrumentation.Native.so, + /net/Google.Protobuf.dll, /net/Microsoft.Extensions.Diagnostics.Abstractions.dll, /net/OpenTelemetry.Api.ProviderBuilderExtensions.dll, /net/OpenTelemetry.Api.dll, @@ -30,6 +31,7 @@ /net/OpenTelemetry.Instrumentation.SqlClient.dll, /net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll, /net/OpenTelemetry.Instrumentation.Wcf.dll, + /net/OpenTelemetry.OpAmp.Client.dll, /net/OpenTelemetry.Resources.Azure.dll, /net/OpenTelemetry.Resources.Container.dll, /net/OpenTelemetry.Resources.Host.dll, diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt index f9fa7a92be..f6bd853937 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt @@ -3,6 +3,7 @@ /AdditionalDeps/shared/Microsoft.NETCore.App/9.0.0/OpenTelemetry.AutoInstrumentation.AdditionalDeps.deps.json, /LICENSE, /instrument.sh, + /net/Google.Protobuf.dll, /net/Microsoft.Extensions.Diagnostics.Abstractions.dll, /net/OpenTelemetry.Api.ProviderBuilderExtensions.dll, /net/OpenTelemetry.Api.dll, @@ -29,6 +30,7 @@ /net/OpenTelemetry.Instrumentation.SqlClient.dll, /net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll, /net/OpenTelemetry.Instrumentation.Wcf.dll, + /net/OpenTelemetry.OpAmp.Client.dll, /net/OpenTelemetry.Resources.Azure.dll, /net/OpenTelemetry.Resources.Container.dll, /net/OpenTelemetry.Resources.Host.dll, diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt index 4cec95c98b..f93d2cde18 100644 --- a/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt +++ b/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt @@ -3,6 +3,7 @@ \AdditionalDeps\shared\Microsoft.NETCore.App\9.0.0\OpenTelemetry.AutoInstrumentation.AdditionalDeps.deps.json, \LICENSE, \instrument.sh, + \net\Google.Protobuf.dll, \net\Microsoft.Extensions.Diagnostics.Abstractions.dll, \net\OpenTelemetry.Api.ProviderBuilderExtensions.dll, \net\OpenTelemetry.Api.dll, @@ -29,6 +30,7 @@ \net\OpenTelemetry.Instrumentation.SqlClient.dll, \net\OpenTelemetry.Instrumentation.StackExchangeRedis.dll, \net\OpenTelemetry.Instrumentation.Wcf.dll, + \net\OpenTelemetry.OpAmp.Client.dll, \net\OpenTelemetry.Resources.Azure.dll, \net\OpenTelemetry.Resources.Container.dll, \net\OpenTelemetry.Resources.Host.dll, @@ -43,6 +45,7 @@ \net\System.ServiceModel.Primitives.dll, \net\System.ServiceModel.dll, \net\ruleEngine.json, + \netfx\Google.Protobuf.dll, \netfx\Microsoft.Bcl.AsyncInterfaces.dll, \netfx\Microsoft.Extensions.Configuration.Abstractions.dll, \netfx\Microsoft.Extensions.Configuration.Binder.dll, @@ -75,6 +78,7 @@ \netfx\OpenTelemetry.Instrumentation.Runtime.dll, \netfx\OpenTelemetry.Instrumentation.SqlClient.dll, \netfx\OpenTelemetry.Instrumentation.Wcf.dll, + \netfx\OpenTelemetry.OpAmp.Client.dll, \netfx\OpenTelemetry.Resources.Azure.dll, \netfx\OpenTelemetry.Resources.Host.dll, \netfx\OpenTelemetry.Resources.OperatingSystem.dll, @@ -86,6 +90,7 @@ \netfx\System.AppContext.dll, \netfx\System.Buffers.dll, \netfx\System.Collections.Concurrent.dll, + \netfx\System.Collections.Immutable.dll, \netfx\System.Collections.NonGeneric.dll, \netfx\System.Collections.Specialized.dll, \netfx\System.Collections.dll, diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs new file mode 100644 index 0000000000..88250ae050 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Util; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.AutoInstrumentation.Tests.Util; + +public class ResourceHelperTests +{ + [Fact] + public void AggregateResources_AllProvidersNull_ReturnsEmptyResource() + { + var attributes1 = new Dictionary + { + { "service.name", "my-service" }, + { "service.version", "1.0.0" } + }; + + var attributes2 = new Dictionary + { + { "service.name", "my-service-2" }, + { "service.namespace", "my-namespace" } + }; + + var tracerProvider = Sdk + .CreateTracerProviderBuilder() + .ConfigureResource(resource => + { + resource.Clear(); + resource.AddAttributes(attributes1); + }) + .Build(); + + var meterProvider = Sdk + .CreateMeterProviderBuilder() + .ConfigureResource(resource => + { + resource.Clear(); + resource.AddAttributes(attributes2); + }) + .Build(); + + var resource = ResourceHelper.AggregateResources(tracerProvider, meterProvider); + + Assert.Collection( + resource.Attributes, + attribute => + { + Assert.Equal("service.name", attribute.Key); + Assert.Equal("my-service-2", attribute.Value); + }, + attribute => + { + Assert.Equal("service.namespace", attribute.Key); + Assert.Equal("my-namespace", attribute.Value); + }, + attribute => + { + Assert.Equal("service.version", attribute.Key); + Assert.Equal("1.0.0", attribute.Value); + }); + } +}