destructurama / attributed

Use attributes to control how complex types are logged to Serilog.
Apache License 2.0
263 stars 32 forks source link

"CreatePropertyValue" returns null when attribute inherits from ITypeDestructuringAttribute #111

Closed aholeton closed 6 months ago

aholeton commented 6 months ago

I was attempting to create a custom attribute that needs to inspect multiple properties on an object to determine whether or not a specific property should be masked. When using propertyValueFactory.CreatePropertyValue(value, true); it looks like it is returning null as opposed to an object.

If this is not the right way to go about this, then any help would be appreciated on what I should be doing instead.

Describe the bug When creating a custom attribute inheriting from ITypeDestructuringAttribute, the method propertyValueFactory.CreatePropertyValue(value, true), always returns null.

Expected behavior Something other than null.

Screenshots and any additional context

[AttributeUsage(AttributeTargets.Class)]
public class LogSensitiveDataAttribute : Attribute, ITypeDestructuringAttribute
{
    public LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory)
    {
        // this always returns null
        var result = propertyValueFactory.CreatePropertyValue(value, true);

        // other code
    }
}
sungam3r commented 6 months ago

You get into recursive trap with aforementioned code. Look:

изображение

Eventually you end in that method in Serilog core project: изображение

You may enable SelfLog to verify that.

Code to reproduce:

using Destructurama.Attributed.Tests.Support;
using NUnit.Framework;
using Serilog.Core;
using Serilog.Events;
using Shouldly;

namespace Destructurama.Attributed.Tests;

// https://github.com/destructurama/attributed/issues/111
[TestFixture]
public class Issue111
{
    [Test]
   public void Issue_111()
    {
        var customized = new MyClass();
        var evt = DelegatingSink.Execute(customized);
        evt.Properties["Customized"].ShouldBe(ScalarValue.Null);
    }

    [LogSensitiveData]
    public class MyClass
    {
        public string Foo { get; set; } = "Foo123";

        public string Bar { get; set; } = "Bar123";
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class LogSensitiveDataAttribute : Attribute, ITypeDestructuringAttribute
    {
        public LogEventPropertyValue CreateLogEventPropertyValue(object? value, ILogEventPropertyValueFactory propertyValueFactory)
        {
            // this always returns null
            var result = propertyValueFactory.CreatePropertyValue(value, true);

            return result;
        }
    }
}
aholeton commented 6 months ago

Thanks for taking the time to look at this. I need to inspect multiple properties to determine if a specific property should be masked. Given your example, is it possible to inspect property Foo to determine whether property Bar should be masked?

sungam3r commented 6 months ago

You can do this via reflection or by any other mean but not by calling CreatePropertyValue passing the value itself which in turn calls into LogSensitiveDataAttribute again.