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 all 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
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"sdk": {
"version": "2.2.105"
}
}
}
4 changes: 3 additions & 1 deletion 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
17 changes: 9 additions & 8 deletions src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Serilog.Events;
using Serilog.Extensions.Hosting;
using Serilog.Parsing;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Serilog.AspNetCore
{
Expand All @@ -29,7 +29,7 @@ class RequestLoggingMiddleware
readonly RequestDelegate _next;
readonly DiagnosticContext _diagnosticContext;
readonly MessageTemplate _messageTemplate;

readonly Func<HttpContext, Exception, LogEventLevel> _getLevel;
static readonly LogEventProperty[] NoProperties = new LogEventProperty[0];

public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnosticContext, RequestLoggingOptions options)
Expand All @@ -38,6 +38,7 @@ public RequestLoggingMiddleware(RequestDelegate next, DiagnosticContext diagnost
_next = next ?? throw new ArgumentNullException(nameof(next));
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));

_getLevel = options.GetLevel;
_messageTemplate = new MessageTemplateParser().Parse(options.MessageTemplate);
}

Expand Down Expand Up @@ -71,10 +72,10 @@ 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 = _getLevel(httpContext, ex);

if (!logger.IsEnabled(level)) return false;

if (!collector.TryComplete(out var collectedProperties))
collectedProperties = NoProperties;

Expand All @@ -97,7 +98,7 @@ static double GetElapsedMilliseconds(long start, long stop)
{
return (stop - start) * 1000 / (double)Stopwatch.Frequency;
}

static string GetPath(HttpContext httpContext)
{
return httpContext.Features.Get<IHttpRequestFeature>()?.RawTarget ?? httpContext.Request.Path.ToString();
Expand Down
48 changes: 48 additions & 0 deletions src/Serilog.AspNetCore/AspNetCore/RequestLoggingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2019 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Microsoft.AspNetCore.Http;
using Serilog.Events;
using System;

namespace Serilog.AspNetCore
{
/// <summary>
/// Contains options for the <see cref="Serilog.AspNetCore.RequestLoggingMiddleware"/>.
/// </summary>
public class RequestLoggingOptions
{
/// <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; }

/// <summary>
/// Gets or sets the function returning the <see cref="LogEventLevel"/> based on the <see cref="HttpContext"/> and on the <see cref="Exception" /> if something wrong happend
/// The default behavior returns LogEventLevel.Error when HttpStatusCode is greater than 499 or if Exception is not null.
/// </summary>
/// <value>
/// The function returning the <see cref="LogEventLevel"/>.
/// </value>
public Func<HttpContext, Exception, LogEventLevel> GetLevel { get; set; }

internal RequestLoggingOptions() { }
}
}
28 changes: 0 additions & 28 deletions src/Serilog.AspNetCore/RequestLoggingOptions.cs

This file was deleted.

44 changes: 40 additions & 4 deletions src/Serilog.AspNetCore/SerilogApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Serilog.AspNetCore;
using Serilog.Events;

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

static Func<HttpContext, Exception, LogEventLevel> DefaultGetLevel =
(ctx, ex) => ex != null
? LogEventLevel.Error
: ctx.Response.StatusCode > 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 +52,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">A <see cref="System.Action{T}" /> to configure the provided <see cref="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
{
GetLevel = DefaultGetLevel,
MessageTemplate = DefaultRequestCompletionMessageTemplate
};
configureOptions?.Invoke(opts);

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

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