diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs index d1ca2b0b6b..e04744ecdf 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs @@ -51,6 +51,33 @@ internal partial class ConfigurationKeys /// public const string EnabledResourceDetectorTemplate = "OTEL_DOTNET_AUTO_{0}_RESOURCE_DETECTOR_ENABLED"; + /// + /// Configuration key template for resource attributes. + /// + public const string ResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES"; + + /// + /// Configuration key for setting the service name. + /// + public const string ServiceName = "OTEL_SERVICE_NAME"; + + /// + /// Configuration keys for file based configuration. + /// + public static class FileBasedConfiguration + { + /// + /// Configuration key for enabling file based configuration. + /// + public const string Enabled = "OTEL_EXPERIMENTAL_FILE_BASED_CONFIGURATION_ENABLED"; + + /// + /// Configuration key for the path to the configuration file. + /// Default is "config.yaml". + /// + public const string FileName = "OTEL_EXPERIMENTAL_CONFIG_FILE"; + } + /// /// Configuration keys for traces. /// diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs index 02bd7ee8bd..802a2a4e59 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs @@ -7,7 +7,7 @@ internal class FailFastSettings : Settings { public bool FailFast { get; private set; } - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { FailFast = configuration.GetBool(ConfigurationKeys.FailFast) ?? false; } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs index a69463520a..89426149fe 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Reflection; using Vendors.YamlDotNet.Core; using Vendors.YamlDotNet.Core.Events; using Vendors.YamlDotNet.Serialization; diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs index 9863a486b3..22c7e91aed 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs @@ -4,6 +4,4 @@ namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; [AttributeUsage(AttributeTargets.Class)] -internal sealed class EmptyObjectOnEmptyYamlAttribute : Attribute -{ -} +internal sealed class EmptyObjectOnEmptyYamlAttribute : Attribute; diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/Parser.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/Parser.cs new file mode 100644 index 0000000000..fbdaf8eb17 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/Parser.cs @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; +using Vendors.YamlDotNet.Serialization.NodeDeserializers; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; + +internal static class Parser +{ + public static YamlConfiguration ParseYaml(string filePath) + { + var deserializer = new DeserializerBuilder() + .WithNodeDeserializer(existing => new ConditionalDeserializer(existing), s => s.InsteadOf()) + .WithTypeConverter(new EnvVarTypeConverter()) + .IgnoreUnmatchedProperties() + .Build(); + + var yaml = File.ReadAllText(filePath); + var config = deserializer.Deserialize(yaml); + return config; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceAttribute.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceAttribute.cs new file mode 100644 index 0000000000..fae559405a --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceAttribute.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 ResourceAttribute +{ + /// + /// Gets or sets the name of the resource attribute. + /// + [YamlMember(Alias = "name")] + public string Name { get; set; } = null!; + + /// + /// Gets or sets the value of the resource attribute. + /// + [YamlMember(Alias = "value")] + public object Value { get; set; } = null!; + + /// + /// Gets or sets the type of the resource attribute. + /// + [YamlMember(Alias = "type")] + public string Type { get; set; } = "string"; +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceConfiguration.cs new file mode 100644 index 0000000000..886c906ced --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceConfiguration.cs @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class ResourceConfiguration +{ + /// + /// Gets or sets the list of resource attributes. + /// + [YamlMember(Alias = "attributes")] + public List? Attributes { get; set; } + + /// + /// Gets or sets the attributes list for the resource. + /// + [YamlMember(Alias = "attributes_list")] + public string? AttributesList { get; set; } + + public List> ParseAttributes() + { + var resourceAttributesWithPriority = new Dictionary(); + + if (Attributes != null) + { + foreach (var attr in Attributes) + { + if (!resourceAttributesWithPriority.ContainsKey(attr.Name)) + { + // TODO parse type and converting the value accordingly. + resourceAttributesWithPriority.Add(attr.Name, attr.Value); + } + } + } + + if (AttributesList != null) + { + const char attributeListSplitter = ','; + char[] attributeKeyValueSplitter = ['=']; + + var rawAttributes = AttributesList.Split(attributeListSplitter); + foreach (var rawKeyValuePair in rawAttributes) + { + var keyValuePair = rawKeyValuePair.Split(attributeKeyValueSplitter, 2); + if (keyValuePair.Length != 2) + { + continue; + } + + var key = keyValuePair[0].Trim(); + + if (!resourceAttributesWithPriority.ContainsKey(key)) + { + resourceAttributesWithPriority.Add(key, keyValuePair[1].Trim()); + } + } + } + + return resourceAttributesWithPriority.ToList(); + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs new file mode 100644 index 0000000000..8e0fbf259e --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/YamlConfiguration.cs @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class YamlConfiguration +{ + /// + /// Gets or sets the file format version. + /// The yaml format is documented at + /// https://github.com/open-telemetry/opentelemetry-configuration/tree/main/schema + /// + [YamlMember(Alias = "file_format")] + public string? FileFormat { get; set; } + + /// + /// Gets or sets the resource configuration. + /// Configure resource for all signals. + /// If omitted, the default resource is used. + /// + [YamlMember(Alias = "resource")] + public ResourceConfiguration? Resource { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs index 732af52102..3f54323096 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs @@ -10,11 +10,6 @@ internal class GeneralSettings : Settings /// public IList Plugins { get; } = new List(); - /// - /// Gets the list of enabled resource detectors. - /// - public IReadOnlyList EnabledResourceDetectors { get; private set; } = new List(); - /// /// Gets a value indicating whether the event should trigger /// the flushing of telemetry data. @@ -32,7 +27,7 @@ internal class GeneralSettings : Settings /// public bool ProfilerEnabled { get; private set; } - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { var providerPlugins = configuration.GetString(ConfigurationKeys.ProviderPlugins); if (providerPlugins != null) @@ -43,12 +38,6 @@ protected override void OnLoad(Configuration configuration) } } - var resourceDetectorsEnabledByDefault = configuration.GetBool(ConfigurationKeys.ResourceDetectorEnabled) ?? true; - - EnabledResourceDetectors = configuration.ParseEnabledEnumList( - enabledByDefault: resourceDetectorsEnabledByDefault, - enabledConfigurationTemplate: ConfigurationKeys.EnabledResourceDetectorTemplate); - FlushOnUnhandledException = configuration.GetBool(ConfigurationKeys.FlushOnUnhandledException) ?? false; SetupSdk = configuration.GetBool(ConfigurationKeys.SetupSdk) ?? true; diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs index 7d98e346e2..0b15291aa5 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs @@ -43,7 +43,7 @@ internal class LogSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { LogsEnabled = configuration.GetBool(ConfigurationKeys.Logs.LogsEnabled) ?? true; LogExporters = ParseLogExporter(configuration); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs index a3a54e2c6e..ed5dd28906 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs @@ -38,7 +38,7 @@ internal class MetricSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { MetricExporters = ParseMetricExporter(configuration); if (MetricExporters.Contains(MetricsExporter.Otlp)) diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs index a19e30063f..381b1aaf54 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs @@ -8,21 +8,24 @@ namespace OpenTelemetry.AutoInstrumentation.Configurations; internal static class ResourceConfigurator { - internal const string ServiceNameAttribute = "service.name"; - - public static ResourceBuilder CreateResourceBuilder(IReadOnlyList enabledResourceDetectors) + public static ResourceBuilder CreateResourceBuilder(ResourceSettings resourceSettings) { var resourceBuilder = ResourceBuilder - .CreateEmpty() // Don't use CreateDefault because it puts service name unknown by default. - .AddEnvironmentVariableDetector() - .AddTelemetrySdk() - .AddAttributes(new KeyValuePair[] - { + .CreateEmpty(); // Don't use CreateDefault because it puts service name unknown by default. + + if (resourceSettings.EnvironmentalVariablesDetectorEnabled) + { + resourceBuilder.AddEnvironmentVariableDetector(); + } + + resourceBuilder.AddTelemetrySdk() + .AddAttributes([ new(Constants.DistributionAttributes.TelemetryDistroNameAttributeName, Constants.DistributionAttributes.TelemetryDistroNameAttributeValue), new(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName, AutoInstrumentationVersion.Version) - }); + ]) + .AddAttributes(resourceSettings.Resources); - foreach (var enabledResourceDetector in enabledResourceDetectors) + foreach (var enabledResourceDetector in resourceSettings.EnabledDetectors) { resourceBuilder = enabledResourceDetector switch { @@ -39,10 +42,10 @@ public static ResourceBuilder CreateResourceBuilder(IReadOnlyList kvp.Key == ServiceNameAttribute)) + if (resource.Attributes.All(kvp => kvp.Key != Constants.ResourceAttributes.AttributeServiceName)) { // service.name was not configured yet use the fallback. - resourceBuilder.AddAttributes(new KeyValuePair[] { new(ServiceNameAttribute, ServiceNameConfigurator.GetFallbackServiceName()) }); + resourceBuilder.AddAttributes([new(Constants.ResourceAttributes.AttributeServiceName, ServiceNameConfigurator.GetFallbackServiceName())]); } var pluginManager = Instrumentation.PluginManager; diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceSettings.cs new file mode 100644 index 0000000000..1554a16757 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceSettings.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +namespace OpenTelemetry.AutoInstrumentation.Configurations; + +internal class ResourceSettings : Settings +{ + /// + /// Gets or sets the list of enabled resource detectors. + /// + public IReadOnlyList EnabledDetectors { get; set; } = []; + + /// + /// Gets or sets the list of enabled resources. + /// + public IReadOnlyList> Resources { get; set; } = []; + + /// + /// Gets or sets a value indicating whether environmental variables resource detector is enabled. + /// + public bool EnvironmentalVariablesDetectorEnabled { get; set; } = true; + + protected override void OnLoadEnvVar(Configuration configuration) + { + var resourceDetectorsEnabledByDefault = configuration.GetBool(ConfigurationKeys.ResourceDetectorEnabled) ?? true; + + EnabledDetectors = configuration.ParseEnabledEnumList( + enabledByDefault: resourceDetectorsEnabledByDefault, + enabledConfigurationTemplate: ConfigurationKeys.EnabledResourceDetectorTemplate); + } + + protected override void OnLoadFile(YamlConfiguration configuration) + { + EnvironmentalVariablesDetectorEnabled = false; + + Resources = configuration.Resource?.ParseAttributes() ?? []; + + // TODO initialize EnabledDetectors from file configuration + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs index c0c0b14725..98fdb8c10c 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Diagnostics.CodeAnalysis; using OpenTelemetry.AutoInstrumentation.Logging; namespace OpenTelemetry.AutoInstrumentation.Configurations; @@ -18,7 +17,7 @@ internal class SdkSettings : Settings /// public IList Propagators { get; private set; } = new List(); - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { Propagators = ParsePropagator(configuration); } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs index eece253424..16991d34cd 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs @@ -1,6 +1,9 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; + namespace OpenTelemetry.AutoInstrumentation.Configurations; /// @@ -8,18 +11,35 @@ namespace OpenTelemetry.AutoInstrumentation.Configurations; /// internal abstract class Settings { + private static readonly bool IsYamlConfigEnabled = Environment.GetEnvironmentVariable(ConfigurationKeys.FileBasedConfiguration.Enabled) == "true"; + private static readonly Lazy YamlConfiguration = new(ReadYamlConfiguration); + public static T FromDefaultSources(bool failFast) where T : Settings, new() { - var configuration = new Configuration(failFast, new EnvironmentConfigurationSource(failFast)); - var settings = new T(); - settings.Load(configuration); - return settings; + if (IsYamlConfigEnabled) + { + var settings = new T(); + settings.LoadFile(YamlConfiguration.Value); + return settings; + } + else + { + var configuration = new Configuration(failFast, new EnvironmentConfigurationSource(failFast)); + var settings = new T(); + settings.LoadEnvVar(configuration); + return settings; + } + } + + public void LoadEnvVar(Configuration configuration) + { + OnLoadEnvVar(configuration); } - public void Load(Configuration configuration) + public void LoadFile(YamlConfiguration configuration) { - OnLoad(configuration); + OnLoadFile(configuration); } /// @@ -27,5 +47,25 @@ public void Load(Configuration configuration) /// using the specified to initialize values. /// /// The to use when retrieving configuration values. - protected abstract void OnLoad(Configuration configuration); + protected abstract void OnLoadEnvVar(Configuration configuration); + + /// + /// Initializes a new instance of the class + /// using the specified to initialize values. + /// + /// The to use when retrieving configuration values. + protected virtual void OnLoadFile(YamlConfiguration configuration) + { + } + + private static YamlConfiguration ReadYamlConfiguration() + { + var configFile = Environment.GetEnvironmentVariable(ConfigurationKeys.FileBasedConfiguration.FileName) ?? "config.yaml"; + // TODO validate file existence + + var config = Parser.ParseYaml(configFile); + + // TODO validate file format version https://github.com/open-telemetry/opentelemetry-configuration/blob/4f185c07eaaffc18c9ad34a46085e7ad6625fca0/README.md#file-format + return config; + } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs index 69e21d9e07..78732e0da8 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs @@ -53,7 +53,7 @@ internal class TracerSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + protected override void OnLoadEnvVar(Configuration configuration) { TracesExporters = ParseTracesExporter(configuration); if (TracesExporters.Contains(TracesExporter.Otlp)) diff --git a/src/OpenTelemetry.AutoInstrumentation/Constants.cs b/src/OpenTelemetry.AutoInstrumentation/Constants.cs index bee459c4af..7471950386 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Constants.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Constants.cs @@ -24,6 +24,11 @@ public static class HttpSpanAttributes public const string AttributeHttpResponseHeaderPrefix = "http.response.header"; } + public static class ResourceAttributes + { + public const string AttributeServiceName = "service.name"; + } + public static class ConfigurationValues { public const string None = "none"; diff --git a/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs b/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs index 38e90be5b3..ff7aea146f 100644 --- a/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs +++ b/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs @@ -9,11 +9,11 @@ internal static class HeaderConfigurationExtensions { public static IReadOnlyList ParseHeaders(this Configuration source, string key, Func stringToHeaderCacheConverter) { - var headers = source.ParseList(key, ','); + var headers = source.ParseList(key, Constants.ConfigurationValues.Separator); if (headers.Count == 0) { - return Array.Empty(); + return []; } return headers.Select(stringToHeaderCacheConverter).ToArray(); diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs index 2cbea65037..5177a6d691 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs @@ -56,6 +56,8 @@ internal static LoggerProvider? LoggerProvider internal static Lazy GeneralSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); + internal static Lazy ResourceSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); + internal static Lazy TracerSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); internal static Lazy MetricSettings { get; } = new(() => Settings.FromDefaultSources(FailFastSettings.Value.FailFast)); @@ -134,7 +136,7 @@ public static void Initialize() var builder = Sdk .CreateTracerProviderBuilder() .InvokePluginsBefore(_pluginManager) - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(ResourceSettings.Value)) .UseEnvironmentVariables(LazyInstrumentationLoader, TracerSettings.Value, _pluginManager) .InvokePluginsAfter(_pluginManager); @@ -156,7 +158,7 @@ public static void Initialize() var builder = Sdk .CreateMeterProviderBuilder() .InvokePluginsBefore(_pluginManager) - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(ResourceSettings.Value)) .UseEnvironmentVariables(LazyInstrumentationLoader, MetricSettings.Value, _pluginManager) .InvokePluginsAfter(_pluginManager); @@ -355,7 +357,7 @@ private static void InitializeBufferProcessing(TimeSpan exportInterval, TimeSpan // TODO: plugins support var loggerProvider = loggerProviderBuilder! - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(ResourceSettings.Value)) .UseEnvironmentVariables(LazyInstrumentationLoader, LogSettings.Value, _pluginManager!) .Build(); Logger.Information("OpenTelemetry logger provider initialized."); diff --git a/src/OpenTelemetry.AutoInstrumentation/Logger/LoggerInitializer.cs b/src/OpenTelemetry.AutoInstrumentation/Logger/LoggerInitializer.cs index 57a5020e28..e358ff2b16 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Logger/LoggerInitializer.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Logger/LoggerInitializer.cs @@ -57,7 +57,7 @@ private static void AddOpenTelemetryLogs(ILoggingBuilder builder) builder.AddOpenTelemetry(options => { - options.SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(Instrumentation.GeneralSettings.Value.EnabledResourceDetectors)); + options.SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(Instrumentation.ResourceSettings.Value)); options.IncludeFormattedMessage = settings.IncludeFormattedMessage; diff --git a/test/IntegrationTests/Helpers/TestHelper.cs b/test/IntegrationTests/Helpers/TestHelper.cs index 3bd47eae18..e986e8ad81 100644 --- a/test/IntegrationTests/Helpers/TestHelper.cs +++ b/test/IntegrationTests/Helpers/TestHelper.cs @@ -90,6 +90,12 @@ public void EnableDefaultExporters() RemoveEnvironmentVariable("OTEL_LOGS_EXPORTER"); } + public void EnableFileBasedConfigWithDefaultPath() + { + SetEnvironmentVariable("OTEL_EXPERIMENTAL_FILE_BASED_CONFIGURATION_ENABLED", "true"); + SetEnvironmentVariable("OTEL_EXPERIMENTAL_CONFIG_FILE", Path.Combine(EnvironmentHelper.GetTestApplicationApplicationOutputDirectory(), "config.yaml")); + } + public (string StandardOutput, string ErrorOutput, int ProcessId) RunTestApplication(TestSettings? testSettings = null) { // RunTestApplication starts the test application, wait up to DefaultProcessTimeout. diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/AssemblyInfo.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/AssemblyInfo.cs new file mode 100644 index 0000000000..e47741118c --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/AssemblyInfo.cs @@ -0,0 +1,5 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedGeneralSettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedGeneralSettingsTests.cs new file mode 100644 index 0000000000..52ab2d51be --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedGeneralSettingsTests.cs @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations; +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; +using Xunit; + +namespace OpenTelemetry.AutoInstrumentation.Tests.Configurations.FileBased; + +public class FilebasedGeneralSettingsTests +{ + [Fact] + public void LoadFile_MergesBaseAndCustomResourceAttributes() + { + var resource = new ResourceConfiguration + { + AttributesList = "custom1=value1,custom2=value2", + Attributes = + [ + new() { Name = "custom1", Value = "OverridesAttributesList" }, + new() { Name = "custom3", Value = "value3" } + ] + }; + + var conf = new YamlConfiguration { Resource = resource }; + var settings = new ResourceSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.Equal("OverridesAttributesList", result["custom1"]); + Assert.Equal("value2", result["custom2"]); + Assert.Equal("value3", result["custom3"]); + } + + [Fact] + public void LoadFile_AttributesListOnly_IsParsedCorrectly() + { + var resource = new ResourceConfiguration + { + AttributesList = "key1=value1,key2=value2" + }; + + var conf = new YamlConfiguration { Resource = resource }; + var settings = new ResourceSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void LoadFile_AttributesOnly_IsParsedCorrectly() + { + var resource = new ResourceConfiguration + { + Attributes = + [ + new() { Name = "a", Value = "1" }, + new() { Name = "b", Value = "2" } + ] + }; + + var conf = new YamlConfiguration { Resource = resource }; + var settings = new ResourceSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.Equal("1", result["a"]); + Assert.Equal("2", result["b"]); + } + + [Fact] + public void LoadFile_AttributesListOverwrittenByAttributes() + { + var resource = new ResourceConfiguration + { + AttributesList = "key1=fromList", + Attributes = + [ + new() { Name = "key1", Value = "fromAttributes" } + ] + }; + + var conf = new YamlConfiguration { Resource = resource }; + var settings = new ResourceSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.Equal("fromAttributes", result["key1"]); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFile.yaml b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFile.yaml new file mode 100644 index 0000000000..a6722a1859 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFile.yaml @@ -0,0 +1,7 @@ +file_format: "1.0-rc.1" + +resource: + attributes: + - name: service.name + value: unknown_service + attributes_list: diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFileEnvVars.yaml b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFileEnvVars.yaml new file mode 100644 index 0000000000..53b1031487 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFileEnvVars.yaml @@ -0,0 +1,7 @@ +file_format: "1.0-rc.1" + +resource: + attributes: + - name: service.name + value: ${OTEL_SERVICE_NAME} + attributes_list: ${OTEL_RESOURCE_ATTRIBUTES} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/ParserTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/ParserTests.cs new file mode 100644 index 0000000000..ddec15a763 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/ParserTests.cs @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Xunit; + +namespace OpenTelemetry.AutoInstrumentation.Tests.Configurations.FileBased; + +[Collection("Non-Parallel Collection")] +public class ParserTests +{ + [Fact] + public void Parse_FullConfigYaml_ShouldPopulateModelCorrectly() + { + var config = Parser.ParseYaml("Configurations/FileBased/Files/TestFile.yaml"); + + Assert.NotNull(config); + + Assert.Equal("1.0-rc.1", config.FileFormat); + + Assert.NotNull(config.Resource); + Assert.NotNull(config.Resource.Attributes); + Assert.Single(config.Resource.Attributes); + + var serviceAttr = config.Resource.Attributes.First(); + Assert.Equal("service.name", serviceAttr.Name); + Assert.Equal("unknown_service", serviceAttr.Value); + + Assert.NotNull(config.Resource.AttributesList); + Assert.Empty(config.Resource.AttributesList); + } + + [Fact] + public void Parse_EnvVarYaml_ShouldPopulateModelCompletely() + { + Environment.SetEnvironmentVariable("OTEL_SDK_DISABLED", "true"); + Environment.SetEnvironmentVariable("OTEL_SERVICE_NAME", "my‑service"); + Environment.SetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES", "key=value"); + + var config = Parser.ParseYaml("Configurations/FileBased/Files/TestFileEnvVars.yaml"); + + Assert.Equal("1.0-rc.1", config.FileFormat); + var serviceAttr = config.Resource?.Attributes?.First(a => a.Name == "service.name"); + Assert.NotNull(serviceAttr); + Assert.Equal("my‑service", serviceAttr.Value); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/PluginManagerTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/PluginManagerTests.cs index bac9d714d7..2c1317b8c7 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/PluginManagerTests.cs +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/PluginManagerTests.cs @@ -158,7 +158,7 @@ private static GeneralSettings GetSettings(string assemblyQualifiedName) })); var settings = new GeneralSettings(); - settings.Load(config); + settings.LoadEnvVar(config); return settings; } diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/ServiceNameConfiguratorTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/ServiceNameConfiguratorTests.cs index 58497f2728..510ff3713f 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/ServiceNameConfiguratorTests.cs +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/ServiceNameConfiguratorTests.cs @@ -6,6 +6,7 @@ namespace OpenTelemetry.AutoInstrumentation.Tests.Configurations; +[Collection("Non-Parallel Collection")] public class ServiceNameConfiguratorTests { private const string ServiceName = "service.name"; @@ -14,7 +15,7 @@ public class ServiceNameConfiguratorTests [Fact] public void GetFallbackServiceName() { - var resourceBuilder = ResourceConfigurator.CreateResourceBuilder(new List()); + var resourceBuilder = ResourceConfigurator.CreateResourceBuilder(new ResourceSettings()); var resource = resourceBuilder.Build(); var serviceName = resource.Attributes.FirstOrDefault(a => a.Key == ServiceName).Value as string; @@ -29,7 +30,7 @@ public void ServiceName_Retained_EnvVarSet() { Environment.SetEnvironmentVariable(OtelServiceVariable, setServiceName); - var resourceBuilder = ResourceConfigurator.CreateResourceBuilder(Array.Empty()); + var resourceBuilder = ResourceConfigurator.CreateResourceBuilder(new ResourceSettings()); var resource = resourceBuilder.Build(); var serviceName = resource.Attributes.FirstOrDefault(a => a.Key == ServiceName).Value as string; diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs index 5a6d530e09..d7b6845017 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/SettingsTests.cs @@ -31,10 +31,19 @@ internal void GeneralSettings_DefaultValues() var settings = Settings.FromDefaultSources(false); Assert.Empty(settings.Plugins); - Assert.NotEmpty(settings.EnabledResourceDetectors); Assert.False(settings.FlushOnUnhandledException); } + [Fact] + internal void ResourceSettings_DefaultValues() + { + var settings = Settings.FromDefaultSources(false); + + Assert.NotEmpty(settings.EnabledDetectors); + Assert.True(settings.EnvironmentalVariablesDetectorEnabled); + Assert.Empty(settings.Resources); + } + [Fact] internal void TracerSettings_DefaultValues() { @@ -401,14 +410,14 @@ internal void FlushOnUnhandledException_DependsOnCorrespondingEnvVariable(string [InlineData("PROCESS", ResourceDetector.Process)] [InlineData("OPERATINGSYSTEM", ResourceDetector.OperatingSystem)] [InlineData("HOST", ResourceDetector.Host)] - internal void GeneralSettings_Instrumentations_SupportedValues(string resourceDetector, ResourceDetector expectedResourceDetector) + internal void ResourceSettings_ResourceDetectors_SupportedValues(string resourceDetector, ResourceDetector expectedResourceDetector) { Environment.SetEnvironmentVariable(ConfigurationKeys.ResourceDetectorEnabled, "false"); Environment.SetEnvironmentVariable(string.Format(ConfigurationKeys.EnabledResourceDetectorTemplate, resourceDetector), "true"); - var settings = Settings.FromDefaultSources(false); + var settings = Settings.FromDefaultSources(false); - Assert.Equal([expectedResourceDetector], settings.EnabledResourceDetectors); + Assert.Equal([expectedResourceDetector], settings.EnabledDetectors); } private static void ClearEnvVars() diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/OpenTelemetry.AutoInstrumentation.Tests.csproj b/test/OpenTelemetry.AutoInstrumentation.Tests/OpenTelemetry.AutoInstrumentation.Tests.csproj index 3323675c6f..0aa0bc3fb7 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/OpenTelemetry.AutoInstrumentation.Tests.csproj +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/OpenTelemetry.AutoInstrumentation.Tests.csproj @@ -11,4 +11,13 @@ + + + PreserveNewest + + + PreserveNewest + + +