diff --git a/build/dependencies.props b/build/dependencies.props index a525a22332..8b1cdffd43 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -2,7 +2,7 @@ 2.0.0-* 0.4.0-* - 2.1.0-beta2 + 2.1.0-beta3 4.3.0 2.1.0-* 4.7.1 diff --git a/sample/ApplicationInsightsHostingStartupSample/Startup.cs b/sample/ApplicationInsightsHostingStartupSample/Startup.cs index c0c172a3d3..2231d5e14c 100644 --- a/sample/ApplicationInsightsHostingStartupSample/Startup.cs +++ b/sample/ApplicationInsightsHostingStartupSample/Startup.cs @@ -22,13 +22,27 @@ namespace IISSample services.AddMvc(); } - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IServiceProvider serviceProvider) + public void ConfigureJavaScript(IApplicationBuilder app) { - var logger = loggerFactory.CreateLogger("Requests"); - app.UseMvcWithDefaultRoute(); + } + + public void ConfigureDefaultLogging(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + ConfigureLoggingMiddleware(app, loggerFactory); + } + + public void ConfigureCustomLogging(IApplicationBuilder app, ILoggerFactory loggerFactory, IServiceProvider serviceProvider) + { + loggerFactory.AddApplicationInsights(serviceProvider, (s, level) => s.Contains("o")); + ConfigureLoggingMiddleware(app, loggerFactory); + } + + private static void ConfigureLoggingMiddleware(IApplicationBuilder app, ILoggerFactory loggerFactory) + { app.Map("/log", logApp => logApp.Run(context => { + var oldChannel = TelemetryConfiguration.Active.TelemetryChannel; TelemetryConfiguration.Active.TelemetryChannel = new CurrentResponseTelemetryChannel(context.Response); var systemLogger = loggerFactory.CreateLogger("System.Namespace"); @@ -51,53 +65,10 @@ namespace IISSample specificLogger.LogInformation("Specific information log"); specificLogger.LogWarning("Specific warning log"); - TelemetryConfiguration.Active.TelemetryChannel = null; + TelemetryConfiguration.Active.TelemetryChannel = oldChannel; return Task.CompletedTask; })); - app.Run(async (context) => - { - logger.LogDebug("Received request: " + context.Request.Method + " " + context.Request.Path); - - context.Response.ContentType = "text/plain"; - await context.Response.WriteAsync("Hello World - " + DateTimeOffset.Now + Environment.NewLine); - await context.Response.WriteAsync(Environment.NewLine); - - await context.Response.WriteAsync("Address:" + Environment.NewLine); - await context.Response.WriteAsync("Scheme: " + context.Request.Scheme + Environment.NewLine); - await context.Response.WriteAsync("Host: " + context.Request.Headers["Host"] + Environment.NewLine); - await context.Response.WriteAsync("PathBase: " + context.Request.PathBase.Value + Environment.NewLine); - await context.Response.WriteAsync("Path: " + context.Request.Path.Value + Environment.NewLine); - await context.Response.WriteAsync("Query: " + context.Request.QueryString.Value + Environment.NewLine); - await context.Response.WriteAsync(Environment.NewLine); - - await context.Response.WriteAsync("Connection:" + Environment.NewLine); - await context.Response.WriteAsync("RemoteIp: " + context.Connection.RemoteIpAddress + Environment.NewLine); - await context.Response.WriteAsync("RemotePort: " + context.Connection.RemotePort + Environment.NewLine); - await context.Response.WriteAsync("LocalIp: " + context.Connection.LocalIpAddress + Environment.NewLine); - await context.Response.WriteAsync("LocalPort: " + context.Connection.LocalPort + Environment.NewLine); - await context.Response.WriteAsync("ClientCert: " + context.Connection.ClientCertificate + Environment.NewLine); - await context.Response.WriteAsync(Environment.NewLine); - - await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine); - await context.Response.WriteAsync(Environment.NewLine); - - await context.Response.WriteAsync("Headers:" + Environment.NewLine); - foreach (var header in context.Request.Headers) - { - await context.Response.WriteAsync(header.Key + ": " + header.Value + Environment.NewLine); - } - await context.Response.WriteAsync(Environment.NewLine); - - await context.Response.WriteAsync("Environment Variables:" + Environment.NewLine); - var vars = Environment.GetEnvironmentVariables(); - foreach (var key in vars.Keys.Cast().OrderBy(key => key, StringComparer.OrdinalIgnoreCase)) - { - var value = vars[key]; - await context.Response.WriteAsync(key + ": " + value + Environment.NewLine); - } - await context.Response.WriteAsync(Environment.NewLine); - }); } public static void Main(string[] args) @@ -110,8 +81,11 @@ namespace IISSample var host = new WebHostBuilder() .ConfigureLogging((hostingContext, factory) => { - factory.UseConfiguration(hostingContext.Configuration.GetSection("Logging")) - .AddConsole(); + if (hostingContext.Configuration["WIRE_LOGGING_CONFIGURATION"]?.ToLowerInvariant() != "false") + { + factory.UseConfiguration(hostingContext.Configuration.GetSection("Logging")); + } + factory.AddConsole(); }) .UseKestrel() .UseStartup() diff --git a/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerConfiguration.cs b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerConfiguration.cs new file mode 100644 index 0000000000..f34dfc8671 --- /dev/null +++ b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerConfiguration.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.ApplicationInsights.HostingStartup +{ + internal class ApplicationInsightsLoggerConfiguration + { + private const string ApplicationInsightsLoggerFactory = "Microsoft.ApplicationInsights.AspNetCore.Logging.ApplicationInsightsLoggerProvider"; + private const string ApplicationInsightsLoggerLevelSection = "Logging:" + ApplicationInsightsLoggerFactory + ":LogLevel"; + private const string ApplicationInsightsSettingsFile = "ApplicationInsights.settings.json"; + + private static readonly KeyValuePair[] _defaultLoggingLevels = { + new KeyValuePair("Microsoft", LogLevel.Warning), + new KeyValuePair("System", LogLevel.Warning), + new KeyValuePair(null, LogLevel.Information) + }; + + public static void ConfigureLogging(IConfigurationBuilder configurationBuilder) + { + // Skip adding default rules when debugger is attached + // we want to send all events to VS + if (!Debugger.IsAttached) + { + configurationBuilder.AddInMemoryCollection(GetDefaultLoggingSettings()); + } + + var home = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(home)) + { + var settingsFile = Path.Combine(home, "site", "diagnostics", ApplicationInsightsSettingsFile); + configurationBuilder.AddJsonFile(settingsFile, optional: true); + } + } + + public static bool ApplyDefaultFilter(string name, LogLevel level) + { + foreach (var pair in _defaultLoggingLevels) + { + // Default is null + if (pair.Key == null || name.StartsWith(pair.Key, StringComparison.Ordinal)) + { + return level >= pair.Value; + } + } + + return false; + } + + public static bool HasLoggingConfigured(IConfiguration configuration) + { + return configuration?.GetSection(ApplicationInsightsLoggerLevelSection) != null; + } + + private static KeyValuePair[] GetDefaultLoggingSettings() + { + return _defaultLoggingLevels.Select(pair => + { + var key = pair.Key ?? "Default"; + var optionsKey = $"{ApplicationInsightsLoggerLevelSection}:{key}"; + return new KeyValuePair(optionsKey, pair.Value.ToString()); + }).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerStartupFilter.cs b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerStartupFilter.cs index abde0fd5be..f84a020861 100644 --- a/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerStartupFilter.cs +++ b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsLoggerStartupFilter.cs @@ -11,15 +11,36 @@ namespace Microsoft.AspNetCore.ApplicationInsights.HostingStartup { internal class ApplicationInsightsLoggerStartupFilter : IStartupFilter { - private readonly Func _noFilter = (s, level) => true; - public Action Configure(Action next) { return builder => { var loggerFactory = builder.ApplicationServices.GetService(); + // We need to disable filtering on logger, filtering would be done by LoggerFactory - loggerFactory.AddApplicationInsights(builder.ApplicationServices, _noFilter); + var loggerEnabled = true; + Action disableCallback = () => loggerEnabled = false; + + if (loggerFactory is LoggerFactory stronglyTypedLoggerFactory && + ApplicationInsightsLoggerConfiguration.HasLoggingConfigured(stronglyTypedLoggerFactory.Configuration)) + { + // We detected that logger settings got to LoggerFactory configuration and + // defaults would be applied + loggerFactory.AddApplicationInsights( + builder.ApplicationServices, + (s, level) => loggerEnabled, + disableCallback); + } + else + { + // It's not AspNetCore LoggerFactory or configuration was not wired + // just add a logger with default settings + loggerFactory.AddApplicationInsights( + builder.ApplicationServices, + (s, level) => loggerEnabled && ApplicationInsightsLoggerConfiguration.ApplyDefaultFilter(s, level), + disableCallback); + } + next(builder); }; } diff --git a/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsStartupLoader.cs b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsStartupLoader.cs index 2141279525..75357e1de4 100644 --- a/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsStartupLoader.cs +++ b/src/Microsoft.AspNetCore.ApplicationInsights.HostingStartup/ApplicationInsightsStartupLoader.cs @@ -1,14 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; [assembly: HostingStartup(typeof(Microsoft.AspNetCore.ApplicationInsights.HostingStartup.ApplicationInsightsHostingStartup))] @@ -23,14 +17,6 @@ namespace Microsoft.AspNetCore.ApplicationInsights.HostingStartup /// public class ApplicationInsightsHostingStartup : IHostingStartup { - private const string ApplicationInsightsLoggerFactory = "Microsoft.ApplicationInsights.AspNetCore.Logging.ApplicationInsightsLoggerProvider"; - private const string ApplicationInsightsSettingsFile = "ApplicationInsights.settings.json"; - - private static readonly KeyValuePair[] _defaultLoggingLevels = { - new KeyValuePair("Microsoft", "Warning"), - new KeyValuePair("System", "Warning"), - new KeyValuePair("Default", "Information") - }; /// /// Calls UseApplicationInsights @@ -38,37 +24,12 @@ namespace Microsoft.AspNetCore.ApplicationInsights.HostingStartup /// public void Configure(IWebHostBuilder builder) { - builder.ConfigureAppConfiguration((context, configurationBuilder) => ConfigureLogging(configurationBuilder)); + builder.ConfigureAppConfiguration((context, configurationBuilder) => ApplicationInsightsLoggerConfiguration.ConfigureLogging(configurationBuilder)); builder.UseApplicationInsights(); builder.ConfigureServices(InitializeServices); } - private static void ConfigureLogging(IConfigurationBuilder configurationBuilder) - { - // Skip adding default rules when debugger is attached - // we want to send all events to VS - if (!Debugger.IsAttached) - { - configurationBuilder.AddInMemoryCollection(GetDefaultLoggingSettings()); - } - - var home = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(home)) - { - var settingsFile = Path.Combine(home, "site", "diagnostics", ApplicationInsightsSettingsFile); - configurationBuilder.AddJsonFile(settingsFile, optional: true); - } - } - - private static KeyValuePair[] GetDefaultLoggingSettings() - { - return _defaultLoggingLevels.Select(pair => - { - var key = $"Logging:{ApplicationInsightsLoggerFactory}:LogLevel:{pair.Key}"; - return new KeyValuePair(key, pair.Value); - }).ToArray(); - } /// /// Adds the Javascript to the . diff --git a/src/Microsoft.AspNetCore.AzureAppServicesIntegration/AppServicesWebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.AzureAppServicesIntegration/AppServicesWebHostBuilderExtensions.cs index a8687866ae..6bad6976c8 100644 --- a/src/Microsoft.AspNetCore.AzureAppServicesIntegration/AppServicesWebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.AzureAppServicesIntegration/AppServicesWebHostBuilderExtensions.cs @@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.Hosting { throw new ArgumentNullException(nameof(hostBuilder)); } - +#pragma warning disable 618 hostBuilder.ConfigureLogging(loggerFactory => loggerFactory.AddAzureWebAppDiagnostics()); - +#pragma warning restore 618 return hostBuilder; } } diff --git a/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsJavaScriptSnippetTest.cs b/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsJavaScriptSnippetTest.cs index cc7244d0b5..e73b2667ca 100644 --- a/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsJavaScriptSnippetTest.cs +++ b/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsJavaScriptSnippetTest.cs @@ -41,6 +41,7 @@ namespace ApplicationInsightsJavaScriptSnippetTest TargetFramework = "netcoreapp2.0", Configuration = GetCurrentBuildConfiguration(), ApplicationType = applicationType, + EnvironmentName = "JavaScript", EnvironmentVariables = { new KeyValuePair( diff --git a/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsLoggingTest.cs b/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsLoggingTest.cs index ef8086af5d..cb52a5dfb9 100644 --- a/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsLoggingTest.cs +++ b/test/Microsoft.AspNetCore.ApplicationInsights.HostingStartup.Tests/ApplicationInsightsLoggingTest.cs @@ -20,19 +20,132 @@ namespace ApplicationInsightsJavaScriptSnippetTest [Theory] [InlineData(ApplicationType.Portable)] [InlineData(ApplicationType.Standalone)] - public async Task ScriptInjected(ApplicationType applicationType) + public async Task DefaultAILogFiltersApplied(ApplicationType applicationType) { + var responseText = await RunRequest(applicationType, "DefaultLogging", true); + AssertDefaultLogs(responseText); + } + + [Theory] + [InlineData(ApplicationType.Portable)] + [InlineData(ApplicationType.Standalone)] + public async Task DefaultAILogFiltersAppliedWithoutConfiguration(ApplicationType applicationType) + { + var responseText = await RunRequest(applicationType, "DefaultLogging", false); + AssertDefaultNoConfigurationLogs(responseText); + } + + [Theory] + [InlineData(ApplicationType.Portable)] + [InlineData(ApplicationType.Standalone)] + public async Task CustomAILogFiltersApplied(ApplicationType applicationType) + { + var responseText = await RunRequest(applicationType, "CustomLogging", true); + AssertCustomLogs(responseText); + } + + private static void AssertDefaultLogs(string responseText) + { + // Enabled by default + Assert.Contains("System warning log", responseText); + // Disabled by default + Assert.DoesNotContain("System information log", responseText); + // Disabled by default + Assert.DoesNotContain("System trace log", responseText); + + // Enabled by default + Assert.Contains("Microsoft warning log", responseText); + // Disabled by default but overridden by ApplicationInsights.settings.json + Assert.Contains("Microsoft information log", responseText); + // Disabled by default + Assert.DoesNotContain("Microsoft trace log", responseText); + + // Enabled by default + Assert.Contains("Custom warning log", responseText); + // Enabled by default + Assert.Contains("Custom information log", responseText); + // Disabled by default + Assert.DoesNotContain("Custom trace log", responseText); + + // Enabled by default + Assert.Contains("Specific warning log", responseText); + // Enabled by default + Assert.Contains("Specific information log", responseText); + // Disabled by default but overridden by ApplicationInsights.settings.json + Assert.Contains("Specific trace log", responseText); + } + + private static void AssertDefaultNoConfigurationLogs(string responseText) + { + // Enabled by default + Assert.Contains("System warning log", responseText); + // Disabled by default + Assert.DoesNotContain("System information log", responseText); + // Disabled by default + Assert.DoesNotContain("System trace log", responseText); + + // Enabled by default + Assert.Contains("Microsoft warning log", responseText); + // Disabled by default + Assert.DoesNotContain("Microsoft information log", responseText); + // Disabled by default + Assert.DoesNotContain("Microsoft trace log", responseText); + + // Enabled by default + Assert.Contains("Custom warning log", responseText); + // Enabled by default + Assert.Contains("Custom information log", responseText); + // Disabled by default + Assert.DoesNotContain("Custom trace log", responseText); + + // Enabled by default + Assert.Contains("Specific warning log", responseText); + // Enabled by default + Assert.Contains("Specific information log", responseText); + // Disabled by default + Assert.DoesNotContain("Specific trace log", responseText); + } + + private static void AssertCustomLogs(string responseText) + { + // Custom logger allows only namespaces with 'o' in the name + + Assert.DoesNotContain("System warning log", responseText); + Assert.DoesNotContain("System information log", responseText); + Assert.DoesNotContain("System trace log", responseText); + + // Enabled by default + Assert.Contains("Microsoft warning log", responseText); + Assert.Contains("Microsoft information log", responseText); + Assert.DoesNotContain("Microsoft trace log", responseText); + + // Enabled by default + Assert.Contains("Custom warning log", responseText); + Assert.Contains("Custom information log", responseText); + Assert.DoesNotContain("Custom trace log", responseText); + + // Enabled by default + Assert.DoesNotContain("Specific warning log", responseText); + Assert.DoesNotContain("Specific information log", responseText); + Assert.DoesNotContain("Specific trace log", responseText); + } + + private async Task RunRequest(ApplicationType applicationType, string environment, bool wireConfiguration) + { + string responseText; var testName = $"ApplicationInsightsLoggingTest_{applicationType}"; using (StartLog(out var loggerFactory, testName)) { var logger = loggerFactory.CreateLogger(nameof(ApplicationInsightsJavaScriptSnippetTest)); - var deploymentParameters = new DeploymentParameters(GetApplicationPath(), ServerType.Kestrel, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + var deploymentParameters = new DeploymentParameters(GetApplicationPath(), ServerType.Kestrel, + RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { PublishApplicationBeforeDeployment = true, PreservePublishedApplicationForDebugging = PreservePublishedApplicationForDebugging, TargetFramework = "netcoreapp2.0", Configuration = GetCurrentBuildConfiguration(), ApplicationType = applicationType, + EnvironmentName = environment, EnvironmentVariables = { new KeyValuePair( @@ -41,6 +154,9 @@ namespace ApplicationInsightsJavaScriptSnippetTest new KeyValuePair( "HOME", Path.Combine(GetApplicationPath(), "home")), + new KeyValuePair( + "ASPNETCORE_WIRE_LOGGING_CONFIGURATION", + wireConfiguration.ToString()), }, }; @@ -56,39 +172,12 @@ namespace ApplicationInsightsJavaScriptSnippetTest logger: logger, cancellationToken: deploymentResult.HostShutdownToken); Assert.False(response == null, "Response object is null because the client could not " + - "connect to the server after multiple retries"); + "connect to the server after multiple retries"); - var responseText = await response.Content.ReadAsStringAsync(); - - // Enabled by default - Assert.Contains("System warning log", responseText); - // Disabled by default - Assert.DoesNotContain("System information log", responseText); - // Disabled by default - Assert.DoesNotContain("System trace log", responseText); - - // Enabled by default - Assert.Contains("Microsoft warning log", responseText); - // Disabled by default but overridden by ApplicationInsights.settings.json - Assert.Contains("Microsoft information log", responseText); - // Disabled by default - Assert.DoesNotContain("Microsoft trace log", responseText); - - // Enabled by default - Assert.Contains("Custom warning log", responseText); - // Enabled by default - Assert.Contains("Custom information log", responseText); - // Disabled by default - Assert.DoesNotContain("Custom trace log", responseText); - - // Enabled by default - Assert.Contains("Specific warning log", responseText); - // Enabled by default - Assert.Contains("Specific information log", responseText); - // Disabled by default but overridden by ApplicationInsights.settings.json - Assert.Contains("Specific trace log", responseText); + responseText = await response.Content.ReadAsStringAsync(); } } + return responseText; } } }