diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md index cc19b4d7ec17..e5bab2b2a3a2 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/CHANGELOG.md @@ -13,6 +13,12 @@ property can be set to `false` to disable live metrics. ([#41872](https://github.com/Azure/azure-sdk-for-net/pull/41872)) +- Added an experimental feature for logs emitted within an active tracing + context to follow the Activity's sampling decision. The feature can be enabled + by setting `OTEL_DOTNET_AZURE_MONITOR_EXPERIMENTAL_ENABLE_LOG_SAMPLING` + environment variable to `true`. + ([#41665](https://github.com/Azure/azure-sdk-for-net/pull/41665)) + ### Breaking Changes ### Bugs Fixed diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs index 11aa7d65bd1a..00e375019bd5 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs @@ -51,6 +51,15 @@ public void ConfigureFailed(Exception ex) } } + [NonEvent] + public void GetEnvironmentVariableFailed(string envVarName, Exception ex) + { + if (IsEnabled(EventLevel.Error)) + { + GetEnvironmentVariableFailed(envVarName, ex.FlattenException().ToInvariantString()); + } + } + [Event(1, Message = "Failed to configure AzureMonitorOptions using the connection string from environment variables due to an exception: {0}", Level = EventLevel.Error)] public void ConfigureFailed(string exceptionMessage) => WriteEvent(1, exceptionMessage); @@ -62,5 +71,8 @@ public void ConfigureFailed(Exception ex) [Event(4, Message = "Vendor instrumentation added for: {0}.", Level = EventLevel.Verbose)] public void VendorInstrumentationAdded(string packageName) => WriteEvent(4, packageName); + + [Event(5, Message = "Failed to Read environment variable {0}, exception: {1}", Level = EventLevel.Error)] + public void GetEnvironmentVariableFailed(string envVarName, string exceptionMessage) => WriteEvent(5, envVarName, exceptionMessage); } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/LogFilteringProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/LogFilteringProcessor.cs new file mode 100644 index 000000000000..ede796dcb060 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/LogFilteringProcessor.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using OpenTelemetry.Logs; +using OpenTelemetry; + +namespace Azure.Monitor.OpenTelemetry.AspNetCore +{ + internal class LogFilteringProcessor : BatchLogRecordExportProcessor + { + public LogFilteringProcessor(BaseExporter exporter) + : base(exporter) + { + } + + public override void OnEnd(LogRecord logRecord) + { + if (logRecord.SpanId == default || logRecord.TraceFlags == ActivityTraceFlags.Recorded) + { + base.OnEnd(logRecord); + } + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs index b2ca06d7a1bc..707847e5d4d1 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs @@ -27,6 +27,8 @@ public static class OpenTelemetryBuilderExtensions { private const string SqlClientInstrumentationPackageName = "OpenTelemetry.Instrumentation.SqlClient"; + private const string EnableLogSamplingEnvVar = "OTEL_DOTNET_AZURE_MONITOR_EXPERIMENTAL_ENABLE_LOG_SAMPLING"; + /// /// Configures Azure Monitor for logging, distributed tracing, and metrics. /// @@ -137,7 +139,29 @@ public static OpenTelemetryBuilder UseAzureMonitor(this OpenTelemetryBuilder bui builder.Services.AddOptions() .Configure>((loggingOptions, azureOptions) => { - loggingOptions.AddAzureMonitorLogExporter(o => azureOptions.Get(Options.DefaultName).SetValueToExporterOptions(o)); + var azureMonitorOptions = azureOptions.Get(Options.DefaultName); + + bool enableLogSampling = false; + try + { + var enableLogSamplingEnvVar = Environment.GetEnvironmentVariable(EnableLogSamplingEnvVar); + bool.TryParse(enableLogSamplingEnvVar, out enableLogSampling); + } + catch (Exception ex) + { + AzureMonitorAspNetCoreEventSource.Log.GetEnvironmentVariableFailed(EnableLogSamplingEnvVar, ex); + } + + if (enableLogSampling) + { + var azureMonitorExporterOptions = new AzureMonitorExporterOptions(); + azureMonitorOptions.SetValueToExporterOptions(azureMonitorExporterOptions); + loggingOptions.AddProcessor(new LogFilteringProcessor(new AzureMonitorLogExporter(azureMonitorExporterOptions))); + } + else + { + loggingOptions.AddAzureMonitorLogExporter(o => azureMonitorOptions.SetValueToExporterOptions(o)); + } }); // Register a configuration action so that when diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/E2EAzureMonitorDistroTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/E2EAzureMonitorDistroTests.cs index 84b311b5a8a7..25dec56b68f9 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/E2EAzureMonitorDistroTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Tests/E2EAzureMonitorDistroTests.cs @@ -16,6 +16,9 @@ using System.Collections.Generic; using System.Linq; using Xunit.Abstractions; +using OpenTelemetry.Logs; +using OpenTelemetry; +using System.Reflection; namespace Azure.Monitor.OpenTelemetry.AspNetCore.Tests { @@ -77,6 +80,45 @@ public async Task ValidateTelemetryExport() // TODO: This test needs to assert telemetry content. (ie: sample rate) } + [Theory] + [InlineData(null)] + [InlineData("true")] + [InlineData("True")] + [InlineData("False")] + [InlineData("false")] + public void ValidateLogFilteringProcessorIsAddedToLoggerProvider(string enableLogSampling) + { + try + { + Environment.SetEnvironmentVariable("OTEL_DOTNET_AZURE_MONITOR_EXPERIMENTAL_ENABLE_LOG_SAMPLING", enableLogSampling); + + var sv = new ServiceCollection(); + sv.AddOpenTelemetry().UseAzureMonitor(o => o.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000"); + + var sp = sv.BuildServiceProvider(); + var loggerProvider = sp.GetRequiredService(); + var sdkProvider = typeof(OpenTelemetryLoggerProvider).GetField("Provider", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(loggerProvider); + var processor = sdkProvider?.GetType().GetProperty("Processor", BindingFlags.Instance | BindingFlags.Public)?.GetMethod?.Invoke(sdkProvider, null); + + Assert.NotNull(processor); + + if (enableLogSampling != null && enableLogSampling.Equals("true" , StringComparison.OrdinalIgnoreCase)) + { + Assert.True(processor is LogFilteringProcessor); + Assert.True(processor is BatchLogRecordExportProcessor); + } + else + { + Assert.True(processor is not LogFilteringProcessor); + Assert.True(processor is BatchLogRecordExportProcessor); + } + } + finally + { + Environment.SetEnvironmentVariable("OTEL_DOTNET_AZURE_MONITOR_EXPERIMENTAL_ENABLE_LOG_SAMPLING", null); + } + } + private void WaitForRequest(MockTransport transport) { SpinWait.SpinUntil(