Skip to content

Ability to customize LogEventLevel used in UseSerilogRequestLogging middleware #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 11 additions & 2 deletions samples/InlineInitializationSample/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Builder;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Events;

namespace InlineInitializationSample
{
Expand Down Expand Up @@ -46,7 +48,14 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
// Write streamlined request completion events, instead of the more verbose ones from the framework.
// To use the default framework request logging instead, remove this line and set the "Microsoft"
// level in appsettings.json to "Information".
app.UseSerilogRequestLogging();
app.UseSerilogRequestLogging(opts =>
{
opts.GetLogEventLevel = statusCode =>
statusCode == HttpStatusCode.InternalServerError
? LogEventLevel.Error
: LogEventLevel.Information;
// OR opts.GetLogEventLevel = _ => LogEventLevel.Information;
});

app.UseStaticFiles();
app.UseCookiePolicy();
Expand Down
9 changes: 6 additions & 3 deletions src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
Expand All @@ -29,15 +30,17 @@ class RequestLoggingMiddleware
readonly RequestDelegate _next;
readonly DiagnosticContext _diagnosticContext;
readonly MessageTemplate _messageTemplate;
readonly Func<HttpStatusCode, LogEventLevel> _getLogEventLevel;

static readonly LogEventProperty[] NoProperties = new LogEventProperty[0];

public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
_next = next ?? throw new ArgumentNullException(nameof(next));
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));

_getLogEventLevel = options.GetLogEventLevel;
_messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate);
}

Expand Down Expand Up @@ -71,8 +74,8 @@ public async Task Invoke(HttpContext httpContext)
bool LogCompletion(HttpContext httpContext, DiagnosticContextCollector collector, int statusCode, double elapsedMs, Exception ex)
{
var logger = Log.ForContext<RequestLoggingMiddleware>();
var level = statusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information;

var level = _getLogEventLevel((HttpStatusCode)statusCode);
if (!logger.IsEnabled(level)) return false;

if (!collector.TryComplete(out var collectedProperties))
Expand Down
34 changes: 27 additions & 7 deletions src/Serilog.AspNetCore/RequestLoggingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Serilog.Events;
using System;
using System.Net;

namespace Serilog
{
class RequestLoggingOptions
/// <summary>
/// Contains options for the Serilog.AspNetCore.RequestLoggingMiddleware.
/// </summary>
public class RequestLoggingOptions
{
public string MessageTemplate { get; }
/// <summary>
/// Gets or sets the message template. The default value is
/// <c>"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"</c>. The
/// template can contain any of the placeholders from the default template, names of properties
/// added by ASP.NET Core, and names of properties added to the <see cref="IDiagnosticContext"/>.
/// </summary>
/// <value>
/// The message template.
/// </value>
public string MessageTemplate { get; set; }

public RequestLoggingOptions(string messageTemplate)
{
MessageTemplate = messageTemplate ?? throw new ArgumentNullException(nameof(messageTemplate));
}
/// <summary>
/// Gets or sets the function returning the LogEventLevel based on the HttpStatusCode.
/// The default behavior returns LogEventLevel.Error when HttpStatusCode is greater than 499
/// </summary>
/// <value>
/// The function returning the LogEventLevel.
/// </value>
public Func<HttpStatusCode, LogEventLevel> GetLogEventLevel { get; set; }

internal RequestLoggingOptions() { }
}
}
}
41 changes: 37 additions & 4 deletions src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
// limitations under the License.

using System;
using System.Net;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Serilog.AspNetCore;
using Serilog.Events;

namespace Serilog
{
Expand All @@ -26,6 +29,9 @@ public static class SerilogApplicationBuilderExtensions
const string DefaultRequestCompletionMessageTemplate =
"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";

static readonly Func<HttpStatusCode, LogEventLevel> DefaultGetLogEventLevel =
s => (int) s > 499 ? LogEventLevel.Error : LogEventLevel.Information;

/// <summary>
/// Adds middleware for streamlined request logging. Instead of writing HTTP request information
/// like method, path, timing, status code and exception details
Expand All @@ -43,11 +49,38 @@ public static class SerilogApplicationBuilderExtensions
/// <returns>The application builder.</returns>
public static IApplicationBuilder UseSerilogRequestLogging(
this IApplicationBuilder app,
string messageTemplate = DefaultRequestCompletionMessageTemplate)
string messageTemplate)
=> app.UseSerilogRequestLogging(opts => opts.MessageTemplate = messageTemplate);

/// <summary>
/// Adds middleware for streamlined request logging. Instead of writing HTTP request information
/// like method, path, timing, status code and exception details
/// in several events, this middleware collects information during the request (including from
/// <see cref="IDiagnosticContext"/>), and writes a single event at request completion. Add this
/// in <c>Startup.cs</c> before any handlers whose activities should be logged.
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="configureOptions"> An System.Action`1 to configure the provided <see cref="Serilog.RequestLoggingOptions" />.</param>
/// <returns>The application builder.</returns>
public static IApplicationBuilder UseSerilogRequestLogging(
this IApplicationBuilder app,
Action<RequestLoggingOptions> configureOptions = null)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));
return app.UseMiddleware<RequestLoggingMiddleware>(new RequestLoggingOptions(messageTemplate));

var opts = new RequestLoggingOptions
{
GetLogEventLevel = DefaultGetLogEventLevel,
MessageTemplate = DefaultRequestCompletionMessageTemplate
};
configureOptions?.Invoke(opts);

if (opts.MessageTemplate == null)
throw new ArgumentException($"{nameof(opts.MessageTemplate)} cannot be null.");
if (opts.GetLogEventLevel == null)
throw new ArgumentException($"{nameof(opts.GetLogEventLevel)} cannot be null.");

return app.UseMiddleware<RequestLoggingMiddleware>(opts);
}
}
}
}