From 39c15f8974130ba2a027f4467709bb93aa9eb1e3 Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:09:53 -0700 Subject: [PATCH 1/7] Suppress additional polling logs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- .../Extensions/ScriptLoggingBuilderExtensions.cs | 4 ++++ .../ScriptLoggingBuilderExtensionsTests.cs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index eca26e5b06..822f87b3ee 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -23,8 +23,12 @@ public static class ScriptLoggingBuilderExtensions // suppressed to reduce noise in the FunctionsLogs table. private static readonly HashSet _suppressedCategories = new(StringComparer.Ordinal) { + "Host.Triggers.Kafka", + "Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", + "Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", + "Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", "Host.Executor" }; diff --git a/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs b/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs index 26cc2054a9..8509b419d3 100644 --- a/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs +++ b/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs @@ -62,6 +62,14 @@ public void Filter_RespectsMinLevelAndCategory(string category, LogLevel actualL [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Trace)] [InlineData("Host.Executor", LogLevel.Debug)] [InlineData("Host.Executor", LogLevel.Trace)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Trace)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Trace)] + [InlineData("Host.Triggers.Kafka", LogLevel.Debug)] + [InlineData("Host.Triggers.Kafka", LogLevel.Trace)] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Trace)] public void Filter_SuppressedCategory_DebugAndTrace_ReturnsFalse(string category, LogLevel level) { Assert.False(ScriptLoggingBuilderExtensions.Filter(category, level, LogLevel.Trace)); @@ -74,6 +82,14 @@ public void Filter_SuppressedCategory_DebugAndTrace_ReturnsFalse(string category [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Error)] [InlineData("Host.Executor", LogLevel.Information)] [InlineData("Host.Executor", LogLevel.Critical)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Information)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Warning)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Information)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Error)] + [InlineData("Host.Triggers.Kafka", LogLevel.Information)] + [InlineData("Host.Triggers.Kafka", LogLevel.Critical)] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Information)] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Warning)] public void Filter_SuppressedCategory_InformationAndAbove_ReturnsTrue(string category, LogLevel level) { Assert.True(ScriptLoggingBuilderExtensions.Filter(category, level, LogLevel.Trace)); From 2b5161addd94f44120be513d34a1783cf4723e8d Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 00:49:30 -0700 Subject: [PATCH 2/7] Filter polling logs by event name Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- .../Diagnostics/SystemLogger.cs | 53 +++++- .../ScriptLoggingBuilderExtensions.cs | 25 --- .../Eventing/SystemLoggerTests.cs | 173 ++++++++++++++++++ .../ScriptLoggingBuilderExtensionsTests.cs | 44 +---- 4 files changed, 227 insertions(+), 68 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index bc49530caa..5494841b49 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -16,6 +16,32 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics { public class SystemLogger : ILogger { + // Preserve the category-wide suppression introduced in #11603. + private static readonly HashSet _suppressedCategories = new(StringComparer.Ordinal) + { + "Host.Executor", + "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl" + }; + + // Extension polling events are suppressed by name so other diagnostics in the same category remain available. + private static readonly Dictionary> _suppressedEvents = new(StringComparer.Ordinal) + { + ["Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener"] = new(StringComparer.Ordinal) + { + "PollBlobContainer" + }, + ["Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"] = new(StringComparer.Ordinal) + { + "BackoffDelay", + "GetMessages" + }, + ["Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener"] = new(StringComparer.Ordinal) + { + "BackoffDelay", + "GetMessages" + } + }; + private readonly string _categoryName; private readonly string _functionName; private readonly string _hostInstanceId; @@ -114,6 +140,14 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _eventManager.Publish(new FunctionIndexingEvent(nameof(FunctionIndexingException), source, exception)); } + string eventName = !string.IsNullOrEmpty(eventId.Name) ? eventId.Name : stateEventName ?? string.Empty; + eventName = isDiagnosticEvent ? $"DiagnosticEvent-{diagnosticEventErrorCode}" : eventName; + + if (!_debugStateProvider.InDiagnosticMode && ShouldSuppress(source, eventName, logLevel)) + { + return; + } + // If we don't have a message, there's nothing to log. string formattedMessage = formatter?.Invoke(state, exception); if (string.IsNullOrEmpty(formattedMessage)) @@ -152,9 +186,6 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except // Apply standard event properties. // Note: we must be sure to default any null values to empty string // otherwise the ETW event will fail to be persisted (silently). - string eventName = !string.IsNullOrEmpty(eventId.Name) ? eventId.Name : stateEventName ?? string.Empty; - eventName = isDiagnosticEvent ? $"DiagnosticEvent-{diagnosticEventErrorCode}" : eventName; - string activityId = stateActivityId ?? scopeActivityId ?? string.Empty; var options = _appServiceOptions; string subscriptionId = options.SubscriptionId ?? string.Empty; @@ -178,5 +209,21 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _eventGenerator.LogFunctionTraceEvent(logLevel, subscriptionId, appName, functionName, eventName, source, details, formattedMessage, innerExceptionType, innerExceptionMessage, invocationId, _hostInstanceId, activityId, runtimeSiteName, slotName, DateTime.UtcNow); } + + private static bool ShouldSuppress(string category, string eventName, LogLevel logLevel) + { + if (logLevel >= LogLevel.Information) + { + return false; + } + + if (_suppressedCategories.Contains(category)) + { + return true; + } + + return _suppressedEvents.TryGetValue(category, out HashSet eventNames) + && eventNames.Contains(eventName); + } } } \ No newline at end of file diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index 822f87b3ee..44096cd5b2 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.Azure.WebJobs.Script; @@ -19,19 +18,6 @@ public static class ScriptLoggingBuilderExtensions { private static readonly ConcurrentDictionary _filteredCategoryCache = new(); - // High-volume extension categories whose Debug/Trace logs are - // suppressed to reduce noise in the FunctionsLogs table. - private static readonly HashSet _suppressedCategories = new(StringComparer.Ordinal) - { - "Host.Triggers.Kafka", - "Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", - "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", - "Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", - "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", - "Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", - "Host.Executor" - }; - public static ILoggingBuilder AddForwardingLogger(this ILoggingBuilder builder) { ArgumentNullException.ThrowIfNull(builder); @@ -65,20 +51,9 @@ internal static bool Filter(string category, LogLevel actualLevel, LogLevel minL return false; } - // Suppress Debug/ Trace from high-volume extension categories. - if (actualLevel < LogLevel.Information && IsSuppressedCategory(category)) - { - return false; - } - return true; } - private static bool IsSuppressedCategory(string category) - { - return _suppressedCategories.Contains(category); - } - private static bool IsFiltered(string category) { return _filteredCategoryCache.GetOrAdd( diff --git a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs index ee90c33548..1d1eea6ab1 100644 --- a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs +++ b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs @@ -108,6 +108,179 @@ public void Log_Verbose_EmitsExpectedEvent() _mockEventGenerator.VerifyAll(); } + [Theory] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", "GetMessages")] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", "BackoffDelay")] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", "GetMessages")] + [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", "BackoffDelay")] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "PollBlobContainer")] + public void Log_SuppressedEvent_DoesNotEmitEvent(string category, string eventName) + { + bool formatterInvoked = false; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + localLogger.Log( + LogLevel.Debug, + new EventId(1, eventName), + state: string.Empty, + exception: null, + (state, exception) => + { + formatterInvoked = true; + return "TestMessage"; + }); + + Assert.False(formatterInvoked); + _mockEventGenerator.Verify( + p => p.LogFunctionTraceEvent( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Never); + } + + [Theory] + [InlineData("Host.Executor")] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl")] + public void Log_SuppressedCategory_DoesNotEmitEvent(string category) + { + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + localLogger.LogDebug("TestMessage"); + + _mockEventGenerator.Verify( + p => p.LogFunctionTraceEvent( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Never); + } + + [Fact] + public void Log_SuppressedStateEventName_DoesNotEmitEvent() + { + const string category = "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + var state = new Dictionary + { + [ScriptConstants.LogPropertyEventNameKey] = "GetMessages" + }; + + localLogger.Log(LogLevel.Debug, eventId: 0, state, null, (logState, exception) => "TestMessage"); + + _mockEventGenerator.Verify( + p => p.LogFunctionTraceEvent( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Never); + } + + [Theory] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", "HandlingStorageException", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", "GetMessages", LogLevel.Information)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "BlobMessageEnqueued", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", "", LogLevel.Debug)] + [InlineData("Host.Triggers.Kafka", "", LogLevel.Debug)] + [InlineData("Host.Executor", "", LogLevel.Information)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", "", LogLevel.Error)] + public void Log_NonSuppressedEvent_EmitsEvent(string category, string eventName, LogLevel logLevel) + { + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + logLevel, + _subscriptionId, + _websiteName, + string.Empty, + eventName, + category, + string.Empty, + "TestMessage", + string.Empty, + string.Empty, + string.Empty, + _hostInstanceId, + string.Empty, + _runtimeSiteName, + _slotName, + It.IsAny())); + + localLogger.Log(logLevel, new EventId(1, eventName), string.Empty, null, (state, exception) => "TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + + [Fact] + public void Log_SuppressedEventInDiagnosticMode_EmitsEvent() + { + const string category = "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"; + const string eventName = "GetMessages"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + _inDiagnosticMode = true; + + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + LogLevel.Debug, + _subscriptionId, + _websiteName, + string.Empty, + eventName, + category, + string.Empty, + "TestMessage", + string.Empty, + string.Empty, + string.Empty, + _hostInstanceId, + string.Empty, + _runtimeSiteName, + _slotName, + It.IsAny())); + + localLogger.Log(LogLevel.Debug, new EventId(1, eventName), string.Empty, null, (state, exception) => "TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + [Fact] public void Log_Verbose_LogData_EmitsExpectedEvent() { diff --git a/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs b/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs index 8509b419d3..969778fa12 100644 --- a/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs +++ b/test/WebJobs.Script.Tests/Extensions/ScriptLoggingBuilderExtensionsTests.cs @@ -55,51 +55,15 @@ public void Filter_RespectsMinLevelAndCategory(string category, LogLevel actualL Assert.Equal(expected, ScriptLoggingBuilderExtensions.Filter(category, actualLevel, minLevel)); } - [Theory] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", LogLevel.Debug)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", LogLevel.Trace)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Debug)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Trace)] - [InlineData("Host.Executor", LogLevel.Debug)] - [InlineData("Host.Executor", LogLevel.Trace)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Debug)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Trace)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Debug)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Trace)] - [InlineData("Host.Triggers.Kafka", LogLevel.Debug)] - [InlineData("Host.Triggers.Kafka", LogLevel.Trace)] - [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Debug)] - [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Trace)] - public void Filter_SuppressedCategory_DebugAndTrace_ReturnsFalse(string category, LogLevel level) - { - Assert.False(ScriptLoggingBuilderExtensions.Filter(category, level, LogLevel.Trace)); - } - - [Theory] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", LogLevel.Information)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", LogLevel.Warning)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Information)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", LogLevel.Error)] - [InlineData("Host.Executor", LogLevel.Information)] - [InlineData("Host.Executor", LogLevel.Critical)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Information)] - [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Warning)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Information)] - [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Error)] - [InlineData("Host.Triggers.Kafka", LogLevel.Information)] - [InlineData("Host.Triggers.Kafka", LogLevel.Critical)] - [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Information)] - [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", LogLevel.Warning)] - public void Filter_SuppressedCategory_InformationAndAbove_ReturnsTrue(string category, LogLevel level) - { - Assert.True(ScriptLoggingBuilderExtensions.Filter(category, level, LogLevel.Trace)); - } - [Theory] [InlineData("Microsoft.Azure.WebJobs.Host", LogLevel.Debug)] [InlineData("Microsoft.Azure.WebJobs.Host", LogLevel.Trace)] [InlineData("Host.Startup", LogLevel.Debug)] [InlineData("Function.MyFunc", LogLevel.Trace)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", LogLevel.Debug)] + [InlineData("Microsoft.Azure.WebJobs.EventHubs.Listeners.EventHubListener.PartitionProcessor", LogLevel.Debug)] + [InlineData("Host.Triggers.Kafka", LogLevel.Debug)] public void Filter_NonSuppressedCategory_DebugAndTrace_ReturnsTrue(string category, LogLevel level) { Assert.True(ScriptLoggingBuilderExtensions.Filter(category, level, LogLevel.Trace)); From 140705f197913da9914fa3ad268fb00f1e647ceb Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 01:13:58 -0700 Subject: [PATCH 3/7] Suppress high-volume blob scan events Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs | 2 ++ test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index 5494841b49..1a6ca3188a 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -28,6 +28,8 @@ public class SystemLogger : ILogger { ["Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener"] = new(StringComparer.Ordinal) { + "BlobAlreadyProcessed", + "BlobDoesNotMatchPattern", "PollBlobContainer" }, ["Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"] = new(StringComparer.Ordinal) diff --git a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs index 1d1eea6ab1..300ec03907 100644 --- a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs +++ b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs @@ -114,6 +114,8 @@ public void Log_Verbose_EmitsExpectedEvent() [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", "GetMessages")] [InlineData("Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", "BackoffDelay")] [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "PollBlobContainer")] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "BlobAlreadyProcessed")] + [InlineData("Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", "BlobDoesNotMatchPattern")] public void Log_SuppressedEvent_DoesNotEmitEvent(string category, string eventName) { bool formatterInvoked = false; From 5f87b3114617ca431e53571face5ab0adb648599 Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 09:28:35 -0700 Subject: [PATCH 4/7] Optimize log suppression hot path Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- .../Diagnostics/SystemLogger.cs | 91 ++++++++++++------- .../Eventing/SystemLoggerTests.cs | 31 +++++++ 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index 1a6ca3188a..ffe30a2595 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -14,41 +14,22 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics { - public class SystemLogger : ILogger + internal enum SystemLogSuppressionRule { - // Preserve the category-wide suppression introduced in #11603. - private static readonly HashSet _suppressedCategories = new(StringComparer.Ordinal) - { - "Host.Executor", - "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl" - }; - - // Extension polling events are suppressed by name so other diagnostics in the same category remain available. - private static readonly Dictionary> _suppressedEvents = new(StringComparer.Ordinal) - { - ["Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener"] = new(StringComparer.Ordinal) - { - "BlobAlreadyProcessed", - "BlobDoesNotMatchPattern", - "PollBlobContainer" - }, - ["Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"] = new(StringComparer.Ordinal) - { - "BackoffDelay", - "GetMessages" - }, - ["Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener"] = new(StringComparer.Ordinal) - { - "BackoffDelay", - "GetMessages" - } - }; + None, + Category, + Queue, + Blob + } + public class SystemLogger : ILogger + { private readonly string _categoryName; private readonly string _functionName; private readonly string _hostInstanceId; private readonly bool _isUserFunction; private readonly LogLevel _logLevel; + private readonly SystemLogSuppressionRule _suppressionRule; private readonly IEnvironment _environment; private readonly IEventGenerator _eventGenerator; private readonly IDebugStateProvider _debugStateProvider; @@ -63,6 +44,7 @@ public SystemLogger(string hostInstanceId, string categoryName, IEventGenerator _eventGenerator = eventGenerator; _categoryName = categoryName ?? string.Empty; _logLevel = LogLevel.Debug; + _suppressionRule = GetSuppressionRule(_categoryName); _functionName = LogCategories.IsFunctionCategory(_categoryName) ? _categoryName.Split('.')[1] : null; _isUserFunction = LogCategories.IsFunctionUserCategory(_categoryName); _hostInstanceId = hostInstanceId; @@ -90,6 +72,12 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } + if ((_suppressionRule == SystemLogSuppressionRule.Category || !string.IsNullOrEmpty(eventId.Name)) + && ShouldSuppress(eventId.Name, logLevel)) + { + return; + } + // Enumerate all the state values once, capturing the values we'll use below - last one wins. string stateSourceValue = null; string stateFunctionName = null; @@ -145,7 +133,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except string eventName = !string.IsNullOrEmpty(eventId.Name) ? eventId.Name : stateEventName ?? string.Empty; eventName = isDiagnosticEvent ? $"DiagnosticEvent-{diagnosticEventErrorCode}" : eventName; - if (!_debugStateProvider.InDiagnosticMode && ShouldSuppress(source, eventName, logLevel)) + if (string.IsNullOrEmpty(eventId.Name) + && (_suppressionRule is SystemLogSuppressionRule.Queue or SystemLogSuppressionRule.Blob) + && ShouldSuppress(eventName, logLevel)) { return; } @@ -212,20 +202,53 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _eventGenerator.LogFunctionTraceEvent(logLevel, subscriptionId, appName, functionName, eventName, source, details, formattedMessage, innerExceptionType, innerExceptionMessage, invocationId, _hostInstanceId, activityId, runtimeSiteName, slotName, DateTime.UtcNow); } - private static bool ShouldSuppress(string category, string eventName, LogLevel logLevel) + private bool ShouldSuppress(string eventName, LogLevel logLevel) { if (logLevel >= LogLevel.Information) { return false; } - if (_suppressedCategories.Contains(category)) + bool isSuppressionCandidate = _suppressionRule switch + { + SystemLogSuppressionRule.Category => true, + SystemLogSuppressionRule.Queue => string.Equals(eventName, "BackoffDelay", StringComparison.Ordinal) + || string.Equals(eventName, "GetMessages", StringComparison.Ordinal), + SystemLogSuppressionRule.Blob => string.Equals(eventName, "BlobAlreadyProcessed", StringComparison.Ordinal) + || string.Equals(eventName, "BlobDoesNotMatchPattern", StringComparison.Ordinal) + || string.Equals(eventName, "PollBlobContainer", StringComparison.Ordinal), + _ => false + }; + + if (!isSuppressionCandidate) + { + return false; + } + + // Trace logs only pass IsEnabled when diagnostic mode is active. + return logLevel >= _logLevel && !_debugStateProvider.InDiagnosticMode; + } + + private static SystemLogSuppressionRule GetSuppressionRule(string category) + { + if (string.Equals(category, "Host.Executor", StringComparison.Ordinal) + || string.Equals(category, "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", StringComparison.Ordinal)) + { + return SystemLogSuppressionRule.Category; + } + + if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", StringComparison.Ordinal) + || string.Equals(category, "Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", StringComparison.Ordinal)) + { + return SystemLogSuppressionRule.Queue; + } + + if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", StringComparison.Ordinal)) { - return true; + return SystemLogSuppressionRule.Blob; } - return _suppressedEvents.TryGetValue(category, out HashSet eventNames) - && eventNames.Contains(eventName); + return SystemLogSuppressionRule.None; } } } \ No newline at end of file diff --git a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs index 300ec03907..e3a0428f0a 100644 --- a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs +++ b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs @@ -250,6 +250,7 @@ public void Log_NonSuppressedEvent_EmitsEvent(string category, string eventName, localLogger.Log(logLevel, new EventId(1, eventName), string.Empty, null, (state, exception) => "TestMessage"); _mockEventGenerator.VerifyAll(); + _debugStateProvider.VerifyGet(p => p.InDiagnosticMode, Times.Never); } [Fact] @@ -283,6 +284,36 @@ public void Log_SuppressedEventInDiagnosticMode_EmitsEvent() _mockEventGenerator.VerifyAll(); } + [Fact] + public void Log_SuppressedCategoryInDiagnosticMode_EmitsEvent() + { + const string category = "Host.Executor"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + _inDiagnosticMode = true; + + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + LogLevel.Debug, + _subscriptionId, + _websiteName, + string.Empty, + string.Empty, + category, + string.Empty, + "TestMessage", + string.Empty, + string.Empty, + string.Empty, + _hostInstanceId, + string.Empty, + _runtimeSiteName, + _slotName, + It.IsAny())); + + localLogger.LogDebug("TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + [Fact] public void Log_Verbose_LogData_EmitsExpectedEvent() { From 9a7ea8732466669e65f00ce18605eb4d7b05a4ed Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 09:37:27 -0700 Subject: [PATCH 5/7] Address review feedback on suppression refactor Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- .../Diagnostics/SystemLogger.cs | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index ffe30a2595..9092d59453 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -14,14 +14,6 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics { - internal enum SystemLogSuppressionRule - { - None, - Category, - Queue, - Blob - } - public class SystemLogger : ILogger { private readonly string _categoryName; @@ -29,7 +21,7 @@ public class SystemLogger : ILogger private readonly string _hostInstanceId; private readonly bool _isUserFunction; private readonly LogLevel _logLevel; - private readonly SystemLogSuppressionRule _suppressionRule; + private readonly SuppressionRule _suppressionRule; private readonly IEnvironment _environment; private readonly IEventGenerator _eventGenerator; private readonly IDebugStateProvider _debugStateProvider; @@ -56,6 +48,25 @@ public SystemLogger(string hostInstanceId, string categoryName, IEventGenerator _appServiceOptions = appServiceOptionsMonitor.CurrentValue; } + /// + /// Identifies how high-volume logs are suppressed for a given category. Resolved once per + /// logger instance so the per-log check on the hot path is a simple branch. + /// + private enum SuppressionRule + { + /// Nothing is suppressed for this category. + None, + + /// All Debug/Trace logs in the category are suppressed. + Category, + + /// Only the named queue polling events are suppressed. + Queue, + + /// Only the named blob scan events are suppressed. + Blob + } + public IDisposable BeginScope(TState state) => _scopeProvider.Push(state); public bool IsEnabled(LogLevel logLevel) @@ -72,7 +83,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } - if ((_suppressionRule == SystemLogSuppressionRule.Category || !string.IsNullOrEmpty(eventId.Name)) + // FunctionIndexingException must still be propagated below, so never short-circuit it here. + if (exception is not FunctionIndexingException + && (_suppressionRule == SuppressionRule.Category || !string.IsNullOrEmpty(eventId.Name)) && ShouldSuppress(eventId.Name, logLevel)) { return; @@ -134,7 +147,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except eventName = isDiagnosticEvent ? $"DiagnosticEvent-{diagnosticEventErrorCode}" : eventName; if (string.IsNullOrEmpty(eventId.Name) - && (_suppressionRule is SystemLogSuppressionRule.Queue or SystemLogSuppressionRule.Blob) + && (_suppressionRule is SuppressionRule.Queue or SuppressionRule.Blob) && ShouldSuppress(eventName, logLevel)) { return; @@ -211,10 +224,10 @@ private bool ShouldSuppress(string eventName, LogLevel logLevel) bool isSuppressionCandidate = _suppressionRule switch { - SystemLogSuppressionRule.Category => true, - SystemLogSuppressionRule.Queue => string.Equals(eventName, "BackoffDelay", StringComparison.Ordinal) + SuppressionRule.Category => true, + SuppressionRule.Queue => string.Equals(eventName, "BackoffDelay", StringComparison.Ordinal) || string.Equals(eventName, "GetMessages", StringComparison.Ordinal), - SystemLogSuppressionRule.Blob => string.Equals(eventName, "BlobAlreadyProcessed", StringComparison.Ordinal) + SuppressionRule.Blob => string.Equals(eventName, "BlobAlreadyProcessed", StringComparison.Ordinal) || string.Equals(eventName, "BlobDoesNotMatchPattern", StringComparison.Ordinal) || string.Equals(eventName, "PollBlobContainer", StringComparison.Ordinal), _ => false @@ -225,30 +238,31 @@ private bool ShouldSuppress(string eventName, LogLevel logLevel) return false; } - // Trace logs only pass IsEnabled when diagnostic mode is active. - return logLevel >= _logLevel && !_debugStateProvider.InDiagnosticMode; + // Diagnostic mode logs everything. Trace only reaches this point when diagnostic mode is + // active, since IsEnabled otherwise filters it out, so it is covered by the same check. + return !_debugStateProvider.InDiagnosticMode; } - private static SystemLogSuppressionRule GetSuppressionRule(string category) + private static SuppressionRule GetSuppressionRule(string category) { if (string.Equals(category, "Host.Executor", StringComparison.Ordinal) || string.Equals(category, "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", StringComparison.Ordinal)) { - return SystemLogSuppressionRule.Category; + return SuppressionRule.Category; } if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", StringComparison.Ordinal) || string.Equals(category, "Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", StringComparison.Ordinal)) { - return SystemLogSuppressionRule.Queue; + return SuppressionRule.Queue; } if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", StringComparison.Ordinal)) { - return SystemLogSuppressionRule.Blob; + return SuppressionRule.Blob; } - return SystemLogSuppressionRule.None; + return SuppressionRule.None; } } -} \ No newline at end of file +} From de85e295099db8dc6825cdbd33ca0dbec60068d9 Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 10:59:07 -0700 Subject: [PATCH 6/7] Simplify log suppression to a declarative table Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- release_notes.md | 3 +- .../Diagnostics/SystemLogger.cs | 91 +++++-------------- 2 files changed, 24 insertions(+), 70 deletions(-) diff --git a/release_notes.md b/release_notes.md index 48ea313d8f..3c65ec27b3 100644 --- a/release_notes.md +++ b/release_notes.md @@ -10,4 +10,5 @@ - Ensure the gRPC server is available when an app transitions online after starting with app_offline.htm. - Extract host-managed worker and Grpc Server behavior into Azure.Functions.Rpc.Server.csproj (#11916) - Use a structured composite key for the authorization cache to prevent cache key collisions. -- Prevent extension system keys from being regenerated and overwritten when the startup context cache is stale, which previously could invalidate already-published extension webhook URLs (e.g. Event Grid, Durable Task). \ No newline at end of file +- Prevent extension system keys from being regenerated and overwritten when the startup context cache is stale, which previously could invalidate already-published extension webhook URLs (e.g. Event Grid, Durable Task). +- Suppress Storage queue and blob listener polling noise from telemetry by event name, preserving listener lifecycle and diagnostic logs (#11947) \ No newline at end of file diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index 9092d59453..b853b3b4a3 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -16,12 +16,27 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics { public class SystemLogger : ILogger { + // High-volume logs that are suppressed from system telemetry (FunctionsLogs) below Information. + // An empty event name list suppresses the whole category; otherwise only the listed EventNames + // are suppressed, so other diagnostics in that category are still reported. + // To suppress more logs, add a category here - no other changes are required. + private static readonly Dictionary _suppressedLogs = new(StringComparer.Ordinal) + { + ["Host.Executor"] = [], // An empty list suppresses every event in the category. + ["Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl"] = [], + ["Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"] = ["GetMessages", "BackoffDelay"], + ["Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener"] = ["GetMessages", "BackoffDelay"], + ["Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener"] = ["PollBlobContainer", "BlobAlreadyProcessed", "BlobDoesNotMatchPattern"] + }; + private readonly string _categoryName; private readonly string _functionName; private readonly string _hostInstanceId; private readonly bool _isUserFunction; private readonly LogLevel _logLevel; - private readonly SuppressionRule _suppressionRule; + + // Null when nothing is suppressed for this category. Resolved once so the per-log check stays cheap. + private readonly string[] _suppressedEventNames; private readonly IEnvironment _environment; private readonly IEventGenerator _eventGenerator; private readonly IDebugStateProvider _debugStateProvider; @@ -36,7 +51,7 @@ public SystemLogger(string hostInstanceId, string categoryName, IEventGenerator _eventGenerator = eventGenerator; _categoryName = categoryName ?? string.Empty; _logLevel = LogLevel.Debug; - _suppressionRule = GetSuppressionRule(_categoryName); + _suppressedLogs.TryGetValue(_categoryName, out _suppressedEventNames); _functionName = LogCategories.IsFunctionCategory(_categoryName) ? _categoryName.Split('.')[1] : null; _isUserFunction = LogCategories.IsFunctionUserCategory(_categoryName); _hostInstanceId = hostInstanceId; @@ -48,25 +63,6 @@ public SystemLogger(string hostInstanceId, string categoryName, IEventGenerator _appServiceOptions = appServiceOptionsMonitor.CurrentValue; } - /// - /// Identifies how high-volume logs are suppressed for a given category. Resolved once per - /// logger instance so the per-log check on the hot path is a simple branch. - /// - private enum SuppressionRule - { - /// Nothing is suppressed for this category. - None, - - /// All Debug/Trace logs in the category are suppressed. - Category, - - /// Only the named queue polling events are suppressed. - Queue, - - /// Only the named blob scan events are suppressed. - Blob - } - public IDisposable BeginScope(TState state) => _scopeProvider.Push(state); public bool IsEnabled(LogLevel logLevel) @@ -83,14 +79,6 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except return; } - // FunctionIndexingException must still be propagated below, so never short-circuit it here. - if (exception is not FunctionIndexingException - && (_suppressionRule == SuppressionRule.Category || !string.IsNullOrEmpty(eventId.Name)) - && ShouldSuppress(eventId.Name, logLevel)) - { - return; - } - // Enumerate all the state values once, capturing the values we'll use below - last one wins. string stateSourceValue = null; string stateFunctionName = null; @@ -146,9 +134,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except string eventName = !string.IsNullOrEmpty(eventId.Name) ? eventId.Name : stateEventName ?? string.Empty; eventName = isDiagnosticEvent ? $"DiagnosticEvent-{diagnosticEventErrorCode}" : eventName; - if (string.IsNullOrEmpty(eventId.Name) - && (_suppressionRule is SuppressionRule.Queue or SuppressionRule.Blob) - && ShouldSuppress(eventName, logLevel)) + if (_suppressedEventNames is not null && ShouldSuppress(eventName, logLevel)) { return; } @@ -222,47 +208,14 @@ private bool ShouldSuppress(string eventName, LogLevel logLevel) return false; } - bool isSuppressionCandidate = _suppressionRule switch - { - SuppressionRule.Category => true, - SuppressionRule.Queue => string.Equals(eventName, "BackoffDelay", StringComparison.Ordinal) - || string.Equals(eventName, "GetMessages", StringComparison.Ordinal), - SuppressionRule.Blob => string.Equals(eventName, "BlobAlreadyProcessed", StringComparison.Ordinal) - || string.Equals(eventName, "BlobDoesNotMatchPattern", StringComparison.Ordinal) - || string.Equals(eventName, "PollBlobContainer", StringComparison.Ordinal), - _ => false - }; - - if (!isSuppressionCandidate) + // An empty list suppresses the entire category. + if (_suppressedEventNames.Length > 0 && Array.IndexOf(_suppressedEventNames, eventName) < 0) { return false; } - // Diagnostic mode logs everything. Trace only reaches this point when diagnostic mode is - // active, since IsEnabled otherwise filters it out, so it is covered by the same check. + // Diagnostic mode logs everything. return !_debugStateProvider.InDiagnosticMode; } - - private static SuppressionRule GetSuppressionRule(string category) - { - if (string.Equals(category, "Host.Executor", StringComparison.Ordinal) - || string.Equals(category, "Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl", StringComparison.Ordinal)) - { - return SuppressionRule.Category; - } - - if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener", StringComparison.Ordinal) - || string.Equals(category, "Microsoft.Azure.WebJobs.Host.Queues.Listeners.QueueListener", StringComparison.Ordinal)) - { - return SuppressionRule.Queue; - } - - if (string.Equals(category, "Microsoft.Azure.WebJobs.Extensions.Storage.Blobs.Listeners.BlobListener", StringComparison.Ordinal)) - { - return SuppressionRule.Blob; - } - - return SuppressionRule.None; - } } -} +} \ No newline at end of file From 086b4215627005a302b1dd9dcf6729744003ffe4 Mon Sep 17 00:00:00 2001 From: Rohit Ranjan <90008725+RohitRanjanMS@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:09:45 -0700 Subject: [PATCH 7/7] Add edge case coverage for log suppression Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fc802fbd-2254-4fb7-81a2-58c1da953c68 --- .../Diagnostics/SystemLogger.cs | 1 + .../Eventing/SystemLoggerTests.cs | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs index b853b3b4a3..76391485da 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/SystemLogger.cs @@ -201,6 +201,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _eventGenerator.LogFunctionTraceEvent(logLevel, subscriptionId, appName, functionName, eventName, source, details, formattedMessage, innerExceptionType, innerExceptionMessage, invocationId, _hostInstanceId, activityId, runtimeSiteName, slotName, DateTime.UtcNow); } + // Only called when _suppressedEventNames is not null, so the category has suppression configured. private bool ShouldSuppress(string eventName, LogLevel logLevel) { if (logLevel >= LogLevel.Information) diff --git a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs index e3a0428f0a..61aee63f17 100644 --- a/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs +++ b/test/WebJobs.Script.Tests/Eventing/SystemLoggerTests.cs @@ -154,6 +154,89 @@ public void Log_SuppressedEvent_DoesNotEmitEvent(string category, string eventNa Times.Never); } + [Theory] + [InlineData(null)] + [InlineData("")] + public void Log_NullOrEmptyCategory_DoesNotThrow(string category) + { + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())); + + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + localLogger.LogDebug("TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + + [Fact] + public void Log_SuppressedCategory_UnnamedEvent_IsEmitted() + { + // Listener lifecycle logs carry no EventName and must survive named-event suppression. + const string category = "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + LogLevel.Debug, _subscriptionId, _websiteName, string.Empty, string.Empty, category, string.Empty, + "Storage queue listener started", string.Empty, string.Empty, string.Empty, _hostInstanceId, + string.Empty, _runtimeSiteName, _slotName, It.IsAny())); + + localLogger.LogDebug("Storage queue listener started"); + + _mockEventGenerator.VerifyAll(); + } + + [Fact] + public void Log_SuppressedCategory_NullStateEventName_DoesNotThrow() + { + const string category = "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + var state = new Dictionary + { + [ScriptConstants.LogPropertyEventNameKey] = null + }; + + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + LogLevel.Debug, _subscriptionId, _websiteName, string.Empty, string.Empty, category, string.Empty, + "TestMessage", string.Empty, string.Empty, string.Empty, _hostInstanceId, + string.Empty, _runtimeSiteName, _slotName, It.IsAny())); + + localLogger.Log(LogLevel.Debug, eventId: 0, state, null, (s, e) => "TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + + [Fact] + public void Log_SuppressedEvent_TraceLevel_DoesNotThrow() + { + // Trace only passes IsEnabled in diagnostic mode, where suppression is bypassed. + const string category = "Microsoft.Azure.WebJobs.Extensions.Storage.Common.Listeners.QueueListener"; + var localLogger = new SystemLogger(_hostInstanceId, category, _mockEventGenerator.Object, _environment, _debugStateProvider.Object, null, new LoggerExternalScopeProvider(), _appServiceOptions); + + localLogger.Log(LogLevel.Trace, new EventId(1, "GetMessages"), string.Empty, null, (s, e) => "TestMessage"); + + _mockEventGenerator.Verify( + p => p.LogFunctionTraceEvent( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()), + Times.Never); + + _inDiagnosticMode = true; + _mockEventGenerator.Setup(p => p.LogFunctionTraceEvent( + LogLevel.Trace, _subscriptionId, _websiteName, string.Empty, "GetMessages", category, string.Empty, + "TestMessage", string.Empty, string.Empty, string.Empty, _hostInstanceId, + string.Empty, _runtimeSiteName, _slotName, It.IsAny())); + + localLogger.Log(LogLevel.Trace, new EventId(1, "GetMessages"), string.Empty, null, (s, e) => "TestMessage"); + + _mockEventGenerator.VerifyAll(); + } + [Theory] [InlineData("Host.Executor")] [InlineData("Microsoft.Azure.WebJobs.EventHubs.EventHubProducerClientImpl")]