Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fdd7589
ILogger implementation for ApplicationInsights
RamjotSingh Dec 7, 2018
7e21838
Adding one more tag
RamjotSingh Dec 7, 2018
d36c7b4
Taking PR comments
RamjotSingh Dec 17, 2018
2fcabd8
Taking PR comments
RamjotSingh Dec 17, 2018
a600dad
Using AI packages for 2.8.1
RamjotSingh Dec 18, 2018
1c1c736
Aligning the Options with the existing ConsoleLoggerOptions
RamjotSingh Dec 18, 2018
591ef6b
Removing IDisposable from AppInsightsLoggers and Provider no longer c…
RamjotSingh Dec 18, 2018
314972f
Renaming a file
RamjotSingh Dec 18, 2018
f9728ec
Adding unit tests and shifting from using TelemetryConfiguration to I…
RamjotSingh Dec 18, 2018
218c5c9
Removing uneeded flag.
RamjotSingh Dec 18, 2018
28f50a3
Adding extensions to specify Instrumentation key as part of logger re…
RamjotSingh Dec 18, 2018
615b150
PR comments and adding sourcelink support
RamjotSingh Dec 19, 2018
b1662ac
Taking PR comments
RamjotSingh Dec 20, 2018
14acccf
Moving back one version from latest SemVer version package since buil…
RamjotSingh Dec 21, 2018
75e0691
Moving Desktop analyzers one version back
RamjotSingh Dec 21, 2018
694b12e
Merge branch 'develop' into ramjsing/develop-ApplicationInsightsLogger
RamjotSingh Dec 21, 2018
1788dc2
TargetFrameworks -> TargetFramework
RamjotSingh Dec 21, 2018
919caa5
Merge branch 'ramjsing/develop-ApplicationInsightsLogger' of https://…
RamjotSingh Dec 21, 2018
299474b
Restoring to TargetFrameworks
RamjotSingh Dec 21, 2018
1eea0a3
Removing Analyzers
RamjotSingh Dec 21, 2018
701651e
Merge branch 'develop' into ramjsing/develop-ApplicationInsightsLogger
cijothomas Dec 31, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Logging.sln
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILogger", "src\ILogger\ILogger.csproj", "{7D942802-E85E-4C3C-B97E-67A3867B7CBA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
test\CommonTestShared\CommonTestShared.projitems*{3b9ab7fa-562d-4e4e-86e3-3348426bc0d9}*SharedItemsImports = 13
Expand Down Expand Up @@ -225,6 +227,14 @@ 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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -251,6 +261,7 @@ 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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AC8888B1-0E98-49D7-BE15-EB97A6261C0D}
Expand Down
193 changes: 193 additions & 0 deletions src/ILogger/ApplicationInsightsLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// -----------------------------------------------------------------------
// <copyright file="ApplicationInsightsLogger.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.
// All rights reserved. 2013
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Extensions.Logging.ApplicationInsights
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;

/// <summary>
/// Application insights logger implementation for <see cref="ILogger"/>.
/// </summary>
/// <seealso cref="ILogger" />
public class ApplicationInsightsLogger : ILogger
{
private readonly string categoryName;
private readonly TelemetryClient telemetryClient;
private readonly ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions;

/// <summary>
/// Creates a new instance of <see cref="ApplicationInsightsLogger"/>.
/// </summary>
public ApplicationInsightsLogger(
string categoryName,
TelemetryClient telemetryClient,
ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions)
{
this.categoryName = categoryName;
this.telemetryClient = telemetryClient;
this.applicationInsightsLoggerOptions = applicationInsightsLoggerOptions ?? throw new ArgumentNullException(nameof(applicationInsightsLoggerOptions));
}

/// <summary>
/// Gets or sets the external scope provider.
/// </summary>
internal IExternalScopeProvider ExternalScopeProvider { get; set; }

/// <summary>
/// Begins a logical operation scope.
/// </summary>
/// <typeparam name="TState">Current state.</typeparam>
/// <param name="state">The identifier for the scope.</param>
/// <returns>
/// An IDisposable that ends the logical operation scope on dispose.
/// </returns>
public IDisposable BeginScope<TState>(TState state)
{
return this.ExternalScopeProvider != null ? this.ExternalScopeProvider.Push(state) : NullScope.Instance;
}

/// <summary>
/// Checks if the given <paramref name="logLevel" /> is enabled.
/// </summary>
/// <param name="logLevel">level to be checked.</param>
/// <returns>
/// <c>true</c> if enabled.
/// </returns>
public bool IsEnabled(LogLevel logLevel)
{
return this.telemetryClient.IsEnabled();
}

/// <summary>
/// Writes a log entry.
/// </summary>
/// <typeparam name="TState">State being passed along.</typeparam>
/// <param name="logLevel">Entry will be written on this level.</param>
/// <param name="eventId">Id of the event.</param>
/// <param name="state">The entry to be written. Can be also an object.</param>
/// <param name="exception">The exception related to this entry.</param>
/// <param name="formatter">Function to create a <c>string</c> message of the <paramref name="state" /> and <paramref name="exception" />.</param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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);
}
}
}

/// <summary>
/// Converts the <see cref="LogLevel"/> into corresponding Application insights <see cref="SeverityLevel"/>.
/// </summary>
/// <param name="logLevel">Logging log level.</param>
/// <returns>Application insights corresponding SeverityLevel for the LogLevel.</returns>
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;
}
}

/// <summary>
/// Populates the state, scope and event information for the logging event.
/// </summary>
/// <typeparam name="TState">State information for the current event.</typeparam>
/// <param name="telemetryItem">Telemetry item.</param>
/// <param name="state">Event state information.</param>
/// <param name="eventId">Event Id information.</param>
private void PopulateTelemetry<TState>(ISupportProperties telemetryItem, TState state, EventId eventId)
{
IDictionary<string, string> 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 IReadOnlyList<KeyValuePair<string, object>> stateDictionary)
{
foreach (KeyValuePair<string, object> 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<KeyValuePair<string, object>>.
// 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 IReadOnlyList<KeyValuePair<string, object>> activeScopeDictionary)
{
foreach (KeyValuePair<string, object> 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();
}
}
}
}
}
}
30 changes: 30 additions & 0 deletions src/ILogger/ApplicationInsightsLoggerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// -----------------------------------------------------------------------
// <copyright file="ApplicationInsightsLoggerOptions.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.
// All rights reserved. 2013
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Extensions.Logging.ApplicationInsights
{
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.Logging;

/// <summary>
/// <see cref="ApplicationInsightsLoggerOptions"/> defines the custom behavior of the tracing information sent to Application Insights.
/// </summary>
public class ApplicationInsightsLoggerOptions
{
/// <summary>
/// Gets or sets a value indicating whether to track exceptions as <see cref="ExceptionTelemetry"/>.
/// Defaults to true.
/// </summary>
public bool TrackExceptionsAsExceptionTelemetry { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether the Scope information is excluded from telemetry or not.
/// Defaults to true.
/// </summary>
public bool IncludeScopes { get; set; } = true;
}
}
120 changes: 120 additions & 0 deletions src/ILogger/ApplicationInsightsLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// -----------------------------------------------------------------------
// <copyright file="ApplicationInsightsLoggerProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.
// All rights reserved. 2013
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Extensions.Logging.ApplicationInsights
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Implementation;
using Microsoft.Extensions.Options;

/// <summary>
/// Application insights logger provider.
/// </summary>
/// <seealso cref="Microsoft.Extensions.Logging.ILoggerProvider" />
/// <seealso cref="Microsoft.Extensions.Logging.ISupportExternalScope" />
[ProviderAlias("ApplicationInsights")]
public class ApplicationInsightsLoggerProvider : ILoggerProvider, ISupportExternalScope
{
/// <summary>
/// The application insights logger options.
/// </summary>
private readonly ApplicationInsightsLoggerOptions applicationInsightsLoggerOptions;

/// <summary>
/// The telemetry client to be used to log messages to Application Insights.
/// </summary>
private readonly TelemetryClient telemetryClient;

/// <summary>
/// The collection of application insights loggers stored against categoryName.
/// categoryName -> <see cref="ApplicationInsightsLogger"/> map.
/// </summary>
private readonly ConcurrentDictionary<string, ApplicationInsightsLogger> applicationInsightsLoggers
= new ConcurrentDictionary<string, ApplicationInsightsLogger>();

/// <summary>
/// The external scope provider to allow setting scope data in messages.
/// </summary>
private IExternalScopeProvider externalScopeProvider;

/// <summary>
/// Initializes a new instance of the <see cref="ApplicationInsightsLoggerProvider"/> class.
/// </summary>
/// <param name="telemetryConfiguration">The telemetry configuration.</param>
/// <param name="applicationInsightsLoggerOptions">The application insights logger options.</param>
/// <exception cref="System.ArgumentNullException">
/// telemetryConfiguration
/// or
/// loggingFilter
/// or
/// applicationInsightsLoggerOptions.
/// </exception>
public ApplicationInsightsLoggerProvider(
TelemetryConfiguration telemetryConfiguration,
IOptions<ApplicationInsightsLoggerOptions> applicationInsightsLoggerOptions)
{
if (telemetryConfiguration == null)
{
throw new ArgumentNullException(nameof(telemetryConfiguration));
}

this.applicationInsightsLoggerOptions = applicationInsightsLoggerOptions?.Value ?? throw new ArgumentNullException(nameof(applicationInsightsLoggerOptions));

this.telemetryClient = new TelemetryClient(telemetryConfiguration);
this.telemetryClient.Context.GetInternalContext().SdkVersion = SdkVersionUtils.GetSdkVersion("il:");
}

/// <summary>
/// Creates a new <see cref="ILogger" /> instance.
/// </summary>
/// <param name="categoryName">The category name for messages produced by the logger.</param>
/// <returns>An <see cref="ILogger"/> instance to be used for logging.</returns>
public ILogger CreateLogger(string categoryName)
{
return new ApplicationInsightsLogger(
categoryName,
this.telemetryClient,
this.applicationInsightsLoggerOptions)
{
ExternalScopeProvider = this.externalScopeProvider,
};
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Sets the scope provider. This method also updates all the existing logger to also use the new ScopeProvider.
/// </summary>
/// <param name="externalScopeProvider">The external scope provider.</param>
public void SetScopeProvider(IExternalScopeProvider externalScopeProvider)
{
this.externalScopeProvider = externalScopeProvider;
}

/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="releasedManagedResources">Release managed resources.</param>
protected virtual void Dispose(bool releasedManagedResources)
{
// Nothing to dispose right now.
}
}
}
Loading