From c11ad1b4134aedf1ea0c41dd3a8b3d10cec7a127 Mon Sep 17 00:00:00 2001 From: Robert McCabe Date: Sun, 19 Apr 2020 14:18:42 +0100 Subject: [PATCH] Update repo to latest --- README.md | 84 ++++- ...Telemetry.AzureAppInsights.sln.DotSettings | 5 +- .../AppInsights.cs | 227 ++++++++++--- ...oud.Core.Telemetry.AzureAppInsights.csproj | 31 +- .../Extensions/LoggingBuilderExtensions.cs | 77 ++++- .../Extensions/ServiceCollectionExtensions.cs | 60 ++++ .../{ => Properties}/AssemblyInfo.cs | 0 src/CodeCoverage.runsettings | 61 ---- src/Coverlet.runsettings | 15 + src/Tests/AppInsightsTest.cs | 305 ++++++++++++++++-- ...re.Telemetry.AzureAppInsights.Tests.csproj | 22 +- src/nuget.config | 2 +- 12 files changed, 702 insertions(+), 187 deletions(-) create mode 100644 src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/ServiceCollectionExtensions.cs rename src/Cloud.Core.Telemetry.AzureAppInsights/{ => Properties}/AssemblyInfo.cs (100%) delete mode 100644 src/CodeCoverage.runsettings create mode 100644 src/Coverlet.runsettings diff --git a/README.md b/README.md index 6cef83a..e3bfed1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -# **Cloud.Core.Telemetry.AzureAppInsights** +# **Cloud.Core.Telemetry.AzureAppInsights** [![Build status](https://dev.azure.com/cloudcoreproject/CloudCore/_apis/build/status/Cloud.Core/Cloud.Core.Telemetry.AzureAppInsights_Package)](https://dev.azure.com/cloudcoreproject/CloudCore/_build/latest?definitionId=10) ![Code Coverage](https://cloud1core.blob.core.windows.net/codecoveragebadges/Cloud.Core.Telemetry.AzureAppInsights-LineCoverage.png) +[![Cloud.Core.Configuration package in Cloud.Core feed in Azure Artifacts](https://feeds.dev.azure.com/cloudcoreproject/dfc5e3d0-a562-46fe-8070-7901ac8e64a0/_apis/public/Packaging/Feeds/8949198b-5c74-42af-9d30-e8c462acada6/Packages/e71ddf20-f66a-45da-b672-c32798cf1e51/Badge)](https://dev.azure.com/cloudcoreproject/CloudCore/_packaging?_a=package&feed=8949198b-5c74-42af-9d30-e8c462acada6&package=e71ddf20-f66a-45da-b672-c32798cf1e51&preferRelease=true) + -
+
Azure specific implementation of telemetry logging. Uses the ITelemeteryLogger interface from _Cloud.Core_ and can be used in conjunction with the Telemetry Logging Provider classes found in the _Cloud.Core.Telemetry.Logging_ package. -
## Usage @@ -24,11 +25,73 @@ Any of the logging methods can also handle exception, such as: ```csharp logger.LogWarning(new Exception("Something's gone wrong!")); +`` + +## Config + +There are a number of ways to create an instance of the AppInsights logger, the simpliest and most direct way is the following: + +```csharp +var logger = new AppInsightsLogger("instrumentationKey", LogLevel.Debug); +``` + +However, typical use case is as an instance of the `ITelemetryLogger` interface and with an IServiceCollection (in a Startup.cs file). Therefore, leveraging the extensions methods that have been built for convenience as follows during the Logger configuration is probably easier: + +```csharp +public class Startup +{ + public void ConfigureAppConfiguration(IConfigurationBuilder builder) + { + // Read config from config sources... + builder.UseKubernetesContainerConfig(); + } + + public void ConfigureLogging(IConfiguration config, ILoggingBuilder builder) + { + builder.AddConfiguration(config.GetSection("Logging")); + + // Add some default loggers. + builder.AddConsole(); + builder.AddDebug(); + + // Add your app insights telemetry logger here! + + // 1. Implicitly using instrumentation key. + builder.AddAppInsightsLogger(); + + // 2. Explicitly specifying the instrumentation key. + var instrumentationKey = config.GetValue("InstrumentationKey"); + builder.AddAppInsightsLogger(instrumentationKey); + } + + public void ConfigureServices(IConfiguration config, ILogger logger, IServiceCollection services) + { + // Configure services... + } +} ``` +If you use the first example (implicitly AddingAppInsightsLogger) without specifying config, the code will look for the instrumentation key in one of the following config settings: + +1. _InstrumentationKey_ +2. _APPINSIGHTS_INSTRUMENTATIONKEY_ +3. _Logging:InstrumentationKey_ +4. _AppInsights:InstrumentationKey_ + +If It cannot find it here, it will throw an invalid argument exception. + +The second example (explicit setting of instrumentation key), you pass it an instruementation key you have loaded yourself. + +Both examples will look for log level from config specified in the "Logging:LogLevel:Telemetry" setting. If it cannot find that, it will default to _Logging:LogLevel:Default_ for its setting. Failing that, it falls back to info. + +## Further Reading + +Read more about logging providers and filters here: +https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1 + ## Test Coverage A threshold will be added to this package to ensure the test coverage is above 80% for branches, functions and lines. If it's not above the required threshold -(threshold that will be implemented on ALL of the new core repositories going forward), then the build will fail. +(threshold that will be implemented on ALL of the core repositories to gurantee a satisfactory level of testing), then the build will fail. ## Compatibility This package has has been written in .net Standard and can be therefore be referenced from a .net Core or .net Framework application. The advantage of utilising from a .net Core application, @@ -36,14 +99,17 @@ is that it can be deployed and run on a number of host operating systems, such a Windows (or Linux using Mono). ## Setup -This package requires the .net Core 2.1 SDK, it can be downloaded here: -https://www.microsoft.com/net/download/dotnet-core/2.1 +This package is built using .net Standard 2.1 and requires the .net Core 3.1 SDK, it can be downloaded here: +https://www.microsoft.com/net/download/dotnet-core/ IDE of Visual Studio or Visual Studio Code, can be downloaded here: https://visualstudio.microsoft.com/downloads/ ## How to access this package -All of the Cloud.Core.* packages are published to our internal NuGet feed. To consume this on your local development machine, please add the following feed to your feed sources in Visual Studio: -TBC - +All of the Cloud.Core.* packages are published to a internal NuGet feed. To consume this on your local development machine, please add the following feed to your feed sources in Visual Studio: +https://pkgs.dev.azure.com/cloudcoreproject/CloudCore/_packaging/Cloud.Core/nuget/v3/index.json + For help setting up, follow this article: https://docs.microsoft.com/en-us/vsts/package/nuget/consume?view=vsts + + + diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights.sln.DotSettings b/src/Cloud.Core.Telemetry.AzureAppInsights.sln.DotSettings index cb1f1a6..02cec62 100644 --- a/src/Cloud.Core.Telemetry.AzureAppInsights.sln.DotSettings +++ b/src/Cloud.Core.Telemetry.AzureAppInsights.sln.DotSettings @@ -1,9 +1,12 @@  + ExplicitlyExcluded + SUGGESTION + SUGGESTION SUGGESTION SUGGESTION SUGGESTION SUGGESTION - SUGGESTION + SUGGESTION True DO_NOT_SHOW SUGGESTION diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights/AppInsights.cs b/src/Cloud.Core.Telemetry.AzureAppInsights/AppInsights.cs index 52e3061..2b2e65e 100644 --- a/src/Cloud.Core.Telemetry.AzureAppInsights/AppInsights.cs +++ b/src/Cloud.Core.Telemetry.AzureAppInsights/AppInsights.cs @@ -1,4 +1,17 @@ -namespace Cloud.Core.Telemetry.AzureAppInsights +// *********************************************************************** +// Assembly : Cloud.Core.Telemetry.AzureAppInsights +// Author : rmccabe +// Created : 04-19-2020 +// +// Last Modified By : rmccabe +// Last Modified On : 04-19-2020 +// *********************************************************************** +// +// Cloud.Core.Telemetry.AzureAppInsights +// +// +// *********************************************************************** +namespace Cloud.Core.Telemetry.AzureAppInsights { using Diagnostics = System.Diagnostics; using System; @@ -8,21 +21,33 @@ using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights; using System.Runtime.CompilerServices; + using System.Net.Http; + using Newtonsoft.Json; /// - /// Azure specific Application Insights implementation. + /// Azure specific Application Insights implementation. /// /// public class AppInsightsLogger : ITelemetryLogger { - private readonly string _instrumentationKey; - private readonly LogLevel _level; - private TelemetryClient _client; - public string InstrumentationKey => _instrumentationKey; - public LogLevel LogLevel => _level; + /// + /// Gets the instrumentation key. + /// + /// The instrumentation key. + public string InstrumentationKey { get; } + + /// + /// Gets the log level. + /// + /// The log level. + public LogLevel LogLevel { get; } + /// + /// Gets the telemetry client. + /// + /// The client. internal TelemetryClient Client { get @@ -30,8 +55,9 @@ internal TelemetryClient Client if (_client == null) { // Initialise client. - TelemetryConfiguration.Active.InstrumentationKey = _instrumentationKey; - _client = new TelemetryClient(); + var config = TelemetryConfiguration.CreateDefault(); + config.InstrumentationKey = InstrumentationKey; + _client = new TelemetryClient(config); // Default application information - who's running the code, guid for session id and operating system being executed on. Client.Context.User.Id = AppDomain.CurrentDomain.FriendlyName; @@ -44,12 +70,14 @@ internal TelemetryClient Client } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// The instrumentation key. + /// The level. public AppInsightsLogger(string instrumentationKey, LogLevel level) { - _instrumentationKey = instrumentationKey; - _level = level; + InstrumentationKey = instrumentationKey; + LogLevel = level; } /// @@ -62,12 +90,10 @@ public void Flush() /// Checks if the given is enabled. /// /// level to be checked. - /// - /// true if enabled. - /// + /// true if enabled. public bool IsEnabled(LogLevel logLevel) { - return logLevel >= _level; + return logLevel >= LogLevel && logLevel != LogLevel.None; } /// @@ -75,7 +101,7 @@ public bool IsEnabled(LogLevel logLevel) /// /// /// The state. - /// + /// IDisposable. public IDisposable BeginScope(T state) { return null; @@ -116,9 +142,9 @@ public void Log(LogLevel logLevel, EventId eventId, T state, Exception except {"Telemetry.EventName", eventId.Name} }; - if (exception != null) + if (exception != null || logLevel >= LogLevel.Error) { - CreateTelemetryException(logLevel, exception, props, string.Empty, string.Empty, -1); + CreateTelemetryException(logLevel, message, exception, props, string.Empty, string.Empty, -1); } else { @@ -158,11 +184,13 @@ public void LogCritical(Exception ex, Dictionary properties = nu [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryException(LogLevel.Critical, ex, properties, callerMemberName, callerFilePath, + CreateTelemetryException(LogLevel.Critical, ex?.GetBaseException().Message, ex, properties, callerMemberName, callerFilePath, callerLineNumber); } - /// Logs the debug. + /// + /// Logs the debug. + /// /// The message. /// The properties. /// Name of the caller member. @@ -176,7 +204,9 @@ public void LogDebug(string message, Dictionary properties = nul callerLineNumber); } - /// Logs the debug. + /// + /// Logs the debug. + /// /// The ex. /// The properties. /// Name of the caller member. @@ -186,7 +216,7 @@ public void LogDebug(Exception ex, Dictionary properties = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryException(LogLevel.Debug, ex, properties, callerMemberName, callerFilePath, + CreateTelemetryException(LogLevel.Debug, ex?.GetBaseException().Message, ex, properties, callerMemberName, callerFilePath, callerLineNumber); } @@ -204,8 +234,7 @@ public void LogWarning(Exception ex, Dictionary properties = nul [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryException(LogLevel.Warning, ex, properties, callerMemberName, callerFilePath, - callerLineNumber); + CreateTelemetryException(LogLevel.Warning, ex?.GetBaseException().Message, ex, properties, callerMemberName, callerFilePath, callerLineNumber); } /// @@ -213,8 +242,9 @@ public void LogError(string message, Dictionary properties = nul [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryEvent(LogLevel.Error, message, properties, callerMemberName, callerFilePath, - callerLineNumber); + var telemetryEx = new TelemetryException(message); + + CreateTelemetryException(LogLevel.Error, message, telemetryEx, properties, callerMemberName, callerFilePath, callerLineNumber); } /// @@ -222,8 +252,15 @@ public void LogError(Exception ex, Dictionary properties = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryException(LogLevel.Error, ex, properties, callerMemberName, callerFilePath, - callerLineNumber); + CreateTelemetryException(LogLevel.Error, ex?.GetBaseException().Message, ex, properties, callerMemberName, callerFilePath, callerLineNumber); + } + + /// + public void LogError(Exception ex, string message, Dictionary properties = null, + [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", + [CallerLineNumber] int callerLineNumber = -1) + { + CreateTelemetryException(LogLevel.Error, message, ex, properties, callerMemberName, callerFilePath, callerLineNumber); } /// @@ -231,8 +268,15 @@ public void LogMetric(string metricName, double metricValue, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) { - CreateTelemetryMetric(LogLevel.Information, metricName, metricValue, null, callerMemberName, callerFilePath, - callerLineNumber); + CreateTelemetryMetric(LogLevel.Information, metricName, metricValue, null, callerMemberName, callerFilePath, callerLineNumber); + } + + /// + public void LogMetric(string metricName, double metricValue, Dictionary properties, + [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", + [CallerLineNumber] int callerLineNumber = -1) + { + CreateTelemetryMetric(LogLevel.Information, metricName, metricValue, properties, callerMemberName, callerFilePath, callerLineNumber); } /// @@ -243,7 +287,7 @@ public void LogMetric(string metricName, double metricValue, /// Name of the caller member. /// The caller file path. /// The caller line number. - /// + /// Dictionary<System.String, System.String>. private Dictionary SetDefaultProperties(LogLevel logLevel, Dictionary properties, string callerMemberName, string callerFilePath, int callerLineNumber) @@ -265,7 +309,9 @@ private Dictionary SetDefaultProperties(LogLevel logLevel, return output; } - /// Creates the telemetry event. + /// + /// Creates the telemetry event. + /// /// The level. /// The message. /// The properties. @@ -298,23 +344,30 @@ private void CreateTelemetryEvent(LogLevel level, string message, Dictionary /// The level. + /// The message to log for the exception. /// The ex. /// The properties. /// Name of the caller member. /// The caller file path. /// The caller line number. - private void CreateTelemetryException(LogLevel level, Exception ex, Dictionary properties, + private void CreateTelemetryException(LogLevel level, string message, Exception ex, Dictionary properties, string callerMemberName, string callerFilePath, int callerLineNumber) { if (!IsEnabled(level)) return; - var telemetry = new ExceptionTelemetry(ex); + var telemetry = new ExceptionTelemetry(ex) { Message = message }; var output = SetDefaultProperties(level, properties, callerMemberName, callerFilePath, callerLineNumber); - if (!string.IsNullOrEmpty(ex.Message) && !telemetry.Properties.ContainsKey("Telemetry.ExceptionMessage")) - telemetry.Properties.Add("Telemetry.ExceptionMessage", ex.Message); + if (ex != null) + { + if (!string.IsNullOrEmpty(ex.Message) && + !telemetry.Properties.ContainsKey("Telemetry.ExceptionMessage")) + { + telemetry.Properties.Add("Telemetry.ExceptionMessage", ex.Message); + } + } foreach (var property in output) { @@ -323,6 +376,7 @@ private void CreateTelemetryException(LogLevel level, Exception ex, Dictionary @@ -335,8 +389,7 @@ private void CreateTelemetryException(LogLevel level, Exception ex, DictionaryName of the caller member. /// The caller file path. /// The caller line number. - private void CreateTelemetryMetric(LogLevel level, string metricName, double metricValue, - Dictionary properties, + private void CreateTelemetryMetric(LogLevel level, string metricName, double metricValue, Dictionary properties, string callerMemberName, string callerFilePath, int callerLineNumber) { if (!IsEnabled(level)) @@ -356,6 +409,100 @@ private void CreateTelemetryMetric(LogLevel level, string metricName, double met } Client.TrackMetric(telemetry); + Client.Flush(); + } + + /// + /// Returns the customDimesions of the log returned from the query. This is only designed for a query that returns a single log. + /// + /// The customDimensions model of the log. This could be unique for each component logging to AppInsights. + /// The App Insights API Key. + /// The App Insights Id. + /// The App Insights query to retrieve the log. + /// T. + public static T GetCustomDimensions(string apiKey, string appInsightsId, string query) where T : class + { + var URL = "http://api.applicationinsights.io/v1/apps/{0}/query?query={1}"; + + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Accept.Add( + new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Add("x-api-key", apiKey); + var req = string.Format(URL, appInsightsId, query); + var responseMessage = client.GetAsync(req).Result; + + var result = responseMessage.Content.ReadAsStringAsync().Result; + var queryResult = JsonConvert.DeserializeObject(result); + + var customDimensionIndex = queryResult.Tables[0].Columns.FindIndex(log => log.Name == "customDimensions"); + return JsonConvert.DeserializeObject(queryResult.Tables[0].Rows[0][customDimensionIndex].ToString()); + } + } + + // The following are classes to allow us to deserialize the Json returned from the Http request for GetCustomDimensions. + /// + /// Class Column. + /// + private class Column + { + /// + /// Gets the name. + /// + /// The name. + public string Name { get; } + } + + /// + /// Class Table. + /// + private class Table + { + /// + /// Gets or sets the columns. + /// + /// The columns. + public List Columns { get; set; } + /// + /// Gets or sets the rows. + /// + /// The rows. + public List> Rows { get; set; } + } + + /// + /// Class BaseLogEntry. + /// + private class BaseLogEntry + { + /// + /// Gets or sets the tables. + /// + /// The tables. + public List Tables { get; set; } + } + + /// + /// Class TelemetryException. + /// Implements the + /// + /// + private class TelemetryException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public TelemetryException(string message) : base(message) { } + } + + /// + /// Finalizes an instance of the class. + /// Flushes on finalize. + /// + ~AppInsightsLogger() // finalizer + { + Flush(); } } -} \ No newline at end of file +} diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights/Cloud.Core.Telemetry.AzureAppInsights.csproj b/src/Cloud.Core.Telemetry.AzureAppInsights/Cloud.Core.Telemetry.AzureAppInsights.csproj index b11801a..502114c 100644 --- a/src/Cloud.Core.Telemetry.AzureAppInsights/Cloud.Core.Telemetry.AzureAppInsights.csproj +++ b/src/Cloud.Core.Telemetry.AzureAppInsights/Cloud.Core.Telemetry.AzureAppInsights.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 Robert McCabe true Cloud.Core.Telemetry.AzureAppInsights @@ -10,29 +10,20 @@ Azure specific implementation of ITelemetryLogger using AppInsights. Cloud.Core.Telemetry.AzureAppInsights - 1.0 + 3.1 + https://cloud1core.blob.core.windows.net/icons/CC.png Robert McCabe + {b4c1dc56-307f-4daa-92de-5f3269d9d358} + https://github.com/rmccabe24/Cloud.Core.AppHost + Private - - - - - - - - - - ..\..\..\..\..\.nuget\packages\newtonsoft.json\11.0.2\lib\netstandard2.0\Newtonsoft.Json.dll - - - ..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7\System.Configuration.dll - - - - - + + + + + diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/LoggingBuilderExtensions.cs b/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/LoggingBuilderExtensions.cs index 17eba81..fcf6d28 100644 --- a/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/LoggingBuilderExtensions.cs +++ b/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/LoggingBuilderExtensions.cs @@ -10,10 +10,11 @@ /// /// Telemetry logger factory adds the AddTelemetryLogger method to the ILoggingBuilder. /// - public static class LoggingBuilderExtensions + public static class LoggingBuilderExtension { /// /// Adds the telemetry logger factory method to the logging builder. + /// Will attempt to pull "InstrumentationKey" from config values "Logging:InstrumentationKey" or directly from "InstrumentationKey" /// /// The builder to add to. /// The modified builder. @@ -23,7 +24,11 @@ public static ILoggingBuilder AddAppInsightsLogger(this ILoggingBuilder builder) { var config = a.GetService(); - var key = config.GetValue("Logging:InstrumentationKey"); + var key = GetInstrumentationKeyFromConfig(config); + + if (key.IsNullOrEmpty()) + throw new InvalidOperationException("Could not find \"InstrumentationKey\" in configuration"); + var defaultLevel = config.GetValue("Logging:LogLevel:Default"); var telemetryLevel = config.GetValue("Logging:LogLevel:Telemetry"); @@ -34,21 +39,65 @@ public static ILoggingBuilder AddAppInsightsLogger(this ILoggingBuilder builder) return builder; } - private static LogLevel GetLogLevel(string defaultLevel, string desiredLevel) + /// + /// Adds the telemetry logger factory method to the logging builder. + /// + /// The builder to add to. + /// Instrumentation key for the Application Insights + /// + public static ILoggingBuilder AddAppInsightsLogger(this ILoggingBuilder builder, string instrumentationKey) + { + if (!instrumentationKey.IsNullOrWhiteSpace()) + { + builder.Services.AddSingleton((a) => + { + var config = a.GetService(); + + var defaultLevel = config.GetValue("Logging:LogLevel:Default"); + var telemetryLevel = config.GetValue("Logging:LogLevel:Telemetry"); + + return new AppInsightsLogger(instrumentationKey, GetLogLevel(defaultLevel, telemetryLevel)); + }); + + builder.Services.AddSingleton(a => new TelemetryLoggerProvider(a.GetService())); + } + return builder; + } + + internal static LogLevel GetLogLevel(string defaultLevel, string desiredLevel) { if (!desiredLevel.IsNullOrEmpty()) defaultLevel = desiredLevel; - - switch (defaultLevel) + + return defaultLevel switch { - case "Trace": return LogLevel.Trace; - case "Information": return LogLevel.Information; - case "Critical": return LogLevel.Critical; - case "Debug": return LogLevel.Debug; - case "Error": return LogLevel.Error; - case "Warning": return LogLevel.Warning; - default: return LogLevel.None; - } + "Trace" => LogLevel.Trace, + "Information" => LogLevel.Information, + "Critical" => LogLevel.Critical, + "Debug" => LogLevel.Debug, + "Error" => LogLevel.Error, + "Warning" => LogLevel.Warning, + _ => LogLevel.None, + }; + } + + internal static string GetInstrumentationKeyFromConfig(IConfiguration config) + { + var key = config.GetValue("AppInsightsInstrumentationKey"); + + if (key.IsNullOrEmpty()) + key = config.GetValue("InstrumentationKey"); + + if (key.IsNullOrEmpty()) + key = config.GetValue("Logging:InstrumentationKey"); + + if (key.IsNullOrEmpty()) + key = config.GetValue("APPINSIGHTS_INSTRUMENTATIONKEY"); + + if (key.IsNullOrEmpty()) + key = config.GetValue("AppInsights:InstrumentationKey"); + + return key; } } -} \ No newline at end of file +} diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/ServiceCollectionExtensions.cs b/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..52ba69a --- /dev/null +++ b/src/Cloud.Core.Telemetry.AzureAppInsights/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,60 @@ +namespace Microsoft.Extensions.DependencyInjection +{ + using System; + using Cloud.Core; + using Cloud.Core.Telemetry.AzureAppInsights; + using Configuration; + using Logging; + + /// + /// Class ServiceCollectionExtensions. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds the application insights telemetry singlton (ITelemetry). + /// + /// The service collection to extend. + /// IServiceCollection. + public static IServiceCollection AddAppInsightsTelemetry(this IServiceCollection services) + { + services.AddSingleton((a) => + { + var config = a.GetService(); + + var key = LoggingBuilderExtension.GetInstrumentationKeyFromConfig(config); + + if (key.IsNullOrEmpty()) + throw new InvalidOperationException("Could not find \"InstrumentationKey\" in configuration"); + + var defaultLevel = config.GetValue("Logging:LogLevel:Default"); + var telemetryLevel = config.GetValue("Logging:LogLevel:Telemetry"); + + return new AppInsightsLogger(key, LoggingBuilderExtension.GetLogLevel(defaultLevel, telemetryLevel)); + }); + + return services; + } + + /// + /// Adds the application insights telemetry singlton (ITelemetry). + /// + /// The service collection to extend. + /// The insturmentation key. + /// IServiceCollection. + public static IServiceCollection AddAppInsightsTelemetry(this IServiceCollection services, string insturmentationKey) + { + services.AddSingleton((a) => + { + var config = a.GetService(); + + var defaultLevel = config.GetValue("Logging:LogLevel:Default"); + var telemetryLevel = config.GetValue("Logging:LogLevel:Telemetry"); + + return new AppInsightsLogger(insturmentationKey, LoggingBuilderExtension.GetLogLevel(defaultLevel, telemetryLevel)); + }); + + return services; + } + } +} diff --git a/src/Cloud.Core.Telemetry.AzureAppInsights/AssemblyInfo.cs b/src/Cloud.Core.Telemetry.AzureAppInsights/Properties/AssemblyInfo.cs similarity index 100% rename from src/Cloud.Core.Telemetry.AzureAppInsights/AssemblyInfo.cs rename to src/Cloud.Core.Telemetry.AzureAppInsights/Properties/AssemblyInfo.cs diff --git a/src/CodeCoverage.runsettings b/src/CodeCoverage.runsettings deleted file mode 100644 index b48c82d..0000000 --- a/src/CodeCoverage.runsettings +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - .*\.dll$ - - - .*xunit.* - .*fluentassertions.* - .*tests.* - - - - - - .*\.Program\..* - .*\.Startup\..* - - - True - True - True - False - - - - - - - - - - - - - - - - - True - false - False - False - - - - - - \ No newline at end of file diff --git a/src/Coverlet.runsettings b/src/Coverlet.runsettings new file mode 100644 index 0000000..195ed0e --- /dev/null +++ b/src/Coverlet.runsettings @@ -0,0 +1,15 @@ + + + + + + + + [*.Telemetry.AzureAppInsights]* + ExcludeFromCodeCoverage + ../**/Program.cs,../**/Startup.cs + + + + + \ No newline at end of file diff --git a/src/Tests/AppInsightsTest.cs b/src/Tests/AppInsightsTest.cs index 096ec53..a8f2905 100644 --- a/src/Tests/AppInsightsTest.cs +++ b/src/Tests/AppInsightsTest.cs @@ -9,9 +9,10 @@ namespace Cloud.Core.Telemetry.AzureAppInsights.Tests.Unit { + [IsUnit] public class AppInsightsTest { - [Fact, IsUnit] + [Fact] public void Test_Telemetry_IsEnabled() { var logger = new AppInsightsLogger("test", LogLevel.Trace); @@ -20,7 +21,7 @@ public void Test_Telemetry_IsEnabled() Assert.True(logger.IsEnabled(LogLevel.Information)); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogWarningMessage() { AssertExtensions.DoesNotThrow(() => @@ -31,7 +32,7 @@ public void Test_Telemetry_LogWarningMessage() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogWithWrongLevel() { AssertExtensions.DoesNotThrow(() => @@ -42,7 +43,7 @@ public void Test_Telemetry_LogWithWrongLevel() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogCriticalMessage() { AssertExtensions.DoesNotThrow(() => @@ -53,7 +54,7 @@ public void Test_Telemetry_LogCriticalMessage() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogInformationMessage() { AssertExtensions.DoesNotThrow(() => @@ -77,7 +78,7 @@ public void Test_Telemetry_LogInformationMessage() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogVerboseMessage() { AssertExtensions.DoesNotThrow(() => @@ -88,7 +89,7 @@ public void Test_Telemetry_LogVerboseMessage() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogErrorMessage() { AssertExtensions.DoesNotThrow(() => @@ -99,7 +100,7 @@ public void Test_Telemetry_LogErrorMessage() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogWarningDebug() { AssertExtensions.DoesNotThrow(() => @@ -110,7 +111,7 @@ public void Test_Telemetry_LogWarningDebug() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogWarningException() { AssertExtensions.DoesNotThrow(() => @@ -121,7 +122,7 @@ public void Test_Telemetry_LogWarningException() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LoCriticalException() { AssertExtensions.DoesNotThrow(() => @@ -132,7 +133,7 @@ public void Test_Telemetry_LoCriticalException() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogExceptionWithMessage() { AssertExtensions.DoesNotThrow(() => @@ -143,7 +144,18 @@ public void Test_Telemetry_LogExceptionWithMessage() }); } - [Fact, IsUnit] + [Fact] + public void Test_Telemetry_LogExceptionWithMessageAndProperties() + { + AssertExtensions.DoesNotThrow(() => + { + var logger = new AppInsightsLogger("test", LogLevel.Trace); + logger.LogError(new Exception(), "Some error", new Dictionary() { { "test", "test" } }); + logger.Flush(); + }); + } + + [Fact] public void Test_Telemetry_ExceptionWithMessageObject() { AssertExtensions.DoesNotThrow(() => @@ -154,7 +166,7 @@ public void Test_Telemetry_ExceptionWithMessageObject() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_ExceptionWithMessageFullParams() { AssertExtensions.DoesNotThrow(() => @@ -165,7 +177,7 @@ public void Test_Telemetry_ExceptionWithMessageFullParams() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_ExceptionWithDictionary() { AssertExtensions.DoesNotThrow(() => @@ -176,7 +188,7 @@ public void Test_Telemetry_ExceptionWithDictionary() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelDebug() { AssertExtensions.DoesNotThrow(() => @@ -187,7 +199,7 @@ public void Test_Telemetry_LogLevelDebug() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelError() { AssertExtensions.DoesNotThrow(() => @@ -198,7 +210,7 @@ public void Test_Telemetry_LogLevelError() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelInfo() { AssertExtensions.DoesNotThrow(() => @@ -209,7 +221,7 @@ public void Test_Telemetry_LogLevelInfo() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelInfoWithException() { AssertExtensions.DoesNotThrow(() => @@ -220,7 +232,7 @@ public void Test_Telemetry_LogLevelInfoWithException() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelInfoWithEvent() { AssertExtensions.DoesNotThrow(() => @@ -231,7 +243,7 @@ public void Test_Telemetry_LogLevelInfoWithEvent() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelNoneWithException() { AssertExtensions.DoesNotThrow(() => @@ -242,21 +254,21 @@ public void Test_Telemetry_LogLevelNoneWithException() }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogLevelNone() { AssertExtensions.DoesNotThrow(() => { var logger = new AppInsightsLogger("test", LogLevel.None); logger.Log(LogLevel.Trace, new EventId(1, "test"), "test", null, null); - logger.LogError(new Exception(), null, null); + logger.LogError(new Exception(), null, null, null, null); logger.LogMetric("test", Double.MinValue); logger.Log(LogLevel.Trace, new EventId(1, "test"), "test", null, null); logger.Flush(); }); } - [Fact, IsUnit] + [Fact] public void Test_Telemetry_LogMetric() { AssertExtensions.DoesNotThrow(() => @@ -267,7 +279,18 @@ public void Test_Telemetry_LogMetric() }); } - [Fact, IsUnit] + [Fact] + public void Test_Telemetry_LogMetricWithProperties() + { + AssertExtensions.DoesNotThrow(() => + { + var logger = new AppInsightsLogger("test", LogLevel.Trace); + logger.LogMetric("test", 3.1, new Dictionary { { "a", "a" }, { "b", "b" } }); + logger.Flush(); + }); + } + + [Fact] public void Test_BuilderExtension_Critical() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -296,7 +319,7 @@ public void Test_BuilderExtension_Critical() logger.InstrumentationKey.Should().Be("test"); } - [Fact, IsUnit] + [Fact] public void Test_BuilderExtension_Debug() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -314,7 +337,7 @@ public void Test_BuilderExtension_Debug() builder.AddConfiguration(config); builder.AddAppInsightsLogger(); }); - + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; logger.Should().NotBeNull(); @@ -322,7 +345,7 @@ public void Test_BuilderExtension_Debug() logger.InstrumentationKey.Should().Be("test"); } - [Fact, IsUnit] + [Fact] public void Test_BuilderExtension_Information() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -346,8 +369,8 @@ public void Test_BuilderExtension_Information() logger.LogLevel.Should().Be(LogLevel.Information); logger.InstrumentationKey.Should().Be("test"); } - - [Fact, IsUnit] + + [Fact] public void Test_BuilderExtension_Warning() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -372,7 +395,7 @@ public void Test_BuilderExtension_Warning() logger.InstrumentationKey.Should().Be("test"); } - [Fact, IsUnit] + [Fact] public void Test_BuilderExtension_Error() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -397,7 +420,7 @@ public void Test_BuilderExtension_Error() logger.InstrumentationKey.Should().Be("test"); } - [Fact, IsUnit] + [Fact] public void Test_BuilderExtension_None() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -422,7 +445,7 @@ public void Test_BuilderExtension_None() logger.InstrumentationKey.Should().Be("test"); } - [Fact, IsUnit] + [Fact] public void Test_BuilderExtension_Trace() { IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() @@ -446,5 +469,223 @@ public void Test_BuilderExtension_Trace() logger.LogLevel.Should().Be(LogLevel.Trace); logger.InstrumentationKey.Should().Be("test"); } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_BothDefined() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:Instrumentationkey", "testing"), + new KeyValuePair("Logging:LogLevel:Default", "Critical") + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var prov = collection.BuildServiceProvider(); + + var logger = prov.GetService() as AppInsightsLogger; + var logProvider = prov.GetService(); + + logProvider.Should().NotBeNull(); + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Critical); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Critical() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Critical") + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var prov = collection.BuildServiceProvider(); + + var logger = prov.GetService() as AppInsightsLogger; + var logProvider = prov.GetService(); + + logProvider.Should().NotBeNull(); + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Critical); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Debug() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Information"), + new KeyValuePair("Logging:LogLevel:Telemetry", "Debug"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Debug); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Information() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Information"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Information); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Warning() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Warning"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Warning); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Error() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Error"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Error); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_None() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "None"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.None); + logger.InstrumentationKey.Should().Be("test"); + } + + [Fact] + public void Test_BuilderExtensionWithInstrumentationKey_Trace() + { + IConfiguration config = new ConfigurationBuilder().AddInMemoryCollection(new List>() + { + new KeyValuePair("Logging:LogLevel:Default", "Trace"), + + }).Build(); + + var instrumentationKey = "test"; + + IServiceCollection collection = new ServiceCollection() + .AddSingleton(config) + .AddLogging(builder => + { + builder.AddConfiguration(config); + builder.AddAppInsightsLogger(instrumentationKey); + }); + + var logger = collection.BuildServiceProvider().GetService() as AppInsightsLogger; + + logger.Should().NotBeNull(); + logger.LogLevel.Should().Be(LogLevel.Trace); + logger.InstrumentationKey.Should().Be("test"); + } } } diff --git a/src/Tests/Cloud.Core.Telemetry.AzureAppInsights.Tests.csproj b/src/Tests/Cloud.Core.Telemetry.AzureAppInsights.Tests.csproj index 27b41b4..fe9916a 100644 --- a/src/Tests/Cloud.Core.Telemetry.AzureAppInsights.Tests.csproj +++ b/src/Tests/Cloud.Core.Telemetry.AzureAppInsights.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp3.1 false Robert McCabe Robert McCabe @@ -10,15 +10,19 @@ - - - - - - - + + all - runtime; build; native; contentfiles; analyzers + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/nuget.config b/src/nuget.config index b5502b1..c6721ee 100644 --- a/src/nuget.config +++ b/src/nuget.config @@ -5,6 +5,6 @@ - + \ No newline at end of file