diff --git a/CHANGELOG.md b/CHANGELOG.md index 22da1541ea..8c3bc7437f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h - Extend support for [RabbitMQ.Client](https://www.nuget.org/packages/RabbitMQ.Client/) traces instrumentation for versions `5.*`. +- Support for [file based configuration](./docs/config.md#configuration-examples) ### Changed diff --git a/docs/config.md b/docs/config.md index db21585e43..056479868a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -45,6 +45,34 @@ with environment variables taking precedence over `App.config` or `Web.config` f `SiteName\VirtualPath` ex: `MySite\MyApp` - If that is not the case it will use the name of the application [entry Assembly](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getentryassembly?view=net-7.0). +4. File-based Configuration (Experimental) + + > **Status:** [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) + > For more information about the OpenTelemetry configuration specification, see: + > **[File-based configuration documentation](https://opentelemetry.io/docs/specs/otel/configuration/sdk/)** + + You can configure OpenTelemetry using a YAML file. This method is disabled + by default and must be explicitly enabled. + + To enable file-based configuration, set the following environment variable: + + ```bash + OTEL_EXPERIMENTAL_FILE_BASED_CONFIGURATION_ENABLED=true + ``` + + By default, the value is false. + + You can also specify the configuration file path (default: config.yaml): + + ```bash + OTEL_EXPERIMENTAL_CONFIG_FILE=/path/to/config.yaml + ``` + + In your config file you can use environment variables in format: ${ENVIRONMENT_VARIABLE} + instead of "value" + + See [configuration examples](#configuration-examples) + By default we recommend using environment variables for configuration. However, if given setting supports it, then: @@ -503,3 +531,338 @@ 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) | + +## Configuration Examples + +### Global Configuration + +``` yaml +# The file format version. +# The yaml format is documented at +# +file_format: "0.4" +# Configure if the SDK is disabled or not. +# If omitted or null, false is used +disabled: false +# Configure the log level of the internal logger used by the SDK. +# If omitted, info is used. +log_level: info +# Configure if the Fail Fast is enabled or not. +# If omitted or null, false is used +fail_fast: false +# Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. +attribute_limits: + # Configure max attribute value size. + # Value must be non-negative. + # If omitted or null, there is no limit. + attribute_value_length_limit: 4096 + # Configure max attribute count. + # Value must be non-negative. + # If omitted or null, 128 is used. + attribute_count_limit: 128 +``` + +### Propagator Configuration + +You can configure text map context propagators directly in YAML or via the +`OTEL_PROPAGATORS` environment variable. +For more details and updates, see: [Propagators list and documentation](https://opentelemetry.io/docs/zero-code/dotnet/configuration/#propagators) + +``` yaml +propagator: + # Composite propagators are evaluated together. + # Entries from .composite_list are appended here (duplicates are filtered out). + composite: + tracecontext: # W3C Trace Context propagator + baggage: # W3C Baggage propagator + b3: # B3 single-header propagator + b3multi: # Zipkin B3 multi-header propagator + # Alternatively, configure via a comma-separated list (same format as OTEL_PROPAGATORS). + composite_list: ${OTEL_PROPAGATORS} +``` + +### Resource Configuration + +You can configure text map context propagators directly in YAML or via the +`OTEL_RESOURCE_ATTRIBUTES` environment variable. + +``` yaml +resource: +# Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. +# Entries must contain .name and .value, and may optionally include .type. If an entry's .type omitted or null, string is used. +# The .value's type must match the .type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. + attributes: + - name: service.name + value: unknown_service + type: string + # Alternatively, configure via a comma-separated list (same format as OTEL_RESOURCE_ATTRIBUTES). + attributes_list: ${OTEL_RESOURCE_ATTRIBUTES} +``` + +### Resource Detectors Configuration + +For more details and updates, see: [Resource Detectors list and documentation](https://opentelemetry.io/docs/zero-code/dotnet/configuration/#resource-detectors) + +``` yaml +resource: + detection/development: + detectors: + azureappservice: # Detects Azure App Service resource information + container: # Detects container resource info (container.* attributes) [Core only] + host: # Detects host resource info (host.* attributes) + operatingsystem: # Detects OS-level attributes (os.*) + process: # Detects process-level attributes (process.*) + processruntime: # Detects process runtime attributes (process.runtime.*) + service: # Detects service.name and service.instance.id +``` + +### Tracer Provider Configuration + +``` yaml +tracer_provider: + processors: + # Configure a batch span processor. + batch: + # Configure delay interval (in milliseconds) between two consecutive exports. + # Value must be non-negative. + # If omitted or null, 5000 is used. + schedule_delay: 5000 + # Configure maximum allowed time (in milliseconds) to export data. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 30000 is used. + export_timeout: 30000 + # Configure maximum queue size. Value must be positive. + # If omitted or null, 2048 is used. + max_queue_size: 2048 + # Configure maximum batch size. Value must be positive. + # If omitted or null, 512 is used. + max_export_batch_size: 512 + # Configure exporters. + exporter: + # Configure the OTLP with HTTP transport exporter to enable it. + otlp_http: + # Configure endpoint, including the trace specific path. + # If omitted or null, http://localhost:4318/v1/traces is used + endpoint: http://localhost:4318/v1/traces + # Configure max time (in milliseconds) to wait for each export. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 10000 is used. + timeout: 10000 + # Configure headers. Entries have higher priority than entries from .headers_list. + # If an entry's .value is null, the entry is ignored. + headers: + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + # If omitted or null, no headers are added. + headers_list: ${OTEL_EXPORTER_OTLP_TRACES_HEADERS} + # Configure the OTLP with gRPC transport exporter to enable it. + otlp_grpc: + # Configuration otlp_grpc is the same as otlp_http. + # if otlp_http is used it will override otlp_grpc. + # On .NET Framework, the grpc OTLP exporter protocol is not supported. + # Configure the zipkin exporter to enable it. + zipkin: + # Configure endpoint. + # If omitted or null, http://localhost:9411/api/v2/spans is used. + endpoint: http://localhost:9411/api/v2/spans + # Configure max time (in milliseconds) to wait for each export. + # Value must be non-negative. A value of 0 indicates indefinite. + # If omitted or null, 10000 is used. + timeout: 10000 + # Add the console exporter to enable it. + console: +``` + +### Logger Provider Configuration + +``` yaml +logger_provider: + processors: + # Configure a batch span processor. + batch: + # Configure delay interval (in milliseconds) between two consecutive exports. + # Value must be non-negative. + # If omitted or null, 5000 is used. + schedule_delay: 5000 + # Configure maximum allowed time (in milliseconds) to export data. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 30000 is used. + export_timeout: 30000 + # Configure maximum queue size. Value must be positive. + # If omitted or null, 2048 is used. + max_queue_size: 2048 + # Configure maximum batch size. Value must be positive. + # If omitted or null, 512 is used. + max_export_batch_size: 512 + # Configure exporters. + exporter: + # Configure the OTLP with HTTP transport exporter to enable it. + otlp_http: + # Configure endpoint, including the trace specific path. + # If omitted or null, http://localhost:4318/v1/logs is used + endpoint: http://localhost:4318/v1/logs + # Configure max time (in milliseconds) to wait for each export. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 10000 is used. + timeout: 10000 + # Configure headers. Entries have higher priority than entries from .headers_list. + # If an entry's .value is null, the entry is ignored. + headers: + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + # If omitted or null, no headers are added. + headers_list: ${OTEL_EXPORTER_OTLP_LOGS_HEADERS} + # Configure the OTLP with gRPC transport exporter to enable it. + otlp_grpc: + # Configuration otlp_grpc is the same as otlp_http. + # if otlp_http is used it will override otlp_grpc. + # On .NET Framework, the grpc OTLP exporter protocol is not supported. + # Add the console exporter to enable it. + console: +``` + +### Meter Provider Configuration + +``` yaml +meter_provider: + readers: + # Configure a periodic metric reader. + periodic: + # Configure delay interval (in milliseconds) between start of two consecutive exports. + # Value must be non-negative. + # If omitted or null, 60000 is used. + interval: 60000 + # Configure maximum allowed time (in milliseconds) to export data. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 30000 is used. + timeout: 30000 + # Configure exporters. + exporter: + # Configure the OTLP with HTTP transport exporter to enable it. + otlp_http: + # Configure endpoint, including the trace specific path. + # If omitted or null, http://localhost:4318/v1/metrics is used + endpoint: http://localhost:4318/v1/metrics + # Configure max time (in milliseconds) to wait for each export. + # Value must be non-negative. A value of 0 indicates no limit (infinity). + # If omitted or null, 10000 is used. + timeout: 10000 + # Configure headers. Entries have higher priority than entries from .headers_list. + # If an entry's .value is null, the entry is ignored. + headers: + - name: api-key + value: "1234" + # Configure headers. Entries have lower priority than entries from .headers. + # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. + # If omitted or null, no headers are added. + headers_list: ${OTEL_EXPORTER_OTLP_METRICS_HEADERS} + # Configure the OTLP with gRPC transport exporter to enable it. + otlp_grpc: + # Configuration otlp_grpc is the same as otlp_http. + # if otlp_http is used it will override otlp_grpc. + # On .NET Framework, the grpc OTLP exporter protocol is not supported. + # Add the console exporter to enable it. + console: + # Configure a pull based metric reader. + pull: + exporter: + # Add the prometheus exporter to enable it. + # Do NOT use in production. + # Prometheus exporter is intended for the inner dev loop. Production environments can use a combination of OTLP exporter with OpenTelemetry Collector having otlp receiver and prometheus exporter. + prometheus: +``` + +### Instrumentation Configuration + +You can configure traces, metrics, and logs instrumentations. +For more details and updates, see: [Instrumentation list and documentation](https://opentelemetry.io/docs/zero-code/dotnet/instrumentations/) + +``` yaml +instrumentation/development: + dotnet: + traces: + aspnet: # ASP.NET (.NET Framework) MVC/WebApi [Framework only] + aspnetcore: # ASP.NET Core [Core only] + azure: # Azure SDK [Core & Framework] + elasticsearch: # Elastic.Clients.Elasticsearch [Core & Framework] + elastictransport: # Elastic.Transport (>=0.4.16) [Core & Framework] + entityframeworkcore: # Entity Framework Core (>=6.0.12) [Core only] + graphql: # GraphQL (>=7.5.0) [Core only] + grpcnetclient: # Grpc.Net.Client (>=2.52.0 & <3.0.0) [Core only] + httpclient: # System.Net.Http.HttpClient [Core & Framework] + kafka: # Confluent.Kafka (>=1.4.0 & <3.0.0) [Core & Framework] + masstransit: # MassTransit (>=8.0.0) [Core only] + mongodb: # MongoDB.Driver (>=2.7.0 <4.0.0) [Core & Framework] + mysqlconnector: # MySqlConnector (>=2.0.0) [Core only] + mysqldata: # MySql.Data (>=8.1.0) [Core only] + npgsql: # Npgsql (>=6.0.0) [Core only] + nservicebus: # NServiceBus (>=8.0.0 & <10.0.0) [Core & Framework] + oraclemda: # Oracle.ManagedDataAccess (>=23.4.0) [Core only] + rabbitmq: # RabbitMQ.Client (>=6.0.0) [Core & Framework] + quartz: # Quartz (>=3.4.0, not supported < .NET Framework 4.7.2) + sqlclient: # Microsoft.Data.SqlClient & System.Data.SqlClient [Core & Framework] + stackexchangeredis: # StackExchange.Redis (>=2.6.122 & <3.0.0) [Core only] + wcfclient: # WCF Client [Core & Framework] + wcfservice: # WCF Service [Framework only] + metrics: + aspnetcore: # ASP.NET Core metrics [Core only] + httpclient: # HttpClient metrics [Core & Framework] + netruntime: # .NET Runtime metrics [Core only] + nservicebus: # NServiceBus metrics [Core & Framework] + process: # Process metrics [Core & Framework] + sqlclient: # SQL Client metrics [Core & Framework] + logs: + ilogger: # Microsoft.Extensions.Logging (>=9.0.0) [Core & Framework] + log4net: # log4net (>=2.0.13 && <4.0.0) [Core & Framework] +``` + +### Instrumentation options + +You can configure directly in YAML or via environment variables. + +``` yaml +instrumentation/development: + dotnet: + traces: + entityframeworkcore: + # Whether the Entity Framework Core instrumentation can pass SQL statements through the db.statement attribute. Queries might contain sensitive information. If set to false, db.statement is recorded only for executing stored procedures. + # Default is false + set_db_statement_for_text: false + graphql: + # Whether the GraphQL instrumentation can pass raw queries through the graphql.document attribute. Queries might contain sensitive information. + # Default is false + set_document: false + oraclemda: + # Whether the Oracle Client instrumentation can pass SQL statements through the db.statement attribute. Queries might contain sensitive information. If set to false, db.statement is recorded only for executing stored procedures. + # Default is false + set_db_statement_for_text: false + sqlclient: + # Whether the SQL Client instrumentation can pass SQL statements through the db.statement attribute. Queries might contain sensitive information. If set to false, db.statement is recorded only for executing stored procedures. + # Not supported on .NET Framework for System.Data.SqlClient. + # Default is false + set_db_statement_for_text: false + aspnet: + # A comma-separated list of HTTP header names. ASP.NET instrumentations will capture HTTP request header values for all configured header names. + capture_request_headers: "X-Key=Value" + # A comma-separated list of HTTP header names. ASP.NET instrumentations will capture HTTP response header values for all configured header names. + capture_response_headers: "X-Key=Value" + aspnetcore: + # A comma-separated list of HTTP header names. ASP.NET Core instrumentations will capture HTTP request header values for all configured header names. + capture_request_headers: "X-Key=Value" + # A comma-separated list of HTTP header names. ASP.NET Core instrumentations will capture HTTP response header values for all configured header names. + capture_response_headers: "X-Key=Value" + httpclient: + # A comma-separated list of HTTP header names. HTTP Client instrumentations will capture HTTP request header values for all configured header names. + capture_request_headers: "X-Key=Value" + # A comma-separated list of HTTP header names. HTTP Client instrumentations will capture HTTP response header values for all configured header names. + capture_response_headers: "X-Key=Value" + grpcnetclient: + # A comma-separated list of gRPC metadata names. Grpc.Net.Client instrumentations will capture gRPC request metadata values for all configured metadata names. + capture_request_metadata: "X-Key=Value" + # A comma-separated list of gRPC metadata names. Grpc.Net.Client instrumentations will capture gRPC response metadata values for all configured metadata names. + capture_response_metadata: "X-Key=Value" +``` diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs index d1ca2b0b6b..ee13632e22 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs @@ -51,6 +51,16 @@ 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 traces. /// @@ -92,6 +102,38 @@ public static class Traces /// public const string AdditionalLegacySources = "OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_LEGACY_SOURCES"; + /// + /// Configuration key for ZipkinEndpoint. + /// + public const string ZipkinEndpoint = "OTEL_EXPORTER_ZIPKIN_ENDPOINT"; + + /// + /// Configuration keys for Batch Span Processor options. + /// + public static class BatchSpanProcessorConfig + { + /// + /// Configuration key for configuring the delay interval (in milliseconds) between two consecutive exports. + /// + public const string ScheduleDelay = "OTEL_BSP_SCHEDULE_DELAY"; + + /// + /// Configuration key for configuring the maximum allowed time (in milliseconds) to export data. + /// + public const string ExportTimeout = "OTEL_BSP_EXPORT_TIMEOUT"; + + /// + /// Configuration key for configuring the maximum queue size. + /// + public const string MaxQueueSize = "OTEL_BSP_MAX_QUEUE_SIZE"; + + /// + /// Configuration key for configuring the maximum batch size. + /// Must be less than or equal to OTEL_BSP_MAX_QUEUE_SIZE. + /// + public const string MaxExportBatchSize = "OTEL_BSP_MAX_EXPORT_BATCH_SIZE"; + } + /// /// Configuration keys for instrumentation options. /// @@ -193,6 +235,16 @@ public static class Metrics /// Configuration key for additional names to be added to the meter at the startup. /// public const string AdditionalSources = "OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES"; + + /// + /// Configuration key for metric reader export interval. + /// + public const string ExportInterval = "OTEL_METRIC_EXPORT_INTERVAL"; + + /// + /// Configuration key for metric reader export timeout. + /// + public const string ExportTimeout = "OTEL_METRIC_EXPORT_TIMEOUT"; } /// @@ -244,5 +296,17 @@ public static class Sdk /// Default is "tracecontext,baggage". /// public const string Propagators = "OTEL_PROPAGATORS"; + + /// + /// Configuration key for the maximum allowed length of attribute values. + /// Default is no limit. Valid values are non-negative integers. + /// + public const string AttributeValueLengthLimit = "OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"; + + /// + /// Configuration key for the maximum allowed number of attributes per resource, span, or event. + /// Default is 128. Valid values are non-negative integers. + /// + public const string AttributeCountLimit = "OTEL_ATTRIBUTE_COUNT_LIMIT"; } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationMetricHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationMetricHelper.cs index fc473fb342..19acd778d1 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationMetricHelper.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationMetricHelper.cs @@ -151,6 +151,7 @@ public static MeterProviderBuilder AddOtlpExporter(MeterProviderBuilder builder, { return builder.AddOtlpExporter((options, metricReaderOptions) => { + metricReaderOptions.PeriodicExportingMetricReaderOptions = settings.ReaderConfiguration.ToPeriodicExportingMetricReaderOptions(); // Copy Auto settings to SDK settings settings.OtlpSettings?.CopyTo(options); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationTracerHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationTracerHelper.cs index 91bda52666..838c8ac820 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationTracerHelper.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/EnvironmentConfigurationTracerHelper.cs @@ -92,7 +92,7 @@ private static TracerProviderBuilder SetExporter(this TracerProviderBuilder buil { builder = traceExporter switch { - TracesExporter.Zipkin => Wrappers.AddZipkinExporter(builder, pluginManager), + TracesExporter.Zipkin => Wrappers.AddZipkinExporter(builder, settings, pluginManager), TracesExporter.Otlp => Wrappers.AddOtlpExporter(builder, settings, pluginManager), TracesExporter.Console => Wrappers.AddConsoleExporter(builder, pluginManager), _ => throw new ArgumentOutOfRangeException($"Traces exporter '{traceExporter}' is incorrect") @@ -218,9 +218,15 @@ public static TracerProviderBuilder AddConsoleExporter(TracerProviderBuilder bui } [MethodImpl(MethodImplOptions.NoInlining)] - public static TracerProviderBuilder AddZipkinExporter(TracerProviderBuilder builder, PluginManager pluginManager) + public static TracerProviderBuilder AddZipkinExporter(TracerProviderBuilder builder, TracerSettings settings, PluginManager pluginManager) { - return builder.AddZipkinExporter(pluginManager.ConfigureTracesOptions); + return builder.AddZipkinExporter(options => + { + options.BatchExportProcessorOptions = settings.BatchProcessorConfig.ToBatchExportProcessorOptions(); + options.Endpoint = new Uri(settings.ZipkinSettings!.Endpoint); + + pluginManager.ConfigureTracesOptions(options); + }); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -228,6 +234,8 @@ public static TracerProviderBuilder AddOtlpExporter(TracerProviderBuilder builde { return builder.AddOtlpExporter(options => { + options.BatchExportProcessorOptions = settings.BatchProcessorConfig.ToBatchExportProcessorOptions(); + // Copy Auto settings to SDK settings settings.OtlpSettings?.CopyTo(options); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs index 02bd7ee8bd..e8a854d143 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FailFastSettings.cs @@ -1,14 +1,21 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + namespace OpenTelemetry.AutoInstrumentation.Configurations; 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; } + + protected override void OnLoadFile(Conf configuration) + { + FailFast = configuration.FailFast; + } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/AttributeLimits.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/AttributeLimits.cs new file mode 100644 index 0000000000..1aed0eadfc --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/AttributeLimits.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class AttributeLimits +{ + public AttributeLimits() + { + } + + public AttributeLimits(int? attributeValueLengthLimit, int? attributeCountLimit) + { + if (attributeValueLengthLimit.HasValue && attributeValueLengthLimit.Value >= 0) + { + AttributeValueLengthLimit = attributeValueLengthLimit; + } + + if (attributeCountLimit.HasValue && attributeCountLimit.Value >= 0) + { + AttributeCountLimit = attributeCountLimit.Value; + } + } + + /// + /// Gets or sets the maximum attribute value size. + /// Value must be non-negative. + /// If omitted or null, there is no limit. + /// + [YamlMember(Alias = "attribute_value_length_limit")] + public int? AttributeValueLengthLimit { get; set; } + + /// + /// Gets or sets the maximum attribute count. + /// Value must be non-negative. + /// If omitted or null, 128 is used. + /// + [YamlMember(Alias = "attribute_count_limit")] + public int AttributeCountLimit { get; set; } = 128; +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/BatchProcessorConfig.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/BatchProcessorConfig.cs new file mode 100644 index 0000000000..0a0095e8db --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/BatchProcessorConfig.cs @@ -0,0 +1,90 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class BatchProcessorConfig +{ + public BatchProcessorConfig() + { + } + + public BatchProcessorConfig( + int? scheduleDelay = null, + int? exportTimeout = null, + int? maxQueueSize = null, + int? maxExportBatchSize = null) + { + if (scheduleDelay is not null) + { + ScheduleDelay = scheduleDelay.Value; + } + + if (exportTimeout is not null) + { + ExportTimeout = exportTimeout.Value; + } + + if (maxQueueSize is not null) + { + MaxQueueSize = maxQueueSize.Value; + } + + if (maxExportBatchSize is not null) + { + MaxExportBatchSize = maxExportBatchSize.Value; + } + } + + /// + /// Gets or sets the delay interval (in milliseconds) between two consecutive exports. + /// Value must be non-negative. + /// If omitted or null, 5000 is used. + /// + [YamlMember(Alias = "schedule_delay")] + public int ScheduleDelay { get; set; } = 5000; + + /// + /// Gets or sets the maximum allowed time (in milliseconds) to export data. + /// Value must be non-negative. A value of 0 indicates no limit (infinity). + /// If omitted or null, 30000 is used. + /// + [YamlMember(Alias = "export_timeout")] + public int ExportTimeout { get; set; } = 30000; + + /// + /// Gets or sets the maximum queue size. + /// Value must be positive. + /// If omitted or null, 2048 is used. + /// + [YamlMember(Alias = "max_queue_size")] + public int MaxQueueSize { get; set; } = 2048; + + /// + /// Gets or sets the maximum batch size. + /// Value must be positive. + /// If omitted or null, 512 is used. + /// + [YamlMember(Alias = "max_export_batch_size")] + public int MaxExportBatchSize { get; set; } = 512; + + /// + /// Gets or sets the exporters. + /// + [YamlMember(Alias = "exporter")] + public ExporterConfig? Exporter { get; set; } + + public BatchExportProcessorOptions ToBatchExportProcessorOptions() + { + return new BatchExportProcessorOptions + { + ScheduledDelayMilliseconds = this.ScheduleDelay, + ExporterTimeoutMilliseconds = this.ExportTimeout, + MaxQueueSize = this.MaxQueueSize, + MaxExportBatchSize = this.MaxExportBatchSize + }; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureHeadersConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureHeadersConfiguration.cs new file mode 100644 index 0000000000..2d261c844f --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureHeadersConfiguration.cs @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +[EmptyObjectOnEmptyYaml] +internal class CaptureHeadersConfiguration +{ + /// + /// Gets or sets a comma-separated list of HTTP header names. + /// Instrumentations will capture HTTP request header values for all configured header names. + /// + [YamlMember(Alias = "capture_request_headers")] + public string? CaptureRequestHeaders { get; set; } + + /// + /// Gets or sets a comma-separated list of HTTP header names. + /// Instrumentations will capture HTTP response header values for all configured header names. + /// + [YamlMember(Alias = "capture_response_headers")] + public string? CaptureResponseHeaders { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureMetadataConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureMetadataConfiguration.cs new file mode 100644 index 0000000000..5f0f32e2af --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/CaptureMetadataConfiguration.cs @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +[EmptyObjectOnEmptyYaml] +internal class CaptureMetadataConfiguration +{ + /// + /// Gets or sets a comma-separated list of gRPC metadata names. + /// Grpc.Net.Client instrumentations will capture gRPC request metadata values for all configured metadata names. + /// + [YamlMember(Alias = "capture_request_metadata")] + public string? CaptureRequestMetadata { get; set; } + + /// + /// Gets or sets a comma-separated list of gRPC metadata names. + /// Grpc.Net.Client instrumentations will capture gRPC response metadata values for all configured metadata names. + /// + [YamlMember(Alias = "capture_response_metadata")] + public string? CaptureResponseMetadata { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Conf.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Conf.cs new file mode 100644 index 0000000000..d0ad01ef18 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Conf.cs @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class Conf +{ + /// + /// 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 a value indicating whether the SDK is disabled. + /// If omitted or null, false is used. + /// + [YamlMember(Alias = "disabled")] + public bool Disabled { get; set; } = false; + + /// + /// Gets or sets the log level of the internal logger used by the SDK. + /// If omitted, info is used. + /// + [YamlMember(Alias = "log_level")] + public string LogLevel { get; set; } = "info"; + + /// + /// Gets or sets a value indicating whether the Fail Fast is enabled. + /// If omitted or null, false is used. + /// + [YamlMember(Alias = "fail_fast")] + public bool FailFast { get; set; } = false; + + /// + /// Gets or sets the attribute limits. + /// + [YamlMember(Alias = "attribute_limits")] + public AttributeLimits AttributeLimits { get; set; } = new(); + + /// + /// Gets or sets the logger provider configuration. + /// Configure logger provider. + /// If omitted, a noop logger provider is used. + /// + [YamlMember(Alias = "logger_provider")] + public LoggerProviderConfiguration? LoggerProvider { get; set; } + + /// + /// Gets or sets the meter provider configuration. + /// Configure meter provider. + /// If omitted, a noop meter provider is used. + /// + [YamlMember(Alias = "meter_provider")] + public MeterProviderConfiguration? MeterProvider { get; set; } + + /// + /// Gets or sets the text map context propagator configuration. + /// If omitted, a noop propagator is used. + /// + [YamlMember(Alias = "propagator")] + public PropagatorConfiguration? Propagator { get; set; } + + /// + /// Gets or sets the tracer provider configuration. + /// Configure tracer provider. + /// If omitted, a noop tracer provider is used. + /// + [YamlMember(Alias = "tracer_provider")] + public TracerProviderConfiguration? TracerProvider { 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; } + + /// + /// Gets or sets the instrumentation development configuration. + /// Configure instrumentation. + /// This type is in development and subject to breaking changes in minor versions. + /// + [YamlMember(Alias = "instrumentation/development")] + public InstrumentationDevelopment? InstrumentationDevelopment { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DetectionDevelopment.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DetectionDevelopment.cs new file mode 100644 index 0000000000..0db7b77b1e --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DetectionDevelopment.cs @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class DetectionDevelopment +{ + /// + /// Gets or sets the configuration for resource detectors. + /// If omitted or null, no resource detectors are enabled. + /// + [YamlMember(Alias = "detectors")] + public DotNetDetectors? Detectors { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetDetectors.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetDetectors.cs new file mode 100644 index 0000000000..c5ff335cee --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetDetectors.cs @@ -0,0 +1,71 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class DotNetDetectors +{ + /// + /// Gets or sets the Azure App Service detector configuration. + /// + [YamlMember(Alias = "azureappservice")] + public object? AzureAppService { get; set; } + +#if NET + /// + /// Gets or sets the container detector configuration. + /// + [YamlMember(Alias = "container")] + public object? Container { get; set; } +#endif + + /// + /// Gets or sets the host detector configuration. + /// + [YamlMember(Alias = "host")] + public object? Host { get; set; } + + /// + /// Gets or sets the operating system detector configuration. + /// + [YamlMember(Alias = "operatingsystem")] + public object? OperatingSystem { get; set; } + + /// + /// Gets or sets the process detector configuration. + /// + [YamlMember(Alias = "process")] + public object? Process { get; set; } + + /// + /// Gets or sets the process runtime detector configuration. + /// + [YamlMember(Alias = "processruntime")] + public object? ProcessRuntime { get; set; } + + /// + /// Returns the list of enabled resource detectors. + /// + public IReadOnlyList GetEnabledResourceDetector() + { + var enabled = new List(); + var properties = typeof(DotNetDetectors).GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var prop in properties) + { + var value = prop.GetValue(this); + if (value != null) + { + if (Enum.TryParse(prop.Name, out var resourceDetector)) + { + enabled.Add(resourceDetector); + } + } + } + + return enabled; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetInstrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetInstrumentation.cs new file mode 100644 index 0000000000..d0d99d6bdc --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetInstrumentation.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 DotNetInstrumentation +{ + /// + /// Gets or sets the configuration for .NET traces instrumentation. + /// + [YamlMember(Alias = "traces")] + public DotNetTraces? Traces { get; set; } + + /// + /// Gets or sets the configuration for .NET metrics instrumentation. + /// + [YamlMember(Alias = "metrics")] + public DotNetMetrics? Metrics { get; set; } + + /// + /// Gets or sets the configuration for .NET logs instrumentation. + /// + [YamlMember(Alias = "logs")] + public DotNetLogs? Logs { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetLogs.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetLogs.cs new file mode 100644 index 0000000000..302cad8780 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetLogs.cs @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class DotNetLogs +{ + /// + /// Gets or sets the ILogger logs instrumentation configuration. + /// + [YamlMember(Alias = "ilogger")] + public object? ILogger { get; set; } + + /// + /// Gets or sets the Log4Net logs instrumentation configuration. + /// + [YamlMember(Alias = "log4net")] + public object? Log4Net { get; set; } + + /// + /// Returns the list of enabled log instrumentations. + /// + public IReadOnlyList GetEnabledInstrumentations() + { + var result = new List(); + + if (ILogger != null) + { + result.Add(LogInstrumentation.ILogger); + } + + if (Log4Net != null) + { + result.Add(LogInstrumentation.Log4Net); + } + + return result; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetMetrics.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetMetrics.cs new file mode 100644 index 0000000000..1872d08058 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetMetrics.cs @@ -0,0 +1,79 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class DotNetMetrics +{ +#if NETFRAMEWORK + /// + /// Gets or sets the ASP.NET metrics instrumentation configuration. + /// + [YamlMember(Alias = "aspnet")] + public object? AspNet { get; set; } +#endif + +#if NET + /// + /// Gets or sets the ASP.NET Core metrics instrumentation configuration. + /// + [YamlMember(Alias = "aspnetcore")] + public object? AspNetCore { get; set; } +#endif + + /// + /// Gets or sets the HttpClient metrics instrumentation configuration. + /// + [YamlMember(Alias = "httpclient")] + public object? HttpClient { get; set; } + + /// + /// Gets or sets the .NET runtime metrics instrumentation configuration. + /// + [YamlMember(Alias = "netruntime")] + public object? NetRuntime { get; set; } + + /// + /// Gets or sets the NServiceBus metrics instrumentation configuration. + /// + [YamlMember(Alias = "nservicebus")] + public object? NServiceBus { get; set; } + + /// + /// Gets or sets the process metrics instrumentation configuration. + /// + [YamlMember(Alias = "process")] + public object? Process { get; set; } + + /// + /// Gets or sets the SqlClient metrics instrumentation configuration. + /// + [YamlMember(Alias = "sqlclient")] + public object? SqlClient { get; set; } + + /// + /// Returns the list of enabled metric instrumentations. + /// + public IReadOnlyList GetEnabledInstrumentations() + { + var enabled = new List(); + var properties = typeof(DotNetMetrics).GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var prop in properties) + { + var value = prop.GetValue(this); + if (value != null) + { + if (Enum.TryParse(prop.Name, out var instrumentation)) + { + enabled.Add(instrumentation); + } + } + } + + return enabled; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetTraces.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetTraces.cs new file mode 100644 index 0000000000..fcd6b3c400 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/DotNetTraces.cs @@ -0,0 +1,175 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class DotNetTraces +{ +#if NETFRAMEWORK + /// + /// Gets or sets the ASP.NET traces instrumentation configuration. + /// + [YamlMember(Alias = "aspnet")] + public CaptureHeadersConfiguration? AspNet { get; set; } + + /// + /// Gets or sets the WCF service traces instrumentation configuration. + /// + [YamlMember(Alias = "wcfservice")] + public object? WcfService { get; set; } +#endif + +#if NET + /// + /// Gets or sets the ASP.NET Core traces instrumentation configuration. + /// + [YamlMember(Alias = "aspnetcore")] + public CaptureHeadersConfiguration? AspNetCore { get; set; } + + /// + /// Gets or sets the StackExchange.Redis traces instrumentation configuration. + /// + [YamlMember(Alias = "stackexchangeredis")] + public object? StackExchangeRedis { get; set; } + + /// + /// Gets or sets the Entity Framework Core traces instrumentation configuration. + /// + [YamlMember(Alias = "entityframeworkcore")] + public SetDbStatementForTextConfuguration? EntityFrameworkCore { get; set; } + + /// + /// Gets or sets the GraphQL traces instrumentation configuration. + /// + [YamlMember(Alias = "graphql")] + public SetDocumentConfiguration? GraphQL { get; set; } + + /// + /// Gets or sets the MassTransit traces instrumentation configuration. + /// + [YamlMember(Alias = "masstransit")] + public object? MassTransit { get; set; } + + /// + /// Gets or sets the MySqlData traces instrumentation configuration. + /// + [YamlMember(Alias = "mysqldata")] + public object? MySqlData { get; set; } +#endif + + /// + /// Gets or sets the Azure traces instrumentation configuration. + /// + [YamlMember(Alias = "azure")] + public object? Azure { get; set; } + + /// + /// Gets or sets the Elasticsearch traces instrumentation configuration. + /// + [YamlMember(Alias = "elasticsearch")] + public object? Elasticsearch { get; set; } + + /// + /// Gets or sets the ElasticTransport traces instrumentation configuration. + /// + [YamlMember(Alias = "elastictransport")] + public object? ElasticTransport { get; set; } + + /// + /// Gets or sets the Grpc.Net.Client traces instrumentation configuration. + /// + [YamlMember(Alias = "grpcnetclient")] + public CaptureMetadataConfiguration? GrpcNetClient { get; set; } + + /// + /// Gets or sets the HttpClient traces instrumentation configuration. + /// + [YamlMember(Alias = "httpclient")] + public CaptureHeadersConfiguration? HttpClient { get; set; } + + /// + /// Gets or sets the Kafka traces instrumentation configuration. + /// + [YamlMember(Alias = "kafka")] + public object? Kafka { get; set; } + + /// + /// Gets or sets the MongoDB traces instrumentation configuration. + /// + [YamlMember(Alias = "mongodb")] + public object? MongoDb { get; set; } + + /// + /// Gets or sets the MySqlConnector traces instrumentation configuration. + /// + [YamlMember(Alias = "mysqlconnector")] + public object? MySqlConnector { get; set; } + + /// + /// Gets or sets the Npgsql traces instrumentation configuration. + /// + [YamlMember(Alias = "npgsql")] + public object? Npgsql { get; set; } + + /// + /// Gets or sets the NServiceBus traces instrumentation configuration. + /// + [YamlMember(Alias = "nservicebus")] + public object? NServiceBus { get; set; } + + /// + /// Gets or sets the Oracle MDA traces instrumentation configuration. + /// + [YamlMember(Alias = "oraclemda")] + public SetDbStatementForTextConfuguration? OracleMda { get; set; } + + /// + /// Gets or sets the RabbitMQ traces instrumentation configuration. + /// + [YamlMember(Alias = "rabbitmq")] + public object? RabbitMq { get; set; } + + /// + /// Gets or sets the Quartz traces instrumentation configuration. + /// + [YamlMember(Alias = "quartz")] + public object? Quartz { get; set; } + + /// + /// Gets or sets the SqlClient traces instrumentation configuration. + /// + [YamlMember(Alias = "sqlclient")] + public SetDbStatementForTextConfuguration? SqlClient { get; set; } + + /// + /// Gets or sets the WCF client traces instrumentation configuration. + /// + [YamlMember(Alias = "wcfclient")] + public object? WcfClient { get; set; } + + /// + /// Returns the list of enabled traces instrumentations. + /// + public IReadOnlyList GetEnabledInstrumentations() + { + var enabled = new List(); + var properties = typeof(DotNetTraces).GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var prop in properties) + { + var value = prop.GetValue(this); + if (value != null) + { + if (Enum.TryParse(prop.Name, out var instrumentation)) + { + enabled.Add(instrumentation); + } + } + } + + return enabled; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ExporterConfig.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ExporterConfig.cs new file mode 100644 index 0000000000..8954eea496 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ExporterConfig.cs @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class ExporterConfig +{ + /// + /// Gets or sets the OTLP HTTP exporter configuration. + /// + [YamlMember(Alias = "otlp_http")] + public OtlpHttpExporterConfig? OtlpHttp { get; set; } + + /// + /// Gets or sets the OTLP gRPC exporter configuration. + /// + [YamlMember(Alias = "otlp_grpc")] + public OtlpGrpcExporterConfig? OtlpGrpc { get; set; } + + /// + /// Gets or sets the Zipkin exporter configuration. + /// + [YamlMember(Alias = "zipkin")] + public ZipkinExporterConfig? Zipkin { get; set; } + + /// + /// Gets or sets the Prometheus exporter configuration. + /// + [YamlMember(Alias = "prometheus")] + public object? Prometheus { get; set; } + + /// + /// Gets or sets the console exporter configuration. + /// + [YamlMember(Alias = "console")] + public object? Console { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Header.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Header.cs new file mode 100644 index 0000000000..fcb10a6686 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Header.cs @@ -0,0 +1,21 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class Header +{ + /// + /// Gets or sets the name of the header. + /// + [YamlMember(Alias = "name")] + public string? Name { get; set; } + + /// + /// Gets or sets the value of the header. + /// + [YamlMember(Alias = "value")] + public string? Value { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/InstrumentationDevelopment.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/InstrumentationDevelopment.cs new file mode 100644 index 0000000000..7768f51e0b --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/InstrumentationDevelopment.cs @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class InstrumentationDevelopment +{ + /// + /// Gets or sets the configuration for .NET language-specific instrumentation libraries. + /// + [YamlMember(Alias = "dotnet")] + public DotNetInstrumentation? DotNet { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/LoggerProviderConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/LoggerProviderConfiguration.cs new file mode 100644 index 0000000000..f58dcabfef --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/LoggerProviderConfiguration.cs @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class LoggerProviderConfiguration +{ + /// + /// Gets or sets the processors for the logger provider. + /// + [YamlMember(Alias = "processors")] + public Dictionary Processors { get; set; } = new(); +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/MeterProviderConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/MeterProviderConfiguration.cs new file mode 100644 index 0000000000..7475149378 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/MeterProviderConfiguration.cs @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class MeterProviderConfiguration +{ + /// + /// Gets or sets the readers for the meter provider. + /// + [YamlMember(Alias = "readers")] + public Dictionary Readers { get; set; } = new(); +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpGrpcExporterConfig.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpGrpcExporterConfig.cs new file mode 100644 index 0000000000..c625f53bd6 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpGrpcExporterConfig.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class OtlpGrpcExporterConfig +{ + /// + /// Gets or sets the endpoint for the OTLP gRPC exporter. + /// Configure endpoint. + /// If omitted or null, http://localhost:4317 is used. + /// + [YamlMember(Alias = "endpoint")] + public string? Endpoint { get; set; } + + /// + /// Gets or sets the headers for the exporter. + /// Configure headers. Entries have higher priority than entries from headers_list. + /// If an entry's value is null, the entry is ignored. + /// + [YamlMember(Alias = "headers")] + public List
? Headers { get; set; } + + /// + /// Gets or sets the headers list for the exporter. + /// Configure headers. Entries have lower priority than entries from headers. + /// The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. + /// If omitted or null, no headers are added. + /// + [YamlMember(Alias = "headers_list")] + public string? HeadersList { get; set; } + + /// + /// Gets or sets the maximum time (in milliseconds) to wait for each export. + /// Value must be non-negative. A value of 0 indicates no limit (infinity). + /// If omitted or null, 10000 is used. + /// + [YamlMember(Alias = "timeout")] + public int? Timeout { get; set; } = 10000; +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpHttpExporterConfig.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpHttpExporterConfig.cs new file mode 100644 index 0000000000..db4515148d --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/OtlpHttpExporterConfig.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class OtlpHttpExporterConfig +{ + /// + /// Gets or sets the endpoint for the OTLP HTTP exporter. + /// Configure endpoint. + /// If omitted or null, http://localhost:4318 is used. + /// + [YamlMember(Alias = "endpoint")] + public string? Endpoint { get; set; } + + /// + /// Gets or sets the headers for the exporter. + /// Configure headers. Entries have higher priority than entries from headers_list. + /// If an entry's value is null, the entry is ignored. + /// + [YamlMember(Alias = "headers")] + public List
? Headers { get; set; } + + /// + /// Gets or sets the headers list for the exporter. + /// Configure headers. Entries have lower priority than entries from headers. + /// The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. + /// If omitted or null, no headers are added. + /// + [YamlMember(Alias = "headers_list")] + public string? HeadersList { get; set; } + + /// + /// Gets or sets the maximum time (in milliseconds) to wait for each export. + /// Value must be non-negative. A value of 0 indicates no limit (infinity). + /// If omitted or null, 10000 is used. + /// + [YamlMember(Alias = "timeout")] + public int? Timeout { get; set; } = 10000; +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs new file mode 100644 index 0000000000..89426149fe --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/ConditionalDeserializer.cs @@ -0,0 +1,54 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Core; +using Vendors.YamlDotNet.Core.Events; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; + +internal class ConditionalDeserializer : INodeDeserializer +{ + private readonly INodeDeserializer _inner; + + public ConditionalDeserializer(INodeDeserializer inner) + { + this._inner = inner; + } + + public bool Deserialize( + IParser reader, + Type expectedType, + Func nestedObjectDeserializer, + out object? value, + ObjectDeserializer rootDeserializer) + { + if (!expectedType.IsDefined(typeof(EmptyObjectOnEmptyYamlAttribute), inherit: true)) + { + return _inner.Deserialize(reader, expectedType, nestedObjectDeserializer, out value, rootDeserializer); + } + + if (reader.Accept(out var scalar)) + { + if (string.IsNullOrEmpty(scalar.Value)) + { + value = Activator.CreateInstance(expectedType); + reader.MoveNext(); + return true; + } + } + + if (reader.Accept(out _)) + { + object tempValue = Activator.CreateInstance(expectedType)!; + + bool result = _inner.Deserialize(reader, expectedType, nestedObjectDeserializer, out var deserialized, rootDeserializer); + + value = deserialized ?? tempValue; + return result; + } + + value = null; + return false; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs new file mode 100644 index 0000000000..9863a486b3 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EmptyObjectOnEmptyYamlAttribute.cs @@ -0,0 +1,9 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; + +[AttributeUsage(AttributeTargets.Class)] +internal sealed class EmptyObjectOnEmptyYamlAttribute : Attribute +{ +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EnvVarTypeConverter.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EnvVarTypeConverter.cs new file mode 100644 index 0000000000..05ac3a95ce --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/Parser/EnvVarTypeConverter.cs @@ -0,0 +1,87 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Globalization; +using System.Text.RegularExpressions; +using Vendors.YamlDotNet.Core; +using Vendors.YamlDotNet.Core.Events; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; + +internal class EnvVarTypeConverter : IYamlTypeConverter +{ + private static readonly HashSet SupportedTypes = + [ + typeof(string), + typeof(int), + typeof(long), + typeof(float), + typeof(double), + typeof(bool), + typeof(int?), + typeof(object), + ]; + + public bool Accepts(Type type) + { + return SupportedTypes.Contains(type); + } + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + if (parser.Current is not Scalar scalar) + { + return rootDeserializer(type); + } + + var rawValue = scalar.Value ?? throw new InvalidOperationException("Scalar value is null."); + + var replacedValue = ReplaceEnvVariables(rawValue); + parser.MoveNext(); + + try + { + if (string.IsNullOrWhiteSpace(replacedValue)) + { + if (Nullable.GetUnderlyingType(type) != null) + { + return null; + } + } + + return type switch + { + Type t when t == typeof(string) => replacedValue, + Type t when t == typeof(int) => int.Parse(replacedValue), + Type t when t == typeof(long) => long.Parse(replacedValue), + Type t when t == typeof(float) => float.Parse(replacedValue, CultureInfo.InvariantCulture), + Type t when t == typeof(double) => double.Parse(replacedValue, CultureInfo.InvariantCulture), + Type t when t == typeof(bool) => bool.Parse(replacedValue), + Type t when t == typeof(int?) => int.Parse(replacedValue), + Type t when t == typeof(object) => replacedValue, + _ => throw new NotSupportedException($"Type {type.FullName} is not supported by the converter") + }; + } + catch (Exception ex) + { + throw new FormatException($"Error parsing value '{replacedValue}' as type {type.Name}", ex); + } + } + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + emitter.Emit(new Scalar(value?.ToString() ?? string.Empty)); + } + + private static string ReplaceEnvVariables(string input) + { + var pattern = @"\$\{([A-Z0-9_]+)\}"; + return Regex.Replace(input, pattern, match => + { + var varName = match.Groups[1].Value; + var envValue = Environment.GetEnvironmentVariable(varName); + return envValue ?? match.Value; + }); + } +} 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..7f10b0d13a --- /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 Conf 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/PropagatorConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/PropagatorConfiguration.cs new file mode 100644 index 0000000000..1414c0d5c0 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/PropagatorConfiguration.cs @@ -0,0 +1,21 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class PropagatorConfiguration +{ + /// + /// Gets or sets the composite propagator configuration. + /// + [YamlMember(Alias = "composite")] + public Dictionary? Composite { get; set; } + + /// + /// Gets or sets the composite list for the propagator. + /// + [YamlMember(Alias = "composite_list")] + public string? CompositeList { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ReaderConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ReaderConfiguration.cs new file mode 100644 index 0000000000..27f4d6bd1a --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ReaderConfiguration.cs @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.Metrics; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class ReaderConfiguration +{ + public ReaderConfiguration() + { + } + + public ReaderConfiguration(int? interval, int? timeout) + { + if (interval is not null) + { + Interval = interval.Value; + } + + if (timeout is not null) + { + Timeout = timeout.Value; + } + } + + /// + /// Gets or sets the delay interval (in milliseconds) between the start of two consecutive exports. + /// Value must be non-negative. + /// If omitted or null, 60000 is used. + /// + [YamlMember(Alias = "interval")] + public int Interval { get; set; } = 60000; + + /// + /// Gets or sets the maximum allowed time (in milliseconds) to export data. + /// Value must be non-negative. A value of 0 indicates no limit (infinity). + /// If omitted or null, 30000 is used. + /// + [YamlMember(Alias = "timeout")] + public int Timeout { get; set; } = 30000; + + /// + /// Gets or sets the exporter configuration for the reader. + /// + [YamlMember(Alias = "exporter")] + public ExporterConfig? Exporter { get; set; } = new(); + + public PeriodicExportingMetricReaderOptions ToPeriodicExportingMetricReaderOptions() + { + return new PeriodicExportingMetricReaderOptions + { + ExportIntervalMilliseconds = Interval, + ExportTimeoutMilliseconds = Timeout + }; + } +} 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..b7b43b9229 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ResourceConfiguration.cs @@ -0,0 +1,43 @@ +// 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; } + + /// + /// Gets or sets the detection development configuration. + /// + [YamlMember(Alias = "detection/development")] + public DetectionDevelopment? DetectionDevelopment { get; set; } + + public List> ParseAttributes() + { + var result = new List>(); + if (Attributes == null) + { + return result; + } + + foreach (var attr in Attributes) + { + result.Add(new KeyValuePair(attr.Name, attr.Value)); + } + + return result; + } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDbStatementForTextConfuguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDbStatementForTextConfuguration.cs new file mode 100644 index 0000000000..2d52867df5 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDbStatementForTextConfuguration.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +[EmptyObjectOnEmptyYaml] +internal class SetDbStatementForTextConfuguration +{ + /// + /// Gets or sets a value indicating whether SQL statements are passed through the db.statement attribute. + /// If false, db.statement is recorded only for executing stored procedures. + /// + [YamlMember(Alias = "set_db_statement_for_text")] + public bool SetDbStatementForText { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDocumentConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDocumentConfiguration.cs new file mode 100644 index 0000000000..b2baee5616 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/SetDocumentConfiguration.cs @@ -0,0 +1,18 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +[EmptyObjectOnEmptyYaml] +internal class SetDocumentConfiguration +{ + /// + /// Gets or sets a value indicating whether the GraphQL instrumentation can pass raw queries through the graphql.document attribute. + /// Queries might contain sensitive information. + /// + [YamlMember(Alias = "set_document")] + public bool SetDocument { get; set; } +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/TracerProviderConfiguration.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/TracerProviderConfiguration.cs new file mode 100644 index 0000000000..21be9eaef9 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/TracerProviderConfiguration.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class TracerProviderConfiguration +{ + [YamlMember(Alias = "processors")] + public Dictionary Processors { get; set; } = new(); +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ZipkinExporterConfig.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ZipkinExporterConfig.cs new file mode 100644 index 0000000000..45fafd8429 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/FileBasedConfiguration/ZipkinExporterConfig.cs @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Vendors.YamlDotNet.Serialization; + +namespace OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + +internal class ZipkinExporterConfig +{ + public ZipkinExporterConfig() + { + } + + public ZipkinExporterConfig(string? endpoint) + { + if (endpoint is not null) + { + Endpoint = endpoint; + } + } + + /// + /// Gets or sets the Zipkin endpoint URL to which spans are exported. + /// If omitted or null, the default value "http://localhost:9411/api/v2/spans" is used. + /// + [YamlMember(Alias = "endpoint")] + public string Endpoint { get; set; } = "http://localhost:9411/api/v2/spans"; +} diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs index 732af52102..43ddbffc11 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; + namespace OpenTelemetry.AutoInstrumentation.Configurations; internal class GeneralSettings : Settings @@ -15,6 +17,11 @@ internal class GeneralSettings : Settings /// public IReadOnlyList EnabledResourceDetectors { get; private set; } = new List(); + /// + /// Gets the list of enabled resources. + /// + public IReadOnlyList> Resources { get; private set; } = new List>(); + /// /// Gets a value indicating whether the event should trigger /// the flushing of telemetry data. @@ -32,7 +39,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,6 +50,35 @@ protected override void OnLoad(Configuration configuration) } } + var baseResources = new List> + { + new(Constants.DistributionAttributes.TelemetryDistroNameAttributeName, Constants.DistributionAttributes.TelemetryDistroNameAttributeValue), + new(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName, AutoInstrumentationVersion.Version) + }; + + var serviceName = configuration.GetString(ConfigurationKeys.ServiceName); + + if (!string.IsNullOrEmpty(serviceName)) + { + baseResources.Add(new KeyValuePair(Constants.ResourceAttributes.AttributeServiceName, serviceName!)); + } + + var resourceAttributes = ParseResourceAttributes(configuration.GetString(ConfigurationKeys.ResourceAttributes)); + if (resourceAttributes != null && resourceAttributes.Count > 0) + { + foreach (var attr in resourceAttributes) + { + if (attr.Key == Constants.ResourceAttributes.AttributeServiceName && !string.IsNullOrEmpty(serviceName)) + { + continue; // OTEL_SERVICE_NAME takes precedence + } + + baseResources.Add(attr); + } + } + + Resources = baseResources; + var resourceDetectorsEnabledByDefault = configuration.GetBool(ConfigurationKeys.ResourceDetectorEnabled) ?? true; EnabledResourceDetectors = configuration.ParseEnabledEnumList( @@ -54,4 +90,78 @@ protected override void OnLoad(Configuration configuration) ProfilerEnabled = configuration.GetString(ConfigurationKeys.ProfilingEnabled) == "1"; } + + protected override void OnLoadFile(Conf configuration) + { + var detectors = configuration.Resource?.DetectionDevelopment?.Detectors; + if (detectors != null) + { + EnabledResourceDetectors = detectors.GetEnabledResourceDetector(); + } + + var baseResources = new List> + { + new(Constants.DistributionAttributes.TelemetryDistroNameAttributeName, Constants.DistributionAttributes.TelemetryDistroNameAttributeValue), + new(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName, AutoInstrumentationVersion.Version) + }; + + var resourceAttributesWithPriority = configuration.Resource?.ParseAttributes() ?? []; + + var resourceAttributes = ParseResourceAttributes(configuration.Resource?.AttributesList); + + var merged = new Dictionary(); + foreach (var kv in baseResources) + { + merged[kv.Key] = kv.Value; + } + + foreach (var kv in resourceAttributesWithPriority) + { + if (!merged.ContainsKey(kv.Key)) + { + merged[kv.Key] = kv.Value; + } + } + + if (resourceAttributes != null) + { + foreach (var kv in resourceAttributes) + { + if (!merged.ContainsKey(kv.Key)) + { + merged[kv.Key] = kv.Value; + } + } + } + + Resources = merged.ToList(); + + SetupSdk = !configuration.Disabled; + } + + private static List> ParseResourceAttributes(string? resourceAttributes) + { + if (string.IsNullOrEmpty(resourceAttributes)) + { + return []; + } + + var attributeListSplitter = ','; + var attributeKeyValueSplitter = '='; + var attributes = new List>(); + + var rawAttributes = resourceAttributes!.Split(attributeListSplitter); + foreach (var rawKeyValuePair in rawAttributes) + { + var keyValuePair = rawKeyValuePair.Split(attributeKeyValueSplitter); + if (keyValuePair.Length != 2) + { + continue; + } + + attributes.Add(new KeyValuePair(keyValuePair[0].Trim(), keyValuePair[1].Trim())); + } + + return attributes; + } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/InstrumentationOptions.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/InstrumentationOptions.cs index d9b98939b2..a96b3ec83d 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/InstrumentationOptions.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/InstrumentationOptions.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.AutoInstrumentation.HeadersCapture; namespace OpenTelemetry.AutoInstrumentation.Configurations; @@ -31,28 +32,81 @@ internal InstrumentationOptions(Configuration configuration) SqlClientSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.SqlClientSetDbStatementForText) ?? false; } + internal InstrumentationOptions(DotNetTraces? instrumentationConfiguration) + { + if (instrumentationConfiguration != null) + { +#if NETFRAMEWORK + if (instrumentationConfiguration.AspNet != null) + { + AspNetInstrumentationCaptureRequestHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.AspNet.CaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache); + AspNetInstrumentationCaptureResponseHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.AspNet.CaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache); + } +#endif +#if NET + if (instrumentationConfiguration.AspNetCore != null) + { + AspNetCoreInstrumentationCaptureRequestHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.AspNetCore.CaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache); + AspNetCoreInstrumentationCaptureResponseHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.AspNetCore.CaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache); + } + + if (instrumentationConfiguration.EntityFrameworkCore != null) + { + EntityFrameworkCoreSetDbStatementForText = instrumentationConfiguration.EntityFrameworkCore.SetDbStatementForText; + } + + if (instrumentationConfiguration.GraphQL != null) + { + GraphQLSetDocument = instrumentationConfiguration.GraphQL.SetDocument; + } +#endif + + if (instrumentationConfiguration.GrpcNetClient != null) + { + GrpcNetClientInstrumentationCaptureRequestMetadata = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.GrpcNetClient.CaptureRequestMetadata, AdditionalTag.CreateGrpcRequestCache); + GrpcNetClientInstrumentationCaptureResponseMetadata = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.GrpcNetClient.CaptureResponseMetadata, AdditionalTag.CreateGrpcResponseCache); + } + + if (instrumentationConfiguration.HttpClient != null) + { + HttpInstrumentationCaptureRequestHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.HttpClient.CaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache); + HttpInstrumentationCaptureResponseHeaders = HeaderConfigurationExtensions.ParseHeaders(instrumentationConfiguration.HttpClient.CaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache); + } + + if (instrumentationConfiguration.OracleMda != null) + { + OracleMdaSetDbStatementForText = instrumentationConfiguration.OracleMda.SetDbStatementForText; + } + + if (instrumentationConfiguration.SqlClient != null) + { + SqlClientSetDbStatementForText = instrumentationConfiguration.SqlClient.SetDbStatementForText; + } + } + } + #if NETFRAMEWORK /// /// Gets the list of HTTP request headers to be captured as the span tags by ASP.NET instrumentation. /// - public IReadOnlyList AspNetInstrumentationCaptureRequestHeaders { get; } + public IReadOnlyList AspNetInstrumentationCaptureRequestHeaders { get; } = []; /// /// Gets the list of HTTP response headers to be captured as the span tags by ASP.NET instrumentation. /// - public IReadOnlyList AspNetInstrumentationCaptureResponseHeaders { get; } + public IReadOnlyList AspNetInstrumentationCaptureResponseHeaders { get; } = []; #endif #if NET /// /// Gets the list of HTTP request headers to be captured as the span tags by ASP.NET Core instrumentation. /// - public IReadOnlyList AspNetCoreInstrumentationCaptureRequestHeaders { get; } + public IReadOnlyList AspNetCoreInstrumentationCaptureRequestHeaders { get; } = []; /// /// Gets the list of HTTP response headers to be captured as the span tags by ASP.NET Core instrumentation. /// - public IReadOnlyList AspNetCoreInstrumentationCaptureResponseHeaders { get; } + public IReadOnlyList AspNetCoreInstrumentationCaptureResponseHeaders { get; } = []; /// /// Gets a value indicating whether text query in Entity Framework Core can be passed as a db.statement tag. @@ -68,22 +122,22 @@ internal InstrumentationOptions(Configuration configuration) /// /// Gets the list of request metadata to be captured as the span tags by Grpc.Net.Client instrumentation. /// - public IReadOnlyList GrpcNetClientInstrumentationCaptureRequestMetadata { get; } + public IReadOnlyList GrpcNetClientInstrumentationCaptureRequestMetadata { get; } = []; /// /// Gets the list of response metadata to be captured as the span tags by Grpc.Net.Client instrumentation. /// - public IReadOnlyList GrpcNetClientInstrumentationCaptureResponseMetadata { get; } + public IReadOnlyList GrpcNetClientInstrumentationCaptureResponseMetadata { get; } = []; /// /// Gets the list of HTTP request headers to be captured as the span tags by HTTP instrumentation. /// - public IReadOnlyList HttpInstrumentationCaptureRequestHeaders { get; } + public IReadOnlyList HttpInstrumentationCaptureRequestHeaders { get; } = []; /// /// Gets the list of HTTP response headers to be captured as the span tags by HTTP instrumentation. /// - public IReadOnlyList HttpInstrumentationCaptureResponseHeaders { get; } + public IReadOnlyList HttpInstrumentationCaptureResponseHeaders { get; } = []; /// /// Gets a value indicating whether text query in Oracle Client can be passed as a db.statement tag. diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs index 7d98e346e2..df05805544 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/LogSettings.cs @@ -1,8 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.AutoInstrumentation.Configurations.Otlp; using OpenTelemetry.AutoInstrumentation.Logging; +using Vendors.YamlDotNet.Core.Tokens; namespace OpenTelemetry.AutoInstrumentation.Configurations; @@ -43,7 +45,12 @@ internal class LogSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + /// + /// Gets tracing Batch Processor Configuration. + /// + public BatchProcessorConfig BatchProcessorConfig { get; private set; } = new(); + + protected override void OnLoadEnvVar(Configuration configuration) { LogsEnabled = configuration.GetBool(ConfigurationKeys.Logs.LogsEnabled) ?? true; LogExporters = ParseLogExporter(configuration); @@ -52,6 +59,12 @@ protected override void OnLoad(Configuration configuration) OtlpSettings = new OtlpSettings(OtlpSignalType.Logs, configuration); } + BatchProcessorConfig = new BatchProcessorConfig( + scheduleDelay: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.ScheduleDelay), + exportTimeout: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.ExportTimeout), + maxQueueSize: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.MaxQueueSize), + maxExportBatchSize: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.MaxExportBatchSize)); + IncludeFormattedMessage = configuration.GetBool(ConfigurationKeys.Logs.IncludeFormattedMessage) ?? false; EnableLog4NetBridge = configuration.GetBool(ConfigurationKeys.Logs.EnableLog4NetBridge) ?? false; @@ -64,6 +77,51 @@ protected override void OnLoad(Configuration configuration) enabledConfigurationTemplate: ConfigurationKeys.Logs.EnabledLogsInstrumentationTemplate); } + protected override void OnLoadFile(Conf configuration) + { + if (configuration.LoggerProvider != null && + configuration.LoggerProvider.Processors != null && + configuration.LoggerProvider.Processors.TryGetValue("batch", out var batchProcessorConfig)) + { + BatchProcessorConfig = batchProcessorConfig; + LogsEnabled = true; + var logExporters = new List(); + var exporters = batchProcessorConfig.Exporter; + if (exporters != null) + { + if (exporters.OtlpGrpc != null) + { + logExporters.Add(LogExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Logs, exporters.OtlpGrpc); + } + + if (exporters.OtlpHttp != null) + { + logExporters.Add(LogExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Logs, exporters.OtlpHttp); + } + + if (exporters.Console != null) + { + logExporters.Add(LogExporter.Console); + } + + LogExporters = logExporters; + } + } + else + { + LogsEnabled = false; + } + + var logsInstrumentations = configuration.InstrumentationDevelopment?.DotNet?.Logs; + + if (logsInstrumentations != null) + { + EnabledInstrumentations = logsInstrumentations.GetEnabledInstrumentations(); + } + } + private static IReadOnlyList ParseLogExporter(Configuration configuration) { var logExporterEnvVar = configuration.GetString(ConfigurationKeys.Logs.Exporter); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs index a3a54e2c6e..069428bfc6 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/MetricSettings.cs @@ -1,6 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Xml; +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.AutoInstrumentation.Configurations.Otlp; using OpenTelemetry.AutoInstrumentation.Logging; @@ -38,8 +40,17 @@ internal class MetricSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + /// + /// Gets Reader configuration. + /// + public ReaderConfiguration ReaderConfiguration { get; private set; } = new(); + + protected override void OnLoadEnvVar(Configuration configuration) { + ReaderConfiguration = new ReaderConfiguration( + interval: configuration.GetInt32(ConfigurationKeys.Metrics.ExportInterval), + timeout: configuration.GetInt32(ConfigurationKeys.Metrics.ExportTimeout)); + MetricExporters = ParseMetricExporter(configuration); if (MetricExporters.Contains(MetricsExporter.Otlp)) { @@ -66,6 +77,68 @@ protected override void OnLoad(Configuration configuration) MetricsEnabled = configuration.GetBool(ConfigurationKeys.Metrics.MetricsEnabled) ?? true; } + protected override void OnLoadFile(Conf configuration) + { + var metricExporters = new List(); + + if (configuration.MeterProvider != null && + configuration.MeterProvider.Readers != null && + configuration.MeterProvider.Readers.TryGetValue("periodic", out var periodicReader) && + periodicReader != null) + { + ReaderConfiguration = periodicReader; + var exporters = periodicReader.Exporter; + if (exporters != null) + { + if (exporters.OtlpGrpc != null) + { + metricExporters.Add(MetricsExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Metrics, exporters.OtlpGrpc); + } + + if (exporters.OtlpHttp != null) + { + metricExporters.Add(MetricsExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Metrics, exporters.OtlpHttp); + } + + if (exporters.Console != null) + { + metricExporters.Add(MetricsExporter.Console); + } + } + } + + if (configuration.MeterProvider != null && + configuration.MeterProvider.Readers != null && + configuration.MeterProvider.Readers.TryGetValue("pull", out var pullReader) && + pullReader != null) + { + var exporters = pullReader.Exporter; + if (exporters != null) + { + if (exporters.Prometheus != null) + { + metricExporters.Add(MetricsExporter.Prometheus); + } + } + } + + MetricExporters = metricExporters; + + MetricsEnabled = configuration.MeterProvider != null && + configuration.MeterProvider.Readers != null && + (configuration.MeterProvider.Readers.ContainsKey("periodic") || + configuration.MeterProvider.Readers.ContainsKey("pull")); + + var metricsInstrumentations = configuration.InstrumentationDevelopment?.DotNet?.Metrics; + + if (metricsInstrumentations != null) + { + EnabledInstrumentations = metricsInstrumentations.GetEnabledInstrumentations(); + } + } + private static IReadOnlyList ParseMetricExporter(Configuration configuration) { var metricsExporterEnvVar = configuration.GetString(ConfigurationKeys.Metrics.Exporter); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs index 366ad01c8d..7c17000407 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/Otlp/OtlpSettings.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.Exporter; namespace OpenTelemetry.AutoInstrumentation.Configurations.Otlp; @@ -25,6 +26,30 @@ public OtlpSettings(OtlpSignalType signalType, Configuration configuration) Endpoint = configuration.GetUri(priorityVar); } + public OtlpSettings(OtlpSignalType signalType, OtlpHttpExporterConfig configuration) + { + Protocol = OtlpExportProtocol.HttpProtobuf; + + Headers = CombineHeaders(configuration.Headers, configuration.HeadersList); + + TimeoutMilliseconds = configuration.Timeout; + + Endpoint = !string.IsNullOrEmpty(configuration.Endpoint) ? new Uri(configuration.Endpoint) : null; + } + + public OtlpSettings(OtlpSignalType signalType, OtlpGrpcExporterConfig configuration) + { +#pragma warning disable CS0618 // OtlpExportProtocol.Grpc is obsolete + Protocol = OtlpExportProtocol.Grpc; +#pragma warning restore CS0618 // OtlpExportProtocol.Grpc is obsolete + + Headers = CombineHeaders(configuration.Headers, configuration.HeadersList); + + TimeoutMilliseconds = configuration.Timeout; + + Endpoint = !string.IsNullOrEmpty(configuration.Endpoint) ? new Uri(configuration.Endpoint) : null; + } + /// /// Gets the OTLP transport protocol. Supported values: Grpc and HttpProtobuf. /// @@ -70,6 +95,46 @@ public void CopyTo(OtlpExporterOptions options) } } + private static string? CombineHeaders(List
? headers, string? headersList) + { + var headerDict = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // Parse headersList (lower priority) + if (!string.IsNullOrWhiteSpace(headersList)) + { + var pairs = headersList!.Split([','], StringSplitOptions.RemoveEmptyEntries); + foreach (var pair in pairs) + { + var parts = pair.Split('='); + if (parts.Length == 2) + { + var key = parts[0]; + var value = parts[1]; + if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value)) + { + headerDict[key] = value; + } + } + } + } + + // Override with headers (higher priority) + if (headers != null) + { + foreach (var header in headers) + { + if (!string.IsNullOrWhiteSpace(header?.Name) && !string.IsNullOrWhiteSpace(header?.Value)) + { + headerDict[header!.Name!] = header!.Value!; + } + } + } + + return headerDict.Count == 0 + ? null + : string.Join(",", headerDict.Select(kvp => $"{kvp.Key}={kvp.Value}")); + } + private static OtlpExportProtocol? GetExporterOtlpProtocol(OtlpSignalType signalType, Configuration configuration) { // the default in SDK is grpc. http/protobuf should be default for our purposes diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs index a19e30063f..97cff12973 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ResourceConfigurator.cs @@ -8,19 +8,13 @@ namespace OpenTelemetry.AutoInstrumentation.Configurations; internal static class ResourceConfigurator { - internal const string ServiceNameAttribute = "service.name"; - - public static ResourceBuilder CreateResourceBuilder(IReadOnlyList enabledResourceDetectors) + public static ResourceBuilder CreateResourceBuilder(IReadOnlyList enabledResourceDetectors, IReadOnlyList> resources) { var resourceBuilder = ResourceBuilder .CreateEmpty() // Don't use CreateDefault because it puts service name unknown by default. .AddEnvironmentVariableDetector() .AddTelemetrySdk() - .AddAttributes(new KeyValuePair[] - { - new(Constants.DistributionAttributes.TelemetryDistroNameAttributeName, Constants.DistributionAttributes.TelemetryDistroNameAttributeValue), - new(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName, AutoInstrumentationVersion.Version) - }); + .AddAttributes(resources); foreach (var enabledResourceDetector in enabledResourceDetectors) { @@ -39,10 +33,10 @@ public static ResourceBuilder CreateResourceBuilder(IReadOnlyList kvp.Key == ServiceNameAttribute)) + if (!resource.Attributes.Any(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/SdkSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs index c0c0b14725..e5339a0c6b 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/SdkSettings.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.CodeAnalysis; +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.AutoInstrumentation.Logging; namespace OpenTelemetry.AutoInstrumentation.Configurations; @@ -16,14 +17,88 @@ internal class SdkSettings : Settings /// /// Gets the list of propagators to be used. /// - public IList Propagators { get; private set; } = new List(); + public IList Propagators { get; private set; } = []; - protected override void OnLoad(Configuration configuration) + /// + /// Gets the attribute limits. + /// + public AttributeLimits AttributeLimits { get; private set; } = new(); + + protected override void OnLoadEnvVar(Configuration configuration) { + AttributeLimits = new AttributeLimits( + attributeCountLimit: configuration.GetInt32(ConfigurationKeys.Sdk.AttributeCountLimit), + attributeValueLengthLimit: configuration.GetInt32(ConfigurationKeys.Sdk.AttributeValueLengthLimit)); + Propagators = ParsePropagator(configuration); } - private static IList ParsePropagator(Configuration configuration) + protected override void OnLoadFile(Conf configuration) + { + AttributeLimits = configuration.AttributeLimits; + + if (configuration.Propagator == null) + { + return; + } + + var seenPropagators = new HashSet(); + var resolvedPropagators = new List(); + + if (configuration.Propagator.Composite is Dictionary compositeDict) + { + foreach (var key in compositeDict.Keys) + { + if (seenPropagators.Add(key)) + { + resolvedPropagators.Add(key); + } + } + } + + if (!string.IsNullOrEmpty(configuration.Propagator.CompositeList)) + { + var list = configuration.Propagator.CompositeList!.Split(Constants.ConfigurationValues.Separator); + foreach (var item in list) + { + if (seenPropagators.Add(item)) + { + resolvedPropagators.Add(item); + } + } + } + + foreach (var propagatorName in resolvedPropagators) + { + switch (propagatorName.ToLowerInvariant()) + { + case Constants.ConfigurationValues.Propagators.W3CTraceContext: + Propagators.Add(Propagator.W3CTraceContext); + break; + case Constants.ConfigurationValues.Propagators.W3CBaggage: + Propagators.Add(Propagator.W3CBaggage); + break; + case Constants.ConfigurationValues.Propagators.B3Multi: + Propagators.Add(Propagator.B3Multi); + break; + case Constants.ConfigurationValues.Propagators.B3Single: + Propagators.Add(Propagator.B3Single); + break; + default: + var unsupportedMessage = $"Propagator '{propagatorName}' is not supported."; + Logger.Error(unsupportedMessage); + + if (configuration.FailFast == true) + { + throw new NotSupportedException(unsupportedMessage); + } + + break; + } + } + } + + private static List ParsePropagator(Configuration configuration) { var propagatorEnvVar = configuration.GetString(ConfigurationKeys.Sdk.Propagators); var propagators = new List(); diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/Settings.cs index eece253424..d3a9110f95 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; /// @@ -11,15 +14,33 @@ internal abstract class Settings 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; + var isConfigFileEnabled = Environment.GetEnvironmentVariable("OTEL_EXPERIMENTAL_FILE_BASED_CONFIGURATION_ENABLED") == "true"; + + if (isConfigFileEnabled) + { + var configFile = Environment.GetEnvironmentVariable("OTEL_EXPERIMENTAL_CONFIG_FILE") ?? "config.yaml"; + var config = Parser.ParseYaml(configFile); + var settings = new T(); + settings.LoadFile(config); + 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(Conf configuration) { - OnLoad(configuration); + OnLoadFile(configuration); } /// @@ -27,5 +48,12 @@ 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 abstract void OnLoadFile(Conf configuration); } diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs index 69e21d9e07..869bd56f38 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/TracerSettings.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration; using OpenTelemetry.AutoInstrumentation.Configurations.Otlp; using OpenTelemetry.AutoInstrumentation.Logging; @@ -53,14 +54,35 @@ internal class TracerSettings : Settings /// public OtlpSettings? OtlpSettings { get; private set; } - protected override void OnLoad(Configuration configuration) + /// + /// Gets tracing Zipkin Settings. + /// + public ZipkinExporterConfig? ZipkinSettings { get; private set; } + + /// + /// Gets tracing Batch Processor Configuration. + /// + public BatchProcessorConfig BatchProcessorConfig { get; private set; } = new(); + + protected override void OnLoadEnvVar(Configuration configuration) { + BatchProcessorConfig = new BatchProcessorConfig( + scheduleDelay: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.ScheduleDelay), + exportTimeout: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.ExportTimeout), + maxQueueSize: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.MaxQueueSize), + maxExportBatchSize: configuration.GetInt32(ConfigurationKeys.Traces.BatchSpanProcessorConfig.MaxExportBatchSize)); + TracesExporters = ParseTracesExporter(configuration); if (TracesExporters.Contains(TracesExporter.Otlp)) { OtlpSettings = new OtlpSettings(OtlpSignalType.Traces, configuration); } + if (TracesExporters.Contains(TracesExporter.Zipkin)) + { + ZipkinSettings = new ZipkinExporterConfig(configuration.GetString(ConfigurationKeys.Traces.ZipkinEndpoint)); + } + var instrumentationEnabledByDefault = configuration.GetBool(ConfigurationKeys.Traces.TracesInstrumentationEnabled) ?? configuration.GetBool(ConfigurationKeys.InstrumentationEnabled) ?? true; @@ -93,6 +115,58 @@ protected override void OnLoad(Configuration configuration) InstrumentationOptions = new InstrumentationOptions(configuration); } + protected override void OnLoadFile(Conf configuration) + { + if (configuration.TracerProvider != null && + configuration.TracerProvider.Processors != null && + configuration.TracerProvider.Processors.TryGetValue("batch", out var batchProcessorConfig)) + { + TracesEnabled = true; + BatchProcessorConfig = batchProcessorConfig; + var exporters = BatchProcessorConfig.Exporter; + var tracesExporters = new List(); + if (exporters != null) + { + if (exporters.OtlpGrpc != null) + { + tracesExporters.Add(TracesExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Traces, exporters.OtlpGrpc); + } + + if (exporters.OtlpHttp != null) + { + tracesExporters.Add(TracesExporter.Otlp); + OtlpSettings = new OtlpSettings(OtlpSignalType.Traces, exporters.OtlpHttp); + } + + if (exporters.Zipkin != null) + { + tracesExporters.Add(TracesExporter.Zipkin); + ZipkinSettings = exporters.Zipkin; + } + + if (exporters.Console != null) + { + tracesExporters.Add(TracesExporter.Console); + } + + TracesExporters = tracesExporters; + } + } + else + { + TracesEnabled = false; + } + + var tracesInstrumentations = configuration.InstrumentationDevelopment?.DotNet?.Traces; + if (tracesInstrumentations != null) + { + EnabledInstrumentations = tracesInstrumentations.GetEnabledInstrumentations(); + } + + InstrumentationOptions = new InstrumentationOptions(tracesInstrumentations); + } + private static IReadOnlyList ParseTracesExporter(Configuration configuration) { var tracesExporterEnvVar = configuration.GetString(ConfigurationKeys.Traces.Exporter); 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..8d858bca27 100644 --- a/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs +++ b/src/OpenTelemetry.AutoInstrumentation/HeadersCapture/HeaderConfigurationExtensions.cs @@ -9,11 +9,28 @@ 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(); + } + + public static IReadOnlyList ParseHeaders(string? headersList, Func stringToHeaderCacheConverter) + { + if (string.IsNullOrWhiteSpace(headersList)) + { + return []; + } + + var headers = headersList!.Split([Constants.ConfigurationValues.Separator], StringSplitOptions.RemoveEmptyEntries); + + if (headers.Length == 0) + { + return []; } return headers.Select(stringToHeaderCacheConverter).ToArray(); diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs index 53d0d9f079..51cdceaa2d 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs @@ -134,7 +134,7 @@ public static void Initialize() var builder = Sdk .CreateTracerProviderBuilder() .InvokePluginsBefore(_pluginManager) - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors, GeneralSettings.Value.Resources)) .UseEnvironmentVariables(LazyInstrumentationLoader, TracerSettings.Value, _pluginManager) .InvokePluginsAfter(_pluginManager); @@ -156,7 +156,7 @@ public static void Initialize() var builder = Sdk .CreateMeterProviderBuilder() .InvokePluginsBefore(_pluginManager) - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors, GeneralSettings.Value.Resources)) .UseEnvironmentVariables(LazyInstrumentationLoader, MetricSettings.Value, _pluginManager) .InvokePluginsAfter(_pluginManager); @@ -355,7 +355,7 @@ private static void InitializeBufferProcessing(TimeSpan exportInterval, TimeSpan // TODO: plugins support var loggerProvider = loggerProviderBuilder! - .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors)) + .SetResourceBuilder(ResourceConfigurator.CreateResourceBuilder(GeneralSettings.Value.EnabledResourceDetectors, GeneralSettings.Value.Resources)) .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..17fc623f22 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.GeneralSettings.Value.EnabledResourceDetectors, Instrumentation.GeneralSettings.Value.Resources)); options.IncludeFormattedMessage = settings.IncludeFormattedMessage; diff --git a/src/OpenTelemetry.AutoInstrumentation/Vendors/YamlDotNet/Core/Tokens/BlockMappingStart.cs b/src/OpenTelemetry.AutoInstrumentation/Vendors/YamlDotNet/Core/Tokens/BlockMappingStart.cs index 85a2ad3860..f52242160c 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Vendors/YamlDotNet/Core/Tokens/BlockMappingStart.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Vendors/YamlDotNet/Core/Tokens/BlockMappingStart.cs @@ -1,4 +1,4 @@ -// This file is part of YamlDotNet - A .NET library for YAML. +// This file is part of YamlDotNet - A .NET library for YAML. // Copyright (c) Antoine Aubry and contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/test/IntegrationTests/FileBased/LogsTests.cs b/test/IntegrationTests/FileBased/LogsTests.cs new file mode 100644 index 0000000000..a9f51a4593 --- /dev/null +++ b/test/IntegrationTests/FileBased/LogsTests.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET +using IntegrationTests.Helpers; +using OpenTelemetry.Proto.Logs.V1; +using Xunit.Abstractions; + +namespace IntegrationTests.FileBased; + +public class LogsTests : TestHelper +{ + public LogsTests(ITestOutputHelper output) + : base("Logs", output) + { + } + + [Fact] + public void SubmitLogs() + { + using var collector = new MockLogsCollector(Output, 4318); + SetExporter(collector); + + // When includeFormattedMessage is set to false + // LogRecord is not parsed and body will not have data. + // This is a default collector behavior. + collector.Expect(logRecord => + { + var logsAsString = Convert.ToString(logRecord); + return logsAsString != null && logsAsString.Contains("TestApplication.Logs.Controllers.TestController"); + }); + + SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper"); + EnableFileBasedConfigWithDefaultPath(); + + RunTestApplication(); + + collector.AssertExpectations(); + } +} +#endif + diff --git a/test/IntegrationTests/FileBased/MetricsTests.cs b/test/IntegrationTests/FileBased/MetricsTests.cs new file mode 100644 index 0000000000..8868bc0d2f --- /dev/null +++ b/test/IntegrationTests/FileBased/MetricsTests.cs @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using IntegrationTests.Helpers; +using Xunit.Abstractions; + +namespace IntegrationTests.FileBased; + +public class MetricsTests : TestHelper +{ + public MetricsTests(ITestOutputHelper output) + : base("Smoke", output) + { + SetEnvironmentVariable("LONG_RUNNING", "true"); + } + + [Fact] + [Trait("Category", "EndToEnd")] + public void SubmitMetrics() + { + using var collector = new MockMetricsCollector(Output, 4318); + SetExporter(collector); + collector.Expect("OpenTelemetry.Instrumentation.Process"); + EnableFileBasedConfigWithDefaultPath(); + + using var process = StartTestApplication(); + try + { + collector.AssertExpectations(); + } + finally + { + process?.Kill(); + } + } +} diff --git a/test/IntegrationTests/FileBased/TrasesTests.cs b/test/IntegrationTests/FileBased/TrasesTests.cs new file mode 100644 index 0000000000..1f5bf6983a --- /dev/null +++ b/test/IntegrationTests/FileBased/TrasesTests.cs @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET +using IntegrationTests.Helpers; +using OpenTelemetry.Proto.Trace.V1; +using Xunit.Abstractions; + +namespace IntegrationTests.FileBased; + +public class TrasesTests : TestHelper +{ + public TrasesTests(ITestOutputHelper output) + : base("Http", output) + { + } + + [Fact] + [Trait("Category", "EndToEnd")] + public void SubmitTraces() + { + using var collector = new MockSpansCollector(Output, 4318); + SetExporter(collector); + Span? clientSpan = null; + collector.Expect("System.Net.Http", span => + { + clientSpan = span; + return true; + }); + + Span? serverSpan = null; + collector.Expect("Microsoft.AspNetCore", span => + { + serverSpan = span; + return true; + }); + + EnableFileBasedConfigWithDefaultPath(); + + RunTestApplication(); + + collector.AssertExpectations(); + + // testing context propagation via trace hierarchy + Assert.True(clientSpan!.ParentSpanId.IsEmpty, "parent of client span should be empty"); + Assert.Equal(clientSpan.SpanId, serverSpan!.ParentSpanId); + } +} +#endif + diff --git a/test/IntegrationTests/Helpers/MockLogsCollector.cs b/test/IntegrationTests/Helpers/MockLogsCollector.cs index 6121a9b30c..9d467b60d0 100644 --- a/test/IntegrationTests/Helpers/MockLogsCollector.cs +++ b/test/IntegrationTests/Helpers/MockLogsCollector.cs @@ -35,6 +35,17 @@ public MockLogsCollector(ITestOutputHelper output, string host = "localhost") #endif } + public MockLogsCollector(ITestOutputHelper output, int port, string host = "localhost") + { + _output = output; + +#if NETFRAMEWORK + _listener = new(output, HandleHttpRequests, host, port, "/v1/logs/"); +#else + _listener = new(output, nameof(MockLogsCollector), port, new PathHandler(HandleHttpRequests, "/v1/logs")); +#endif + } + /// /// Gets the TCP port that this collector is listening on. /// diff --git a/test/IntegrationTests/Helpers/MockMetricsCollector.cs b/test/IntegrationTests/Helpers/MockMetricsCollector.cs index f8227fbdad..7cbf4ffc40 100644 --- a/test/IntegrationTests/Helpers/MockMetricsCollector.cs +++ b/test/IntegrationTests/Helpers/MockMetricsCollector.cs @@ -34,6 +34,16 @@ public MockMetricsCollector(ITestOutputHelper output, string host = "localhost") #endif } + public MockMetricsCollector(ITestOutputHelper output, int port, string host = "localhost") + { + _output = output; +#if NETFRAMEWORK + _listener = new(output, HandleHttpRequests, host, port, "/v1/metrics/"); +#else + _listener = new(output, nameof(MockMetricsCollector), port, new PathHandler(HandleHttpRequests, "/v1/metrics")); +#endif + } + /// /// Gets the TCP port that this collector is listening on. /// diff --git a/test/IntegrationTests/Helpers/MockSpansCollector.cs b/test/IntegrationTests/Helpers/MockSpansCollector.cs index 64b10ece25..7193981bb6 100644 --- a/test/IntegrationTests/Helpers/MockSpansCollector.cs +++ b/test/IntegrationTests/Helpers/MockSpansCollector.cs @@ -35,6 +35,17 @@ public MockSpansCollector(ITestOutputHelper output, string host = "localhost") #endif } + public MockSpansCollector(ITestOutputHelper output, int port, string host = "localhost") + { + _output = output; + +#if NETFRAMEWORK + _listener = new TestHttpServer(output, HandleHttpRequests, host, port, "/v1/traces/"); +#else + _listener = new TestHttpServer(output, nameof(MockSpansCollector), port, new PathHandler(HandleHttpRequests, "/v1/traces")); +#endif + } + /// /// Gets the TCP port that this collector is listening on. /// @@ -95,6 +106,7 @@ public void AssertExpectations(TimeSpan? timeout = null) continue; } + Console.WriteLine(resourceSpans); expectationsMet.Add(resourceSpans); missingExpectations.RemoveAt(i); found = true; 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/IntegrationTests/Helpers/TestHttpServer.AspNetCore.cs b/test/IntegrationTests/Helpers/TestHttpServer.AspNetCore.cs index d062f5fe48..e4c29cf0df 100644 --- a/test/IntegrationTests/Helpers/TestHttpServer.AspNetCore.cs +++ b/test/IntegrationTests/Helpers/TestHttpServer.AspNetCore.cs @@ -48,6 +48,36 @@ public TestHttpServer(ITestOutputHelper output, string name, params PathHandler[ WriteOutput($"Listening on: {string.Join(',', pathHandlers.Select(handler => $"{address}{handler.Path}"))}"); } + public TestHttpServer(ITestOutputHelper output, string name, int port, params PathHandler[] pathHandlers) + { + _output = output; + _name = name; + Port = port; + + _listener = new WebHostBuilder() + .UseKestrel(options => + options.Listen(IPAddress.Loopback, port)) + .Configure(x => + { + foreach (var pathHandler in pathHandlers) + { + x.Map(pathHandler.Path, x => + { + x.Run(pathHandler.Delegate); + }); + } + }) + .Build(); + + _listener.Start(); + + var address = _listener.ServerFeatures! + .Get()! + .Addresses + .First(); + WriteOutput($"Listening on: {string.Join(',', pathHandlers.Select(handler => $"{address}{handler.Path}"))}"); + } + /// /// Gets the TCP port that this listener is listening on. /// diff --git a/test/IntegrationTests/Helpers/TestHttpServer.NetFramework.cs b/test/IntegrationTests/Helpers/TestHttpServer.NetFramework.cs index b0eadb7398..59e669b70c 100644 --- a/test/IntegrationTests/Helpers/TestHttpServer.NetFramework.cs +++ b/test/IntegrationTests/Helpers/TestHttpServer.NetFramework.cs @@ -32,6 +32,23 @@ public TestHttpServer(ITestOutputHelper output, Action requ _listenerThread.Start(); } + public TestHttpServer(ITestOutputHelper output, Action requestHandler, string host, int port, string sufix = "/") + { + _output = output; + _requestHandler = requestHandler; + + Port = port; + + _listener = new HttpListener(); + _listener.Start(); + var prefix = new UriBuilder("http", host, port, sufix).ToString(); // See https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistenerprefixcollection.add?redirectedfrom=MSDN&view=net-6.0#remarks for info about the host value. + _listener.Prefixes.Add(prefix); + WriteOutput($"Listening on '{prefix}'"); + + _listenerThread = new Thread(HandleHttpRequests); + _listenerThread.Start(); + } + /// /// Gets the TCP port that this listener is listening on. /// 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/DetectorsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/DetectorsTests.cs new file mode 100644 index 0000000000..fb44a2fc64 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/DetectorsTests.cs @@ -0,0 +1,47 @@ +// 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 DetectorsTests +{ + [Fact] + public void GetEnabledResourceDetector_ReturnsCorrectEnabledDetectors() + { + var detectors = new DotNetDetectors + { + AzureAppService = new object(), + Host = new object(), + OperatingSystem = null, + Process = new object(), + ProcessRuntime = null + }; + +#if NET + detectors.Container = new object(); +#endif + + var result = detectors.GetEnabledResourceDetector(); + + var expected = new List + { + ResourceDetector.AzureAppService, + ResourceDetector.Host, + ResourceDetector.Process + }; + +#if NET + expected.Add(ResourceDetector.Container); +#endif + + Assert.Equal(expected.Count, result.Count); + foreach (var detector in expected) + { + Assert.Contains(detector, result); + } + } +} 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..b44f81cc5b --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedGeneralSettingsTests.cs @@ -0,0 +1,193 @@ +// 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 Conf { Resource = resource }; + var settings = new GeneralSettings(); + + 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"]); + + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroNameAttributeName)); + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName)); + } + + [Fact] + public void LoadFile_AttributesListOnly_IsParsedCorrectly() + { + var resource = new ResourceConfiguration + { + AttributesList = "key1=value1,key2=value2" + }; + + var conf = new Conf { Resource = resource }; + var settings = new GeneralSettings(); + + 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 Conf { Resource = resource }; + var settings = new GeneralSettings(); + + 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_BaseAttributes_AreAlwaysIncluded() + { + var settings = new GeneralSettings(); + settings.LoadFile(new Conf()); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroNameAttributeName)); + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName)); + } + + [Fact] + public void LoadFile_AttributesListOverwrittenByAttributes() + { + var resource = new ResourceConfiguration + { + AttributesList = "key1=fromList", + Attributes = + [ + new() { Name = "key1", Value = "fromAttributes" } + ] + }; + + var conf = new Conf { Resource = resource }; + var settings = new GeneralSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.Equal("fromAttributes", result["key1"]); + } + + [Fact] + public void LoadFile_NullAttributesHandledGracefully() + { + var resource = new ResourceConfiguration + { + AttributesList = null, + Attributes = null + }; + + var conf = new Conf { Resource = resource }; + var settings = new GeneralSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroNameAttributeName)); + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName)); + } + + [Fact] + public void LoadFile_EmptyAttributesList_StillIncludesBaseAttributes() + { + var resource = new ResourceConfiguration + { + AttributesList = string.Empty + }; + + var conf = new Conf { Resource = resource }; + var settings = new GeneralSettings(); + + settings.LoadFile(conf); + + var result = settings.Resources.ToDictionary(kv => kv.Key, kv => kv.Value); + + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroNameAttributeName)); + Assert.True(result.ContainsKey(Constants.DistributionAttributes.TelemetryDistroVersionAttributeName)); + } + + [Fact] + public void LoadFile_SetsSetupSdkFromDisabledFlag() + { + var conf = new Conf + { + Disabled = true + }; + + var settings = new GeneralSettings(); + settings.LoadFile(conf); + + Assert.False(settings.SetupSdk); + } + + [Fact] + public void LoadFile_SetsEnabledResourceDetectors() + { + var conf = new Conf + { + Resource = new ResourceConfiguration + { + DetectionDevelopment = new DetectionDevelopment + { + Detectors = new DotNetDetectors + { + OperatingSystem = new object(), + AzureAppService = new object() + } + } + } + }; + + var settings = new GeneralSettings(); + settings.LoadFile(conf); + + Assert.Contains(ResourceDetector.OperatingSystem, settings.EnabledResourceDetectors); + Assert.Contains(ResourceDetector.AzureAppService, settings.EnabledResourceDetectors); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedLogsSettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedLogsSettingsTests.cs new file mode 100644 index 0000000000..7ae6579e21 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedLogsSettingsTests.cs @@ -0,0 +1,245 @@ +// 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 FilebasedLogsSettingsTests +{ + [Fact] + public void LoadFile_SetsBatchProcessorAndExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig + { + Endpoint = "http://localhost:4317/" + } + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + ScheduleDelay = 2000, + ExportTimeout = 10000, + MaxQueueSize = 1024, + MaxExportBatchSize = 256, + Exporter = exporter + }; + + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.LogsEnabled); + Assert.Single(settings.LogExporters); + Assert.Contains(LogExporter.Otlp, settings.LogExporters); + + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4317/", settings.OtlpSettings.Endpoint.ToString()); + + Assert.Equal(2000, settings.BatchProcessorConfig.ScheduleDelay); + Assert.Equal(10000, settings.BatchProcessorConfig.ExportTimeout); + Assert.Equal(1024, settings.BatchProcessorConfig.MaxQueueSize); + Assert.Equal(256, settings.BatchProcessorConfig.MaxExportBatchSize); + } + + [Fact] + public void LoadFile_DisablesLogs_WhenNoBatchProcessorConfigured() + { + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = [] + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.False(settings.LogsEnabled); + Assert.Empty(settings.LogExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsOtlpHttpExporterCorrectly() + { + var exporter = new ExporterConfig + { + OtlpHttp = new OtlpHttpExporterConfig + { + Endpoint = "http://localhost:4318/" + } + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.LogsEnabled); + Assert.Single(settings.LogExporters); + Assert.Contains(LogExporter.Otlp, settings.LogExporters); + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4318/", settings.OtlpSettings.Endpoint.ToString()); + } + + [Fact] + public void LoadFile_SetsConsoleExporterCorrectly() + { + var exporter = new ExporterConfig + { + Console = new object() + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.LogsEnabled); + Assert.Single(settings.LogExporters); + Assert.Contains(LogExporter.Console, settings.LogExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsMultipleExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig { Endpoint = "http://localhost:4317/" }, + Console = new object() + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.LogsEnabled); + Assert.Equal(2, settings.LogExporters.Count); + Assert.Contains(LogExporter.Otlp, settings.LogExporters); + Assert.Contains(LogExporter.Console, settings.LogExporters); + } + + [Fact] + public void LoadFile_HandlesNullExporterGracefully() + { + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = null + }; + + var conf = new Conf + { + LoggerProvider = new LoggerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.LogsEnabled); + Assert.Empty(settings.LogExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsEnabledInstrumentations_IfPresent() + { + var instrumentation = new DotNetInstrumentation + { + Logs = new DotNetLogs + { + ILogger = new object(), + Log4Net = new object(), + } + }; + + var conf = new Conf + { + InstrumentationDevelopment = new InstrumentationDevelopment + { + DotNet = instrumentation + } + }; + + var settings = new LogSettings(); + + settings.LoadFile(conf); + + Assert.NotNull(settings.EnabledInstrumentations); + Assert.Contains(LogInstrumentation.ILogger, settings.EnabledInstrumentations); + Assert.Contains(LogInstrumentation.Log4Net, settings.EnabledInstrumentations); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedMetricsSettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedMetricsSettingsTests.cs new file mode 100644 index 0000000000..9a425585ed --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedMetricsSettingsTests.cs @@ -0,0 +1,275 @@ +// 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 FilebasedMetricsSettingsTests +{ + [Fact] + public void LoadFile_SetsBatchProcessorAndExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig + { + Endpoint = "http://localhost:4317/" + } + }; + + var readerConfig = new ReaderConfiguration + { + Interval = 1000, + Timeout = 30000, + Exporter = exporter + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "periodic", readerConfig } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Single(settings.MetricExporters); + Assert.Contains(MetricsExporter.Otlp, settings.MetricExporters); + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4317/", settings.OtlpSettings.Endpoint.ToString()); + + Assert.Equal(1000, settings.ReaderConfiguration.Interval); + Assert.Equal(30000, settings.ReaderConfiguration.Timeout); + } + + [Fact] + public void LoadFile_DisablesMetrics_WhenNoReadersConfigured() + { + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = [] + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.False(settings.MetricsEnabled); + Assert.Empty(settings.MetricExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsOtlpHttpExporterCorrectly() + { + var exporter = new ExporterConfig + { + OtlpHttp = new OtlpHttpExporterConfig + { + Endpoint = "http://localhost:4318/" + } + }; + + var readerConfig = new ReaderConfiguration + { + Exporter = exporter + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "periodic", readerConfig } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Single(settings.MetricExporters); + Assert.Contains(MetricsExporter.Otlp, settings.MetricExporters); + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4318/", settings.OtlpSettings.Endpoint.ToString()); + } + + [Fact] + public void LoadFile_SetsConsoleExporterCorrectly() + { + var exporter = new ExporterConfig + { + Console = new object() + }; + + var readerConfig = new ReaderConfiguration + { + Exporter = exporter + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "periodic", readerConfig } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Single(settings.MetricExporters); + Assert.Contains(MetricsExporter.Console, settings.MetricExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsPrometheusExporterCorrectly_FromPullReader() + { + var exporter = new ExporterConfig + { + Prometheus = new object() + }; + + var pullReader = new ReaderConfiguration + { + Exporter = exporter + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "pull", pullReader } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Single(settings.MetricExporters); + Assert.Contains(MetricsExporter.Prometheus, settings.MetricExporters); + } + + [Fact] + public void LoadFile_SetsMultipleExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig { Endpoint = "http://localhost:4317/" }, + Console = new object() + }; + + var periodicReader = new ReaderConfiguration + { + Exporter = exporter + }; + + var pullReader = new ReaderConfiguration + { + Exporter = new ExporterConfig + { + Prometheus = new object() + } + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "periodic", periodicReader }, + { "pull", pullReader } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Equal(3, settings.MetricExporters.Count); + Assert.Contains(MetricsExporter.Otlp, settings.MetricExporters); + Assert.Contains(MetricsExporter.Console, settings.MetricExporters); + Assert.Contains(MetricsExporter.Prometheus, settings.MetricExporters); + } + + [Fact] + public void LoadFile_HandlesNullExporterGracefully() + { + var readerConfig = new ReaderConfiguration + { + Exporter = null + }; + + var conf = new Conf + { + MeterProvider = new MeterProviderConfiguration + { + Readers = new Dictionary + { + { "periodic", readerConfig } + } + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.True(settings.MetricsEnabled); + Assert.Empty(settings.MetricExporters); + Assert.Null(settings.OtlpSettings); + } + + [Fact] + public void LoadFile_SetsEnabledInstrumentations_IfPresent() + { + var instrumentation = new DotNetInstrumentation + { + Metrics = new DotNetMetrics + { + HttpClient = new object(), + NetRuntime = new object() + } + }; + + var conf = new Conf + { + InstrumentationDevelopment = new InstrumentationDevelopment + { + DotNet = instrumentation + } + }; + + var settings = new MetricSettings(); + settings.LoadFile(conf); + + Assert.NotNull(settings.EnabledInstrumentations); + Assert.Contains(MetricInstrumentation.HttpClient, settings.EnabledInstrumentations); + Assert.Contains(MetricInstrumentation.NetRuntime, settings.EnabledInstrumentations); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedSdkSettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedSdkSettingsTests.cs new file mode 100644 index 0000000000..3845ad203b --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedSdkSettingsTests.cs @@ -0,0 +1,119 @@ +// 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 FilebasedSdkSettingsTests +{ + [Fact] + public void Loads_AttributeLimits() + { + var config = new Conf + { + AttributeLimits = new AttributeLimits + { + AttributeCountLimit = 100, + AttributeValueLengthLimit = 256, + } + }; + + var settings = new SdkSettings(); + + settings.LoadFile(config); + + Assert.Equal(100, settings.AttributeLimits.AttributeCountLimit); + Assert.Equal(256, settings.AttributeLimits.AttributeValueLengthLimit); + } + + [Fact] + public void Returns_When_Propagator_IsNull() + { + var config = new Conf { Propagator = null }; + var settings = new SdkSettings(); + + settings.LoadFile(config); + + Assert.Empty(settings.Propagators); + } + + [Fact] + public void Adds_Unique_Propagators_From_Composite() + { + var config = new Conf + { + Propagator = new PropagatorConfiguration + { + Composite = new Dictionary + { + { Constants.ConfigurationValues.Propagators.W3CTraceContext, new object() }, + { Constants.ConfigurationValues.Propagators.W3CBaggage, new object() } + } + } + }; + var settings = new SdkSettings(); + + settings.LoadFile(config); + + Assert.Contains(Propagator.W3CTraceContext, settings.Propagators); + Assert.Contains(Propagator.W3CBaggage, settings.Propagators); + } + + [Fact] + public void Adds_Propagators_From_CompositeList() + { + var config = new Conf + { + Propagator = new PropagatorConfiguration + { + CompositeList = "tracecontext,baggage" + } + }; + var settings = new SdkSettings(); + + settings.LoadFile(config); + + Assert.Contains(Propagator.W3CTraceContext, settings.Propagators); + Assert.Contains(Propagator.W3CBaggage, settings.Propagators); + } + + [Fact] + public void Ignores_Duplicates_From_Composite_And_CompositeList() + { + var config = new Conf + { + Propagator = new PropagatorConfiguration + { + Composite = new Dictionary + { + { "tracecontext", new object() } + }, + CompositeList = "tracecontext,baggage" + } + }; + var settings = new SdkSettings(); + + settings.LoadFile(config); + + Assert.Equal(2, settings.Propagators.Count); + } + + [Fact] + public void Skips_Unknown_Propagator() + { + var config = new Conf + { + Propagator = new PropagatorConfiguration + { + CompositeList = "custom" + } + }; + var settings = new SdkSettings(); + + settings.LoadFile(config); + Assert.Empty(settings.Propagators); + } +} diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedTracesSettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedTracesSettingsTests.cs new file mode 100644 index 0000000000..be4512f4ae --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/FilebasedTracesSettingsTests.cs @@ -0,0 +1,255 @@ +// 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 FilebasedTracesSettingsTests +{ + [Fact] + public void LoadFile_SetsBatchProcessorAndExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig + { + Endpoint = "http://localhost:4317/" + }, + Zipkin = new ZipkinExporterConfig("http://localhost:9411/") + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + ScheduleDelay = 1000, + ExportTimeout = 30000, + MaxQueueSize = 2048, + MaxExportBatchSize = 512, + Exporter = exporter + }; + + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.TracesEnabled); + Assert.Equal(2, settings.TracesExporters.Count); + Assert.Contains(TracesExporter.Otlp, settings.TracesExporters); + Assert.Contains(TracesExporter.Zipkin, settings.TracesExporters); + + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.ZipkinSettings); + + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4317/", settings.OtlpSettings.Endpoint.ToString()); + Assert.Equal("http://localhost:9411/", settings.ZipkinSettings.Endpoint); + + Assert.Equal(1000, settings.BatchProcessorConfig.ScheduleDelay); + Assert.Equal(30000, settings.BatchProcessorConfig.ExportTimeout); + Assert.Equal(2048, settings.BatchProcessorConfig.MaxQueueSize); + Assert.Equal(512, settings.BatchProcessorConfig.MaxExportBatchSize); + } + + [Fact] + public void LoadFile_DisablesTraces_WhenNoBatchProcessorConfigured() + { + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = [] + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.False(settings.TracesEnabled); + Assert.Empty(settings.TracesExporters); + Assert.Null(settings.OtlpSettings); + Assert.Null(settings.ZipkinSettings); + } + + [Fact] + public void LoadFile_SetsOtlpHttpExporterCorrectly() + { + var exporter = new ExporterConfig + { + OtlpHttp = new OtlpHttpExporterConfig + { + Endpoint = "http://localhost:4318/" + } + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.TracesEnabled); + Assert.Single(settings.TracesExporters); + Assert.Contains(TracesExporter.Otlp, settings.TracesExporters); + Assert.NotNull(settings.OtlpSettings); + Assert.NotNull(settings.OtlpSettings.Endpoint); + Assert.Equal("http://localhost:4318/", settings.OtlpSettings.Endpoint.ToString()); + } + + [Fact] + public void LoadFile_SetsConsoleExporterCorrectly() + { + var exporter = new ExporterConfig + { + Console = new object() + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.TracesEnabled); + Assert.Single(settings.TracesExporters); + Assert.Contains(TracesExporter.Console, settings.TracesExporters); + Assert.Null(settings.OtlpSettings); + Assert.Null(settings.ZipkinSettings); + } + + [Fact] + public void LoadFile_SetsMultipleExportersCorrectly() + { + var exporter = new ExporterConfig + { + OtlpGrpc = new OtlpGrpcExporterConfig { Endpoint = "http://localhost:4317/" }, + Console = new object(), + Zipkin = new ZipkinExporterConfig("http://localhost:9411/") + }; + + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = exporter + }; + + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.TracesEnabled); + Assert.Equal(3, settings.TracesExporters.Count); + Assert.Contains(TracesExporter.Otlp, settings.TracesExporters); + Assert.Contains(TracesExporter.Console, settings.TracesExporters); + Assert.Contains(TracesExporter.Zipkin, settings.TracesExporters); + } + + [Fact] + public void LoadFile_HandlesNullExporterGracefully() + { + var batchProcessorConfig = new BatchProcessorConfig + { + Exporter = null + }; + + var conf = new Conf + { + TracerProvider = new TracerProviderConfiguration + { + Processors = new Dictionary + { + { "batch", batchProcessorConfig } + } + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.True(settings.TracesEnabled); + Assert.Empty(settings.TracesExporters); + Assert.Null(settings.OtlpSettings); + Assert.Null(settings.ZipkinSettings); + } + + [Fact] + public void LoadFile_SetsEnabledInstrumentations_IfPresent() + { + var instrumentation = new DotNetInstrumentation + { + Traces = new DotNetTraces + { + Azure = new object(), + Elasticsearch = new object() + } + }; + + var conf = new Conf + { + InstrumentationDevelopment = new InstrumentationDevelopment + { + DotNet = instrumentation + } + }; + + var settings = new TracerSettings(); + + settings.LoadFile(conf); + + Assert.NotNull(settings.EnabledInstrumentations); + Assert.Contains(TracerInstrumentation.Azure, settings.EnabledInstrumentations); + Assert.Contains(TracerInstrumentation.Elasticsearch, settings.EnabledInstrumentations); + } +} 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..5edab78475 --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFile.yaml @@ -0,0 +1,107 @@ +file_format: "0.4" +disabled: false +log_level: info +fail_fast: true +attribute_limits: + attribute_value_length_limit: 4096 + attribute_count_limit: 128 +propagator: + composite: + tracecontext: + baggage: + b3: + b3multi: + composite_list: tracecontext,baggage + +tracer_provider: + processors: + batch: + schedule_delay: 5000 + export_timeout: 30000 + max_queue_size: 2048 + max_export_batch_size: 512 + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/traces + timeout: 10000 + headers: [] + headers_list: + +meter_provider: + readers: + periodic: + interval: 60000 + timeout: 30000 + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/metrics + timeout: 10000 + headers: [] + headers_list: + +logger_provider: + processors: + batch: + schedule_delay: 1000 + export_timeout: 30000 + max_queue_size: 2048 + max_export_batch_size: 512 + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/logs + timeout: 10000 + headers: [] + headers_list: + +resource: + attributes: + - name: service.name + value: unknown_service + attributes_list: + detection/development: + detectors: + azureappservice: + container: + host: + operatingsystem: + process: + processruntime: + + +instrumentation/development: + dotnet: + traces: + aspnet: + aspnetcore: + azure: + elasticsearch: + elastictransport: + entityframeworkcore: + graphql: + grpcnetclient: + httpclient: + kafka: + masstransit: + mongodb: + mysqlconnector: + mysqldata: + npgsql: + nservicebus: + oraclemda: + rabbitmq: + quartz: + sqlclient: + stackexchangeredis: + wcfclient: + wcfservice: + metrics: + aspnet: + aspnetcore: + httpclient: + netruntime: + nservicebus: + process: + sqlclient: + logs: + ilogger: + log4net: 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..a64ce312fd --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/Files/TestFileEnvVars.yaml @@ -0,0 +1,61 @@ +file_format: "0.4" +disabled: ${OTEL_SDK_DISABLED} +log_level: ${OTEL_LOG_LEVEL} + +resource: + attributes: + - name: service.name + value: ${OTEL_SERVICE_NAME} + attributes_list: ${OTEL_RESOURCE_ATTRIBUTES} + detection/development: + detectors: + azureappservice: + container: + host: + operatingsystem: + process: + processruntime: + +attribute_limits: + attribute_value_length_limit: ${OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT} + attribute_count_limit: ${OTEL_ATTRIBUTE_COUNT_LIMIT} +propagator: + composite_list: ${OTEL_PROPAGATORS} + +tracer_provider: + processors: + batch: + schedule_delay: ${OTEL_BSP_SCHEDULE_DELAY} + export_timeout: ${OTEL_BSP_EXPORT_TIMEOUT} + max_queue_size: ${OTEL_BSP_MAX_QUEUE_SIZE} + max_export_batch_size: ${OTEL_BSP_MAX_EXPORT_BATCH_SIZE} + exporter: + otlp_http: + endpoint: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} + timeout: ${OTEL_EXPORTER_OTLP_TRACES_TIMEOUT} + headers_list: ${OTEL_EXPORTER_OTLP_TRACES_HEADERS} + + +meter_provider: + readers: + periodic: + interval: ${OTEL_METRIC_EXPORT_INTERVAL} + timeout: ${OTEL_METRIC_EXPORT_TIMEOUT} + exporter: + otlp_http: + endpoint: ${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT} + timeout: ${OTEL_EXPORTER_OTLP_METRICS_TIMEOUT} + headers_list: ${OTEL_EXPORTER_OTLP_METRICS_HEADERS} + +logger_provider: + processors: + batch: + schedule_delay: ${OTEL_BLRP_SCHEDULE_DELAY} + export_timeout: ${OTEL_BLRP_EXPORT_TIMEOUT} + max_queue_size: ${OTEL_BLRP_MAX_QUEUE_SIZE} + max_export_batch_size: ${OTEL_BLRP_MAX_EXPORT_BATCH_SIZE} + exporter: + otlp_http: + endpoint: ${OTEL_EXPORTER_OTLP_LOGS_ENDPOINT} + timeout: ${OTEL_EXPORTER_OTLP_LOGS_TIMEOUT} + headers_list: ${OTEL_EXPORTER_OTLP_LOGS_HEADERS} \ No newline at end of file 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..b15b0e441a --- /dev/null +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configurations/FileBased/ParserTests.cs @@ -0,0 +1,295 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Reflection; +using OpenTelemetry.AutoInstrumentation.Configurations.FileBasedConfiguration.Parser; +using Vendors.YamlDotNet.Serialization; +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("0.4", config.FileFormat); + Assert.False(config.Disabled); + Assert.Equal("info", config.LogLevel); + Assert.True(config.FailFast); + + Assert.NotNull(config.AttributeLimits); + Assert.Equal(4096, config.AttributeLimits.AttributeValueLengthLimit); + Assert.Equal(128, config.AttributeLimits.AttributeCountLimit); + + Assert.NotNull(config.Propagator); + Assert.Equal("tracecontext,baggage", config.Propagator.CompositeList); + + var composite = config.Propagator.Composite; + Assert.NotNull(composite); + + string[] expectedCompositeKeys = [ + "tracecontext", "baggage", "b3", "b3multi", + ]; + + foreach (var key in expectedCompositeKeys) + { + Assert.True(composite.TryGetValue(key, out var value)); + } + + Assert.NotNull(config.TracerProvider); + Assert.NotNull(config.TracerProvider.Processors); + Assert.True(config.TracerProvider.Processors.ContainsKey("batch")); + var traceBatch = config.TracerProvider.Processors["batch"]; + Assert.NotNull(traceBatch); + Assert.Equal(5000, traceBatch.ScheduleDelay); + Assert.Equal(30000, traceBatch.ExportTimeout); + Assert.Equal(2048, traceBatch.MaxQueueSize); + Assert.Equal(512, traceBatch.MaxExportBatchSize); + Assert.NotNull(traceBatch.Exporter); + Assert.NotNull(traceBatch.Exporter.OtlpHttp); + var traceExporter = traceBatch.Exporter.OtlpHttp; + Assert.NotNull(traceExporter); + Assert.Equal("http://localhost:4318/v1/traces", traceExporter.Endpoint); + Assert.Equal(10000, traceExporter.Timeout); + Assert.NotNull(traceExporter.Headers); + Assert.Empty(traceExporter.Headers); + + Assert.NotNull(traceExporter.HeadersList); + Assert.Empty(traceExporter.HeadersList); + + Assert.NotNull(config.MeterProvider); + Assert.NotNull(config.MeterProvider.Readers); + Assert.True(config.MeterProvider.Readers.ContainsKey("periodic")); + var metricReader = config.MeterProvider.Readers["periodic"]; + Assert.Equal(60000, metricReader.Interval); + Assert.Equal(30000, metricReader.Timeout); + Assert.NotNull(metricReader.Exporter); + var metricExporter = metricReader.Exporter.OtlpHttp; + Assert.NotNull(metricExporter); + Assert.NotNull(metricExporter.Headers); + Assert.Empty(metricExporter.Headers); + Assert.Equal("http://localhost:4318/v1/metrics", metricExporter.Endpoint); + Assert.Equal(10000, metricExporter.Timeout); + Assert.Empty(metricExporter.Headers); + + Assert.NotNull(config.LoggerProvider); + Assert.NotNull(config.LoggerProvider.Processors); + Assert.True(config.LoggerProvider.Processors.ContainsKey("batch")); + var logBatch = config.LoggerProvider.Processors["batch"]; + Assert.Equal(1000, logBatch.ScheduleDelay); + Assert.Equal(30000, logBatch.ExportTimeout); + Assert.Equal(2048, logBatch.MaxQueueSize); + Assert.Equal(512, logBatch.MaxExportBatchSize); + var logExporter = logBatch.Exporter?.OtlpHttp; + Assert.NotNull(logExporter); + Assert.Equal("http://localhost:4318/v1/logs", logExporter.Endpoint); + Assert.Equal(10000, logExporter.Timeout); + Assert.NotNull(logExporter.Headers); + Assert.Empty(logExporter.Headers); + + 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); + + Assert.NotNull(config.Resource.DetectionDevelopment); + +#if NET + string[] expectedDetecors = [ + "azureappservice", "container", "host", "operatingsystem", "process", "processruntime" + ]; +#endif +#if NETFRAMEWORK + string[] expectedDetecors = [ + "azureappservice", "host", "operatingsystem", "process", "processruntime" + ]; +#endif + + var detectors = config.Resource.DetectionDevelopment.Detectors; + Assert.NotNull(detectors); + + foreach (var alias in expectedDetecors) + { + AssertAliasPropertyExists(detectors, alias); + } + + Assert.NotNull(config.InstrumentationDevelopment); + Assert.NotNull(config.InstrumentationDevelopment.DotNet); + +#if NET + string[] expectedTraces = [ + "aspnetcore", "azure", "elasticsearch", "elastictransport", + "entityframeworkcore", "graphql", "grpcnetclient", "httpclient", + "kafka", "masstransit", "mongodb", "mysqlconnector", + "mysqldata", "npgsql", "nservicebus", "oraclemda", "rabbitmq", + "quartz", "sqlclient", "stackexchangeredis", "wcfclient" + ]; +#endif +#if NETFRAMEWORK + string[] expectedTraces = [ + "aspnet", "azure", "elasticsearch", "elastictransport", + "grpcnetclient", "httpclient", "kafka", "mongodb", + "mysqlconnector", "npgsql", "nservicebus", "oraclemda", + "rabbitmq", "quartz", "sqlclient", "wcfclient", "wcfservice" + ]; +#endif + + var traces = config.InstrumentationDevelopment.DotNet.Traces; + Assert.NotNull(traces); + + foreach (var alias in expectedTraces) + { + AssertAliasPropertyExists(traces, alias); + } + +#if NET + string[] expectedMetrics = + [ + "aspnetcore", "httpclient", "netruntime", + "nservicebus", "process", "sqlclient" + ]; +#endif +#if NETFRAMEWORK + string[] expectedMetrics = + [ + "aspnet", "httpclient", "netruntime", + "nservicebus", "process", "sqlclient" + ]; +#endif + + var metrics = config.InstrumentationDevelopment.DotNet.Metrics; + Assert.NotNull(metrics); + + foreach (var alias in expectedMetrics) + { + AssertAliasPropertyExists(metrics, alias); + } + + string[] expectedLogs = ["ilogger", "log4net"]; + + var logs = config.InstrumentationDevelopment.DotNet.Logs; + Assert.NotNull(logs); + + foreach (var alias in expectedLogs) + { + AssertAliasPropertyExists(logs, alias); + } + } + + [Fact] + public void Parse_EnvVarYaml_ShouldPopulateModelCompletely() + { + Environment.SetEnvironmentVariable("OTEL_SDK_DISABLED", "true"); + Environment.SetEnvironmentVariable("OTEL_LOG_LEVEL", "debug"); + Environment.SetEnvironmentVariable("OTEL_SERVICE_NAME", "my‑service"); + Environment.SetEnvironmentVariable("OTEL_RESOURCE_ATTRIBUTES", "key=value"); + + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "256"); + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_COUNT_LIMIT", "256"); + Environment.SetEnvironmentVariable("OTEL_PROPAGATORS", "tracecontext,baggage"); + + Environment.SetEnvironmentVariable("OTEL_BSP_SCHEDULE_DELAY", "7000"); + Environment.SetEnvironmentVariable("OTEL_BSP_EXPORT_TIMEOUT", "35000"); + Environment.SetEnvironmentVariable("OTEL_BSP_MAX_QUEUE_SIZE", "4096"); + Environment.SetEnvironmentVariable("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "1024"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "http://collector:4318/v1/traces"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT", "15000"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "header1=value1,header2=value2"); + + Environment.SetEnvironmentVariable("OTEL_METRIC_EXPORT_INTERVAL", "65000"); + Environment.SetEnvironmentVariable("OTEL_METRIC_EXPORT_TIMEOUT", "35000"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "http://collector:4318/v1/metrics"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_TIMEOUT", "15000"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_HEADERS", "mheader=value"); + + Environment.SetEnvironmentVariable("OTEL_BLRP_SCHEDULE_DELAY", "1500"); + Environment.SetEnvironmentVariable("OTEL_BLRP_EXPORT_TIMEOUT", "40000"); + Environment.SetEnvironmentVariable("OTEL_BLRP_MAX_QUEUE_SIZE", "4096"); + Environment.SetEnvironmentVariable("OTEL_BLRP_MAX_EXPORT_BATCH_SIZE", "1024"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "http://collector:4318/v1/logs"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_LOGS_TIMEOUT", "15000"); + Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_LOGS_HEADERS", "lheader=value"); + + var config = Parser.ParseYaml("Configurations/FileBased/Files/TestFileEnvVars.yaml"); + + Assert.Equal("0.4", config.FileFormat); + Assert.True(config.Disabled); + Assert.Equal("debug", config.LogLevel); + + Assert.NotNull(config.AttributeLimits); + Assert.Equal(256, config.AttributeLimits.AttributeValueLengthLimit); + Assert.Equal(256, config.AttributeLimits.AttributeCountLimit); + + Assert.NotNull(config.Propagator); + Assert.Equal("tracecontext,baggage", config.Propagator.CompositeList); + + Assert.NotNull(config.TracerProvider); + Assert.NotNull(config.TracerProvider.Processors); + Assert.True(config.TracerProvider.Processors.ContainsKey("batch")); + var traceBatch = config.TracerProvider.Processors["batch"]; + Assert.NotNull(traceBatch); + Assert.NotNull(traceBatch.Exporter); + Assert.NotNull(traceBatch.Exporter.OtlpHttp); + var traceExporter = traceBatch.Exporter.OtlpHttp; + Assert.NotNull(traceExporter); + Assert.Equal(7000, traceBatch.ScheduleDelay); + Assert.Equal(35000, traceBatch.ExportTimeout); + Assert.Equal(4096, traceBatch.MaxQueueSize); + Assert.Equal(1024, traceBatch.MaxExportBatchSize); + Assert.Equal("http://collector:4318/v1/traces", traceExporter.Endpoint); + Assert.Equal(15000, traceBatch.Exporter.OtlpHttp.Timeout); + + Assert.NotNull(config.MeterProvider); + Assert.NotNull(config.MeterProvider.Readers); + Assert.True(config.MeterProvider.Readers.ContainsKey("periodic")); + var metricReader = config.MeterProvider.Readers["periodic"]; + Assert.Equal(65000, metricReader.Interval); + Assert.Equal(35000, metricReader.Timeout); + Assert.NotNull(metricReader.Exporter); + var metricExporter = metricReader.Exporter.OtlpHttp; + Assert.NotNull(metricExporter); + Assert.Equal("http://collector:4318/v1/metrics", metricExporter.Endpoint); + + Assert.NotNull(config.LoggerProvider); + Assert.NotNull(config.LoggerProvider.Processors); + Assert.True(config.LoggerProvider.Processors.ContainsKey("batch")); + var logBatch = config.LoggerProvider.Processors["batch"]; + Assert.Equal(1500, logBatch.ScheduleDelay); + Assert.Equal(40000, logBatch.ExportTimeout); + Assert.Equal(4096, logBatch.MaxQueueSize); + Assert.Equal(1024, logBatch.MaxExportBatchSize); + Assert.NotNull(logBatch); + Assert.NotNull(logBatch.Exporter); + Assert.NotNull(logBatch.Exporter.OtlpHttp); + var logExporter = logBatch.Exporter.OtlpHttp; + Assert.Equal("http://collector:4318/v1/logs", logExporter.Endpoint); + var serviceAttr = config.Resource?.Attributes?.First(a => a.Name == "service.name"); + Assert.NotNull(serviceAttr); + Assert.Equal("my‑service", serviceAttr.Value); + } + + private static void AssertAliasPropertyExists(T obj, string alias) + { + Assert.NotNull(obj); + + var prop = typeof(T).GetProperties() + .FirstOrDefault(p => p.GetCustomAttribute()?.Alias == alias); + + Assert.NotNull(prop); + + var value = prop.GetValue(obj); + Assert.NotNull(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..da07b98ed3 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 List(), []); 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(Array.Empty(), []); var resource = resourceBuilder.Build(); var serviceName = resource.Attributes.FirstOrDefault(a => a.Key == ServiceName).Value as string; 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 + + + diff --git a/test/test-applications/integrations/TestApplication.Http/TestApplication.Http.csproj b/test/test-applications/integrations/TestApplication.Http/TestApplication.Http.csproj index def814a846..93f93c8375 100644 --- a/test/test-applications/integrations/TestApplication.Http/TestApplication.Http.csproj +++ b/test/test-applications/integrations/TestApplication.Http/TestApplication.Http.csproj @@ -8,4 +8,10 @@ + + + PreserveNewest + + + diff --git a/test/test-applications/integrations/TestApplication.Http/config.yaml b/test/test-applications/integrations/TestApplication.Http/config.yaml new file mode 100644 index 0000000000..75dad17cff --- /dev/null +++ b/test/test-applications/integrations/TestApplication.Http/config.yaml @@ -0,0 +1,12 @@ +tracer_provider: + processors: + batch: + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/traces + +instrumentation/development: + dotnet: + traces: + aspnetcore: + httpclient: \ No newline at end of file diff --git a/test/test-applications/integrations/TestApplication.Logs/TestApplication.Logs.csproj b/test/test-applications/integrations/TestApplication.Logs/TestApplication.Logs.csproj index ce7844057a..4c87d9e79d 100644 --- a/test/test-applications/integrations/TestApplication.Logs/TestApplication.Logs.csproj +++ b/test/test-applications/integrations/TestApplication.Logs/TestApplication.Logs.csproj @@ -4,4 +4,10 @@ net9.0;net8.0 + + + PreserveNewest + + + diff --git a/test/test-applications/integrations/TestApplication.Logs/config.yaml b/test/test-applications/integrations/TestApplication.Logs/config.yaml new file mode 100644 index 0000000000..d370a547db --- /dev/null +++ b/test/test-applications/integrations/TestApplication.Logs/config.yaml @@ -0,0 +1,11 @@ +logger_provider: + processors: + batch: + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/logs + +instrumentation/development: + dotnet: + logs: + ilogger: diff --git a/test/test-applications/integrations/TestApplication.Smoke/TestApplication.Smoke.csproj b/test/test-applications/integrations/TestApplication.Smoke/TestApplication.Smoke.csproj index bf48edbe9b..03795a4d38 100644 --- a/test/test-applications/integrations/TestApplication.Smoke/TestApplication.Smoke.csproj +++ b/test/test-applications/integrations/TestApplication.Smoke/TestApplication.Smoke.csproj @@ -7,4 +7,10 @@ + + + PreserveNewest + + + diff --git a/test/test-applications/integrations/TestApplication.Smoke/config.yaml b/test/test-applications/integrations/TestApplication.Smoke/config.yaml new file mode 100644 index 0000000000..5199ddae69 --- /dev/null +++ b/test/test-applications/integrations/TestApplication.Smoke/config.yaml @@ -0,0 +1,14 @@ +meter_provider: + readers: + periodic: + interval: 100 + exporter: + otlp_http: + endpoint: http://localhost:4318/v1/metrics + +instrumentation/development: + dotnet: + metrics: + httpclient: + netruntime: + process: