From 8cb11ae4aaff7e8200e0b888237a6d91c4fced49 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 31 Oct 2024 10:19:20 -0700 Subject: [PATCH 01/10] Add RestrictHostLogs config --- .../Config/FunctionsHostingConfigOptions.cs | 16 ++++++++++++++++ src/WebJobs.Script/ScriptConstants.cs | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs b/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs index f389c57cc1..31bf50cbb6 100644 --- a/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs +++ b/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs @@ -85,6 +85,22 @@ public bool SwtIssuerEnabled } } + /// + /// Gets or sets a value indicating whether non-critical logs should be disabled in the host. + /// + public bool RestrictHostLogs + { + get + { + return GetFeatureOrDefault(ScriptConstants.HostingConfigRestrictHostLogs, "1") == "1"; + } + + set + { + _features[ScriptConstants.HostingConfigRestrictHostLogs] = value ? "1" : "0"; + } + } + /// /// Gets feature by name. /// diff --git a/src/WebJobs.Script/ScriptConstants.cs b/src/WebJobs.Script/ScriptConstants.cs index 5f0d49e344..ec3c5b9718 100644 --- a/src/WebJobs.Script/ScriptConstants.cs +++ b/src/WebJobs.Script/ScriptConstants.cs @@ -127,6 +127,7 @@ public static class ScriptConstants public const string FeatureFlagEnableHostLogs = "EnableHostLogs"; public const string HostingConfigSwtAuthenticationEnabled = "SwtAuthenticationEnabled"; public const string HostingConfigSwtIssuerEnabled = "SwtIssuerEnabled"; + public const string HostingConfigRestrictHostLogs = "RestrictHostLogs"; public const string SiteAzureFunctionsUriFormat = "https://{0}.azurewebsites.net/azurefunctions"; public const string ScmSiteUriFormat = "https://{0}.scm.azurewebsites.net"; @@ -215,7 +216,7 @@ public static class ScriptConstants public static readonly long DefaultMaxRequestBodySize = 104857600; public static readonly ImmutableArray SystemLogCategoryPrefixes = ImmutableArray.Create("Microsoft.Azure.WebJobs.", "Function.", "Worker.", "Host."); - public static readonly ImmutableArray RestrictedSystemLogCategoryPrefixes = ImmutableArray.Create("Host.Startup"); + public static readonly ImmutableArray RestrictedSystemLogCategoryPrefixes = ImmutableArray.Create("Host.Startup."); public static readonly string FunctionsHostingConfigSectionName = "FunctionsHostingConfig"; public static readonly string MaximumSupportedBundleV3Version = "FunctionRuntimeV3MaxBundleV3Version"; From a35d9f41b3b218b21740f158ff27d6c88831ed0b Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 31 Oct 2024 10:26:38 -0700 Subject: [PATCH 02/10] Utilize RestrictHostLogs flag for logging filters --- .../Diagnostics/ILoggingBuilderExtensions.cs | 4 +-- .../ScriptLoggingBuilderExtensions.cs | 27 ++++++++++++++----- .../ScriptHostBuilderExtensions.cs | 10 ++++++- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/WebJobs.Script.WebHost/Diagnostics/ILoggingBuilderExtensions.cs b/src/WebJobs.Script.WebHost/Diagnostics/ILoggingBuilderExtensions.cs index 811ccc8854..2f4505a3a0 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/ILoggingBuilderExtensions.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/ILoggingBuilderExtensions.cs @@ -8,12 +8,12 @@ namespace Microsoft.Extensions.Logging { public static class ILoggingBuilderExtensions { - public static void AddWebJobsSystem(this ILoggingBuilder builder) where T : SystemLoggerProvider + public static void AddWebJobsSystem(this ILoggingBuilder builder, bool restrictHostLogs = false) where T : SystemLoggerProvider { builder.Services.AddSingleton(); // Log all logs to SystemLogger - builder.AddDefaultWebJobsFilters(LogLevel.Trace); + builder.AddDefaultWebJobsFilters(LogLevel.Trace, restrictHostLogs); } } } diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index f1cc8586c5..79b5d8ec54 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.Azure.WebJobs.Script; -using Microsoft.Azure.WebJobs.Script.Config; using Microsoft.Azure.WebJobs.Script.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -16,16 +15,24 @@ namespace Microsoft.Extensions.Logging public static class ScriptLoggingBuilderExtensions { private static ConcurrentDictionary _filteredCategoryCache = new ConcurrentDictionary(); + private static ImmutableArray _allowedLogCategoryPrefixes = ScriptConstants.SystemLogCategoryPrefixes; - public static ILoggingBuilder AddDefaultWebJobsFilters(this ILoggingBuilder builder) + // For testing only + internal static ImmutableArray AllowedSystemLogPrefixes => _allowedLogCategoryPrefixes; + + public static ILoggingBuilder AddDefaultWebJobsFilters(this ILoggingBuilder builder, bool restrictHostLogs = false) { + SetSystemLogCategoryPrefixes(restrictHostLogs); + builder.SetMinimumLevel(LogLevel.None); builder.AddFilter((c, l) => Filter(c, l, LogLevel.Information)); return builder; } - public static ILoggingBuilder AddDefaultWebJobsFilters(this ILoggingBuilder builder, LogLevel level) where T : ILoggerProvider + public static ILoggingBuilder AddDefaultWebJobsFilters(this ILoggingBuilder builder, LogLevel level, bool restrictHostLogs = false) where T : ILoggerProvider { + SetSystemLogCategoryPrefixes(restrictHostLogs); + builder.AddFilter(null, LogLevel.None); builder.AddFilter((c, l) => Filter(c, l, level)); return builder; @@ -38,11 +45,17 @@ internal static bool Filter(string category, LogLevel actualLevel, LogLevel minL private static bool IsFiltered(string category) { - ImmutableArray systemLogCategoryPrefixes = FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs) - ? ScriptConstants.SystemLogCategoryPrefixes - : ScriptConstants.RestrictedSystemLogCategoryPrefixes; + return _filteredCategoryCache.GetOrAdd(category, c => _allowedLogCategoryPrefixes.Any(p => c.StartsWith(p))); + } - return _filteredCategoryCache.GetOrAdd(category, c => systemLogCategoryPrefixes.Any(p => category.StartsWith(p))); + private static void SetSystemLogCategoryPrefixes(bool restrictHostLogs) + { + // Once restrictHostLogs is set to true, it stays true for the rest of the application's lifetime + // Set _allowedLogCategoryPrefixes to the restricted prefixes and clear the filter cache + if (restrictHostLogs) + { + _allowedLogCategoryPrefixes = ScriptConstants.RestrictedSystemLogCategoryPrefixes; + } } public static void AddConsoleIfEnabled(this ILoggingBuilder builder, HostBuilderContext context) diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index c705b3137d..7c7760ca94 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -81,7 +81,9 @@ public static IHostBuilder AddScriptHost(this IHostBuilder builder, // Host configuration builder.ConfigureLogging((context, loggingBuilder) => { - loggingBuilder.AddDefaultWebJobsFilters(); + var hostingConfigOptions = applicationOptions.RootServiceProvider.GetService>(); + var restrictHostLogs = RestrictHostLogs(hostingConfigOptions.Value, SystemEnvironment.Instance); + loggingBuilder.AddDefaultWebJobsFilters(restrictHostLogs); string loggingPath = ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "Logging"); loggingBuilder.AddConfiguration(context.Configuration.GetSection(loggingPath)); @@ -492,6 +494,12 @@ private static IDistributedLockManager GetBlobLockManager(IServiceProvider provi } } + private static bool RestrictHostLogs(FunctionsHostingConfigOptions options, IEnvironment environment) + { + // Feature flag should take precedence over the host configuration + return !FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs, environment) && options.RestrictHostLogs; + } + /// /// Gets and removes the specified value, if it exists and is of type T. /// Throws an InvalidOperationException if the key does not exist or is not of type T. From f010215ab85499b30246fcb0f2c395aa73ef13b2 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 31 Oct 2024 10:53:37 -0700 Subject: [PATCH 03/10] Update tests --- .../TestHostBuilderExtensions.cs | 17 ++++- .../DefaultDependencyValidatorTests.cs | 41 +++++----- .../FunctionsHostingConfigOptionsTest.cs | 33 ++++++-- .../Configuration/RestrictHostLogsTests.cs | 75 +++++++++++++++++++ 4 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs diff --git a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs index a1f55a1442..a6c6894868 100644 --- a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs +++ b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs @@ -29,13 +29,13 @@ namespace Microsoft.WebJobs.Script.Tests { public static class TestHostBuilderExtensions { - public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder builder, Action configure = null, bool runStartupHostedServices = false) + public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder builder, Action configure = null, bool runStartupHostedServices = false, IEnvironment environment = null) { - return builder.ConfigureDefaultTestWebScriptHost(null, configure, runStartupHostedServices); + return builder.ConfigureDefaultTestWebScriptHost(null, configure, runStartupHostedServices, environment: environment); } public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder builder, Action configureWebJobs, - Action configure = null, bool runStartupHostedServices = false, Action configureRootServices = null) + Action configure = null, bool runStartupHostedServices = false, Action configureRootServices = null, IEnvironment environment = null) { var webHostOptions = new ScriptApplicationHostOptions() { @@ -48,9 +48,18 @@ public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder b // Register root services var services = new ServiceCollection(); + + if (environment is not null) + { + services.AddSingleton(environment); + } + else + { + AddMockedSingleton(services); + } + AddMockedSingleton(services); AddMockedSingleton(services); - AddMockedSingleton(services); AddMockedSingleton(services); AddMockedSingleton(services); AddMockedSingleton(services); diff --git a/test/WebJobs.Script.Tests/Configuration/DefaultDependencyValidatorTests.cs b/test/WebJobs.Script.Tests/Configuration/DefaultDependencyValidatorTests.cs index 8bfda63aab..e9fa0f9422 100644 --- a/test/WebJobs.Script.Tests/Configuration/DefaultDependencyValidatorTests.cs +++ b/test/WebJobs.Script.Tests/Configuration/DefaultDependencyValidatorTests.cs @@ -36,27 +36,28 @@ public async Task Validator_AllValid() [Fact] public async Task Validator_InvalidServices_LogsError() { - SystemEnvironment.Instance.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableHostLogs); - - LogMessage invalidServicesMessage = await RunTest(configureJobHost: s => + using (new TestScopedEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableHostLogs)) { - s.AddSingleton(); - s.AddSingleton(); - s.AddSingleton(new MyMetricsLogger()); - - // Try removing system logger - var descriptor = s.Single(p => p.ImplementationType == typeof(SystemLoggerProvider)); - s.Remove(descriptor); - }); - - Assert.NotNull(invalidServicesMessage); - - IEnumerable messageLines = invalidServicesMessage.Exception.Message.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()); - Assert.Equal(5, messageLines.Count()); - Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyHostedService).AssemblyQualifiedName)); - Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyScriptEventManager).AssemblyQualifiedName)); - Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyMetricsLogger).AssemblyQualifiedName)); - Assert.Contains(messageLines, p => p.StartsWith("[Missing]") && p.EndsWith(typeof(SystemLoggerProvider).AssemblyQualifiedName)); + LogMessage invalidServicesMessage = await RunTest(configureJobHost: s => + { + s.AddSingleton(); + s.AddSingleton(); + s.AddSingleton(new MyMetricsLogger()); + + // Try removing system logger + var descriptor = s.Single(p => p.ImplementationType == typeof(SystemLoggerProvider)); + s.Remove(descriptor); + }); + + Assert.NotNull(invalidServicesMessage); + + IEnumerable messageLines = invalidServicesMessage.Exception.Message.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()); + Assert.Equal(5, messageLines.Count()); + Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyHostedService).AssemblyQualifiedName)); + Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyScriptEventManager).AssemblyQualifiedName)); + Assert.Contains(messageLines, p => p.StartsWith("[Invalid]") && p.EndsWith(typeof(MyMetricsLogger).AssemblyQualifiedName)); + Assert.Contains(messageLines, p => p.StartsWith("[Missing]") && p.EndsWith(typeof(SystemLoggerProvider).AssemblyQualifiedName)); + } } [Fact] diff --git a/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs b/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs index aa72b0fd22..46e4aea5fd 100644 --- a/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs +++ b/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs @@ -135,14 +135,35 @@ public void SwtIssuerEnabled_ReturnsExpectedValue() Assert.False(options.SwtIssuerEnabled); } - internal static IHostBuilder GetScriptHostBuilder(string fileName, string fileContent) + [Fact] + public void RestrictHostLogs_ReturnsExpectedValue() + { + FunctionsHostingConfigOptions options = new FunctionsHostingConfigOptions(); + + // defaults to true + Assert.True(options.RestrictHostLogs); + + // returns true when explicitly enabled + options.Features[ScriptConstants.HostingConfigRestrictHostLogs] = "1"; + Assert.True(options.RestrictHostLogs); + + // returns false when disabled + options.Features[ScriptConstants.HostingConfigRestrictHostLogs] = "0"; + Assert.False(options.RestrictHostLogs); + } + + internal static IHostBuilder GetScriptHostBuilder(string fileName, string fileContent, IEnvironment environment = null) { if (!string.IsNullOrEmpty(fileContent)) { File.WriteAllText(fileName, fileContent); } - TestEnvironment environment = new TestEnvironment(); + if (environment is null) + { + environment = new TestEnvironment(); + } + environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionsPlatformConfigFilePath, fileName); IHost webHost = new HostBuilder() @@ -161,7 +182,7 @@ internal static IHostBuilder GetScriptHostBuilder(string fileName, string fileCo { services.AddSingleton(); }) - .ConfigureDefaultTestWebScriptHost(null, configureRootServices: (services) => + .ConfigureDefaultTestWebScriptHost(null, environment: environment, configureRootServices: (services) => { services.AddSingleton(webHost.Services.GetService>()); services.AddSingleton(webHost.Services.GetService>()); @@ -170,7 +191,7 @@ internal static IHostBuilder GetScriptHostBuilder(string fileName, string fileCo public class TestService { - public TestService(IOptions options, IOptionsMonitor monitor) + public TestService(IOptions options, IOptionsMonitor monitor) { Options = options; Monitor = monitor; @@ -180,9 +201,9 @@ public TestService(IOptions options, IOpti }); } - public IOptions Options { get; set; } + public IOptions Options { get; set; } - public IOptionsMonitor Monitor { get; set; } + public IOptionsMonitor Monitor { get; set; } } } } diff --git a/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs new file mode 100644 index 0000000000..e815785b11 --- /dev/null +++ b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Script.Config; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using WebJobs.Script.Tests; +using Xunit; + +namespace Microsoft.Azure.WebJobs.Script.Tests.Configuration +{ + public class RestrictHostLogsTests : IAsyncLifetime + { + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + public Task DisposeAsync() + { + // Reset the static _allowedLogCategoryPrefixes field after each test to the default value + typeof(ScriptLoggingBuilderExtensions) + .GetField("_allowedLogCategoryPrefixes", BindingFlags.Static | BindingFlags.NonPublic) + .SetValue(null, ScriptConstants.SystemLogCategoryPrefixes); + + SystemEnvironment.Instance.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, null); + return Task.CompletedTask; + } + + [Theory] + [InlineData(true, false, true)] // RestrictHostLogs is true, FeatureFlag is not set, should result in **restricted** logs. This is the default behaviour of the host. + [InlineData(false, true, false)] // RestrictHostLogs is false, FeatureFlag is set, should result in unrestricted logs + [InlineData(true, true, false)] // RestrictHostLogs is true, FeatureFlag is set, should result in unrestricted logs + [InlineData(false, false, false)] // RestrictHostLogs is false, FeatureFlag is not set, should result in unrestricted log + public async Task RestirctHostLogs_SetsCorrectSystemLogPrefix(bool restrictHostLogs, bool setFeatureFlag, bool shouldResultInRestrictedSystemLogs) + { + using (TempDirectory tempDir = new TempDirectory()) + { + var environment = SystemEnvironment.Instance; + string fileName = Path.Combine(tempDir.Path, "settings.txt"); + string fileContent = restrictHostLogs ? string.Empty : $"{ScriptConstants.HostingConfigRestrictHostLogs}=false"; + + if (setFeatureFlag) + { + environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableHostLogs); + } + + IHost host = FunctionsHostingConfigOptionsTest.GetScriptHostBuilder(fileName, fileContent, environment).Build(); + var testService = host.Services.GetService(); + + await host.StartAsync(); + await Task.Delay(1000); + + Assert.Equal(restrictHostLogs, testService.Options.Value.RestrictHostLogs); + Assert.Equal(setFeatureFlag, FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs, environment)); + + if (shouldResultInRestrictedSystemLogs) + { + Assert.Equal(ScriptConstants.RestrictedSystemLogCategoryPrefixes, ScriptLoggingBuilderExtensions.AllowedSystemLogPrefixes); + } + else + { + Assert.Equal(ScriptConstants.SystemLogCategoryPrefixes, ScriptLoggingBuilderExtensions.AllowedSystemLogPrefixes); + } + + await host.StopAsync(); + } + } + } +} From ad094b6ff04357e7e712425db7a96fcd0f507c3f Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 31 Oct 2024 14:16:57 -0700 Subject: [PATCH 04/10] Refactor --- .../WebScriptHostBuilderExtension.cs | 12 +++++++++++- .../Extensions/ScriptLoggingBuilderExtensions.cs | 11 +++-------- src/WebJobs.Script/ScriptConstants.cs | 2 +- src/WebJobs.Script/ScriptHostBuilderExtensions.cs | 10 +--------- .../TestHostBuilderExtensions.cs | 2 +- .../Configuration/RestrictHostLogsTests.cs | 14 +++++++------- 6 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs index 5a0035c753..426674186d 100644 --- a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs +++ b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs @@ -75,9 +75,13 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP }) .ConfigureLogging(loggingBuilder => { + var hostingConfigOptions = rootServiceProvider.GetService>(); + var restrictHostLogs = RestrictHostLogs(hostingConfigOptions.Value); + loggingBuilder.Services.AddSingleton(); + loggingBuilder.AddDefaultWebJobsFilters(restrictHostLogs); + loggingBuilder.AddWebJobsSystem(restrictHostLogs); - loggingBuilder.AddWebJobsSystem(); if (environment.IsAzureMonitorEnabled()) { loggingBuilder.Services.AddSingleton(); @@ -170,6 +174,12 @@ private static void ConfigureRegisteredBuilders(TBuilder builder, ISer } } + private static bool RestrictHostLogs(FunctionsHostingConfigOptions options) + { + // Feature flag should take precedence over the host configuration + return !FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs) && options.RestrictHostLogs; + } + /// /// Used internally to register Newtonsoft formatters with our ScriptHost. /// diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index 79b5d8ec54..8d48f6b487 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -14,8 +14,8 @@ namespace Microsoft.Extensions.Logging { public static class ScriptLoggingBuilderExtensions { - private static ConcurrentDictionary _filteredCategoryCache = new ConcurrentDictionary(); - private static ImmutableArray _allowedLogCategoryPrefixes = ScriptConstants.SystemLogCategoryPrefixes; + private static ConcurrentDictionary _filteredCategoryCache = new (); + private static ImmutableArray _allowedLogCategoryPrefixes = new (); // For testing only internal static ImmutableArray AllowedSystemLogPrefixes => _allowedLogCategoryPrefixes; @@ -50,12 +50,7 @@ private static bool IsFiltered(string category) private static void SetSystemLogCategoryPrefixes(bool restrictHostLogs) { - // Once restrictHostLogs is set to true, it stays true for the rest of the application's lifetime - // Set _allowedLogCategoryPrefixes to the restricted prefixes and clear the filter cache - if (restrictHostLogs) - { - _allowedLogCategoryPrefixes = ScriptConstants.RestrictedSystemLogCategoryPrefixes; - } + _allowedLogCategoryPrefixes = restrictHostLogs ? ScriptConstants.RestrictedSystemLogCategoryPrefixes : ScriptConstants.SystemLogCategoryPrefixes; } public static void AddConsoleIfEnabled(this ILoggingBuilder builder, HostBuilderContext context) diff --git a/src/WebJobs.Script/ScriptConstants.cs b/src/WebJobs.Script/ScriptConstants.cs index ec3c5b9718..3b1cebce28 100644 --- a/src/WebJobs.Script/ScriptConstants.cs +++ b/src/WebJobs.Script/ScriptConstants.cs @@ -216,7 +216,7 @@ public static class ScriptConstants public static readonly long DefaultMaxRequestBodySize = 104857600; public static readonly ImmutableArray SystemLogCategoryPrefixes = ImmutableArray.Create("Microsoft.Azure.WebJobs.", "Function.", "Worker.", "Host."); - public static readonly ImmutableArray RestrictedSystemLogCategoryPrefixes = ImmutableArray.Create("Host.Startup."); + public static readonly ImmutableArray RestrictedSystemLogCategoryPrefixes = ImmutableArray.Create("Host.Startup"); public static readonly string FunctionsHostingConfigSectionName = "FunctionsHostingConfig"; public static readonly string MaximumSupportedBundleV3Version = "FunctionRuntimeV3MaxBundleV3Version"; diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index 7c7760ca94..c705b3137d 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -81,9 +81,7 @@ public static IHostBuilder AddScriptHost(this IHostBuilder builder, // Host configuration builder.ConfigureLogging((context, loggingBuilder) => { - var hostingConfigOptions = applicationOptions.RootServiceProvider.GetService>(); - var restrictHostLogs = RestrictHostLogs(hostingConfigOptions.Value, SystemEnvironment.Instance); - loggingBuilder.AddDefaultWebJobsFilters(restrictHostLogs); + loggingBuilder.AddDefaultWebJobsFilters(); string loggingPath = ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "Logging"); loggingBuilder.AddConfiguration(context.Configuration.GetSection(loggingPath)); @@ -494,12 +492,6 @@ private static IDistributedLockManager GetBlobLockManager(IServiceProvider provi } } - private static bool RestrictHostLogs(FunctionsHostingConfigOptions options, IEnvironment environment) - { - // Feature flag should take precedence over the host configuration - return !FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs, environment) && options.RestrictHostLogs; - } - /// /// Gets and removes the specified value, if it exists and is of type T. /// Throws an InvalidOperationException if the key does not exist or is not of type T. diff --git a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs index a6c6894868..24ca6c2f7c 100644 --- a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs +++ b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs @@ -55,7 +55,7 @@ public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder b } else { - AddMockedSingleton(services); + services.AddSingleton(SystemEnvironment.Instance); } AddMockedSingleton(services); diff --git a/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs index e815785b11..5dce564e7a 100644 --- a/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs +++ b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using System; +using System.Collections.Immutable; using System.IO; using System.Reflection; using System.Threading.Tasks; @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using WebJobs.Script.Tests; using Xunit; +using static Microsoft.Azure.WebJobs.Script.Tests.Configuration.FunctionsHostingConfigOptionsTest; namespace Microsoft.Azure.WebJobs.Script.Tests.Configuration { @@ -26,7 +27,7 @@ public Task DisposeAsync() // Reset the static _allowedLogCategoryPrefixes field after each test to the default value typeof(ScriptLoggingBuilderExtensions) .GetField("_allowedLogCategoryPrefixes", BindingFlags.Static | BindingFlags.NonPublic) - .SetValue(null, ScriptConstants.SystemLogCategoryPrefixes); + .SetValue(null, default(ImmutableArray)); SystemEnvironment.Instance.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, null); return Task.CompletedTask; @@ -41,23 +42,22 @@ public async Task RestirctHostLogs_SetsCorrectSystemLogPrefix(bool restrictHostL { using (TempDirectory tempDir = new TempDirectory()) { - var environment = SystemEnvironment.Instance; string fileName = Path.Combine(tempDir.Path, "settings.txt"); string fileContent = restrictHostLogs ? string.Empty : $"{ScriptConstants.HostingConfigRestrictHostLogs}=false"; if (setFeatureFlag) { - environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableHostLogs); + SystemEnvironment.Instance.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebJobsFeatureFlags, ScriptConstants.FeatureFlagEnableHostLogs); } - IHost host = FunctionsHostingConfigOptionsTest.GetScriptHostBuilder(fileName, fileContent, environment).Build(); - var testService = host.Services.GetService(); + IHost host = GetScriptHostBuilder(fileName, fileContent).Build(); + var testService = host.Services.GetService(); await host.StartAsync(); await Task.Delay(1000); Assert.Equal(restrictHostLogs, testService.Options.Value.RestrictHostLogs); - Assert.Equal(setFeatureFlag, FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs, environment)); + Assert.Equal(setFeatureFlag, FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableHostLogs, SystemEnvironment.Instance)); if (shouldResultInRestrictedSystemLogs) { From 3c9d8456c2a6e85882512beaf71cae77e5b33765 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Fri, 1 Nov 2024 12:58:19 -0700 Subject: [PATCH 05/10] Remove accidental dup --- src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs | 1 - .../Extensions/ScriptLoggingBuilderExtensions.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs index 426674186d..d2b4d407fa 100644 --- a/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs +++ b/src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs @@ -79,7 +79,6 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP var restrictHostLogs = RestrictHostLogs(hostingConfigOptions.Value); loggingBuilder.Services.AddSingleton(); - loggingBuilder.AddDefaultWebJobsFilters(restrictHostLogs); loggingBuilder.AddWebJobsSystem(restrictHostLogs); if (environment.IsAzureMonitorEnabled()) diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index 8d48f6b487..876c7b2d87 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -50,7 +50,13 @@ private static bool IsFiltered(string category) private static void SetSystemLogCategoryPrefixes(bool restrictHostLogs) { + var previous = _allowedLogCategoryPrefixes; _allowedLogCategoryPrefixes = restrictHostLogs ? ScriptConstants.RestrictedSystemLogCategoryPrefixes : ScriptConstants.SystemLogCategoryPrefixes; + + if (!previous.IsDefault && !previous.SequenceEqual(_allowedLogCategoryPrefixes)) + { + _filteredCategoryCache.Clear(); + } } public static void AddConsoleIfEnabled(this ILoggingBuilder builder, HostBuilderContext context) From 5caa59f0c976485af4af9170ae2e26c0b5622037 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Wed, 4 Dec 2024 15:27:07 -0800 Subject: [PATCH 06/10] Change default behaviour --- .../Config/FunctionsHostingConfigOptions.cs | 2 +- .../Extensions/ScriptLoggingBuilderExtensions.cs | 2 +- .../Configuration/FunctionsHostingConfigOptionsTest.cs | 4 ++-- .../Configuration/RestrictHostLogsTests.cs | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs b/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs index 31bf50cbb6..633e8064be 100644 --- a/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs +++ b/src/WebJobs.Script/Config/FunctionsHostingConfigOptions.cs @@ -92,7 +92,7 @@ public bool RestrictHostLogs { get { - return GetFeatureOrDefault(ScriptConstants.HostingConfigRestrictHostLogs, "1") == "1"; + return GetFeatureOrDefault(ScriptConstants.HostingConfigRestrictHostLogs, "0") == "1"; } set diff --git a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs index 876c7b2d87..54e7542d13 100644 --- a/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs +++ b/src/WebJobs.Script/Extensions/ScriptLoggingBuilderExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Logging public static class ScriptLoggingBuilderExtensions { private static ConcurrentDictionary _filteredCategoryCache = new (); - private static ImmutableArray _allowedLogCategoryPrefixes = new (); + private static ImmutableArray _allowedLogCategoryPrefixes = ImmutableArray.Empty; // For testing only internal static ImmutableArray AllowedSystemLogPrefixes => _allowedLogCategoryPrefixes; diff --git a/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs b/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs index 46e4aea5fd..de80967ec5 100644 --- a/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs +++ b/test/WebJobs.Script.Tests/Configuration/FunctionsHostingConfigOptionsTest.cs @@ -140,8 +140,8 @@ public void RestrictHostLogs_ReturnsExpectedValue() { FunctionsHostingConfigOptions options = new FunctionsHostingConfigOptions(); - // defaults to true - Assert.True(options.RestrictHostLogs); + // defaults to false + Assert.False(options.RestrictHostLogs); // returns true when explicitly enabled options.Features[ScriptConstants.HostingConfigRestrictHostLogs] = "1"; diff --git a/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs index 5dce564e7a..932db3b6f7 100644 --- a/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs +++ b/test/WebJobs.Script.Tests/Configuration/RestrictHostLogsTests.cs @@ -34,16 +34,16 @@ public Task DisposeAsync() } [Theory] - [InlineData(true, false, true)] // RestrictHostLogs is true, FeatureFlag is not set, should result in **restricted** logs. This is the default behaviour of the host. - [InlineData(false, true, false)] // RestrictHostLogs is false, FeatureFlag is set, should result in unrestricted logs - [InlineData(true, true, false)] // RestrictHostLogs is true, FeatureFlag is set, should result in unrestricted logs - [InlineData(false, false, false)] // RestrictHostLogs is false, FeatureFlag is not set, should result in unrestricted log + [InlineData(true, false, true)] // RestrictHostLogs is true, FeatureFlag is not set, should result in **restricted** logs. + [InlineData(false, true, false)] // RestrictHostLogs is false, FeatureFlag is set, should result in unrestricted logs. + [InlineData(true, true, false)] // RestrictHostLogs is true, FeatureFlag is set, should result in unrestricted logs. + [InlineData(false, false, false)] // RestrictHostLogs is false, FeatureFlag is not set, should result in unrestricted log. This is the default behaviour of the host. public async Task RestirctHostLogs_SetsCorrectSystemLogPrefix(bool restrictHostLogs, bool setFeatureFlag, bool shouldResultInRestrictedSystemLogs) { using (TempDirectory tempDir = new TempDirectory()) { string fileName = Path.Combine(tempDir.Path, "settings.txt"); - string fileContent = restrictHostLogs ? string.Empty : $"{ScriptConstants.HostingConfigRestrictHostLogs}=false"; + string fileContent = restrictHostLogs ? $"{ScriptConstants.HostingConfigRestrictHostLogs}=true" : string.Empty; // the default value is false if (setFeatureFlag) { From 2a258d0dca739fbc8a5c97538e7fb8d070f92273 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Wed, 4 Dec 2024 16:40:19 -0800 Subject: [PATCH 07/10] Update release notes --- release_notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release_notes.md b/release_notes.md index c8ed5dca88..105beea245 100644 --- a/release_notes.md +++ b/release_notes.md @@ -4,4 +4,5 @@ --> *Please note we have reached end-of-life (EOL) support for v3.x.* For more information on supported runtime versions, please see [here.](https://learn.microsoft.com/en-us/azure/azure-functions/functions-versions?tabs=v4&pivots=programming-language-csharp) -- Disable all non host startup logs for v3.x (#10399) \ No newline at end of file +- Disable all non host startup logs for v3.x (#10399) +- Add hosting config (`DisableHostLogs`) to enable/disable v3.x logs (#10552) \ No newline at end of file From 7998a9d3ba400e8aab0e1e7f82c670efcae82adf Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 5 Dec 2024 14:38:00 -0800 Subject: [PATCH 08/10] Set TreatWarningsAsErrors to false in test project --- test/TestFunctions/TestFunctions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestFunctions/TestFunctions.csproj b/test/TestFunctions/TestFunctions.csproj index 7d48bd8571..99d7e34c06 100644 --- a/test/TestFunctions/TestFunctions.csproj +++ b/test/TestFunctions/TestFunctions.csproj @@ -6,7 +6,7 @@ false - true + false From daf9689a787f615828adb25fd2d5f6b879449071 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 5 Dec 2024 17:13:45 -0800 Subject: [PATCH 09/10] Fix integration tests --- .../Management/InstanceManagerTests.cs | 36 +++++++++++++++---- .../TestHelpers.cs | 15 ++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/test/WebJobs.Script.Tests.Integration/Management/InstanceManagerTests.cs b/test/WebJobs.Script.Tests.Integration/Management/InstanceManagerTests.cs index c782cb7b5d..5de2c6fae4 100644 --- a/test/WebJobs.Script.Tests.Integration/Management/InstanceManagerTests.cs +++ b/test/WebJobs.Script.Tests.Integration/Management/InstanceManagerTests.cs @@ -356,7 +356,7 @@ public async Task ValidateContext_InvalidZipUrl_WebsiteUseZip_ReturnsError() { var environmentSettings = new Dictionary() { - { EnvironmentSettingNames.AzureWebsiteZipDeployment, "http://invalid.com/invalid/dne" } + { EnvironmentSettingNames.AzureWebsiteZipDeployment, "http://invalid.test/invalid/dne" } }; var environment = new TestEnvironment(); @@ -439,7 +439,7 @@ public async Task ValidateContext_Succeeds_For_WebsiteUseZip_Only() { var environment = new Dictionary() { - { EnvironmentSettingNames.AzureWebsiteZipDeployment, "http://microsoft.com" } + { EnvironmentSettingNames.AzureWebsiteZipDeployment, "https://valid-zip.test" } }; var assignmentContext = new HostAssignmentContext { @@ -449,7 +449,19 @@ public async Task ValidateContext_Succeeds_For_WebsiteUseZip_Only() IsWarmupRequest = false }; - string error = await _instanceManager.ValidateContext(assignmentContext); + var handlerMock = new Mock(); + handlerMock.Protected().Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()).ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + }); + + var instanceManager = new InstanceManager(_optionsFactory, TestHelpers.CreateHttpClientFactory(handlerMock.Object).CreateClient(), + _scriptWebEnvironment, _environment, _loggerFactory.CreateLogger(), + new TestMetricsLogger(), null, _runFromPackageHandler, _packageDownloadHandler.Object); + + string error = await instanceManager.ValidateContext(assignmentContext); Assert.Null(error); string[] expectedOutputLines = @@ -539,8 +551,8 @@ public async Task ValidateContext_Succeeds_For_WebsiteUseZip_With_ScmPackageDefi { var environment = new Dictionary() { - { EnvironmentSettingNames.AzureWebsiteZipDeployment, "http://microsoft.com" }, - { EnvironmentSettingNames.ScmRunFromPackage, "http://microsoft.com" } + { EnvironmentSettingNames.AzureWebsiteZipDeployment, "https://valid.test" }, + { EnvironmentSettingNames.ScmRunFromPackage, "https://valid.tests" } }; var assignmentContext = new HostAssignmentContext { @@ -550,7 +562,19 @@ public async Task ValidateContext_Succeeds_For_WebsiteUseZip_With_ScmPackageDefi IsWarmupRequest = false }; - string error = await _instanceManager.ValidateContext(assignmentContext); + var handlerMock = new Mock(); + handlerMock.Protected().Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()).ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + }); + + var instanceManager = new InstanceManager(_optionsFactory, TestHelpers.CreateHttpClientFactory(handlerMock.Object).CreateClient(), + _scriptWebEnvironment, _environment, _loggerFactory.CreateLogger(), + new TestMetricsLogger(), null, _runFromPackageHandler, _packageDownloadHandler.Object); + + string error = await instanceManager.ValidateContext(assignmentContext); Assert.Null(error); string[] expectedOutputLines = diff --git a/test/WebJobs.Script.Tests.Shared/TestHelpers.cs b/test/WebJobs.Script.Tests.Shared/TestHelpers.cs index 2b01586f2f..9166f6ae22 100644 --- a/test/WebJobs.Script.Tests.Shared/TestHelpers.cs +++ b/test/WebJobs.Script.Tests.Shared/TestHelpers.cs @@ -23,6 +23,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.WebJobs.Script.Tests; +using Moq; using Xunit.Abstractions; namespace Microsoft.Azure.WebJobs.Script.Tests @@ -475,5 +476,19 @@ public static IAzureStorageProvider GetAzureStorageProvider(IConfiguration confi var azureStorageProvider = tempHost.Services.GetRequiredService(); return azureStorageProvider; } + + /// + /// Mock an HttpClientFactory and its CreateClient functionality. + /// + /// Some tests pass a mock HttpHandler into their HttpClient. + /// IHttpClientFactory. + public static IHttpClientFactory CreateHttpClientFactory(HttpMessageHandler handler = null) + { + var httpClient = handler == null ? new HttpClient() : new HttpClient(handler); + var mockFactory = new Mock(); + mockFactory.Setup(m => m.CreateClient(It.IsAny())) + .Returns(httpClient); + return mockFactory.Object; + } } } \ No newline at end of file From 09a37c6e8e06fc4de47ad4cd72ef0724ccb5fc4b Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Thu, 5 Dec 2024 17:19:01 -0800 Subject: [PATCH 10/10] Skip EventHubTrigger E2E test due to auth changes --- .../EventHubs/EventHubsEndToEndTestsBase.cs | 2 +- .../WebHostEndToEnd/SamplesEndToEndTests_Node.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/WebJobs.Script.Tests.Integration/EventHubs/EventHubsEndToEndTestsBase.cs b/test/WebJobs.Script.Tests.Integration/EventHubs/EventHubsEndToEndTestsBase.cs index 180dcfc8b7..977dda03dc 100644 --- a/test/WebJobs.Script.Tests.Integration/EventHubs/EventHubsEndToEndTestsBase.cs +++ b/test/WebJobs.Script.Tests.Integration/EventHubs/EventHubsEndToEndTestsBase.cs @@ -21,7 +21,7 @@ public EventHubsEndToEndTestsBase(TestFixture fixture) _fixture = fixture; } - [Fact] + [Fact(Skip = "SAS authentication has been disabled for the namespace.")] public async Task EventHub() { // Event Hub needs the following environment vars: diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests_Node.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests_Node.cs index 50061eb9d9..759f8fc0d8 100644 --- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests_Node.cs +++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SamplesEndToEndTests_Node.cs @@ -39,7 +39,7 @@ public SamplesEndToEndTests_Node(TestFixture fixture) _settingsManager = ScriptSettingsManager.Instance; } - [Fact] + [Fact(Skip = "SAS authentication has been disabled for the namespace.")] public async Task EventHubTrigger() { // write 3 events