Search code examples
c#asp.netserilog

How to format serilog property names?


I'm using Serilog and Serilog.Formatting.Compact to write logs into a file. How can I dynamically format property names?

I want each property name to include its type.

For example: bool_property_name, string_property_name, date_property_name....

logger.Information("this is a message with {property_name}", "value");

or

logger
    .ForContext("property_name", "value")
    .Information("this is a message");

should be in the log:

{ ...., string_property_name: "value" }

Solution

  • So this is the solution i found.

    Some important points about this solution to notice:

    • It doesn't format the message template.
    • It formats every property - including properties added by third party enrichers for example.
    • Its still not in production. I will update if any bugs are found.
    public class LogEventPropertiesNameFormatter
    {
        public void Format(LogEvent logEvent)
        {
            var keys = new List<string>(logEvent.Properties.Keys);
    
            foreach (var key in keys)
            {
                logEvent.AddPropertyIfAbsent(Visit(key, logEvent.Properties[key]));
                logEvent.RemovePropertyIfPresent(key);
            }
        }
    
        private LogEventProperty Visit(string name, LogEventPropertyValue value)
        {
            switch (value)
            {
            case null:
                throw new ArgumentNullException(nameof(value));
            case ScalarValue scalar:
                return VisitScalar(name, scalar);
            case SequenceValue sequence:
                return VisitSequence(name, sequence);
            case StructureValue scalar:
                return VisitStructure(name, scalar);
            case DictionaryValue dictionary:
                return VisitDictionary(name, dictionary);
            default:
                return new LogEventProperty(name, value);
        }
    
        private LogEventProperty VisitDictionary(string name, DictionaryValue dictionary)
        {
            var formattedElements = new Dictionary<ScalarValue, LogEventPropertyValue>(dictionary.Elements.Count);
    
            foreach (var element in dictionary.Elements)
            {
                var property = Visit(element.Key.Value.ToString(), element.Value);
                formattedElements.Add(new ScalarValue(property.Name), property.Value);
            }
    
            return LogEventProperty($"dict_{name}", new DictionaryValue(formattedElements));
        }
    
        private LogEventProperty VisitStructure(string name, StructureValue structure)
        {
            var properties = structure.Properties.Select(p => Visit(p.Name, p.Value));
            return new LogEventProperty($"struct_{name}", new StructureValue(properties));
        }
    
        private LogEventProperty VisitSequence(string name, SequenceValue sequence)
        {
            var elements = sequence.Elements.Select(e => Visit(null, e).Value);
            return new LogEventProperty($"seq_{name}", new SequenceValue(elements));
        }
    
        private LogEventProperty VisitScalar(string name, ScalarValue scalar)
        {
            return new LogEventProperty($"{GetScalarValueType(scalar)}_{name}", scalar);
        }
    
        private string GetScalarValueType(ScalarValue value)
        {
            switch (value.Value)
            {
                case null:
                    return "null";
                case string _:
                case TimeSpan _:
                    return "s";
                case int _:
                case uint _:
                case long _:
                case ulong _:
                case decimal _:
                case byte _:
                case sbyte _:
                case short _:
                case ushort _:
                case double _:
                case float _:
                    return "n";
                case bool _:
                    return "b";
                case DateTime _:
                case DateTimeOffset _:
                    return "d";
                default:
                    return "o";
            }
        }
    }
    
    public class CompactJsonFormatterTypeOverride : ITextFormatter
    {
        private readonly ITextFormatter _textFormatter;
        private readonly LogEventPropertiesNameFormatter _logEventPropertiesNameFormatter;
    
        public CompactJsonFormatterTypeOverride()
        {
            _textFormatter = new CompactJsonFormatter();
            _logEventPropertiesNameFormatter = new LogEventPropertiesNameFormatter();
        }
    
        public void Format(LogEvent logEvent, TextWriter output)
        {
            _logEventPropertiesNameFormatter.Format(logEvent);
            _textFormatter.Format(logEvent, output);
        }
    }
    

    Then in the logger configuration:

    new LoggerConfiguration().
        WriteTo.File(new CompactJsonFormatterTypeOverride(), "log.log")
        .CreateLogger();