Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using StackExchange.Exceptional;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StackExchange.Exceptional;
using System;

namespace Microsoft.AspNetCore.Builder
Expand All @@ -16,7 +18,11 @@ public static class ExceptionalBuilderExtensions
public static IApplicationBuilder UseExceptional(this IApplicationBuilder builder)
{
_ = builder ?? throw new ArgumentNullException(nameof(builder));

var loggerFactory = builder.ApplicationServices.GetRequiredService<ILoggerFactory>();
loggerFactory.AddProvider(builder.ApplicationServices.GetRequiredService<ExceptionalLoggerProvider>());

return builder.UseMiddleware<ExceptionalMiddleware>();
}
}
}
}
62 changes: 62 additions & 0 deletions src/StackExchange.Exceptional.AspNetCore/ExceptionalLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace StackExchange.Exceptional
{
class ExceptionalLogger : ILogger
{
private readonly string _category;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<ExceptionalSettings> _settings;
private readonly bool _ignored;
private static readonly HashSet<string> _ignoredCategories = new HashSet<string>
{
typeof(ExceptionalMiddleware).FullName, // exceptional middleware calls some ILogger stuff itself, ignore those calls)
};

public ExceptionalLogger(string category, IOptions<ExceptionalSettings> settings, IHttpContextAccessor httpContextAccessor = null)
{
_category = category;
_settings = settings;
_httpContextAccessor = httpContextAccessor;
_ignored = _ignoredCategories.Contains(category);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could attach something to Exception.Data, to prevent the same exception from being logged twice.

A reasonable case of this happening would be if an exception gets logged to an ILogger, is then rethrown, is ultimately captured by the middleware, and is logged again...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into exactly that while doing my own kinda version of this and doing that worked great:

/// <summary>
/// This is used internally to stop an exception from being logged twice
/// <para />
/// This is usually needed when code closer to the source logs extra information e.g. ids
/// and then rethrows to trigger the error middleware, which ends up logging it again
/// <para />
/// Returns true if the exception has already been logged, or false otherwise
/// </summary>
/// <param name="exception"></param>
public static bool ExceptionLogged(Exception exception)
{
    var key = $"Exceptional.Logged";
    var logged = exception.Data.Contains(key);
    if (!logged)
    {
        exception.Data[key] = true;
    }
    return logged;
}

}

public IDisposable BeginScope<TState>(TState state)
{
return null;
}

public bool IsEnabled(LogLevel logLevel)
{
return !_ignored;
// TODO compare against settings and add support for per-category log levels, or we can just leave it up to LogFilters to decide later
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (_ignored) return;
if (exception == null) return;

var customData = new Dictionary<string, string>
{
["AspNetCore.LogLevel"] = logLevel + "",
["AspNetCore.EventId.Id"] = eventId.Id + "",
["AspNetCore.EventId.Name"] = eventId.Name,
["AspNetCore.Message"] = formatter(state, exception),
};

if (_httpContextAccessor?.HttpContext is HttpContext context)
{
exception.Log(context, _category, customData: customData);
}
else
{
exception.LogNoContext(_category, customData: customData);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace StackExchange.Exceptional
{
class ExceptionalLoggerProvider : ILoggerProvider
{
private readonly IOptions<ExceptionalSettings> _settings;
private readonly IHttpContextAccessor _httpContextAccessor;

public ExceptionalLoggerProvider(IOptions<ExceptionalSettings> settings, IHttpContextAccessor httpContextAccessor = null)
{
_settings = settings;
_httpContextAccessor = httpContextAccessor;
}

ILogger ILoggerProvider.CreateLogger(string categoryName)
=> new ExceptionalLogger(categoryName, _settings, _httpContextAccessor);

void IDisposable.Dispose()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.Extensions.Configuration;
using StackExchange.Exceptional;
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -36,6 +38,10 @@ public static IServiceCollection AddExceptional(this IServiceCollection services
// When done configuring, set the background settings object for non-context logging.
services.Configure<ExceptionalSettings>(Exceptional.Configure);

// setup for ILogger & co
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<ExceptionalLoggerProvider>();

return services;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<ItemGroup>
<ProjectReference Include="../StackExchange.Exceptional.Shared/StackExchange.Exceptional.Shared.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.0" />
Comment thread
NickCraver marked this conversation as resolved.
Outdated
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
</ItemGroup>
Expand Down