diff --git a/Logging.sln b/Logging.sln
index 8abc07d7..a896def2 100644
--- a/Logging.sln
+++ b/Logging.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2036
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28315.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EE933574-C82B-4E59-A0D1-05328197B937}"
EndProject
@@ -63,6 +63,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogTarget.NetCoreApp10.Tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Log4NetAppender.NetCoreApp10.Tests", "test\Log4NetAppender.NetCoreApp10.Tests\Log4NetAppender.NetCoreApp10.Tests.csproj", "{F74826DB-53B1-4588-BD02-4DD25DCBF292}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILogger", "src\ILogger\ILogger.csproj", "{7D942802-E85E-4C3C-B97E-67A3867B7CBA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILogger.NetStandard.Tests", "test\ILogger.NetStandard.Tests\ILogger.NetStandard.Tests.csproj", "{4C1B862F-8F19-4D0F-834D-694DDE0438AE}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\CommonTestShared\CommonTestShared.projitems*{3b9ab7fa-562d-4e4e-86e3-3348426bc0d9}*SharedItemsImports = 13
@@ -225,6 +229,22 @@ Global
{F74826DB-53B1-4588-BD02-4DD25DCBF292}.Release|Any CPU.Build.0 = Release|Any CPU
{F74826DB-53B1-4588-BD02-4DD25DCBF292}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F74826DB-53B1-4588-BD02-4DD25DCBF292}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE}.Release|Mixed Platforms.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -251,6 +271,8 @@ Global
{11E4A92F-154E-450B-8508-437C28744BC2} = {2FCC45B3-D820-405D-87FA-467C96465BB1}
{B24CB3C6-A3A0-4190-97E4-E847C7B1A691} = {2FCC45B3-D820-405D-87FA-467C96465BB1}
{F74826DB-53B1-4588-BD02-4DD25DCBF292} = {2FCC45B3-D820-405D-87FA-467C96465BB1}
+ {7D942802-E85E-4C3C-B97E-67A3867B7CBA} = {EE933574-C82B-4E59-A0D1-05328197B937}
+ {4C1B862F-8F19-4D0F-834D-694DDE0438AE} = {2FCC45B3-D820-405D-87FA-467C96465BB1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AC8888B1-0E98-49D7-BE15-EB97A6261C0D}
diff --git a/src/ILogger/ApplicationInsightsLogger.cs b/src/ILogger/ApplicationInsightsLogger.cs
new file mode 100644
index 00000000..0ab4f0c3
--- /dev/null
+++ b/src/ILogger/ApplicationInsightsLogger.cs
@@ -0,0 +1,193 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Extensions.Logging.ApplicationInsights
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Text;
+ using Microsoft.ApplicationInsights;
+ using Microsoft.ApplicationInsights.DataContracts;
+
+ ///
+ /// Application insights logger implementation for .
+ ///
+ ///
+ public class ApplicationInsightsLogger : ILogger
+ {
+ private readonly string categoryName;
+ private readonly TelemetryClient telemetryClient;
+ private readonly ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public ApplicationInsightsLogger(
+ string categoryName,
+ TelemetryClient telemetryClient,
+ ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions)
+ {
+ this.categoryName = categoryName;
+ this.telemetryClient = telemetryClient;
+ this.applicationInsightsLoggerOptions = applicationInsightsLoggerOptions ?? throw new ArgumentNullException(nameof(applicationInsightsLoggerOptions));
+ }
+
+ ///
+ /// Gets or sets the external scope provider.
+ ///
+ internal IExternalScopeProvider ExternalScopeProvider { get; set; }
+
+ ///
+ /// Begins a logical operation scope.
+ ///
+ /// Current state.
+ /// The identifier for the scope.
+ ///
+ /// An IDisposable that ends the logical operation scope on dispose.
+ ///
+ public IDisposable BeginScope(TState state)
+ {
+ return this.ExternalScopeProvider != null ? this.ExternalScopeProvider.Push(state) : NullScope.Instance;
+ }
+
+ ///
+ /// Checks if the given is enabled.
+ ///
+ /// level to be checked.
+ ///
+ /// true if enabled.
+ ///
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return this.telemetryClient.IsEnabled();
+ }
+
+ ///
+ /// Writes a log entry.
+ ///
+ /// State being passed along.
+ /// Entry will be written on this level.
+ /// Id of the event.
+ /// The entry to be written. Can be also an object.
+ /// The exception related to this entry.
+ /// Function to create a string message of the and .
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (this.IsEnabled(logLevel))
+ {
+ if (exception == null || !this.applicationInsightsLoggerOptions.TrackExceptionsAsExceptionTelemetry)
+ {
+ TraceTelemetry traceTelemetry = new TraceTelemetry(
+ formatter(state, exception),
+ ApplicationInsightsLogger.GetSeverityLevel(logLevel));
+ this.PopulateTelemetry(traceTelemetry, state, eventId);
+ this.telemetryClient.TrackTrace(traceTelemetry);
+ }
+ else
+ {
+ ExceptionTelemetry exceptionTelemetry = new ExceptionTelemetry(exception)
+ {
+ Message = formatter(state, exception),
+ SeverityLevel = ApplicationInsightsLogger.GetSeverityLevel(logLevel),
+ };
+
+ this.PopulateTelemetry(exceptionTelemetry, state, eventId);
+ this.telemetryClient.TrackException(exceptionTelemetry);
+ }
+ }
+ }
+
+ ///
+ /// Converts the into corresponding Application insights .
+ ///
+ /// Logging log level.
+ /// Application insights corresponding SeverityLevel for the LogLevel.
+ private static SeverityLevel GetSeverityLevel(LogLevel logLevel)
+ {
+ switch (logLevel)
+ {
+ case LogLevel.Critical:
+ return SeverityLevel.Critical;
+ case LogLevel.Error:
+ return SeverityLevel.Error;
+ case LogLevel.Warning:
+ return SeverityLevel.Warning;
+ case LogLevel.Information:
+ return SeverityLevel.Information;
+ case LogLevel.Debug:
+ case LogLevel.Trace:
+ default:
+ return SeverityLevel.Verbose;
+ }
+ }
+
+ ///
+ /// Populates the state, scope and event information for the logging event.
+ ///
+ /// State information for the current event.
+ /// Telemetry item.
+ /// Event state information.
+ /// Event Id information.
+ private void PopulateTelemetry(ISupportProperties telemetryItem, TState state, EventId eventId)
+ {
+ IDictionary dict = telemetryItem.Properties;
+ dict["CategoryName"] = this.categoryName;
+
+ if (eventId.Id != 0)
+ {
+ dict["EventId"] = eventId.Id.ToString(CultureInfo.InvariantCulture);
+ }
+
+ if (!string.IsNullOrEmpty(eventId.Name))
+ {
+ dict["EventName"] = eventId.Name;
+ }
+
+ if (this.applicationInsightsLoggerOptions.IncludeScopes)
+ {
+ if (state is IReadOnlyCollection> stateDictionary)
+ {
+ foreach (KeyValuePair item in stateDictionary)
+ {
+ dict[item.Key] = Convert.ToString(item.Value, CultureInfo.InvariantCulture);
+ }
+ }
+
+ if (this.ExternalScopeProvider != null)
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ this.ExternalScopeProvider.ForEachScope(
+ (activeScope, builder) =>
+ {
+ // Ideally we expect that the scope to implement IReadOnlyList>.
+ // But this is not guaranteed as user can call BeginScope and pass anything. Hence
+ // we try to resolve the scope as Dictionary and if we fail, we just serialize the object and add it.
+
+ if (activeScope is IReadOnlyCollection> activeScopeDictionary)
+ {
+ foreach (KeyValuePair item in activeScopeDictionary)
+ {
+ dict[item.Key] = Convert.ToString(item.Value, CultureInfo.InvariantCulture);
+ }
+ }
+ else
+ {
+ builder.Append(" => ").Append(activeScope);
+ }
+ },
+ stringBuilder);
+
+ if (stringBuilder.Length > 0)
+ {
+ dict["Scope"] = stringBuilder.ToString();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ILogger/ApplicationInsightsLoggerOptions.cs b/src/ILogger/ApplicationInsightsLoggerOptions.cs
new file mode 100644
index 00000000..838d9b3c
--- /dev/null
+++ b/src/ILogger/ApplicationInsightsLoggerOptions.cs
@@ -0,0 +1,30 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Extensions.Logging.ApplicationInsights
+{
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.Extensions.Logging;
+
+ ///
+ /// defines the custom behavior of the tracing information sent to Application Insights.
+ ///
+ public class ApplicationInsightsLoggerOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether to track exceptions as .
+ /// Defaults to true.
+ ///
+ public bool TrackExceptionsAsExceptionTelemetry { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether the Scope information is included from telemetry or not.
+ /// Defaults to true.
+ ///
+ public bool IncludeScopes { get; set; } = true;
+ }
+}
\ No newline at end of file
diff --git a/src/ILogger/ApplicationInsightsLoggerProvider.cs b/src/ILogger/ApplicationInsightsLoggerProvider.cs
new file mode 100644
index 00000000..05f2a024
--- /dev/null
+++ b/src/ILogger/ApplicationInsightsLoggerProvider.cs
@@ -0,0 +1,111 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Extensions.Logging.ApplicationInsights
+{
+ using System;
+ using System.Collections.Concurrent;
+ using Microsoft.ApplicationInsights;
+ using Microsoft.ApplicationInsights.Extensibility;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.Implementation;
+ using Microsoft.Extensions.Options;
+
+ ///
+ /// Application insights logger provider.
+ ///
+ ///
+ ///
+ [ProviderAlias("ApplicationInsights")]
+ public class ApplicationInsightsLoggerProvider : ILoggerProvider, ISupportExternalScope
+ {
+ ///
+ /// The application insights logger options.
+ ///
+ private readonly ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions;
+
+ ///
+ /// The telemetry client to be used to log messages to Application Insights.
+ ///
+ private readonly TelemetryClient telemetryClient;
+
+ ///
+ /// The external scope provider to allow setting scope data in messages.
+ ///
+ private IExternalScopeProvider externalScopeProvider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The telemetry configuration options..
+ /// The application insights logger options.
+ ///
+ /// telemetryConfiguration
+ /// or
+ /// loggingFilter
+ /// or
+ /// applicationInsightsLoggerOptions.
+ ///
+ public ApplicationInsightsLoggerProvider(
+ IOptions telemetryConfigurationOptions,
+ IOptions applicationInsightsLoggerOptions)
+ {
+ if (telemetryConfigurationOptions?.Value == null)
+ {
+ throw new ArgumentNullException(nameof(telemetryConfigurationOptions));
+ }
+
+ this.applicationInsightsLoggerOptions = applicationInsightsLoggerOptions?.Value ?? throw new ArgumentNullException(nameof(applicationInsightsLoggerOptions));
+
+ this.telemetryClient = new TelemetryClient(telemetryConfigurationOptions.Value);
+ this.telemetryClient.Context.GetInternalContext().SdkVersion = SdkVersionUtils.GetSdkVersion("il:");
+ }
+
+ ///
+ /// Creates a new instance.
+ ///
+ /// The category name for messages produced by the logger.
+ /// An instance to be used for logging.
+ public ILogger CreateLogger(string categoryName)
+ {
+ return new ApplicationInsightsLogger(
+ categoryName,
+ this.telemetryClient,
+ this.applicationInsightsLoggerOptions)
+ {
+ ExternalScopeProvider = this.externalScopeProvider,
+ };
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Sets the scope provider. This method also updates all the existing logger to also use the new ScopeProvider.
+ ///
+ /// The external scope provider.
+ public void SetScopeProvider(IExternalScopeProvider externalScopeProvider)
+ {
+ this.externalScopeProvider = externalScopeProvider;
+ }
+
+ ///
+ /// Releases unmanaged and - optionally - managed resources.
+ ///
+ /// Release managed resources.
+ protected virtual void Dispose(bool releasedManagedResources)
+ {
+ // Nothing to dispose right now.
+ }
+ }
+}
diff --git a/src/ILogger/ApplicationInsightsLoggingBuilderExtensions.cs b/src/ILogger/ApplicationInsightsLoggingBuilderExtensions.cs
new file mode 100644
index 00000000..80e65e2f
--- /dev/null
+++ b/src/ILogger/ApplicationInsightsLoggingBuilderExtensions.cs
@@ -0,0 +1,102 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Extensions.Logging
+{
+ using System;
+ using Microsoft.ApplicationInsights.Extensibility;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.DependencyInjection.Extensions;
+ using Microsoft.Extensions.Logging.ApplicationInsights;
+
+ ///
+ /// Extensions methods to add and configure application insights logger.
+ ///
+ public static class ApplicationInsightsLoggingBuilderExtensions
+ {
+ ///
+ /// Adds an ApplicationInsights logger named 'ApplicationInsights' to the factory.
+ ///
+ /// The to use.
+ /// Logging builder with Application Insights added to it.
+ public static ILoggingBuilder AddApplicationInsights(this ILoggingBuilder builder)
+ {
+ return builder.AddApplicationInsights((applicationInsightsOptions) => { });
+ }
+
+ ///
+ /// Adds an ApplicationInsights logger named 'ApplicationInsights' to the factory.
+ ///
+ /// The to use.
+ /// Application insights instrumentation key.
+ /// Logging builder with Application Insights added to it.
+ public static ILoggingBuilder AddApplicationInsights(
+ this ILoggingBuilder builder,
+ string instrumentationKey)
+ {
+ return builder.AddApplicationInsights(
+ (telemetryConfiguration) => telemetryConfiguration.InstrumentationKey = instrumentationKey,
+ (applicationInsightsOptions) => { });
+ }
+
+ ///
+ /// Adds an ApplicationInsights logger named 'ApplicationInsights' to the factory.
+ ///
+ /// The to use.
+ /// Application insights instrumentation key.
+ /// Action to configure ApplicationInsights logger.
+ /// Logging builder with Application Insights added to it.
+ public static ILoggingBuilder AddApplicationInsights(
+ this ILoggingBuilder builder,
+ string instrumentationKey,
+ Action configureApplicationInsightsLoggerOptions)
+ {
+ return builder.AddApplicationInsights(
+ (telemetryConfiguration) => telemetryConfiguration.InstrumentationKey = instrumentationKey,
+ configureApplicationInsightsLoggerOptions);
+ }
+
+ ///
+ /// Adds an ApplicationInsights logger named 'ApplicationInsights' to the factory.
+ ///
+ /// The to use.
+ /// Action to configure ApplicationInsights logger.
+ public static ILoggingBuilder AddApplicationInsights(
+ this ILoggingBuilder builder,
+ Action configureApplicationInsightsLoggerOptions)
+ {
+ return builder.AddApplicationInsights(
+ (telemetryConfiguration) => { },
+ configureApplicationInsightsLoggerOptions);
+ }
+
+ ///
+ /// Adds an ApplicationInsights logger named 'ApplicationInsights' to the factory.
+ ///
+ /// The to use.
+ /// Action to configure telemetry configuration.
+ /// Action to configure ApplicationInsights logger.
+ private static ILoggingBuilder AddApplicationInsights(
+ this ILoggingBuilder builder,
+ Action configureTelemetryConfiguration,
+ Action configureApplicationInsightsLoggerOptions)
+ {
+ if (configureApplicationInsightsLoggerOptions == null)
+ {
+ throw new ArgumentNullException(nameof(configureApplicationInsightsLoggerOptions));
+ }
+
+ // Initialize IOptions user can keep on configuring it furthur if they want to.
+ builder.Services.Configure(configureTelemetryConfiguration);
+
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ builder.Services.Configure(configureApplicationInsightsLoggerOptions);
+
+ return builder;
+ }
+ }
+}
diff --git a/src/ILogger/ILogger.csproj b/src/ILogger/ILogger.csproj
new file mode 100644
index 00000000..12157ee8
--- /dev/null
+++ b/src/ILogger/ILogger.csproj
@@ -0,0 +1,67 @@
+
+
+ netstandard2.0
+ Microsoft.Extensions.Logging.ApplicationInsights
+ Microsoft.Extensions.Logging.ApplicationInsights
+ false
+ false
+ false
+ false
+ false
+
+ true
+
+
+ true
+
+
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+
+
+
+
+
+
+ True
+ True
+ Microsoft.Extensions.Logging.ApplicationInsights
+ Microsoft Logging Extensions for ApplicationInsights
+ Application Insights ILogger allows forwarding events from ILogger to Application Insights. Application Insights will collect your logs from multiple sources and provide rich powerful search capabilities. Privacy statement: https://go.microsoft.com/fwlink/?LinkId=512156
+ $(PackageTags) ILogger ILoggerBuilder ILoggerProvider
+
+
+
+ full
+ true
+
+
+
+ false
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+ All
+
+
+
+
+
+
+
+
diff --git a/src/ILogger/NullScope.cs b/src/ILogger/NullScope.cs
new file mode 100644
index 00000000..72edec41
--- /dev/null
+++ b/src/ILogger/NullScope.cs
@@ -0,0 +1,32 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation.
+// All rights reserved. 2013
+//
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Extensions.Logging.ApplicationInsights
+{
+ using System;
+
+ ///
+ /// An empty scope without any logic.
+ ///
+ internal class NullScope : IDisposable
+ {
+ private NullScope()
+ {
+ }
+
+ public static NullScope Instance { get; } = new NullScope();
+
+#pragma warning disable CA1063 // Implement IDisposable Correctly - Nothing at all to dispose.
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+#pragma warning restore CA1063 // Implement IDisposable Correctly - Nothing at all to dispose.
+ {
+ }
+ }
+}
diff --git a/test/ILogger.NetStandard.Tests/ILogger.NetStandard.Tests.csproj b/test/ILogger.NetStandard.Tests/ILogger.NetStandard.Tests.csproj
new file mode 100644
index 00000000..d26bb7fc
--- /dev/null
+++ b/test/ILogger.NetStandard.Tests/ILogger.NetStandard.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netcoreapp2.1
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/ILogger.NetStandard.Tests/ILoggerIntegrationTests.cs b/test/ILogger.NetStandard.Tests/ILoggerIntegrationTests.cs
new file mode 100644
index 00000000..4c057c04
--- /dev/null
+++ b/test/ILogger.NetStandard.Tests/ILoggerIntegrationTests.cs
@@ -0,0 +1,302 @@
+//
+// Copyright © Microsoft. All Rights Reserved.
+//
+
+namespace Microsoft.ApplicationInsights
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using Microsoft.ApplicationInsights.Channel;
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.Extensibility;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Microsoft.Extensions.Logging.ApplicationInsights;
+ using Microsoft.Extensions.Options;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ ///
+ /// Tests the integration for Application Insights.
+ ///
+ [TestClass]
+ public class ILoggerIntegrationTests
+ {
+ ///
+ /// Ensures that is invoked when user logs using .
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerIsInvokedWhenUsingILogger()
+ {
+ List itemsReceived = new List();
+
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration((telemetryItem, telemetryProcessor) =>
+ {
+ itemsReceived.Add(telemetryItem);
+ });
+
+ ILogger testLogger = serviceProvider.GetRequiredService>();
+
+ testLogger.LogInformation("Testing");
+ testLogger.LogError(new Exception("TestException"), "Exception");
+ testLogger.LogInformation(new EventId(100, "TestEvent"), "TestingEvent");
+ testLogger.LogCritical("Critical");
+ testLogger.LogTrace("Trace");
+ testLogger.LogWarning("Warning");
+ testLogger.LogDebug("Debug");
+
+ Assert.AreEqual(7, itemsReceived.Count);
+
+ Assert.IsTrue((itemsReceived[2] as ISupportProperties).Properties.ContainsKey("EventId"));
+ Assert.IsTrue((itemsReceived[2] as ISupportProperties).Properties.ContainsKey("EventName"));
+ Assert.AreEqual("100", (itemsReceived[2] as ISupportProperties).Properties["EventId"]);
+ Assert.AreEqual("TestEvent", (itemsReceived[2] as ISupportProperties).Properties["EventName"]);
+
+ Assert.AreEqual("Microsoft.ApplicationInsights.ILoggerIntegrationTests", (itemsReceived[2] as ISupportProperties).Properties["CategoryName"]);
+ Assert.AreEqual("Microsoft.ApplicationInsights.ILoggerIntegrationTests", (itemsReceived[0] as ISupportProperties).Properties["CategoryName"]);
+
+ Assert.AreEqual(SeverityLevel.Information, (itemsReceived[0] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Error, (itemsReceived[1] as ExceptionTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Information, (itemsReceived[2] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Critical, (itemsReceived[3] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Verbose, (itemsReceived[4] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Warning, (itemsReceived[5] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual(SeverityLevel.Verbose, (itemsReceived[6] as TraceTelemetry).SeverityLevel);
+
+ Assert.AreEqual("Testing", (itemsReceived[0] as TraceTelemetry).Message);
+ Assert.AreEqual("Exception", (itemsReceived[1] as ExceptionTelemetry).Message);
+ Assert.AreEqual("TestingEvent", (itemsReceived[2] as TraceTelemetry).Message);
+ Assert.AreEqual("Critical", (itemsReceived[3] as TraceTelemetry).Message);
+ Assert.AreEqual("Trace", (itemsReceived[4] as TraceTelemetry).Message);
+ Assert.AreEqual("Warning", (itemsReceived[5] as TraceTelemetry).Message);
+ Assert.AreEqual("Debug", (itemsReceived[6] as TraceTelemetry).Message);
+ }
+
+ ///
+ /// Ensures that the switch is honored
+ /// and exceptions are logged as trace messages when value is true.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerLogsExceptionAsExceptionWhenSwitchIsTrue()
+ {
+ List itemsReceived = new List();
+
+ // Case where Exceptions are logged as Exception Telemetry.
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration(
+ (telemetryItem, telemetryProcessor) => itemsReceived.Add(telemetryItem),
+ configureTelemetryConfiguration: null,
+ configureApplicationInsightsOptions: (appInsightsLoggerOptions) => appInsightsLoggerOptions.TrackExceptionsAsExceptionTelemetry = true);
+
+ ILogger testLogger = serviceProvider.GetRequiredService>();
+
+ testLogger.LogInformation("Testing");
+ testLogger.LogError(new Exception("TestException"), "Exception");
+
+ Assert.IsInstanceOfType(itemsReceived[0], typeof(TraceTelemetry));
+ Assert.IsInstanceOfType(itemsReceived[1], typeof(ExceptionTelemetry));
+
+ Assert.AreEqual("Testing", (itemsReceived[0] as TraceTelemetry).Message);
+ Assert.AreEqual("Exception", (itemsReceived[1] as ExceptionTelemetry).Message);
+ }
+
+ ///
+ /// Ensures that the switch is honored
+ /// and exceptions are logged as trace messages when value is false.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerLogsExceptionAsTraceWhenSwitchIsFalse()
+ {
+ List itemsReceived = new List();
+
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration(
+ (telemetryItem, telemetryProcessor) => itemsReceived.Add(telemetryItem),
+ configureTelemetryConfiguration: null,
+ configureApplicationInsightsOptions: (appInsightsLoggerOptions) => appInsightsLoggerOptions.TrackExceptionsAsExceptionTelemetry = false);
+
+ ILogger testLogger = serviceProvider.GetRequiredService>();
+
+ testLogger.LogInformation("Testing");
+ testLogger.LogError(new Exception("TestException"), "Exception");
+
+ Assert.IsInstanceOfType(itemsReceived[0], typeof(TraceTelemetry));
+ Assert.IsInstanceOfType(itemsReceived[1], typeof(TraceTelemetry));
+
+ Assert.AreEqual(SeverityLevel.Information, (itemsReceived[0] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual("Testing", (itemsReceived[0] as TraceTelemetry).Message);
+
+ Assert.AreEqual(SeverityLevel.Error, (itemsReceived[1] as TraceTelemetry).SeverityLevel);
+ Assert.AreEqual("Exception", (itemsReceived[1] as TraceTelemetry).Message);
+ }
+
+ ///
+ /// Ensures that the switch is honored and scopes are added
+ /// when switch is true.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerAddsScopeWhenSwitchIsTrue()
+ {
+ List itemsReceived = new List();
+
+ // Case where Scope is included.
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration(
+ (telemetryItem, telemetryProcessor) => itemsReceived.Add(telemetryItem),
+ configureTelemetryConfiguration: null,
+ configureApplicationInsightsOptions: (appInsightsLoggerOptions) => appInsightsLoggerOptions.IncludeScopes = true);
+
+ ILogger testLogger = serviceProvider.GetRequiredService>();
+
+ using (testLogger.BeginScope("TestScope"))
+ {
+ using (testLogger.BeginScope>>(new Dictionary { { "Key", "Value" } }))
+ {
+ testLogger.LogInformation("Testing");
+ testLogger.LogError(new Exception("TestException"), "Exception");
+ }
+ }
+
+ Assert.AreEqual(" => TestScope", (itemsReceived[0] as ISupportProperties).Properties["Scope"]);
+ Assert.AreEqual("Value", (itemsReceived[0] as ISupportProperties).Properties["Key"]);
+
+ Assert.AreEqual(" => TestScope", (itemsReceived[1] as ISupportProperties).Properties["Scope"]);
+ Assert.AreEqual("Value", (itemsReceived[1] as ISupportProperties).Properties["Key"]);
+
+ Assert.AreEqual("Testing", (itemsReceived[0] as TraceTelemetry).Message);
+ Assert.AreEqual("Exception", (itemsReceived[1] as ExceptionTelemetry).Message);
+ }
+
+ ///
+ /// Ensures that the switch is honored and scopes are excluded
+ /// when switch is false.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerDoesNotAddScopeWhenSwitchIsFalse()
+ {
+ List itemsReceived = new List();
+
+ // Case where Scope is NOT Included
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration(
+ (telemetryItem, telemetryProcessor) => itemsReceived.Add(telemetryItem),
+ configureTelemetryConfiguration: null,
+ configureApplicationInsightsOptions: (appInsightsLoggerOptions) => appInsightsLoggerOptions.IncludeScopes = false);
+
+ ILogger testLogger = serviceProvider.GetRequiredService>();
+
+ using (testLogger.BeginScope("TestScope"))
+ {
+ using (testLogger.BeginScope>>(new Dictionary { { "Key", "Value" } }))
+ {
+ testLogger.LogInformation("Testing");
+ testLogger.LogError(new Exception("TestException"), "Exception");
+ }
+ }
+
+ Assert.IsFalse((itemsReceived[0] as ISupportProperties).Properties.ContainsKey("Scope"));
+ Assert.IsFalse((itemsReceived[0] as ISupportProperties).Properties.ContainsKey("Key"));
+
+ Assert.IsFalse((itemsReceived[1] as ISupportProperties).Properties.ContainsKey("Scope"));
+ Assert.IsFalse((itemsReceived[1] as ISupportProperties).Properties.ContainsKey("Key"));
+
+ Assert.AreEqual("Testing", (itemsReceived[0] as TraceTelemetry).Message);
+ Assert.AreEqual("Exception", (itemsReceived[1] as ExceptionTelemetry).Message);
+ }
+
+ ///
+ /// Test to ensure Instrumentation key is set correctly.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void ApplicationInsightsLoggerInstrumentationKeyIsSetCorrectly()
+ {
+ // Create DI container.
+ IServiceCollection services = new ServiceCollection();
+
+ services.AddLogging(loggingBuilder =>
+ {
+ loggingBuilder.AddApplicationInsights("TestAIKey");
+ });
+
+ TelemetryConfiguration telemetryConfiguration = services
+ .BuildServiceProvider()
+ .GetRequiredService>().Value;
+
+ Assert.AreEqual("TestAIKey", telemetryConfiguration.InstrumentationKey);
+ }
+
+ ///
+ /// Ensures that the default are as expected.
+ ///
+ [TestMethod]
+ [TestCategory("ILogger")]
+ public void DefaultLoggerOptionsAreCorrectlyRegistered()
+ {
+ IServiceProvider serviceProvider = ILoggerIntegrationTests.SetupApplicationInsightsLoggerIntegration(
+ (telemetryItem, telemetryProcessor) => { });
+
+ IOptions registeredOptions =
+ serviceProvider.GetRequiredService>();
+
+ Assert.IsTrue(registeredOptions.Value.TrackExceptionsAsExceptionTelemetry);
+ Assert.IsTrue(registeredOptions.Value.IncludeScopes);
+ }
+
+ ///
+ /// Sets up the Application insights logger.
+ ///
+ /// Callback to execute when telemetry items are emitted.
+ /// Action to configure telemetry configuration.
+ /// Action to configure logger options.
+ /// Action to add, configure services to DI container.
+ /// Built DI container.
+ private static IServiceProvider SetupApplicationInsightsLoggerIntegration(
+ Action telemetryActionCallback,
+ Action configureTelemetryConfiguration = null,
+ Action configureApplicationInsightsOptions = null,
+ Func configureServices = null)
+ {
+ // Create DI container.
+ IServiceCollection services = new ServiceCollection();
+
+ // Configure the Telemetry configuration to be used to send data to AI.
+ services.Configure(telemetryConfiguration =>
+ {
+ telemetryConfiguration.TelemetryProcessorChainBuilder.Use((existingProcessor) =>
+ {
+ return new TestTelemetryProcessor(existingProcessor, telemetryActionCallback);
+ }).Build();
+ });
+
+ if (configureTelemetryConfiguration != null)
+ {
+ services.Configure(configureTelemetryConfiguration);
+ }
+
+ services.AddLogging(loggingBuilder =>
+ {
+ if (configureApplicationInsightsOptions != null)
+ {
+ loggingBuilder.AddApplicationInsights(configureApplicationInsightsOptions);
+ }
+ else
+ {
+ loggingBuilder.AddApplicationInsights();
+ }
+
+ loggingBuilder.SetMinimumLevel(LogLevel.Trace);
+ });
+
+ if (configureServices != null)
+ {
+ services = configureServices.Invoke(services);
+ }
+
+ IServiceProvider serviceProvider = services.BuildServiceProvider();
+
+ return serviceProvider;
+ }
+ }
+}
diff --git a/test/ILogger.NetStandard.Tests/TestTelemetryProcessor.cs b/test/ILogger.NetStandard.Tests/TestTelemetryProcessor.cs
new file mode 100644
index 00000000..c4055ad4
--- /dev/null
+++ b/test/ILogger.NetStandard.Tests/TestTelemetryProcessor.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.Extensibility;
+
+namespace Microsoft.ApplicationInsights
+{
+ ///
+ /// Test telemetry processor which gives access to the telemetry items as it passes through the pipeline.
+ ///
+ internal class TestTelemetryProcessor : ITelemetryProcessor
+ {
+ private readonly ITelemetryProcessor nextTelemetryProcessor;
+ private readonly Action telemetryActionCallback;
+
+ ///
+ /// Initializes a new instances of the class.
+ ///
+ /// Next telemetry processor to invoke.
+ /// Action to invoke when the telemetry item is received.
+ public TestTelemetryProcessor(ITelemetryProcessor nextTelemetryProcessor, Action telemetryActionCallback)
+ {
+ this.nextTelemetryProcessor = nextTelemetryProcessor;
+ this.telemetryActionCallback = telemetryActionCallback;
+ }
+
+ ///
+ /// Invokes the callback registered by the user.
+ ///
+ /// Telemetry item.
+ public void Process(ITelemetry item)
+ {
+ telemetryActionCallback.Invoke(item, this.nextTelemetryProcessor);
+ }
+ }
+}