PhotoFlow Documentation Help

Audit Logging

PhotoFlow implements comprehensive audit logging for performance monitoring, activity tracking, and debugging.

Overview

The audit log system provides:

  • Performance monitoring for all operations

  • Activity tracking with detailed context

  • Async processing with internal queuing

  • Error tracking for debugging

Architecture

Storage

Core Services

Audit Layer

API Layer

Controller

AuditedFolderService

AuditLogService

FolderService

Internal Queue

PostgreSQL

AuditLog Entity

public class AuditLogEntity : BaseEntity<Guid> { public string ServiceName { get; private set; } public string Action { get; private set; } public string EntityType { get; private set; } public string EntityId { get; private set; } public string UserId { get; private set; } public string OrganizationId { get; private set; } public string ProviderId { get; private set; } public string RequestId { get; private set; } public string CorrelationId { get; private set; } public string Operation { get; private set; } public string Status { get; private set; } public string ErrorMessage { get; private set; } public string RequestData { get; private set; } // JSON public string ResponseData { get; private set; } // JSON public long ExecutionTimeMs { get; private set; } public string IpAddress { get; private set; } public string UserAgent { get; private set; } public string AdditionalData { get; private set; } // JSON public DateTime? StartedAt { get; private set; } public DateTime? CompletedAt { get; private set; } }

Decorator Pattern

PhotoFlow uses the Decorator Pattern to add audit logging without modifying core services:

public class AuditedFolderService : IFolderService { private readonly IFolderService _folderService; private readonly IAuditLogService _auditLogService; private readonly IAuthContext _authContext; private readonly ILogger<AuditedFolderService> _logger; public async Task<bool> SyncByIdAsync( CloudProviderType provider, Guid folderId, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); Guid auditLogId = Guid.Empty; try { // Create audit log entry at start var auditLog = await _auditLogService.CreateAuditLogAsync( serviceName: "Storage", action: "SyncFolder", entityType: "Folder", entityId: folderId.ToString(), userId: _authContext.UserId, organizationId: _authContext.OrganizationId, requestData: JsonSerializer.Serialize(new { provider, folderId }) ); auditLogId = auditLog.Id; // Execute actual operation var result = await _folderService.SyncByIdAsync( provider, folderId, cancellationToken); stopwatch.Stop(); // Mark as completed await _auditLogService.MarkCompletedAsync( auditLogId, JsonSerializer.Serialize(new { success = result }), stopwatch.ElapsedMilliseconds); return result; } catch (Exception ex) { stopwatch.Stop(); await _auditLogService.MarkFailedAsync( auditLogId, ex.Message, stopwatch.ElapsedMilliseconds); throw; } } }

Async Queue Processing

Audit logs are processed asynchronously to minimize performance impact:

public class InternalAuditLogQueueService : IAuditLogService, IDisposable { private readonly ConcurrentQueue<AuditLogOperation> _queue; private readonly SemaphoreSlim _semaphore; private readonly int _batchSize = 50; private readonly TimeSpan _processingInterval = TimeSpan.FromMilliseconds(100); public Task<AuditLogEntity> CreateAuditLogAsync(...) { // Enqueue and return immediately var operation = new AuditLogOperation { Type = AuditLogOperationType.Create, Entity = AuditLogEntity.Create(...) }; _queue.Enqueue(operation); return Task.FromResult(operation.Entity); } private async Task ProcessQueueAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { var batch = new List<AuditLogOperation>(); // Collect batch while (batch.Count < _batchSize && _queue.TryDequeue(out var op)) { batch.Add(op); } if (batch.Count > 0) { await ProcessBatchAsync(batch); } await Task.Delay(_processingInterval, ct); } } }

Configuration

Service Registration

// Program.cs services.AddScoped<IFolderService, FolderService>(); services.Decorate<IFolderService, AuditedFolderService>(); services.AddSingleton<IAuditLogService, InternalAuditLogQueueService>();

Queue Settings

{ "AuditLog": { "BatchSize": 50, "ProcessingIntervalMs": 100, "MaxConcurrency": 4, "RetentionDays": 90 } }

Performance Queries

Average Execution Time by Operation

SELECT "Operation", AVG("ExecutionTimeMs") as AvgTime, MAX("ExecutionTimeMs") as MaxTime, MIN("ExecutionTimeMs") as MinTime, COUNT(*) as Count FROM "AuditLogs" WHERE "Status" = 'Completed' GROUP BY "Operation" ORDER BY AvgTime DESC;

Slow Operations (> 5 seconds)

SELECT "ServiceName", "Operation", "EntityType", "EntityId", "ExecutionTimeMs", "CompletedAt" FROM "AuditLogs" WHERE "ExecutionTimeMs" > 5000 ORDER BY "ExecutionTimeMs" DESC LIMIT 100;

Failed Operations

SELECT "ServiceName", "Operation", "ErrorMessage", "RequestData", "CompletedAt" FROM "AuditLogs" WHERE "Status" = 'Failed' ORDER BY "CompletedAt" DESC LIMIT 100;

User Activity

SELECT "UserId", COUNT(*) as ActivityCount, AVG("ExecutionTimeMs") as AvgExecutionTime FROM "AuditLogs" WHERE "CreatedAt" > NOW() - INTERVAL '24 hours' GROUP BY "UserId" ORDER BY ActivityCount DESC;

Organization Usage

SELECT "OrganizationId", "Operation", COUNT(*) as UsageCount FROM "AuditLogs" WHERE "CreatedAt" > NOW() - INTERVAL '7 days' GROUP BY "OrganizationId", "Operation" ORDER BY UsageCount DESC;

Correlation Tracking

All operations include correlation IDs for cross-service tracing:

public class CorrelationMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var correlationId = context.Request.Headers["X-Correlation-ID"] .FirstOrDefault() ?? Guid.NewGuid().ToString(); context.Items["CorrelationId"] = correlationId; context.Response.Headers["X-Correlation-ID"] = correlationId; await next(context); } }

Usage in Audit Logs

var auditLog = await _auditLogService.CreateAuditLogAsync( serviceName: "Photoflow", action: "CreateAlbum", correlationId: _httpContext.Items["CorrelationId"]?.ToString() );

Log Levels

Status

Description

Started

Operation began

Completed

Operation succeeded

Failed

Operation failed with error

Cancelled

Operation was cancelled

Retention Policy

Audit logs are retained based on configuration:

public class AuditLogCleanupJob : IHostedService { private readonly int _retentionDays = 90; public async Task ExecuteAsync(CancellationToken ct) { await _repository.DeleteOlderThanAsync( DateTime.UtcNow.AddDays(-_retentionDays), ct); } }

Dashboard Integration

Audit logs power the admin dashboard metrics:

  • Operation counts per service/time period

  • Average response times by endpoint

  • Error rates and patterns

  • User activity heatmaps

  • Organization usage statistics

  • Database Overview

  • Storage Service

  • API Overview

Last modified: 20 December 2025