Skip to content

Serilog

Tayra integrates with Serilog to automatically redact [PersonalData]-annotated properties when objects are logged via Serilog's {@Object} destructuring syntax. This prevents PII from leaking into log files, consoles, or centralized logging platforms.

Prerequisites

Tayra.Serilog requires Serilog 4.x or later.

Install

shell
dotnet add package Tayra.Serilog
powershell
Install-Package Tayra.Serilog

Setup

Standalone (no DI)

csharp
var metadataCache = new PersonalDataMetadataCache();

Log.Logger = new LoggerConfiguration()
    .Destructure.WithTayra(metadataCache)
    .WriteTo.Console()
    .CreateLogger();

With Dependency Injection

csharp
services.AddTayra(opts => opts.LicenseKey = licenseKey); // defaults to InMemoryKeyStore; use PostgreSQL, Vault, etc. in production

services.AddTayraSerilog(opts =>
{
    opts.UsePartialRedaction = true;
});

How It Works

When Serilog encounters {@Object} in a log message template, it calls Tayra's TayraDestructuringPolicy. The policy inspects the object's type metadata and redacts any properties annotated with Tayra PII attributes:

csharp
public class User
{
    [DataSubjectId]
    public string UserId { get; set; }

    [PersonalData]
    public string Name { get; set; }

    [PersonalData]
    public string Email { get; set; }

    public string Role { get; set; } // Not PII
}

var user = new User
{
    UserId = "u1", Name = "Alice",
    Email = "alice@test.com", Role = "Admin"
};
Log.Information("User: {@User}", user);

Without Tayra:

User: {"UserId": "u1", "Name": "Alice", "Email": "alice@test.com", "Role": "Admin"}

With Tayra:

User: {"UserId": "u1", "Name": "[REDACTED]", "Email": "[REDACTED]", "Role": "Admin"}

Objects without any PII attributes are passed through to Serilog's default destructuring unchanged — zero overhead for non-PII types.

Options

csharp
services.AddTayraSerilog(opts =>
{
    opts.RedactedPlaceholder = "[REDACTED]";  // default
    opts.UsePartialRedaction = true;
    opts.IncludeFieldKindHint = true;
    opts.RedactDataSubjectId = false;         // default
});
PropertyTypeDefaultDescription
RedactedPlaceholderstring"[REDACTED]"The placeholder string for redacted values
UsePartialRedactionboolfalseUse the field's masking strategy to produce partially-masked values (e.g. "Al***")
IncludeFieldKindHintboolfalseAppend the field name to the placeholder (e.g. "[REDACTED:Email]")
RedactDataSubjectIdboolfalseAlso redact properties marked with [DataSubjectId]

Supported Field Kinds

The policy handles all Tayra field kinds:

AttributeBehavior
[PersonalData]Replaced with placeholder (or partial redaction / custom MaskValue)
[PersonalData] on List<string>Each element redacted, collection length preserved
[DeepPersonalData]Recursively destructured with PII scrubbing
[DeepPersonalData] on collectionsEach element recursively destructured
[SerializedPersonalData]Replaced with placeholder
[DataSubjectId]Passed through by default; redacted when RedactDataSubjectId = true

Partial Redaction

When UsePartialRedaction is enabled, fields with a redaction strategy produce masked values instead of the full placeholder:

csharp
public class Customer
{
    [PersonalData(Masking = MaskingStrategies.MaskAfter, MaskingParameter = 2)]
    public string Name { get; set; }

    [PersonalData(Masking = MaskingStrategies.MaskEmailDomain)]
    public string Email { get; set; }
}

// Logs: {"Name": "Al***", "Email": "alice@****.***"}

Custom MaskValue

Fields with an explicit MaskValue always use that mask value, regardless of other options:

csharp
[PersonalData(MaskValue = "***NAME***")]
public string Name { get; set; }

// Logs: {"Name": "***NAME***"}

Safety

  • Depth limit: Recursive [DeepPersonalData] traversal is capped at 10 levels to prevent stack overflows
  • Property getter errors: If a property getter throws, the field is logged as null rather than crashing the pipeline
  • Null values: Null PII properties are logged as null, not the placeholder string

See Also