From 181def918d5d11a86b81c39123653148f5b6fa42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:49:33 +0000 Subject: [PATCH 1/4] Initial plan From b64dfd2c774826c10748fd46e6a406fa2af8a0dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:59:40 +0000 Subject: [PATCH 2/4] Implement OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable support Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../ChatCompletion/OpenTelemetryChatClient.cs | 19 ++++- .../OpenTelemetryEmbeddingGenerator.cs | 19 ++++- .../OpenTelemetryConsts.cs | 3 + .../OpenTelemetryChatClientTests.cs | 72 +++++++++++++++++++ .../OpenTelemetryEmbeddingGeneratorTests.cs | 72 +++++++++++++++++++ 5 files changed, 183 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs index f9215060dea..3ed41ef28ca 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs @@ -83,6 +83,20 @@ public OpenTelemetryChatClient(IChatClient innerClient, ILogger? logger = null, ); _jsonSerializerOptions = AIJsonUtilities.DefaultOptions; + + // Set the default value of EnableSensitiveData based on the environment variable + EnableSensitiveData = ShouldEnableSensitiveDataByDefault(); + } + + /// + /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable + /// to determine if sensitive data should be enabled by default. + /// + /// True if the environment variable is set to "true" (case-insensitive), otherwise false. + private static bool ShouldEnableSensitiveDataByDefault() + { + string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); + return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); } /// Gets or sets JSON serialization options to use when formatting chat data into telemetry strings. @@ -110,11 +124,14 @@ protected override void Dispose(bool disposing) /// /// if potentially sensitive information should be included in telemetry; /// if telemetry shouldn't include raw inputs and outputs. - /// The default value is . + /// The default value is , unless the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT + /// environment variable is set to "true" (case-insensitive). /// /// /// By default, telemetry includes metadata, such as token counts, but not raw inputs /// and outputs, such as message content, function call arguments, and function call results. + /// The default value can be overridden by setting the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT + /// environment variable to "true". Explicitly setting this property will override the environment variable. /// public bool EnableSensitiveData { get; set; } diff --git a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs index 402222feaa7..f2892f4777c 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs @@ -81,6 +81,20 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries } #endif ); + + // Set the default value of EnableSensitiveData based on the environment variable + EnableSensitiveData = ShouldEnableSensitiveDataByDefault(); + } + + /// + /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable + /// to determine if sensitive data should be enabled by default. + /// + /// True if the environment variable is set to "true" (case-insensitive), otherwise false. + private static bool ShouldEnableSensitiveDataByDefault() + { + string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); + return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); } /// @@ -89,11 +103,14 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i /// /// if potentially sensitive information should be included in telemetry; /// if telemetry shouldn't include raw inputs and outputs. - /// The default value is . + /// The default value is , unless the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT + /// environment variable is set to "true" (case-insensitive). /// /// /// By default, telemetry includes metadata, such as token counts, but not raw inputs /// and outputs or additional options data. + /// The default value can be overridden by setting the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT + /// environment variable to "true". Explicitly setting this property will override the environment variable. /// public bool EnableSensitiveData { get; set; } diff --git a/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs b/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs index faf7c32e4cb..72ac3f82fcb 100644 --- a/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs +++ b/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs @@ -14,6 +14,9 @@ internal static class OpenTelemetryConsts public const string SecondsUnit = "s"; public const string TokensUnit = "token"; + /// Environment variable name for controlling whether sensitive content should be captured in telemetry. + public const string GenAICaptureMessageContentEnvVar = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"; + public const string ToolTypeFunction = "function"; public const string TypeText = "text"; diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs index 4640b78f75d..6508b647a6d 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs @@ -273,4 +273,76 @@ async static IAsyncEnumerable CallbackAsync( static string ReplaceWhitespace(string? input) => Regex.Replace(input ?? "", @"\s+", " ").Trim(); } + + [Theory] + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData("True", true)] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("yes", false)] // Should only respond to "true" + [InlineData("1", false)] // Should only respond to "true" + public void EnableSensitiveData_DefaultsBasedOnEnvironmentVariable(string? envVarValue, bool expectedDefault) + { + // Arrange: Set up environment variable + string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; + + try + { + if (envVarValue is null) + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", null); + } + else + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", envVarValue); + } + + // Act: Create a new instance + using var innerClient = new TestChatClient(); + using var client = new OpenTelemetryChatClient(innerClient); + + // Assert: Check the default value + Assert.Equal(expectedDefault, client.EnableSensitiveData); + } + finally + { + // Cleanup: Restore original environment variable + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", + string.IsNullOrEmpty(originalValue) ? null : originalValue); + } + } + + [Fact] + public void EnableSensitiveData_ExplicitSettingOverridesEnvironmentVariable() + { + // Arrange: Set environment variable to true + string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; + + try + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true"); + + // Act: Create instance and explicitly set to false + using var innerClient = new TestChatClient(); + using var client = new OpenTelemetryChatClient(innerClient); + + // Verify it defaults to true from environment variable + Assert.True(client.EnableSensitiveData); + + // Explicitly set to false + client.EnableSensitiveData = false; + + // Assert: Explicit setting should override environment variable + Assert.False(client.EnableSensitiveData); + } + finally + { + // Cleanup: Restore original environment variable + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", + string.IsNullOrEmpty(originalValue) ? null : originalValue); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs index c8419338e44..28d7274d188 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs @@ -95,4 +95,76 @@ public async Task ExpectedInformationLogged_Async(string? perRequestModelId, boo Assert.True(activity.Duration.TotalMilliseconds > 0); } + + [Theory] + [InlineData(null, false)] + [InlineData("", false)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData("True", true)] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("yes", false)] // Should only respond to "true" + [InlineData("1", false)] // Should only respond to "true" + public void EnableSensitiveData_DefaultsBasedOnEnvironmentVariable(string? envVarValue, bool expectedDefault) + { + // Arrange: Set up environment variable + string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; + + try + { + if (envVarValue is null) + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", null); + } + else + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", envVarValue); + } + + // Act: Create a new instance + using var innerGenerator = new TestEmbeddingGenerator>(); + using var generator = new OpenTelemetryEmbeddingGenerator>(innerGenerator); + + // Assert: Check the default value + Assert.Equal(expectedDefault, generator.EnableSensitiveData); + } + finally + { + // Cleanup: Restore original environment variable + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", + string.IsNullOrEmpty(originalValue) ? null : originalValue); + } + } + + [Fact] + public void EnableSensitiveData_ExplicitSettingOverridesEnvironmentVariable() + { + // Arrange: Set environment variable to true + string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; + + try + { + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true"); + + // Act: Create instance and explicitly set to false + using var innerGenerator = new TestEmbeddingGenerator>(); + using var generator = new OpenTelemetryEmbeddingGenerator>(innerGenerator); + + // Verify it defaults to true from environment variable + Assert.True(generator.EnableSensitiveData); + + // Explicitly set to false + generator.EnableSensitiveData = false; + + // Assert: Explicit setting should override environment variable + Assert.False(generator.EnableSensitiveData); + } + finally + { + // Cleanup: Restore original environment variable + Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", + string.IsNullOrEmpty(originalValue) ? null : originalValue); + } + } } From 0f933764a38f345139fe98c3329d3dcb703299f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Sep 2025 01:21:34 +0000 Subject: [PATCH 3/4] Move ShouldEnableSensitiveDataByDefault to shared TelemetryHelpers class Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> --- .../ChatCompletion/FunctionInvokingChatClient.cs | 4 ++-- .../ChatCompletion/LoggingChatClient.cs | 2 +- .../ChatCompletion/OpenTelemetryChatClient.cs | 13 +------------ .../OpenTelemetryEmbeddingGenerator.cs | 13 +------------ .../Image/LoggingImageGenerator.cs | 2 +- .../SpeechToText/LoggingSpeechToTextClient.cs | 2 +- .../{LoggingHelpers.cs => TelemetryHelpers.cs} | 16 ++++++++++++++-- 7 files changed, 21 insertions(+), 31 deletions(-) rename src/Libraries/Microsoft.Extensions.AI/{LoggingHelpers.cs => TelemetryHelpers.cs} (62%) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index e907078d535..e4a347ded70 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -1128,7 +1128,7 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul startingTimestamp = Stopwatch.GetTimestamp(); if (_logger.IsEnabled(LogLevel.Trace)) { - LogInvokingSensitive(context.Function.Name, LoggingHelpers.AsJson(context.Arguments, context.Function.JsonSerializerOptions)); + LogInvokingSensitive(context.Function.Name, TelemetryHelpers.AsJson(context.Arguments, context.Function.JsonSerializerOptions)); } else { @@ -1169,7 +1169,7 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul if (result is not null && _logger.IsEnabled(LogLevel.Trace)) { - LogInvocationCompletedSensitive(context.Function.Name, elapsed, LoggingHelpers.AsJson(result, context.Function.JsonSerializerOptions)); + LogInvocationCompletedSensitive(context.Function.Name, elapsed, TelemetryHelpers.AsJson(result, context.Function.JsonSerializerOptions)); } else { diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/LoggingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/LoggingChatClient.cs index aec72eddcdc..c39fa2c0eb6 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/LoggingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/LoggingChatClient.cs @@ -169,7 +169,7 @@ public override async IAsyncEnumerable GetStreamingResponseA } } - private string AsJson(T value) => LoggingHelpers.AsJson(value, _jsonSerializerOptions); + private string AsJson(T value) => TelemetryHelpers.AsJson(value, _jsonSerializerOptions); [LoggerMessage(LogLevel.Debug, "{MethodName} invoked.")] private partial void LogInvoked(string methodName); diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs index 3ed41ef28ca..cdd934b5833 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs @@ -85,18 +85,7 @@ public OpenTelemetryChatClient(IChatClient innerClient, ILogger? logger = null, _jsonSerializerOptions = AIJsonUtilities.DefaultOptions; // Set the default value of EnableSensitiveData based on the environment variable - EnableSensitiveData = ShouldEnableSensitiveDataByDefault(); - } - - /// - /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable - /// to determine if sensitive data should be enabled by default. - /// - /// True if the environment variable is set to "true" (case-insensitive), otherwise false. - private static bool ShouldEnableSensitiveDataByDefault() - { - string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); - return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); + EnableSensitiveData = TelemetryHelpers.ShouldEnableSensitiveDataByDefault(); } /// Gets or sets JSON serialization options to use when formatting chat data into telemetry strings. diff --git a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs index f2892f4777c..f92aa6b54b2 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs @@ -83,18 +83,7 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i ); // Set the default value of EnableSensitiveData based on the environment variable - EnableSensitiveData = ShouldEnableSensitiveDataByDefault(); - } - - /// - /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable - /// to determine if sensitive data should be enabled by default. - /// - /// True if the environment variable is set to "true" (case-insensitive), otherwise false. - private static bool ShouldEnableSensitiveDataByDefault() - { - string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); - return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); + EnableSensitiveData = TelemetryHelpers.ShouldEnableSensitiveDataByDefault(); } /// diff --git a/src/Libraries/Microsoft.Extensions.AI/Image/LoggingImageGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/Image/LoggingImageGenerator.cs index 2eee33456c1..9ca0b8d06d2 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Image/LoggingImageGenerator.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Image/LoggingImageGenerator.cs @@ -102,7 +102,7 @@ public override async Task GenerateAsync( } } - private string AsJson(T value) => LoggingHelpers.AsJson(value, _jsonSerializerOptions); + private string AsJson(T value) => TelemetryHelpers.AsJson(value, _jsonSerializerOptions); [LoggerMessage(LogLevel.Debug, "{MethodName} invoked.")] private partial void LogInvoked(string methodName); diff --git a/src/Libraries/Microsoft.Extensions.AI/SpeechToText/LoggingSpeechToTextClient.cs b/src/Libraries/Microsoft.Extensions.AI/SpeechToText/LoggingSpeechToTextClient.cs index e7bf7850a94..10e499a7e57 100644 --- a/src/Libraries/Microsoft.Extensions.AI/SpeechToText/LoggingSpeechToTextClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/SpeechToText/LoggingSpeechToTextClient.cs @@ -179,7 +179,7 @@ public override async IAsyncEnumerable GetStreamingT } } - private string AsJson(T value) => LoggingHelpers.AsJson(value, _jsonSerializerOptions); + private string AsJson(T value) => TelemetryHelpers.AsJson(value, _jsonSerializerOptions); [LoggerMessage(LogLevel.Debug, "{MethodName} invoked.")] private partial void LogInvoked(string methodName); diff --git a/src/Libraries/Microsoft.Extensions.AI/LoggingHelpers.cs b/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs similarity index 62% rename from src/Libraries/Microsoft.Extensions.AI/LoggingHelpers.cs rename to src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs index 72a7e283988..de439af8809 100644 --- a/src/Libraries/Microsoft.Extensions.AI/LoggingHelpers.cs +++ b/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs @@ -5,13 +5,25 @@ #pragma warning disable S108 // Nested blocks of code should not be left empty #pragma warning disable S2486 // Generic exceptions should not be ignored +using System; using System.Text.Json; namespace Microsoft.Extensions.AI; -/// Provides internal helpers for implementing logging. -internal static class LoggingHelpers +/// Provides internal helpers for implementing telemetry. +internal static class TelemetryHelpers { + /// + /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable + /// to determine if sensitive data should be enabled by default. + /// + /// True if the environment variable is set to "true" (case-insensitive), otherwise false. + public static bool ShouldEnableSensitiveDataByDefault() + { + string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); + return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); + } + /// Serializes as JSON for logging purposes. public static string AsJson(T value, JsonSerializerOptions? options) { From 320b366130386b58206e4315e15759e436f4ad9d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 10 Sep 2025 13:07:10 -0400 Subject: [PATCH 4/4] Address feedback --- .../ChatCompletion/OpenTelemetryChatClient.cs | 5 +- .../OpenTelemetryEmbeddingGenerator.cs | 5 +- .../OpenTelemetryConsts.cs | 2 +- .../TelemetryHelpers.cs | 16 ++--- .../OpenTelemetryChatClientTests.cs | 72 ------------------- .../OpenTelemetryEmbeddingGeneratorTests.cs | 72 ------------------- 6 files changed, 9 insertions(+), 163 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs index cdd934b5833..d06ec3d3b5e 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs @@ -83,9 +83,6 @@ public OpenTelemetryChatClient(IChatClient innerClient, ILogger? logger = null, ); _jsonSerializerOptions = AIJsonUtilities.DefaultOptions; - - // Set the default value of EnableSensitiveData based on the environment variable - EnableSensitiveData = TelemetryHelpers.ShouldEnableSensitiveDataByDefault(); } /// Gets or sets JSON serialization options to use when formatting chat data into telemetry strings. @@ -122,7 +119,7 @@ protected override void Dispose(bool disposing) /// The default value can be overridden by setting the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT /// environment variable to "true". Explicitly setting this property will override the environment variable. /// - public bool EnableSensitiveData { get; set; } + public bool EnableSensitiveData { get; set; } = TelemetryHelpers.EnableSensitiveDataDefault; /// public override object? GetService(Type serviceType, object? serviceKey = null) => diff --git a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs index f92aa6b54b2..4b8f58ed7fb 100644 --- a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs +++ b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs @@ -81,9 +81,6 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries } #endif ); - - // Set the default value of EnableSensitiveData based on the environment variable - EnableSensitiveData = TelemetryHelpers.ShouldEnableSensitiveDataByDefault(); } /// @@ -101,7 +98,7 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i /// The default value can be overridden by setting the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT /// environment variable to "true". Explicitly setting this property will override the environment variable. /// - public bool EnableSensitiveData { get; set; } + public bool EnableSensitiveData { get; set; } = TelemetryHelpers.EnableSensitiveDataDefault; /// public override object? GetService(Type serviceType, object? serviceKey = null) => diff --git a/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs b/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs index 72ac3f82fcb..096b2091908 100644 --- a/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs +++ b/src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs @@ -14,7 +14,7 @@ internal static class OpenTelemetryConsts public const string SecondsUnit = "s"; public const string TokensUnit = "token"; - /// Environment variable name for controlling whether sensitive content should be captured in telemetry. + /// Environment variable name for controlling whether sensitive content should be captured in telemetry by default. public const string GenAICaptureMessageContentEnvVar = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"; public const string ToolTypeFunction = "function"; diff --git a/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs b/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs index de439af8809..0468a93b26f 100644 --- a/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs +++ b/src/Libraries/Microsoft.Extensions.AI/TelemetryHelpers.cs @@ -4,6 +4,7 @@ #pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable S108 // Nested blocks of code should not be left empty #pragma warning disable S2486 // Generic exceptions should not be ignored +#pragma warning disable SA1623 // Property summary documentation should match accessors using System; using System.Text.Json; @@ -13,16 +14,11 @@ namespace Microsoft.Extensions.AI; /// Provides internal helpers for implementing telemetry. internal static class TelemetryHelpers { - /// - /// Checks the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable - /// to determine if sensitive data should be enabled by default. - /// - /// True if the environment variable is set to "true" (case-insensitive), otherwise false. - public static bool ShouldEnableSensitiveDataByDefault() - { - string? envVar = Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar); - return string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); - } + /// Gets a value the OpenTelemetry clients should use for their EnableSensitiveData property's default value. + /// Defaults to false. May be overridden by setting the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT environment variable to "true". + public static bool EnableSensitiveDataDefault { get; } = + Environment.GetEnvironmentVariable(OpenTelemetryConsts.GenAICaptureMessageContentEnvVar) is string envVar && + string.Equals(envVar, "true", StringComparison.OrdinalIgnoreCase); /// Serializes as JSON for logging purposes. public static string AsJson(T value, JsonSerializerOptions? options) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs index 6508b647a6d..4640b78f75d 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs @@ -273,76 +273,4 @@ async static IAsyncEnumerable CallbackAsync( static string ReplaceWhitespace(string? input) => Regex.Replace(input ?? "", @"\s+", " ").Trim(); } - - [Theory] - [InlineData(null, false)] - [InlineData("", false)] - [InlineData("false", false)] - [InlineData("FALSE", false)] - [InlineData("True", true)] - [InlineData("true", true)] - [InlineData("TRUE", true)] - [InlineData("yes", false)] // Should only respond to "true" - [InlineData("1", false)] // Should only respond to "true" - public void EnableSensitiveData_DefaultsBasedOnEnvironmentVariable(string? envVarValue, bool expectedDefault) - { - // Arrange: Set up environment variable - string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; - - try - { - if (envVarValue is null) - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", null); - } - else - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", envVarValue); - } - - // Act: Create a new instance - using var innerClient = new TestChatClient(); - using var client = new OpenTelemetryChatClient(innerClient); - - // Assert: Check the default value - Assert.Equal(expectedDefault, client.EnableSensitiveData); - } - finally - { - // Cleanup: Restore original environment variable - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", - string.IsNullOrEmpty(originalValue) ? null : originalValue); - } - } - - [Fact] - public void EnableSensitiveData_ExplicitSettingOverridesEnvironmentVariable() - { - // Arrange: Set environment variable to true - string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; - - try - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true"); - - // Act: Create instance and explicitly set to false - using var innerClient = new TestChatClient(); - using var client = new OpenTelemetryChatClient(innerClient); - - // Verify it defaults to true from environment variable - Assert.True(client.EnableSensitiveData); - - // Explicitly set to false - client.EnableSensitiveData = false; - - // Assert: Explicit setting should override environment variable - Assert.False(client.EnableSensitiveData); - } - finally - { - // Cleanup: Restore original environment variable - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", - string.IsNullOrEmpty(originalValue) ? null : originalValue); - } - } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs index 28d7274d188..c8419338e44 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/Embeddings/OpenTelemetryEmbeddingGeneratorTests.cs @@ -95,76 +95,4 @@ public async Task ExpectedInformationLogged_Async(string? perRequestModelId, boo Assert.True(activity.Duration.TotalMilliseconds > 0); } - - [Theory] - [InlineData(null, false)] - [InlineData("", false)] - [InlineData("false", false)] - [InlineData("FALSE", false)] - [InlineData("True", true)] - [InlineData("true", true)] - [InlineData("TRUE", true)] - [InlineData("yes", false)] // Should only respond to "true" - [InlineData("1", false)] // Should only respond to "true" - public void EnableSensitiveData_DefaultsBasedOnEnvironmentVariable(string? envVarValue, bool expectedDefault) - { - // Arrange: Set up environment variable - string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; - - try - { - if (envVarValue is null) - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", null); - } - else - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", envVarValue); - } - - // Act: Create a new instance - using var innerGenerator = new TestEmbeddingGenerator>(); - using var generator = new OpenTelemetryEmbeddingGenerator>(innerGenerator); - - // Assert: Check the default value - Assert.Equal(expectedDefault, generator.EnableSensitiveData); - } - finally - { - // Cleanup: Restore original environment variable - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", - string.IsNullOrEmpty(originalValue) ? null : originalValue); - } - } - - [Fact] - public void EnableSensitiveData_ExplicitSettingOverridesEnvironmentVariable() - { - // Arrange: Set environment variable to true - string originalValue = Environment.GetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") ?? ""; - - try - { - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "true"); - - // Act: Create instance and explicitly set to false - using var innerGenerator = new TestEmbeddingGenerator>(); - using var generator = new OpenTelemetryEmbeddingGenerator>(innerGenerator); - - // Verify it defaults to true from environment variable - Assert.True(generator.EnableSensitiveData); - - // Explicitly set to false - generator.EnableSensitiveData = false; - - // Assert: Explicit setting should override environment variable - Assert.False(generator.EnableSensitiveData); - } - finally - { - // Cleanup: Restore original environment variable - Environment.SetEnvironmentVariable("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", - string.IsNullOrEmpty(originalValue) ? null : originalValue); - } - } }