Files
oai-swift/oAI/Services/EmailLogService.swift

173 lines
4.7 KiB
Swift

//
// EmailLogService.swift
// oAI
//
// Service for managing email handler activity logs
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.
import Foundation
import os
@Observable
final class EmailLogService {
static let shared = EmailLogService()
private let db = DatabaseService.shared
private let log = Logger(subsystem: "com.oai.oAI", category: "email-log")
private init() {}
// MARK: - Log Operations
/// Save a successful email processing log
func logSuccess(
sender: String,
subject: String,
emailContent: String,
aiResponse: String,
tokens: Int?,
cost: Double?,
responseTime: TimeInterval?,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: aiResponse,
status: .success,
tokens: tokens,
cost: cost,
responseTime: responseTime,
modelId: modelId
)
db.saveEmailLog(entry)
log.info("Email log saved: \(sender) - \(subject) [SUCCESS]")
}
/// Save a failed email processing log
func logError(
sender: String,
subject: String,
emailContent: String,
errorMessage: String,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: nil,
status: .error,
errorMessage: errorMessage,
modelId: modelId
)
db.saveEmailLog(entry)
log.error("Email log saved: \(sender) - \(subject) [ERROR: \(errorMessage)]")
}
/// Load recent email logs (default: 100 most recent)
func loadLogs(limit: Int = 100) -> [EmailLog] {
do {
return try db.loadEmailLogs(limit: limit)
} catch {
log.error("Failed to load email logs: \(error.localizedDescription)")
return []
}
}
/// Delete a specific log entry
func deleteLog(id: UUID) {
db.deleteEmailLog(id: id)
log.info("Email log deleted: \(id.uuidString)")
}
/// Clear all email logs
func clearAllLogs() {
db.clearEmailLogs()
log.info("All email logs cleared")
}
/// Get total count of email logs
func getLogCount() -> Int {
do {
return try db.getEmailLogCount()
} catch {
log.error("Failed to get email log count: \(error.localizedDescription)")
return 0
}
}
// MARK: - Statistics
/// Get email processing statistics
func getStatistics() -> EmailStatistics {
let logs = loadLogs(limit: 1000) // Last 1000 for stats
let total = logs.count
let successful = logs.filter { $0.status == .success }.count
let errors = logs.filter { $0.status == .error }.count
let totalTokens = logs.compactMap { $0.tokens }.reduce(0, +)
let totalCost = logs.compactMap { $0.cost }.reduce(0.0, +)
let avgResponseTime: TimeInterval?
let responseTimes = logs.compactMap { $0.responseTime }
if !responseTimes.isEmpty {
avgResponseTime = responseTimes.reduce(0, +) / Double(responseTimes.count)
} else {
avgResponseTime = nil
}
return EmailStatistics(
total: total,
successful: successful,
errors: errors,
totalTokens: totalTokens,
totalCost: totalCost,
averageResponseTime: avgResponseTime
)
}
}
// MARK: - Statistics Model
struct EmailStatistics {
let total: Int
let successful: Int
let errors: Int
let totalTokens: Int
let totalCost: Double
let averageResponseTime: TimeInterval?
var successRate: Double {
guard total > 0 else { return 0.0 }
return Double(successful) / Double(total)
}
var errorRate: Double {
guard total > 0 else { return 0.0 }
return Double(errors) / Double(total)
}
}