learning_ai_common_plat/packages/swift-platform-sdk/Sources/BLAuditLogger.swift
saravanakumardb1 ae55616444 fix(swift-sdk): match date decoding strategy in BLAuditLogger
saveEvents used .iso8601 encoding but loadEvents used the default
decoder (.deferredToDate). ISO8601 date strings could not be decoded
back, causing loadEvents() to return [] after the first log — breaking
event rotation and losing all previous audit entries.
2026-02-28 22:55:00 -08:00

83 lines
2.8 KiB
Swift

// Audit Logger
// Generic local audit logger that tracks user actions for debugging.
// Stores events in a rotating JSON file (configurable max entries).
// Product apps configure with a product-specific file name.
import Foundation
/// Audit event stored locally.
public struct BLAuditEvent: Codable, Sendable {
public let id: String
public let action: String
public let details: String?
public let timestamp: Date
public init(action: String, details: String? = nil) {
self.id = UUID().uuidString
self.action = action
self.details = details
self.timestamp = Date()
}
}
/// Generic local audit logger for all ByteLyst iOS apps.
/// Stores events in a rotating JSON file in the Documents directory.
public enum BLAuditLogger {
private static var maxEvents = 1000
private static var fileName = "audit_log.json"
/// Configure the logger with a product-specific file name and max events.
public static func configure(fileName: String = "audit_log.json", maxEvents: Int = 1000) {
self.fileName = fileName
self.maxEvents = maxEvents
}
private static var fileURL: URL {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return docs.appendingPathComponent(fileName)
}
/// Log a user action.
public static func log(_ action: String, details: String? = nil) {
let event = BLAuditEvent(action: action, details: details)
var events = loadEvents()
events.append(event)
// Rotate: keep only the most recent maxEvents
if events.count > maxEvents {
events = Array(events.suffix(maxEvents))
}
saveEvents(events)
}
/// Get all logged events (newest first).
public static func getEvents(limit: Int = 100) -> [BLAuditEvent] {
let events = loadEvents()
return Array(events.suffix(limit).reversed())
}
/// Clear all audit logs.
public static func clear() {
try? FileManager.default.removeItem(at: fileURL)
}
// MARK: - Persistence
private static func loadEvents() -> [BLAuditEvent] {
guard let data = try? Data(contentsOf: fileURL) else { return [] }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return (try? decoder.decode([BLAuditEvent].self, from: data)) ?? []
}
private static func saveEvents(_ events: [BLAuditEvent]) {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
guard let data = try? encoder.encode(events) else { return }
try? data.write(to: fileURL, options: .atomic)
}
}