Skip to content

Commit c1cc709

Browse files
authored
Initial implementation of Microsoft.Extensions.Logging
Initial implementation of Microsoft.Extensions.Logging
2 parents a6a9adb + 96f5f92 commit c1cc709

File tree

8 files changed

+368
-4
lines changed

8 files changed

+368
-4
lines changed

Exceptionless.Net.sln

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26228.9
4+
VisualStudioVersion = 15.0.26730.12
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{728C18BC-4085-4492-B0B2-8211CA209A50}"
77
ProjectSection(SolutionItems) = preProject
@@ -88,6 +88,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.SampleAspNetC
8888
EndProject
8989
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.SampleConsole", "samples\Exceptionless.SampleConsole\Exceptionless.SampleConsole.csproj", "{23645F73-57D8-4D70-BE1A-37903A8E4A18}"
9090
EndProject
91+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Extensions.Logging", "src\Exceptionless.Extensions.Logging\Exceptionless.Extensions.Logging.csproj", "{CA402AB4-D9CE-4053-AE26-94C1B3A4E48E}"
92+
EndProject
9193
Global
9294
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9395
Debug|Any CPU = Debug|Any CPU
@@ -174,6 +176,10 @@ Global
174176
{23645F73-57D8-4D70-BE1A-37903A8E4A18}.Debug|Any CPU.Build.0 = Debug|Any CPU
175177
{23645F73-57D8-4D70-BE1A-37903A8E4A18}.Release|Any CPU.ActiveCfg = Release|Any CPU
176178
{23645F73-57D8-4D70-BE1A-37903A8E4A18}.Release|Any CPU.Build.0 = Release|Any CPU
179+
{CA402AB4-D9CE-4053-AE26-94C1B3A4E48E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
180+
{CA402AB4-D9CE-4053-AE26-94C1B3A4E48E}.Debug|Any CPU.Build.0 = Debug|Any CPU
181+
{CA402AB4-D9CE-4053-AE26-94C1B3A4E48E}.Release|Any CPU.ActiveCfg = Release|Any CPU
182+
{CA402AB4-D9CE-4053-AE26-94C1B3A4E48E}.Release|Any CPU.Build.0 = Release|Any CPU
177183
EndGlobalSection
178184
GlobalSection(SolutionProperties) = preSolution
179185
HideSolutionNode = FALSE
@@ -198,4 +204,7 @@ Global
198204
{D7039656-8587-49CC-8657-5C9BDCFC1C80} = {2CEE12C6-3840-4C01-A952-D3026B0A662A}
199205
{23645F73-57D8-4D70-BE1A-37903A8E4A18} = {2CEE12C6-3840-4C01-A952-D3026B0A662A}
200206
EndGlobalSection
207+
GlobalSection(ExtensibilityGlobals) = postSolution
208+
SolutionGuid = {EBB2CC85-FF87-431B-865F-2F110B2A10E6}
209+
EndGlobalSection
201210
EndGlobal

samples/Exceptionless.SampleAspNetCore/Exceptionless.SampleAspNetCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15+
<ProjectReference Include="..\..\src\Exceptionless.Extensions.Logging\Exceptionless.Extensions.Logging.csproj" />
1516
<ProjectReference Include="..\..\src\Exceptionless\Exceptionless.csproj" />
1617
<ProjectReference Include="..\..\src\Platforms\Exceptionless.AspNetCore\Exceptionless.AspNetCore.csproj" />
1718
</ItemGroup>

samples/Exceptionless.SampleAspNetCore/Startup.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.DependencyInjection;
66
using Microsoft.Extensions.Logging;
7-
using Exceptionless;
87

98
namespace Exceptionless.SampleAspNetCore {
109
public class Startup {
@@ -24,10 +23,14 @@ public void ConfigureServices(IServiceCollection services) {
2423

2524
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
2625
app.UseExceptionless(Configuration);
27-
// OR
26+
//OR
2827
//app.UseExceptionless(new ExceptionlessClient(c => c.ReadFromConfiguration(Configuration)));
29-
// OR
28+
//OR
3029
//app.UseExceptionless("API_KEY_HERE");
30+
//OR
31+
//loggerFactory.AddExceptionless("API_KEY_HERE");
32+
//OR
33+
//loggerFactory.AddExceptionless((c) => c.ReadFromConfiguration(Configuration));
3134

3235
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
3336
loggerFactory.AddDebug();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard1.3;netstandard2.0;net46;net461</TargetFrameworks>
5+
<RootNamespace>Exceptionless.Extensions.Logging</RootNamespace>
6+
<Version>4.0.0</Version>
7+
<Copyright>Copyright (c) 2017 Exceptionless. All rights reserved.</Copyright>
8+
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
9+
<PackageProjectUrl>https://github.com/exceptionless/Exceptionless.Net</PackageProjectUrl>
10+
<PackageIconUrl>https://be.exceptionless.io/img/exceptionless-32.png</PackageIconUrl>
11+
<RepositoryUrl>https://github.com/exceptionless/Exceptionless.Net</RepositoryUrl>
12+
<RepositoryType>git</RepositoryType>
13+
<PackageReleaseNotes>https://github.com/exceptionless/Exceptionless.Net/releases</PackageReleaseNotes>
14+
<PackageTags>Exceptionless;Microsoft.Extensions.Logging;log;logging</PackageTags>
15+
<Description>Exceptionless provider for Microsoft.Extensions.Logging.</Description>
16+
</PropertyGroup>
17+
18+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.3'">
19+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
20+
</ItemGroup>
21+
22+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
23+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
24+
</ItemGroup>
25+
26+
<ItemGroup Condition="'$(TargetFramework)' == 'net46'">
27+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
28+
</ItemGroup>
29+
30+
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
31+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
32+
</ItemGroup>
33+
34+
<ItemGroup>
35+
<ProjectReference Include="..\Exceptionless\Exceptionless.csproj" />
36+
</ItemGroup>
37+
38+
</Project>
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using Exceptionless.Models;
2+
using Microsoft.Extensions.Logging;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace Exceptionless.Extensions.Logging
7+
{
8+
public class ExceptionlessLogger : ILogger
9+
{
10+
static readonly Dictionary<LogLevel, string> LOG_LEVELS = MapLogLevelsToExceptionlessNames();
11+
12+
static Dictionary<LogLevel, string> MapLogLevelsToExceptionlessNames()
13+
{
14+
Dictionary<LogLevel, string> mappings = new Dictionary<LogLevel, string>(7);
15+
mappings.Add(LogLevel.Critical, global::Exceptionless.Logging.LogLevel.Fatal.ToString());
16+
mappings.Add(LogLevel.Debug, global::Exceptionless.Logging.LogLevel.Debug.ToString());
17+
mappings.Add(LogLevel.Error, global::Exceptionless.Logging.LogLevel.Error.ToString());
18+
mappings.Add(LogLevel.Information, global::Exceptionless.Logging.LogLevel.Info.ToString());
19+
mappings.Add(LogLevel.None, global::Exceptionless.Logging.LogLevel.Off.ToString());
20+
mappings.Add(LogLevel.Trace, global::Exceptionless.Logging.LogLevel.Trace.ToString());
21+
mappings.Add(LogLevel.Warning, global::Exceptionless.Logging.LogLevel.Warn.ToString());
22+
23+
return mappings;
24+
}
25+
26+
ExceptionlessClient _Client;
27+
string _Source;
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="ExceptionlessLogger"/> class.
31+
/// </summary>
32+
/// <param name="client">The <see cref="ExceptionlessClient"/> to be used by the logger.</param>
33+
/// <param name="source">The source to tag events with, typically the category.</param>
34+
public ExceptionlessLogger(ExceptionlessClient client, string source)
35+
{
36+
if (client == null)
37+
throw new ArgumentNullException(nameof(client));
38+
39+
_Client = client;
40+
_Source = source;
41+
}
42+
43+
/// <summary>
44+
/// Begins a logical operation scope.
45+
/// </summary>The identifier for the scope.
46+
/// <typeparam name="TState">The type of the state object.</typeparam>
47+
/// <param name="state"></param>
48+
/// <returns>An <see cref="IDisposable"/> that ends the logical operation scope on dispose.</returns>
49+
public IDisposable BeginScope<TState>(TState state)
50+
{
51+
if (state == null)
52+
throw new ArgumentNullException(nameof(state));
53+
54+
// Typically a string will be used as the state for a scope, but the BeginScope extension provides
55+
// a FormattedLogValues and ASP.NET provides multiple scope objects, so just use ToString()
56+
string description = state.ToString();
57+
58+
// If there's data other than a simple string, it'll be added to the event
59+
object stateObj = state is string ? null : (object)state;
60+
61+
// Log scope creation as an event so that there is a parent to tie events together
62+
ExceptionlessLoggingScope scope = new ExceptionlessLoggingScope(description);
63+
LogScope(scope, stateObj);
64+
65+
// Add to stack to support nesting within execution context
66+
ExceptionlessLoggingScope.Push(scope);
67+
return scope;
68+
}
69+
70+
/// <summary>
71+
/// Checks if the given <see cref="LogLevel"/> is enabled.
72+
/// </summary>
73+
/// <param name="logLevel">The level to be checked.</param>
74+
/// <returns>Returns true if enabled.</returns>
75+
public bool IsEnabled(LogLevel logLevel)
76+
{
77+
return true;
78+
}
79+
80+
/// <summary>
81+
/// Writes a log entry.
82+
/// </summary>
83+
/// <typeparam name="TState">The type of the state object.</typeparam>
84+
/// <param name="logLevel">Entry will be written on this level.</param>
85+
/// <param name="eventId">Id of the event.</param>
86+
/// <param name="state">The entry to be written. Can be also an object.</param>
87+
/// <param name="exception">The exception related to this entry.</param>
88+
/// <param name="formatter">Function to create a <see cref="string"/> message of the <paramref name="state"/> and <paramref name="exception"/>.</param>
89+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
90+
{
91+
// Create basic event from client
92+
EventBuilder eb = exception == null ? _Client.CreateEvent() : _Client.CreateException(exception);
93+
eb.SetProperty(Event.KnownDataKeys.Level, LOG_LEVELS[logLevel]);
94+
95+
// Get formatted message
96+
eb.SetMessage(formatter.Invoke(state, exception));
97+
98+
// Add category as source, if available
99+
if (_Source != null)
100+
eb.SetSource(_Source);
101+
102+
// Add event id, if available
103+
if (eventId.Id != 0)
104+
eb.SetProperty("Event Id", eventId.Id);
105+
106+
// If within a scope, add scope's reference id
107+
if (ExceptionlessLoggingScope.Current != null)
108+
eb.SetEventReference("Parent", ExceptionlessLoggingScope.Current.Id);
109+
110+
// The logging framework passes in FormattedLogValues, which implements IEnumerable<KeyValuePair<string, object>>;
111+
// add each property and value as individual objects for proper visibility in Exceptionless
112+
IEnumerable<KeyValuePair<string, object>> stateProps = state as IEnumerable<KeyValuePair<string, object>>;
113+
if (stateProps != null)
114+
{
115+
foreach (KeyValuePair<string, object> prop in stateProps)
116+
{
117+
// Logging the message template is superfluous
118+
if (prop.Key != "{OriginalFormat}")
119+
eb.AddObject(prop.Value, prop.Key);
120+
}
121+
}
122+
// Otherwise, attach the entire object, using its type as the name
123+
else
124+
{
125+
eb.AddObject(state);
126+
}
127+
128+
// Add to client's queue
129+
eb.Submit();
130+
}
131+
132+
/// <summary>
133+
/// Writes a scope creation entry.
134+
/// </summary>
135+
/// <param name="newScope">The <see cref="ExceptionlessLoggingScope"/> being created.</param>
136+
private void LogScope(ExceptionlessLoggingScope newScope, object state)
137+
{
138+
EventBuilder eb = _Client.CreateLog($"Creating scope: {newScope.Description}.", global::Exceptionless.Logging.LogLevel.Other);
139+
140+
// Set event reference id to that of scope object
141+
eb.SetReferenceId(newScope.Id);
142+
143+
// If this is a nested scope, add parent's reference id
144+
if (ExceptionlessLoggingScope.Current != null)
145+
eb.SetEventReference("Parent", ExceptionlessLoggingScope.Current.Id);
146+
147+
if (state != null)
148+
{
149+
IEnumerable<KeyValuePair<string, object>> stateProps = state as IEnumerable<KeyValuePair<string, object>>;
150+
if (stateProps != null)
151+
{
152+
foreach (KeyValuePair<string, object> prop in stateProps)
153+
{
154+
// Logging the message template is superfluous
155+
if (prop.Key != "{OriginalFormat}")
156+
eb.AddObject(prop.Value, prop.Key);
157+
}
158+
}
159+
else
160+
{
161+
eb.AddObject(state);
162+
}
163+
}
164+
165+
eb.Submit();
166+
}
167+
}
168+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Exceptionless.Extensions.Logging;
2+
using Microsoft.Extensions.Logging;
3+
using System;
4+
5+
namespace Exceptionless
6+
{
7+
public static class ExceptionlessLoggerExtensions
8+
{
9+
/// <summary>
10+
/// Adds Exceptionless to the logging pipeline using the default client.
11+
/// </summary>
12+
/// <param name="factory">The <see cref="ILoggerFactory"/>.</param>
13+
/// <returns>The <see cref="ILoggerFactory"/>.</returns>
14+
public static ILoggerFactory AddExceptionless(this ILoggerFactory factory) {
15+
factory.AddProvider(new ExceptionlessLoggerProvider(ExceptionlessClient.Default));
16+
return factory;
17+
}
18+
19+
/// <summary>
20+
/// Adds Exceptionless to the logging pipeline using a new client with the provided api key.
21+
/// </summary>
22+
/// <param name="factory">The <see cref="ILoggerFactory"/>.</param>
23+
/// <param name="apiKey">The project api key.</param>
24+
/// <returns>The <see cref="ILoggerFactory"/>.</returns>
25+
public static ILoggerFactory AddExceptionless(this ILoggerFactory factory, string apiKey)
26+
{
27+
factory.AddProvider(new ExceptionlessLoggerProvider((config) => config.ApiKey = apiKey));
28+
return factory;
29+
}
30+
31+
/// <summary>
32+
/// Adds Exceptionless to the logging pipeline using a new client configured with the provided action.
33+
/// </summary>
34+
/// <param name="factory">The <see cref="ILoggerFactory"/>.</param>
35+
/// <param name="configure">An <see cref="Action{ExceptionlessConfiguration}"/> that applies additional settings and plugins. The project api key must be specified.</param>
36+
/// <returns>The <see cref="ILoggerFactory"/>.</returns>
37+
public static ILoggerFactory AddExceptionless(this ILoggerFactory factory, Action<ExceptionlessConfiguration> configure)
38+
{
39+
factory.AddProvider(new ExceptionlessLoggerProvider(configure));
40+
return factory;
41+
}
42+
}
43+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.Extensions.Logging;
2+
using System;
3+
4+
namespace Exceptionless.Extensions.Logging
5+
{
6+
public class ExceptionlessLoggerProvider : ILoggerProvider
7+
{
8+
ExceptionlessClient _Client;
9+
bool _ShouldDisposeClient;
10+
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="ExceptionlessLoggerProvider"/> class.
13+
/// </summary>
14+
/// <param name="config">An <see cref="ExceptionlessConfiguration"/> which will be provided to created loggers.</param>
15+
public ExceptionlessLoggerProvider(ExceptionlessClient client) {
16+
if (client == null)
17+
throw new ArgumentNullException(nameof(client));
18+
19+
_Client = client;
20+
_ShouldDisposeClient = false;
21+
}
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="ExceptionlessLoggerProvider"/> class.
25+
/// </summary>
26+
/// <param name="configure">An <see cref="Action{ExceptionlessConfiguration}"/> which will be used to configure created loggers.</param>
27+
public ExceptionlessLoggerProvider(Action<ExceptionlessConfiguration> configure) {
28+
if (configure == null)
29+
throw new ArgumentNullException(nameof(configure));
30+
31+
_Client = new ExceptionlessClient(configure);
32+
_Client.Startup();
33+
_ShouldDisposeClient = true;
34+
}
35+
36+
/// <summary>
37+
/// Creates a new <see cref="ILogger"/> instance.
38+
/// </summary>
39+
/// <param name="categoryName">The category name for messages produced by the logger.</param>
40+
/// <returns>An <see cref="ILogger"/></returns>
41+
public ILogger CreateLogger(string categoryName)
42+
{
43+
return new ExceptionlessLogger(_Client, categoryName);
44+
}
45+
46+
public void Dispose()
47+
{
48+
_Client.ProcessQueue();
49+
if (_ShouldDisposeClient)
50+
{
51+
_Client.Shutdown();
52+
((IDisposable)_Client).Dispose();
53+
}
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)