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
MaskCharacter
that 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 theMaskLength
property 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
$type
tag should be excluded from the destructured output. The default isfalse
.
var cfg = new LoggerConfiguration()
.Destructure.WithFluentDestructuringPolicy<ApplicationFluentDestructuringPolicy>(e =>
{
e.IgnoreNullProperties = true;
e.ExcludeTypeTag = true;
})
...