diff --git a/.cspell/other.txt b/.cspell/other.txt index 7115c6cc14..777e68cf67 100644 --- a/.cspell/other.txt +++ b/.cspell/other.txt @@ -44,6 +44,7 @@ NETRUNTIME Npgsql NSERVICEBUS omnisharp +OPAMP OPENTRACING OPERATINGSYSTEM ORACLEMDA diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e0f81e94..c8bbf0ac41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h available for [SqlClient instrumentation](https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/issues/4343). This is disabled by default and can be enabled via the `OTEL_DOTNET_AUTO_SQLCLIENT_NETFX_ILREWRITE_ENABLED` environment variable. +- Experimental support for OpAMP (by default the client is disabled). ### Changed diff --git a/docs/config.md b/docs/config.md index be42d4a8af..d91d8f6b9d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -508,3 +508,11 @@ instead. | `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) | | `OTEL_DOTNET_AUTO_LOGGER` | AutoInstrumentation diagnostic logs sink. (supported values: `none`,`file`,`console`) | `file` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) | | `OTEL_DOTNET_AUTO_LOG_FILE_SIZE` | Maximum size (in bytes) of a single log file created by the Auto Instrumentation | 10 485 760 (10 MB) | [Experimental](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) | \ No newline at end of file diff --git a/docs/file-based-configuration.md b/docs/file-based-configuration.md index 773db8f9e3..1687a42bcf 100644 --- a/docs/file-based-configuration.md +++ b/docs/file-based-configuration.md @@ -260,6 +260,23 @@ instrumentation/development: bridge_enabled: true ``` +### OpAMP + +``` yaml +opamp: + # Configure if OpAMP client is enabled. + # If omitted or null, the client is disabled. + enabled: false + # Configure the server endpoint. If not explicitly set, a default + # URL is returned based on the connection_type. + # connection_type: + # • connection_type: http -> https://localhost:4318/v1/opamp + # • connection_type: websocket -> wss://localhost:4318/v1/opamp + server_url: https://localhost:4318/v1/opamp + # Configure the type of connection used to communicate with the server (for example, http or websocket). + connection_type: http +``` + ### Configuration based instrumentation Documentation for configuration based instrumentation can be found in [nocode-instrumentation.md](nocode-instrumentation.md). diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 9db56f4647..aac80fe3b4 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -26,6 +26,7 @@ + diff --git a/src/OpenTelemetry.AutoInstrumentation.Assemblies.NetFramework/Directory.Packages.props b/src/OpenTelemetry.AutoInstrumentation.Assemblies.NetFramework/Directory.Packages.props index 63bf8b92dc..2adb9327ab 100644 --- a/src/OpenTelemetry.AutoInstrumentation.Assemblies.NetFramework/Directory.Packages.props +++ b/src/OpenTelemetry.AutoInstrumentation.Assemblies.NetFramework/Directory.Packages.props @@ -32,11 +32,49 @@ - + + + + + + + + + - + + + + + + + + + + + - + + + + + + + + + + + - + + + + + + + + + + + \ No newline at end of file diff --git a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h index f65418f83d..437e2637bc 100644 --- a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h +++ b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h @@ -19,6 +19,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() assembly_version_redirect_map_.insert({ { 462, { + { L"Google.Protobuf", {3, 31, 1, 0} }, { L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 10} }, @@ -51,6 +52,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Instrumentation.Runtime", {1, 13, 0, 708} }, { L"OpenTelemetry.Instrumentation.SqlClient", {1, 13, 0, 709} }, { L"OpenTelemetry.Instrumentation.Wcf", {1, 13, 0, 698} }, + { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} }, { L"OpenTelemetry.Resources.Azure", {1, 13, 0, 711} }, { L"OpenTelemetry.Resources.Host", {1, 13, 0, 683} }, { L"OpenTelemetry.Resources.OperatingSystem", {1, 13, 0, 713} }, @@ -62,6 +64,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} }, @@ -163,6 +166,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"System.Xml.XPath.XDocument", {4, 1, 0, 0} }, }}, { 470, { + { L"Google.Protobuf", {3, 31, 1, 0} }, { L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 10} }, @@ -195,6 +199,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Instrumentation.Runtime", {1, 13, 0, 708} }, { L"OpenTelemetry.Instrumentation.SqlClient", {1, 13, 0, 709} }, { L"OpenTelemetry.Instrumentation.Wcf", {1, 13, 0, 698} }, + { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} }, { L"OpenTelemetry.Resources.Azure", {1, 13, 0, 711} }, { L"OpenTelemetry.Resources.Host", {1, 13, 0, 683} }, { L"OpenTelemetry.Resources.OperatingSystem", {1, 13, 0, 713} }, @@ -206,6 +211,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} }, @@ -307,6 +313,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"System.Xml.XPath.XDocument", {4, 1, 0, 0} }, }}, { 471, { + { L"Google.Protobuf", {3, 31, 1, 0} }, { L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 10} }, @@ -338,6 +345,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Instrumentation.Runtime", {1, 13, 0, 708} }, { L"OpenTelemetry.Instrumentation.SqlClient", {1, 13, 0, 709} }, { L"OpenTelemetry.Instrumentation.Wcf", {1, 13, 0, 698} }, + { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} }, { L"OpenTelemetry.Resources.Azure", {1, 13, 0, 711} }, { L"OpenTelemetry.Resources.Host", {1, 13, 0, 683} }, { L"OpenTelemetry.Resources.OperatingSystem", {1, 13, 0, 713} }, @@ -346,6 +354,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Shims.OpenTracing", {1, 0, 0, 0} }, { L"OpenTracing", {0, 12, 1, 0} }, { L"System.Buffers", {4, 0, 5, 0} }, + { L"System.Collections.Immutable", {8, 0, 0, 0} }, { L"System.Data.Common", {4, 2, 0, 0} }, { L"System.Diagnostics.DiagnosticSource", {9, 0, 0, 10} }, { L"System.Diagnostics.StackTrace", {4, 1, 0, 0} }, @@ -368,6 +377,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"System.Xml.XPath.XDocument", {4, 1, 0, 0} }, }}, { 472, { + { L"Google.Protobuf", {3, 31, 1, 0} }, { L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration", {9, 0, 0, 10} }, { L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 10} }, @@ -399,6 +409,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Instrumentation.Runtime", {1, 13, 0, 708} }, { L"OpenTelemetry.Instrumentation.SqlClient", {1, 13, 0, 709} }, { L"OpenTelemetry.Instrumentation.Wcf", {1, 13, 0, 698} }, + { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} }, { L"OpenTelemetry.Resources.Azure", {1, 13, 0, 711} }, { L"OpenTelemetry.Resources.Host", {1, 13, 0, 683} }, { L"OpenTelemetry.Resources.OperatingSystem", {1, 13, 0, 713} }, @@ -407,6 +418,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap() { L"OpenTelemetry.Shims.OpenTracing", {1, 0, 0, 0} }, { L"OpenTracing", {0, 12, 1, 0} }, { L"System.Buffers", {4, 0, 5, 0} }, + { L"System.Collections.Immutable", {8, 0, 0, 0} }, { L"System.Diagnostics.DiagnosticSource", {9, 0, 0, 10} }, { L"System.IO.Pipelines", {9, 0, 0, 10} }, { L"System.Memory", {4, 0, 5, 0} }, diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs index c6420d49a9..39bb0eafa8 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/FileBasedConfiguration/OpAmpConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OpAmpConfiguration.cs new file mode 100644 index 0000000000..bb2bd305cb --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OpAmpConfiguration.cs @@ -0,0 +1,27 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class OpAmpConfiguration +{ + /// + /// Gets or sets a value indicating whether the OpAmp client is enabled. + /// + [YamlMember(Alias = "enabled")] + public bool? Enabled { get; set; } + + /// + /// Gets or sets the URL of the server to which the application connects. + /// + [YamlMember(Alias = "server_url")] + public string? ServerUrl { get; set; } + + /// + /// Gets or sets the type of connection used for communication. + /// + [YamlMember(Alias = "connection_type")] + public string? ConnectionType { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs index 269a9d6a1c..a92aa9bfcc 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs @@ -74,4 +74,10 @@ internal class YamlConfiguration /// [YamlMember(Alias = "no_code/development")] public NoCodeConfiguration? NoCode { get; set; } + + /// + /// Gets or sets the OpAMP settings. + /// + [YamlMember(Alias = "opamp")] + public OpAmpConfiguration? OpAmp { get; set; } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/OpAmpSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/OpAmpSettings.cs new file mode 100644 index 0000000000..b2a8d287cb --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/OpAmpSettings.cs @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; +using OpenTelemetry.AutoInstrumentation.Logging; + +namespace OpenTelemetry.AutoInstrumentation.Configurations; + +internal class OpAmpSettings : Settings +{ + private static readonly IOtelLogger Logger = OtelLogging.GetLogger(); + + /// + /// Gets a value indicating whether the OpAmp client is enabled. + /// + public bool OpAmpClientEnabled { get; private set; } + + /// + /// Gets the URL of the server to which the application connects. + /// + public Uri? ServerUrl { get; private set; } + + /// + /// Gets the type of connection used for communication. + /// + public string? ConnectionType { get; private set; } + + protected override void OnLoadEnvVar(Configuration configuration) + { + OpAmpClientEnabled = configuration.GetBool(ConfigurationKeys.OpAmpEnabled) ?? false; + ServerUrl = GetServerUrl(configuration.GetString(ConfigurationKeys.OpAmpServerUrl), configuration.FailFast); + ConnectionType = GetConnectionType(configuration.GetString(ConfigurationKeys.OpAmpConnectionType), configuration.FailFast); + } + + protected override void OnLoadFile(YamlConfiguration configuration) + { + OpAmpClientEnabled = configuration.OpAmp?.Enabled ?? false; + ServerUrl = GetServerUrl(configuration.OpAmp?.ServerUrl, configuration.FailFast); + ConnectionType = GetConnectionType(configuration.OpAmp?.ConnectionType, configuration.FailFast); + } + + private static Uri? GetServerUrl(string? configurationValue, bool failFast) + { + if (string.IsNullOrWhiteSpace(configurationValue)) + { + // indicates that the default value should be used + return null; + } + + try + { + return new Uri(configurationValue); + } + catch (Exception ex) + { + var errorMessage = $"OpAMP server URL configuration has an invalid value: '{configurationValue}'."; + Logger.Error(ex, errorMessage); + + if (failFast) + { + throw new InvalidOperationException(errorMessage, ex); + } + + return null; + } + } + + private static string? GetConnectionType(string? configurationValue, bool failFast) + { + if (string.IsNullOrWhiteSpace(configurationValue)) + { + // indicates that the default value should be used + return null; + } + + var isValid = configurationValue!.ToLower() is "http" or "websocket"; + if (isValid) + { + return configurationValue!.ToLower(); + } + + var unsupportedMessage = $"OpAMP connection type configuration has an invalid value: '{configurationValue}'."; + Logger.Error(unsupportedMessage); + + if (failFast) + { + throw new NotSupportedException(unsupportedMessage); + } + + return null; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs index d781ec406a..b79e5973f4 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs @@ -14,6 +14,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; @@ -69,6 +70,8 @@ internal static LoggerProvider? LoggerProvider internal static Lazy NoCodeSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); + internal static Lazy OpAmpSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); + /// /// Initialize the OpenTelemetry SDK with a pre-defined set of exporters, shims, and /// instrumentations. @@ -215,6 +218,13 @@ public static void Initialize() { OpenTracingHelper.EnableOpenTracing(_tracerProvider); } + + if (OpAmpSettings.Value.OpAmpClientEnabled) + { + var resources = ResourceHelper.AggregateResources(_tracerProvider, _meterProvider, LoggerProvider); + + OpAmpHelper.EnableOpAmpClient(resources, OpAmpSettings.Value); + } } #if NET @@ -533,6 +543,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..b366971fb1 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs @@ -0,0 +1,140 @@ +// 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, OpAmpSettings opAmpSettins) + { + try + { + _client = new OpAmpClient(settings => ConfigureClient(settings, opAmpSettins, 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, OpAmpSettings opAmpSettings, Resource resources) + { + // Configure connection type. + // Late parse ensures that OpenTelemetry.OpAmp.Client.dll is loaded only when OpAmp is enabled. + var connectionType = ParseConnectionType(opAmpSettings.ConnectionType); + if (connectionType.HasValue) + { + settings.ConnectionType = connectionType.Value; + } + + // Configure server URL. + var serverUrl = opAmpSettings.ServerUrl; + 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 ConnectionType? ParseConnectionType(string? connectionType) + { + return connectionType switch + { + "http" => ConnectionType.Http, + "websocket" => ConnectionType.WebSocket, + _ => null, + }; + } + + 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 79ad3521ce..e31ad87fc6 100644 --- a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj +++ b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj @@ -37,6 +37,7 @@ + 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 822c59b11c..b3d67288d6 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 b44dd7222f..950af321d5 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, @@ -74,6 +77,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, @@ -83,6 +87,7 @@ \netfx\OpenTelemetry.dll, \netfx\OpenTracing.dll, \netfx\System.Buffers.dll, + \netfx\System.Collections.Immutable.dll, \netfx\System.Diagnostics.DiagnosticSource.dll, \netfx\System.IO.Pipelines.dll, \netfx\System.Memory.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); + }); + } +}