Audit Trail
Tayra provides a built-in audit trail system for GDPR Article 30 compliance. Every encryption, decryption, key creation, key deletion, and crypto-shredding operation is logged as a structured audit event. This gives you a complete record of all processing activities involving personal data encryption keys.
GDPR Article 30 — Records of Processing Activities
Article 30 of the GDPR requires organizations to maintain records of processing activities that involve personal data. Tayra's audit trail satisfies this requirement by recording:
- What happened (event type)
- When it happened (timestamp)
- Which key was involved (key ID)
- Whose data was affected (subject ID)
- What entity type was processed (entity type)
- How many fields were involved (field count)
Setup
The DefaultTayraAuditLogger is registered automatically when you call AddTayra(). It writes structured log entries via ILogger and emits ActivityEvent entries on the current OpenTelemetry activity:
var auditServices = new ServiceCollection();
auditServices.AddLogging();
auditServices.AddTayra(opts => opts.LicenseKey = licenseKey);
// The DefaultTayraAuditLogger is registered automatically by AddTayra().
// It logs structured audit events via ILogger and emits ActivityEvents.
// To replace it with a custom implementation:
auditServices.AddSingleton<ITayraAuditLogger, ConsoleAuditLogger>();To disable audit logging, register the NullTayraAuditLogger:
services.AddSingleton<ITayraAuditLogger>(
Tayra.Audit.NullTayraAuditLogger.Instance);Custom Audit Logger
Implement the ITayraAuditLogger interface to send audit events to your compliance system:
/// <summary>
/// Custom audit logger that writes events to the console.
/// In production, you might write to a database, message queue,
/// or external audit system (e.g., Splunk, Datadog, Azure Monitor).
/// </summary>
public class ConsoleAuditLogger : ITayraAuditLogger
{
public void LogEvent(TayraAuditEvent auditEvent)
{
Console.WriteLine(
$" [AUDIT] {auditEvent.Timestamp:O} {auditEvent.EventType} " +
$"KeyId={auditEvent.KeyId} SubjectId={auditEvent.SubjectId} " +
$"EntityType={auditEvent.EntityType} FieldCount={auditEvent.FieldCount}");
}
}The ITayraAuditLogger interface has a single synchronous method:
public interface ITayraAuditLogger
{
void LogEvent(TayraAuditEvent auditEvent);
}Synchronous by Design
The LogEvent method is synchronous to avoid adding async overhead to every encryption operation. Implementations should be fast and non-blocking. If you need to write to a database or external service, buffer events in memory and flush asynchronously (e.g., using a Channel<T> or background service).
Audit Event Model
The TayraAuditEvent record contains all the context for a single audit entry:
// TayraAuditEvent is a record with the following properties:
var exampleEvent = new TayraAuditEvent
{
EventType = TayraAuditEventType.DataEncrypted,
Timestamp = DateTimeOffset.UtcNow, // Auto-set to UtcNow
KeyId = "patient-abc123", // The encryption key involved
SubjectId = "abc123", // The data subject identifier
EntityType = "PatientRecord", // The .NET type being processed
FieldCount = 3, // Number of PII fields encrypted
Details = "Encrypted during save", // Free-text details
};
Console.WriteLine($" EventType: {exampleEvent.EventType}");
Console.WriteLine($" Timestamp: {exampleEvent.Timestamp}");
Console.WriteLine($" KeyId: {exampleEvent.KeyId}");
Console.WriteLine($" SubjectId: {exampleEvent.SubjectId}");
Console.WriteLine($" EntityType: {exampleEvent.EntityType}");
Console.WriteLine($" FieldCount: {exampleEvent.FieldCount}");
Console.WriteLine($" Details: {exampleEvent.Details}");Properties
| Property | Type | Description |
|---|---|---|
EventType | TayraAuditEventType | The type of operation that occurred (required) |
Timestamp | DateTimeOffset | When the event occurred (defaults to UtcNow) |
KeyId | string? | The encryption key ID involved, if applicable |
SubjectId | string? | The data subject identifier, if applicable |
EntityType | string? | The .NET type name of the entity being processed |
FieldCount | int? | The number of PII fields encrypted or decrypted |
Details | string? | Free-text additional details |
Event Types
Tayra emits 14 distinct audit event types covering the full lifecycle of data protection operations:
// All audit event types emitted by Tayra:
var allEventTypes = Enum.GetValues<TayraAuditEventType>();
foreach (var eventType in allEventTypes)
{
Console.WriteLine($" {eventType}");
}
// Output:
// KeyCreated
// KeyAccessed
// KeyDeleted
// BulkKeysDeleted
// DataEncrypted
// DataDecrypted
// CryptoShreddingDetected
// KeyExpired
// DataSubjectAccessExported
// DataSubjectPortableExported
// BreachAssessed
// BreachNotificationGenerated
// DataMigrationCompleted
// DataMigrationVerifiedComplete Event Type Reference
| Event Type | Emitted By | Description |
|---|---|---|
KeyCreated | DefaultCryptoEngine.GetOrCreateKeyAsync, RotateKeyAsync | A new encryption key was generated and stored |
KeyAccessed | DefaultCryptoEngine.GetOrCreateKeyAsync, GetKeyAsync | An encryption key was retrieved (from cache or store) |
KeyDeleted | DefaultCryptoEngine.DeleteKeyAsync | A single encryption key was deleted (crypto-shredding) |
BulkKeysDeleted | DefaultCryptoEngine.DeleteAllKeysAsync | All keys matching a prefix were deleted |
DataEncrypted | DefaultFieldEncrypter.EncryptAsync | PII fields on an entity were encrypted |
DataDecrypted | DefaultFieldEncrypter.DecryptAsync | PII fields on an entity were decrypted |
CryptoShreddingDetected | DefaultFieldEncrypter (during decrypt) | Decryption was attempted but the key was already shredded |
KeyExpired | KeyRetentionBackgroundService | A key was deleted by the retention policy |
DataSubjectAccessExported | DefaultDataSubjectAccessService | A GDPR Art. 15 data subject access report was generated |
DataSubjectPortableExported | DefaultDataSubjectAccessService | A GDPR Art. 20 data portability export was generated |
BreachAssessed | DefaultBreachNotificationService | A data breach impact assessment was performed |
BreachNotificationGenerated | DefaultBreachNotificationService | A breach notification report was generated |
DataMigrationCompleted | Data migration service | A data migration batch was completed |
DataMigrationVerified | Data migration service | A data migration was verified |
Default Logger Behavior
The DefaultTayraAuditLogger performs two actions for each event:
1. Structured Logging via ILogger
Events are logged at Information level with structured properties:
Tayra audit: DataEncrypted KeyId=patient-abc123 SubjectId=abc123 EntityType=PatientRecord FieldCount=3 Details=nullThis integrates with any ILogger provider (Console, Serilog, Application Insights, etc.) and supports structured log queries.
2. OpenTelemetry ActivityEvents
If there is a current Activity (distributed trace), the logger adds an ActivityEvent named "tayra.audit" with tags:
| Tag | Description |
|---|---|
tayra.audit.event_type | The event type as a string |
tayra.audit.key_id | The key ID (if present) |
tayra.audit.subject_id | The subject ID (if present) |
tayra.audit.entity_type | The entity type (if present) |
tayra.audit.field_count | The field count (if present) |
These appear as span events in your distributed tracing backend (Jaeger, Zipkin, Grafana Tempo), giving you a unified view of audit events within request traces.
Compliance Storage
The default logger writes to ILogger, which is typically ephemeral (console, rolling files). For GDPR compliance, configure a persistent log sink (e.g., a database, immutable object store, or compliance-specific platform like Splunk or Azure Monitor) and set an appropriate retention period for audit records.
See Also
- Observability -- Distributed tracing and metrics
- Configuration -- Service registration
- Getting Started -- Core encrypt/decrypt/shred workflow
