locked
Serilog enriching RRS feed

  • Question

  • User-1458727574 posted

    I've managed to get Serilog configured with some very good help from here. I'm trying to extend it a bit more so that when anything hits the controller, all logs to Serilog through that request have a GUID logged against them. This way, I can return the GUID to the end user in the response so that in the event of an error, they can provide the GUID and the logs can easily be found.

    First of all I found this site: https://dotnet-cookbook.cfapps.io/core/scoped-logging-with-serilog/

    I am using the MS SQL Server Sink rather than the console sink.

    Using info on that site I created a new class called CorrelationIdEnricher.cs here:

    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Serilog.Context;
    using System;
    using System.Threading.Tasks;
    
    namespace ACLWebApi.CustomClasses.CorrelationIdEnricher
    {
        public class CorrelationIdEnricher
        {
            private const string CORRELATION_ID_HEADER_NAME = "CorrelationId";
            private readonly RequestDelegate next;
            private readonly ILogger<CorrelationIdEnricher> logger;
    
            public CorrelationIdEnricher(RequestDelegate next, ILogger<CorrelationIdEnricher> logger)
            {
                this.next = next ?? throw new System.ArgumentNullException(nameof(next));
                this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
            }
    
            public async Task Invoke(HttpContext context)
            {
                if (context == null) throw new System.ArgumentNullException(nameof(context));
    
                string correlationId = GetOrAddCorrelationHeader(context);
    
                try
                {
                    //Add as many nested usings as needed, for adding more properties 
                    using (LogContext.PushProperty(CORRELATION_ID_HEADER_NAME, correlationId, true))
                    {
                        await next.Invoke(context);
                    }
                }
                //To make sure that we don't lose the scope in case of an unexpected error
                catch (Exception ex) when (LogOnUnexpectedError(ex))
                {
                    return;
                }
            }
    
            private string GetOrAddCorrelationHeader(HttpContext context)
            {
                if (context == null) throw new System.ArgumentNullException(nameof(context));
    
                if (string.IsNullOrWhiteSpace(context.Request.Headers[CORRELATION_ID_HEADER_NAME]))
                    context.Request.Headers[CORRELATION_ID_HEADER_NAME] = Guid.NewGuid().ToString();
    
                return context.Request.Headers[CORRELATION_ID_HEADER_NAME];
            }
    
            private bool LogOnUnexpectedError(Exception ex)
            {
                logger.LogError(ex, "An unexpected exception occurred when processing GUID for logs");
                return true;
            }
        }
    }

    In appettings I have this section for Serilog configuration:

        "Serilog": {
            "Using": [ "Serilog.Sinks.MSSqlServer" ],
            "MinimumLevel": {
                "Default": "Information",
                "Override": {
                    "Microsoft": "Warning"
                }
            },
            "WriteTo": [
                {
                    "Name": "MSSqlServer",
                    "Args": {
                        "connectionString": "Server=(local);Database=ACLWebAPILogs;trusted_connection=true",
                        "tableName": "ACLWebAPILog",
                        "restrictedToMinimumLevel": "Information",
                        "autoCreateSqlTable": true,
                        "columnOptionsSection": {
                            "disableTriggers": true,
                            "clusteredColumnstoreIndex": false, // setting to true is only available in SQL 2017 and later
                            "removeStandardColumns": [ "MessageTemplate", "Properties" ],
                            "additionalColumns": [
                                {
                                    "ColumnName": "ControllerName",
                                    "DataType": "nvarchar",
                                    "AllowNull": true,
                                    "DataLength": 50
                                },
                                {
                                    "ColumnName": "CorrelationId",
                                    "DataType": "nvarchar",
                                    "AllowNull": true,
                                    "DataLength": 50
                                }
                            ]
                        }
                    }
                }
            ],
            "Enrich": [
                "FromLogContext"
            ]
        }

    In Startup.cs Configure method I have this line:

                app.UseMiddleware<CorrelationIdEnricher>();
    

    When I compile the application the table gets created with the correct columns removed and the correct ones added. There is a ControllerNameEnricher class which adds the controller name to the log context for each request and that works perfectly. When I do a request to any of my end points I have a breakpoint in the CorrelationIdEnricher code and I can see it gets called. Each time I add something to the logs it gets called. It is adding the GUID to the header as I can see it, but for some reason it is not making it to the log table. Any ideas?

    Thanks

    Thursday, May 30, 2019 12:45 PM

Answers

  • User-854763662 posted

    Hi AnyUserNameThatLetsMeIn ,

    According to the code you provided , I reproduced the issue . It seems that the new columnName "CorrelationId" conficts with the naming of a property in Serilog.Core. There is a workaround that you could try to change "CorrelationId" to another name .

    And you could have a look at the following link which is similar to the issue:

    https://stackoverflow.com/a/54760425/10201850  

    Best Regards ,

    Sherry

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 31, 2019 6:48 AM

All replies

  • User-854763662 posted

    Hi AnyUserNameThatLetsMeIn ,

    According to the code you provided , I reproduced the issue . It seems that the new columnName "CorrelationId" conficts with the naming of a property in Serilog.Core. There is a workaround that you could try to change "CorrelationId" to another name .

    And you could have a look at the following link which is similar to the issue:

    https://stackoverflow.com/a/54760425/10201850  

    Best Regards ,

    Sherry

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 31, 2019 6:48 AM
  • User-1458727574 posted

    You are a legend :)

    Many thanks for all your help on this. You have helped me far more than any of the websites I've found and your information has always been spot on. I changed the name and it immediately worked exactly as I had intended. I changed the middleware classes so that there is now only a single class instead of two and combined them so that things get added into the pipeline context in a single step now rather than two. If I need to add any additional data in now, I will easily be able to extend this.

    Once again, many thanks!

    Lee

    Friday, May 31, 2019 8:52 AM