Skip to main content

Logging with Redaction

Use Microsoft.Extensions.Logging with automatic sensitive field redaction.

Overview​

When logging DynamoDB operations, you may need to redact sensitive data like PII, financial information, or authentication tokens. FluentDynamoDB integrates with Microsoft.Extensions.Logging and supports automatic redaction of marked fields.

Configuration​

Basic Logging Setup​

Configure logging at runtime using FluentDynamoDbOptions.WithLogger() and the ToDynamoDbLogger() extension method:

using Microsoft.Extensions.Logging;
using Oproto.FluentDynamoDb;
using Oproto.FluentDynamoDb.Logging.Extensions;

var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug);
});

var options = new FluentDynamoDbOptions()
.WithLogger(loggerFactory.ToDynamoDbLogger<UserTable>());

var table = new UserTable(dynamoDbClient, "users", options);

Dependency Injection​

services.AddSingleton(sp =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var options = new FluentDynamoDbOptions()
.WithLogger(loggerFactory.ToDynamoDbLogger<UserTable>());

return new UserTable(dynamoDbClient, "users", options);
});

Marking Sensitive Fields​

Use the [Sensitive] attribute to mark fields that should be redacted in logs:

[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string Pk { get; set; } = string.Empty;

[SortKey]
[DynamoDbAttribute("sk")]
public string Sk { get; set; } = string.Empty;

[DynamoDbAttribute("userId")]
public string UserId { get; set; } = string.Empty;

[DynamoDbAttribute("name")]
public string Name { get; set; } = string.Empty;

// Sensitive fields - will be redacted in logs
[Sensitive]
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;

[Sensitive]
[DynamoDbAttribute("ssn")]
public string SocialSecurityNumber { get; set; } = string.Empty;

[Sensitive]
[DynamoDbAttribute("creditCard")]
public string CreditCardNumber { get; set; } = string.Empty;

[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
}

Redaction Patterns​

Default Redaction​

By default, sensitive fields are replaced with [REDACTED]:

// When logging this operation:
await table.Users.PutAsync(new User
{
UserId = "user123",
Name = "John Doe",
Email = "[email protected]", // Will be logged as [REDACTED]
SocialSecurityNumber = "123-45-6789" // Will be logged as [REDACTED]
});

// Log output:
// PUT User: { UserId: "user123", Name: "John Doe", Email: "[REDACTED]", SocialSecurityNumber: "[REDACTED]" }

Custom Redaction​

Configure custom redaction patterns:

var options = new FluentDynamoDbOptions()
.WithLogging(loggerFactory, new RedactionOptions
{
RedactionText = "***",
PartialRedaction = true,
PartialRedactionVisibleChars = 4
});

With partial redaction enabled:

Email: "john***" (first 4 chars visible)
CreditCard: "****5678" (last 4 chars visible)

Usage Examples​

Query with Logging​

// Queries are logged with redacted filter values
var users = await table.Users.Query()
.Where(x => x.Status == "active")
.ToListAsync();

// Log output:
// QUERY Users WHERE Status = "active" - Returned 5 items

Update with Logging​

await table.Users.Update("user123")
.Set(x => new { Email = "[email protected]" })
.UpdateAsync();

// Log output:
// UPDATE User "user123" SET Email = "[REDACTED]"

Conditional Operations​

await table.Users.Put(user)
.Where(x => x.Email.AttributeNotExists())
.PutAsync();

// Log output:
// PUT User WHERE Email attribute_not_exists - Success

Get Operations​

var user = await table.Users.GetAsync("user123");

// Log output:
// GET User "user123" - Found
// Response: { UserId: "user123", Name: "John Doe", Email: "[REDACTED]" }

Delete Operations​

await table.Users.DeleteAsync("user123");

// Log output:
// DELETE User "user123" - Success

Compliance​

Automatic redaction helps meet compliance requirements:

GDPR​

  • Personal data is automatically redacted in logs
  • Supports data minimization principles
  • Audit trails don't expose PII

HIPAA​

  • Protected Health Information (PHI) can be marked sensitive
  • Logs remain compliant with disclosure rules

PCI-DSS​

  • Credit card numbers are redacted
  • Cardholder data protected in logs
  • Supports audit requirements without exposing sensitive data

Disabling Logging (Zero Overhead)​

For production environments where logging is not needed, use NoOpLogger.Instance for near-zero overhead:

using Oproto.FluentDynamoDb.Logging;

// Default behavior - no logging, uses NoOpLogger.Instance internally
var table = new UserTable(dynamoDbClient, "users");

// Explicit NoOpLogger (equivalent to default)
var options = new FluentDynamoDbOptions()
.WithLogger(NoOpLogger.Instance);

var table = new UserTable(dynamoDbClient, "users", options);

The NoOpLogger.IsEnabled() method always returns false, causing all logging calls to be skipped with minimal overhead. This is the recommended approach when logging is not required.

Best Practices​

  1. Mark all PII as sensitive - Email, phone, SSN, addresses
  2. Mark financial data - Credit cards, bank accounts, salaries
  3. Mark authentication data - Passwords, tokens, API keys
  4. Use structured logging - Enables better log analysis while maintaining redaction
  5. Test redaction - Verify sensitive data doesn't appear in logs during development
  6. Use NoOpLogger in production - When logging is not needed, use NoOpLogger.Instance for zero overhead