Search code examples
.netserilog

Destructure.ByTransforming<System.Type> does not appear to take effect


I am attempting to destructure System.Type by using just the type name, sans namespace, but am getting an unexpected result.

Setup

The first transformation works as expected, producing the short string (the first 8 characters of a Guid). However, the second is never called:

.Destructure.ByTransforming<Id>(id => id.ToShortString())
.Destructure.ByTransforming<Type>(type => UseUnqualifiedName(type) ? type.Name : type.ToString())

Usage

_logger.LogInfo("Instance loaded: {@Type}.{@Id}", typeof(MyProject.MyNamespace.MyType), id);

Expected

[INF] Instance: MyType.64b8ac0d

Actual

[INF] Instance: MyProject.MyNamespace.MyType.64b8ac0d

In addition, the fully-qualified name is green in the console output. I noticed this is also the case with System.Uri, and wonder if Serilog treats these types specially.

What can I do to affect the destructuring of System.Type?


Solution

  • There are two problems preventing the behavior you expect.

    First, Destructure.ByTransforming<T>() is invariant for T, but at runtime you'll find that the "real" System.Type objects that you encounter are subclasses like RuntimeType.

    A custom IDestructuringPolicy that applies to System.Type and its subclasses can work around this.

    But, second, unfortunately - Serilog already has one of these that's plugged in by default:

    https://github.com/serilog/serilog/blob/dev/src/Serilog/Policies/ReflectionTypesScalarDestructuringPolicy.cs

    Because System.Type can produce such enormous object graphs, and because of various footguns like the RuntimeType gotcha mentioned above, Serilog very early on disabled structured capturing of System.Type and various other reflection values.

    To get around this, you need an ILogEventEnricher that detects property value matching ScalarValue { Value: System.Type } and replaces them with a modified ScalarValue.

    // `Enrich.With(new RecaptureTypeEnricher())`
    class RecaptureTypeEnricher : ILogEventEnricher
    {
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            List<LogEventProperty> updates = null;
            foreach (var property in logEvent.Properties)
            {
                if (property.Value is ScalarValue { Value: Type type })
                {
                    updates ??= new List<LogEventProperty>();
                    updates.Add(new LogEventProperty(property.Key, new ScalarValue(UseUnqualifiedName(type))));
                }
            }
    
            if (updates != null)
            {
                foreach (var update in updates)
                {
                    logEvent.AddOrUpdateProperty(update);
                }
            }
        }
    }
    

    If you're just concerned about formatting, Serilog.Expressions might also offer up some options.