Search code examples
c#asp.net-core

Customize output Serilog


How can I get this output

{
    "@t": "2020-11-03T16:40:21.6133998Z",
    "@m": "HTTP \"POST\" \"/api/Email/SendEmail\" responded 200 in 1.0358 ms",
    "@i": "62d0885c",
    "CorrelationId": "",
    "Host": "localhost:32768",
    "Protocol": "HTTP/1.1",
    "Scheme": "http",
    "ContentType": null,
    "EndpointName": "Email.Controllers.EmailController.SendEmail (Email)",
    "RequestMethod": "POST",
    "RequestPath": "/api/Email/SendEmail",
    "StatusCode": 200,
    "Elapsed": 1.0358,
    "SourceContext": "Serilog.AspNetCore.RequestLoggingMiddleware",
    "RequestId": "",
    "SpanId": "|55f14a36-4918a3efd4f265b9.",
    "TraceId": "55f14a36-4918a3efd4f265b9",
    "ParentId": "",
    "ConnectionId": "0HM402S7EC249"
}

But when I invoke my controller I see and run application I see that

Thats class how I setting my Serilog, what do I do wrong?

 public class LogConfig
    {
        public static void Configure()
        {
            Log.Logger = new LoggerConfiguration()
               .WriteTo.Console(new RenderedCompactJsonFormatter())
              .CreateLogger();
        }

        public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
        {
            diagnosticContext.Set("RequestScheme", httpContext.Request.Host.Value);
            diagnosticContext.Set("Headers", httpContext.Request.Scheme);
        } 
    }


Solution

  • There are three JSON formatters provided by the Serilog project:

    • Serilog.Formatting.Json.JsonFormatter - This is the historical default shipped in the Serilog package. It produces a complete rendering of the log event and supports a few configuration options.
    • Serilog.Formatting.Compact.CompactJsonFormatter - A newer, more space-efficient JSON formatter shipped in Serilog.Formatting.Compact.
    • Serilog.Formatting.Compact.RenderedCompactJsonFormatter - Also shipped in Serilog.Formatting.Compact, this formatter pre-renders message templates into text.

    You could refer the following steps and use Serilog.Formatting.Compact to customize Serilog output:

    1. Install Serilog.AspNetCore via Nuget.

    2. Configure to use Serilog:

       public class Program
       {
           public static int Main(string[] args)
           {
               // CreateHostBuilder(args).Build().Run();
      
               Log.Logger = new LoggerConfiguration()
              .MinimumLevel.Debug()
              .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
              .Enrich.FromLogContext()
              .WriteTo.File(new CompactJsonFormatter(), "log20201104.json")
              .CreateLogger();
      
               try
               {
                   Log.Information("Starting web host");
                   CreateHostBuilder(args).Build().Run();
                   return 0;
               }
               catch (Exception ex)
               {
                   Log.Fatal(ex, "Host terminated unexpectedly");
                   return 1;
               }
               finally
               {
                   Log.CloseAndFlush();
               }
           }
      
           public static IHostBuilder CreateHostBuilder(string[] args) =>
               Host.CreateDefaultBuilder(args)
                   .UseSerilog()
                   .ConfigureWebHostDefaults(webBuilder =>
                   {
                       webBuilder.UseStartup<Startup>();
                   });
       }
      

      Remember to add the following reference:

       using Serilog;
       using Serilog.Events;
       using Serilog.Formatting.Compact;
      
    3. According to your code, it seems that you want to log the request and response information, if that is the case, you could also create a SerilogRequestLogger Middleware. Like this:

       public class SerilogRequestLogger
       {
           readonly RequestDelegate _next;
      
           public SerilogRequestLogger(RequestDelegate next)
           {
               if (next == null) throw new ArgumentNullException(nameof(next));
               _next = next;
           }
      
           public async Task Invoke(HttpContext httpContext)
           {
               if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
      
               // Push the user name into the log context so that it is included in all log entries
               LogContext.PushProperty("UserName", httpContext.User.Identity.Name);
      
               // Getting the request body is a little tricky because it's a stream
               // So, we need to read the stream and then rewind it back to the beginning
               string requestBody = "";
               HttpRequestRewindExtensions.EnableBuffering(httpContext.Request);
               Stream body = httpContext.Request.Body;
               byte[] buffer = new byte[Convert.ToInt32(httpContext.Request.ContentLength)];
               await httpContext.Request.Body.ReadAsync(buffer, 0, buffer.Length);
               requestBody = Encoding.UTF8.GetString(buffer);
               body.Seek(0, SeekOrigin.Begin);
               httpContext.Request.Body = body;
      
               Log.ForContext("RequestHeaders", httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
                  .ForContext("RequestBody", requestBody)
                  .Debug("Request information {RequestMethod} {RequestPath} information", httpContext.Request.Method, httpContext.Request.Path);
      
               Log.Information(string.Format("Request Body: {0} ", requestBody));
      
               var exampleUser = new WebApplication2User { Id = "1001", UserName = "Adam", SecurityStamp = DateTime.Now.ToString() };
               Log.Information("Created {@User} on {Created}", exampleUser, DateTime.Now);
               // The reponse body is also a stream so we need to:
               // - hold a reference to the original response body stream
               // - re-point the response body to a new memory stream
               // - read the response body after the request is handled into our memory stream
               // - copy the response in the memory stream out to the original response stream
               using (var responseBodyMemoryStream = new MemoryStream())
               {
                   var originalResponseBodyReference = httpContext.Response.Body;
                   httpContext.Response.Body = responseBodyMemoryStream;
      
                   await _next(httpContext);
      
                   httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
                   var responseBody = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
                   httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
      
                   Log.ForContext("RequestBody", requestBody)
                      .ForContext("ResponseBody", responseBody)
                      .Debug("Response information {RequestMethod} {RequestPath} {statusCode}", httpContext.Request.Method, httpContext.Request.Path, httpContext.Response.StatusCode);
      
                   await responseBodyMemoryStream.CopyToAsync(originalResponseBodyReference);
               }
           }
       }
      
    4. Configure the SerilogRequestLogger Middleware in the Configure method:

        app.UseMiddleware<SerilogRequestLogger>();
      

    Then, you could use the following code to log information:

                var exampleUser = new WebApplication2User { Id = "1001", UserName = "Adam", SecurityStamp = DateTime.Now.ToString() };
                Log.Information("Created {@User} on {Created}", exampleUser, DateTime.Now);
    

    the result like this:

    {"@t":"2020-11-04T14:19:23.2065796Z","@mt":"Created {@User} on {Created}","User":{"Id":"1001","Age":0,"UserName":"Adam","NormalizedUserName":null,"Email":null,"NormalizedEmail":null,"EmailConfirmed":false,"PasswordHash":null,"SecurityStamp":"11/4/2020 10:19:23 PM","ConcurrencyStamp":"ca564733-9524-446f-8103-dd211cf8b44c","PhoneNumber":null,"PhoneNumberConfirmed":false,"TwoFactorEnabled":false,"LockoutEnd":null,"LockoutEnabled":false,"AccessFailedCount":0,"$type":"WebApplication2User"},"Created":"2020-11-04T22:19:23.2060830+08:00","UserName":null,"RequestId":"80000080-0005-fd00-b63f-84710c7967bb","RequestPath":"/Home/Privacy","SpanId":"|e29bfdb0-4bd9f6a34acb24d8.","TraceId":"e29bfdb0-4bd9f6a34acb24d8","ParentId":""}

    Reference:

    Formatting JSON

    Serilog.Formatting.Compact

    How to add 'request body' in serilog's output .net core?