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
- Mark all PII as sensitive - Email, phone, SSN, addresses
- Mark financial data - Credit cards, bank accounts, salaries
- Mark authentication data - Passwords, tokens, API keys
- Use structured logging - Enables better log analysis while maintaining redaction
- Test redaction - Verify sensitive data doesn't appear in logs during development
- Use NoOpLogger in production - When logging is not needed, use
NoOpLogger.Instancefor zero overhead