Introduction
The best logging package for logging details for C# is Serilog. Learn how to conditionally log specific properties of interest using Serilog IDestructuringPolicy.
Old technique
The former technique was to use [NotLogged] attribute Serilog.Extras.Attributed package on properties to ignore a property.
When possible, avoid adding attributes to a class, as this ties a class to a specific package and pollutes the class unnecessarily.
public interface ICustomer
{
int Id { get; set; }
string WorkTitle { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
DateOnly DateOfBirth { get; set; }
string OfficeEmail { get; set; }
string OfficePhoneNumber { get; set; }
}
public class Customer : ICustomer
{
public int Id { get; set; }
[NotLogged]
public string WorkTitle { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotLogged]
public DateOnly DateOfBirth { get; set; }
[NotLogged]
public string OfficeEmail { get; set; }
[NotLogged]
public string OfficePhoneNumber { get; set; }
}
Note
Serilog.Extras.Attributed NuGet package is no longer maintained, do not use this package.
Preferred technique
The current technique is to implement IDestructuringPolicy. For example, given the following class only properties Id, FirstName and LastName should be logged.
public interface ICustomer
{
int Id { get; set; }
string WorkTitle { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
DateOnly DateOfBirth { get; set; }
string OfficeEmail { get; set; }
string OfficePhoneNumber { get; set; }
}
public class Customer : ICustomer
{
public int Id { get; set; }
public string WorkTitle { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateOnly DateOfBirth { get; set; }
public string OfficeEmail { get; set; }
public string OfficePhoneNumber { get; set; }
}
The class
First checks if the current object implements ICustomer, if so use propertyValueFactory.CreatePropertyValue to define which properties to log.
public class IdentifierFirstLastNamesWithPolicy : IDestructuringPolicy
{
public bool TryDestructure(
object value,
ILogEventPropertyValueFactory propertyValueFactory,
out LogEventPropertyValue result)
{
if (value is ICustomer c)
{
result = propertyValueFactory.CreatePropertyValue(new
{
c.Id,
c.FirstName,
c.LastName
});
return true;
}
result = null;
return false;
}
}
Another example for ICustomer, only log FirstName, LastName and OfficePhoneNumber.
public class FirstLastNamesWithPhonePolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
if (value is ICustomer c)
{
result = propertyValueFactory.CreatePropertyValue(new { c.FirstName, c.LastName, c.OfficePhoneNumber });
return true;
}
result = null;
return false;
}
}
Sample
In this sample output goes to the console and works the same for writing to a database or file.
internal partial class Program
{
static void Main(string[] args)
{
AnsiConsole.MarkupLine("[hotpink]FirstName, LastName and office phone[/]");
Log.Logger = new LoggerConfiguration()
.Destructure.With(new FirstLastNamesWithPhonePolicy())
.WriteTo.Console()
.CreateLogger();
foreach (var customer in MockedData.Customers())
{
Log.Information("Customers {@C}", customer);
}
Console.WriteLine();
AnsiConsole.MarkupLine("[hotpink]Id,FirstName, LastName[/]");
Log.Logger = new LoggerConfiguration()
.Destructure.With(new IdentifierFirstLastNamesWithPolicy())
.WriteTo.Console()
.CreateLogger();
foreach (var customer in MockedData.Customers())
{
Log.Information("Customers {@C}", customer);
}
AnsiConsole.MarkupLine("[yellow]Done[/]");
Console.ReadLine();
}
}
Working with EF Core sample
In this example the model, Person includes masking a credit card property along with using IDataProtector for an ASP.NET Core project.
public partial class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateOnly BirthDate { get; set; }
public string CreditCard { get; set; }
[NotMapped]
public string MaskedCreditCard
{
get
{
if (string.IsNullOrWhiteSpace(CreditCard))
return "XXXX-XXXX-XXXX-XXXX";
var digitsOnly = new string(CreditCard.Where(char.IsDigit).ToArray());
if (digitsOnly.Length < 4)
return "XXXX-XXXX-XXXX-XXXX";
return $"XXXX-XXXX-XXXX-{digitsOnly[^4..]}";
}
}
public void EncryptCreditCard(EncryptionService encryptionService)
{
if (!string.IsNullOrEmpty(CreditCard))
{
CreditCard = encryptionService.Encrypt(CreditCard);
}
}
public void DecryptCreditCard(EncryptionService encryptionService)
{
if (!string.IsNullOrEmpty(CreditCard))
{
CreditCard = encryptionService.Decrypt(CreditCard);
}
}
}
The IDestructuringPolicy follows the same way done in the example above.
public class PersonDestructuringPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
if (value is Person p)
{
result = propertyValueFactory.CreatePropertyValue(
new
{
p.FirstName,
p.LastName,
p.MaskedCreditCard
});
return true;
}
result = null!;
return false;
}
}
Setup in Program.cs
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning)
.MinimumLevel.Override("System", Serilog.Events.LogEventLevel.Warning)
.MinimumLevel.Information()
.Destructure.With(new PersonDestructuringPolicy())
.WriteTo.Console()
.CreateLogger();
builder.Host.UseSerilog();
builder.Services.AddDbContext<Context>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
.LogTo(new DbContextToFileLogger().Log, [DbLoggerCategory.Database.Command.Name], LogLevel.Information));
builder.Services.AddDataProtection();
builder.Services.AddScoped<EncryptionService>();
builder.Services.AddScoped<PersonService>();
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorPages()
.WithStaticAssets();
app.Run();
}
}
Summary
Using the Serilog interface, IDestructuringPolicy allows you to focus on logging specific properties rather than all a class's properties, which can assist, for instance, with diagnosing issues.
Top comments (0)