Skip to content

Filter Azure monitor logs when customer does not subscribe for the categories #10982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public class AzureMonitorDiagnosticLogger : ILogger
private readonly IEventGenerator _eventGenerator;
private readonly IEnvironment _environment;
private readonly IExternalScopeProvider _scopeProvider;
private AppServiceOptions _appServiceOptions;
private bool _isAzureMonitorLoggingEnabled;
private IOptionsMonitor<AppServiceOptions> _appServiceOptionsMonitor;

public AzureMonitorDiagnosticLogger(string category, string hostInstanceId, IEventGenerator eventGenerator, IEnvironment environment, IExternalScopeProvider scopeProvider,
HostNameProvider hostNameProvider, IOptionsMonitor<AppServiceOptions> appServiceOptionsMonitor)
Expand All @@ -40,10 +41,9 @@ public AzureMonitorDiagnosticLogger(string category, string hostInstanceId, IEve
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
_hostNameProvider = hostNameProvider ?? throw new ArgumentNullException(nameof(hostNameProvider));
_ = appServiceOptionsMonitor ?? throw new ArgumentNullException(nameof(appServiceOptionsMonitor));

appServiceOptionsMonitor.OnChange(newOptions => _appServiceOptions = newOptions);
_appServiceOptions = appServiceOptionsMonitor.CurrentValue;
_appServiceOptionsMonitor = appServiceOptionsMonitor ?? throw new ArgumentNullException(nameof(appServiceOptionsMonitor));
UpdateAppServiceOptions(_appServiceOptionsMonitor.CurrentValue);
_appServiceOptionsMonitor.OnChange(UpdateAppServiceOptions);

_roleInstance = _environment.GetInstanceId();

Expand All @@ -55,7 +55,7 @@ public AzureMonitorDiagnosticLogger(string category, string hostInstanceId, IEve
public bool IsEnabled(LogLevel logLevel)
{
// We want to instantiate this Logger in placeholder mode to warm it up, but do not want to log anything.
return !string.IsNullOrEmpty(_hostNameProvider.Value) && !_environment.IsPlaceholderModeEnabled();
return _isAzureMonitorLoggingEnabled && !string.IsNullOrEmpty(_hostNameProvider.Value) && !_environment.IsPlaceholderModeEnabled();
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
Expand Down Expand Up @@ -107,7 +107,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
using (JsonTextWriter writer = new JsonTextWriter(sw) { Formatting = Formatting.None })
{
writer.WriteStartObject();
WritePropertyIfNotNull(writer, "appName", _appServiceOptions.AppName);
WritePropertyIfNotNull(writer, "appName", _appServiceOptionsMonitor.CurrentValue.AppName);
WritePropertyIfNotNull(writer, "roleInstance", _roleInstance);
WritePropertyIfNotNull(writer, "message", formattedMessage);
WritePropertyIfNotNull(writer, "category", _category);
Expand Down Expand Up @@ -136,6 +136,11 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
_eventGenerator.LogAzureMonitorDiagnosticLogEvent(logLevel, _hostNameProvider.Value, AzureMonitorOperationName, AzureMonitorCategoryName, _regionName, sw.ToString());
}

private void UpdateAppServiceOptions(AppServiceOptions appServiceOptions)
{
_isAzureMonitorLoggingEnabled = !(_environment.IsConsumptionOnLegion() && !_appServiceOptionsMonitor.CurrentValue.IsAzureMonitorLoggingEnabled);
}

private static void WritePropertyIfNotNull<T>(JsonTextWriter writer, string propertyName, T propertyValue)
{
if (propertyValue != null)
Expand Down
13 changes: 13 additions & 0 deletions src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static Microsoft.Azure.WebJobs.Script.Utility;

namespace Microsoft.Azure.WebJobs.Script.WebHost
{
Expand Down Expand Up @@ -91,6 +93,17 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
loggingBuilder.AddWebJobsSystem<SystemLoggerProvider>();
if (environment.IsAzureMonitorEnabled())
{
if (environment.IsConsumptionOnLegion())
{
loggingBuilder.Services.AddOptions<LoggerFilterOptions>()
.Configure<IOptionsMonitor<AppServiceOptions>>((filters, options) =>
{
filters.AddFilter<AzureMonitorDiagnosticLoggerProvider>((category, level) =>
{
return options.CurrentValue.IsAzureMonitorLoggingEnabled;
});
});
}
loggingBuilder.Services.AddSingleton<ILoggerProvider, AzureMonitorDiagnosticLoggerProvider>();
}

Expand Down
2 changes: 2 additions & 0 deletions src/WebJobs.Script/Config/AppServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ public class AppServiceOptions
public string RuntimeSiteName { get; set; }

public string SlotName { get; set; }

public bool IsAzureMonitorLoggingEnabled { get; set; }
}
}
3 changes: 3 additions & 0 deletions src/WebJobs.Script/Config/AppServiceOptionsSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Extensions.Options;
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
using static Microsoft.Azure.WebJobs.Script.Utility;

namespace Microsoft.Azure.WebJobs.Script.Configuration
{
Expand All @@ -20,6 +22,7 @@ public void Configure(AppServiceOptions options)
options.SubscriptionId = _environment.GetSubscriptionId() ?? string.Empty;
options.RuntimeSiteName = _environment.GetRuntimeSiteName() ?? string.Empty;
options.SlotName = _environment.GetSlotName() ?? string.Empty;
options.IsAzureMonitorLoggingEnabled = _environment.IsAzureMonitorEnabled();
}
}
}
11 changes: 3 additions & 8 deletions src/WebJobs.Script/Environment/EnvironmentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Azure.WebJobs.Script.Description;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using static Microsoft.Azure.WebJobs.Script.EnvironmentSettingNames;
using static Microsoft.Azure.WebJobs.Script.Utility;

namespace Microsoft.Azure.WebJobs.Script
{
Expand Down Expand Up @@ -69,13 +70,7 @@ public static bool IsEasyAuthEnabled(this IEnvironment environment)
/// </summary>
public static bool IsAzureMonitorEnabled(this IEnvironment environment)
{
string value = environment.GetEnvironmentVariable(AzureMonitorCategories);
if (value == null)
{
return true;
}
string[] categories = value.Split(',');
return categories.Contains(ScriptConstants.AzureMonitorTraceCategory);
return IsAzureMonitorLoggingEnabled(environment.GetEnvironmentVariable(AzureMonitorCategories));
}

/// <summary>
Expand Down Expand Up @@ -337,7 +332,7 @@ public static bool IsLinuxConsumptionOnAtlas(this IEnvironment environment)
string.IsNullOrEmpty(environment.GetEnvironmentVariable(LegionServiceHost));
}

private static bool IsConsumptionOnLegion(this IEnvironment environment)
public static bool IsConsumptionOnLegion(this IEnvironment environment)
{
return !environment.IsAppService() &&
(!string.IsNullOrEmpty(environment.GetEnvironmentVariable(ContainerName)) ||
Expand Down
1 change: 1 addition & 0 deletions src/WebJobs.Script/ScriptConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public static class ScriptConstants
public const string ExtendedPlatformChannelNameUpper = "EXTENDED";

public const string AzureMonitorTraceCategory = "FunctionAppLogs";
public const string DefaultAzureMonitorCategories = "None";

public const string KubernetesManagedAppName = "K8SE_APP_NAME";
public const string KubernetesManagedAppNamespace = "K8SE_APP_NAMESPACE";
Expand Down
16 changes: 16 additions & 0 deletions src/WebJobs.Script/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,22 @@ public static bool TryReadAsBool(IDictionary<string, object> properties, string
return result = false;
}

public static bool IsAzureMonitorLoggingEnabled(string azureMonitorcategoriesSubscribed)
{
if (azureMonitorcategoriesSubscribed == null)
{
return true;
}
if (string.Equals(ScriptConstants.DefaultAzureMonitorCategories, azureMonitorcategoriesSubscribed, StringComparison.Ordinal))
{
// Default value for the env variable is None.
// This is set when customer does not subscribe any category.
return false;
}
string[] categories = azureMonitorcategoriesSubscribed.Split(',');
return categories.Contains(ScriptConstants.AzureMonitorTraceCategory);
}

private class FilteredExpandoObjectConverter : ExpandoObjectConverter
{
public override bool CanWrite => true;
Expand Down
42 changes: 19 additions & 23 deletions test/WebJobs.Script.Tests/Eventing/DiagnosticLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,35 +206,31 @@ public void Log_Sanitizes()
Assert.True(JToken.DeepEquals(actual, expected), $"Actual: {actual.ToString()}{Environment.NewLine}Expected: {expected.ToString()}");
}

[Fact]
public void Log_DisabledIfPlaceholder()
[Theory]
[InlineData(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1", false, false, false)] // Placeholder
[InlineData(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1", true, false, false)] // Placeholder
[InlineData(EnvironmentSettingNames.AzureWebsiteHostName, null, false, false, false)] // NoSiteName
[InlineData(EnvironmentSettingNames.AzureWebsiteHostName, null, true, false, false)] // NoSiteName
[InlineData(EnvironmentSettingNames.AzureWebsiteHostName, "host", false, false, true)]
[InlineData(EnvironmentSettingNames.AzureWebsiteHostName, "host", true, false, false)]
[InlineData(EnvironmentSettingNames.AzureWebsiteHostName, "host", true, true, true)]
public void Log_IsEnabled(string envVariableName, string envVariableVale, bool isConsumptionOnLegion, bool isAzureMonitorEnabled, bool isDisabled)
{
string message = "TestMessage";
string functionInvocationId = Guid.NewGuid().ToString();
string activityId = Guid.NewGuid().ToString();

_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1");

_logger.LogInformation(message);

Assert.False(_logger.IsEnabled(LogLevel.Information));
_mockEventGenerator.Verify(m => m.LogAzureMonitorDiagnosticLogEvent(It.IsAny<LogLevel>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
}

[Fact]
public void Log_DisabledIfNoSiteName()
{
string message = "TestMessage";
string functionInvocationId = Guid.NewGuid().ToString();
string activityId = Guid.NewGuid().ToString();
_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName, null);

// Recreate the logger was we cache the site name in the constructor
ILogger logger = new AzureMonitorDiagnosticLogger(_category, _hostInstanceId, _mockEventGenerator.Object, _environment, new LoggerExternalScopeProvider(), _hostNameProvider, _appServiceOptionsWrapper);
_environment.SetEnvironmentVariable(envVariableName, envVariableVale);
if (isConsumptionOnLegion)
{
_environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteInstanceId, string.Empty);
_environment.SetEnvironmentVariable(EnvironmentSettingNames.ContainerName, "containername");
_environment.SetEnvironmentVariable(EnvironmentSettingNames.LegionServiceHost, "legionhost");
}

logger.LogInformation(message);
_appServiceOptionsWrapper.CurrentValue.IsAzureMonitorLoggingEnabled = isAzureMonitorEnabled;
_appServiceOptionsWrapper.InvokeChanged();

Assert.False(logger.IsEnabled(LogLevel.Information));
Assert.Equal(isDisabled, _logger.IsEnabled(LogLevel.Information));
_mockEventGenerator.Verify(m => m.LogAzureMonitorDiagnosticLogEvent(It.IsAny<LogLevel>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public void IsVMSS_RetrunsExpectedResult(string roleInstanceId, bool expected)
[InlineData(null, true)]
[InlineData("", false)]
[InlineData("Foo,FunctionAppLogs,Bar", true)]
[InlineData("FunctionAppLogs", true)]
[InlineData("None", false)]
[InlineData("Foo,Bar", false)]
public void IsAzureMonitorEnabled_ReturnsExpectedResult(string value, bool expected)
{
Expand Down
Loading