This package makes it possible to manipulate how complex objects are logged to Serilog using Fluent API.
The Destructurama.Attributed package provides convenient ways to configure Serilog complex object logging by using attributes. With these, you can easily ignore some properties, apply masking and so on. But this attribute-based approach does introduce a dependency on Serilog in projects where such a dependency may be undesirable (a similar issue exists with Entity Framework Core and its attribute-based model configuring approach). This package emerged out of the need to eliminate this dependency and provide another way for the developers to configure complex objects logging using a Fluent API.
NuGet\Install-Package Serilog.FluentDestructuring -Version *version_number*dotnet add package Serilog.FluentDestructuring --version *version_number*Define your custom policy and override configure method to specify what destructuring rules to use.
public class ApplicationFluentDestructuringPolicy : FluentDestructuringPolicy
{
protected override void Configure(FluentDestructuringBuilder builder)
{
// Your configurations.
}
}Modify logger configuration.
var cfg = new LoggerConfiguration()
.Destructure.WithFluentDestructuringPolicy<ApplicationFluentDestructuringPolicy>()
...Add configuration for specific entity type right here.
public class ApplicationFluentDestructuringPolicy : FluentDestructuringPolicy
{
protected override void Configure(FluentDestructuringBuilder builder)
{
builder.Entity<UserRegisterRequest>(e =>
{
e.Property(p => p.Email)
.Mask();
e.Property(p => p.Password)
.Ignore();
});
}
}Apply predefined configuration for a specific entity.
public class UserRegisterRequestDestructuringConfiguration : IEntityDestructuringConfiguration<UserRegisterRequest>
{
public void Configure(EntityDestructuringBuilder<UserRegisterRequest> builder)
{
builder.Property(p => p.Email)
.Mask();
builder.Property(p => p.Password)
.Ignore();
}
}
public class ApplicationFluentDestructuringPolicy : FluentDestructuringPolicy
{
protected override void Configure(FluentDestructuringBuilder builder)
{
builder.ApplyConfiguration(new UserRegisterRequestDestructuringConfiguration());
}
}Apply all entity destructuring configurations found in a specified assembly.
public class ApplicationFluentDestructuringPolicy : FluentDestructuringPolicy
{
protected override void Configure(FluentDestructuringBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
}Apply by calling WithAlias method.
builder.Entity<UserRegisterRequest>(e =>
{
e.Property(p => p.Email)
.WithAlias("user_email");
});You can also use custom property name along with a main destructuring rule.
builder.Entity<UserRegisterRequest>(e =>
{
e.Property(p => p.Email)
.Mask()
.WithAlias("user_email");
});Apply by calling Ignore method.
builder.Entity<UserRegisterRequest>(e =>
{
e.Property(p => p.Password)
.Ignore();
});Apply by calling AsScalar method.
// Whole entity.
builder.Entity<UserRegisterRequest>(e => e.AsScalar());
builder.Entity<UserRegisterRequest>(e =>
{
// Individual property.
e.Property(p => p.Passport)
.AsScalar();
});Apply by calling Mask method.
Note that masking works for properties of type string, IEnumerable<string> or derived from it, for example, string[] or List<string>.
- MaskCharacter: The character used for masking. The default is an asterisk
*. - MaskLength: The length of the mask to be applied. This is the number of
MaskCharacterthat will be used to obfuscate the value. The default is10. - PreserveValueLength: Value indicating whether the length of the original value should be preserved when applying the mask. If
true, the masked value will have the same length as the original value, and theMaskLengthproperty will be ignored. The default isfalse.
builder.Entity<UserRegisterRequest>(e =>
{
// Default masking processor with default options.
e.Property(p => p.Email)
.Mask();
// Customize default masking processor behaviour by specify options.
e.Property(e => e.Password)
.Mask(new DefaultMaskingProcessorOptions { PreserveValueLength = true, MaskCharacter = '#' })
});You can use custom masking processor by implementing the IMaskingProcessor interface and passing an instance to one of the overloads of Mask method.
public class PasswordMaskingProcessor : IMaskingProcessor
{
public bool TryMask(string value, out string? maskedValue)
{
// Your implementation.
}
}builder.Entity<UserRegisterRequest>(e =>
{
e.Property(e => e.Password)
.Mask(new PasswordMaskingProcessor());
});You can define the condition under which the destructuring rule will be applied.
builder.Entity<UserRegisterRequest>(e =>
{
// One of predefined conditions.
e.Property(p => p.Email)
.Ignore()
.ApplyWhenNull();
e.Property(e => e.Passport)
.AsScalar()
.WithAlias("user_passport")
.ApplyWhenNotNull();
// Define your custom condition.
e.Property(p => p.Password)
.Mask()
.ApplyWhen(e => !string.IsNullOrWhiteSpace(e.Email) && e.Email.EndsWith("@gmail.com"));
});Only single-level properties are supported.
builder.Entity<UserRegisterRequest>(e =>
{
// Will throw an exception.
e.Property(e => e.Passport.Series)
.Mask();
});Configure inner entities by calling InnerEntity method.
builder.Entity<UserRegisterRequest>(e =>
{
e.InnerEntity(o => o.Passport, x =>
{
x.Property(a => a.Series)
.Mask();
x.Property(a => a.Number)
.Ignore()
.ApplyWhenNull();
})
.WithAlias("user_passport")
.ApplyWhen(e => !string.IsNullOrWhiteSpace(e.Email));
});Or apply predefined configuration.
public class UserPassportRequestDestructuringConfiguration : IEntityDestructuringConfiguration<UserPassportRequest>
{
public void Configure(EntityDestructuringBuilder<UserPassportRequest> builder)
{
builder.Property(a => a.Series)
.Mask();
builder.Property(a => a.Number)
.Ignore()
.ApplyWhenNull();
}
}
builder.Entity<UserRegisterRequest>(e =>
{
e.InnerEntity(o => o.Passport, new UserPassportRequestDestructuringConfiguration())
.WithAlias("user_passport")
.ApplyWhen(e => !string.IsNullOrWhiteSpace(e.Email));
});- IgnoreNullProperties - Indicating whether properties with null values should be ignored during destructuring. The default is
false. - ExcludeTypeTag - Indicating whether the
$typetag should be excluded from the destructured output. The default isfalse.
var cfg = new LoggerConfiguration()
.Destructure.WithFluentDestructuringPolicy<ApplicationFluentDestructuringPolicy>(e =>
{
e.IgnoreNullProperties = true;
e.ExcludeTypeTag = true;
})
...