Initial commit
This commit is contained in:
136
oAI/Utilities/Logging.swift
Normal file
136
oAI/Utilities/Logging.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Logging.swift
|
||||
// oAI
|
||||
//
|
||||
// Dual logging: os.Logger (unified log) + file (~Library/Logs/oAI.log)
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
// MARK: - Log Level
|
||||
|
||||
enum LogLevel: Int, Comparable, CaseIterable, Sendable {
|
||||
case debug = 0
|
||||
case info = 1
|
||||
case warning = 2
|
||||
case error = 3
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .debug: return "DEBUG"
|
||||
case .info: return "INFO"
|
||||
case .warning: return "WARN"
|
||||
case .error: return "ERROR"
|
||||
}
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .debug: return "Debug"
|
||||
case .info: return "Info"
|
||||
case .warning: return "Warning"
|
||||
case .error: return "Error"
|
||||
}
|
||||
}
|
||||
|
||||
static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File Logger
|
||||
|
||||
final class FileLogger: @unchecked Sendable {
|
||||
static let shared = FileLogger()
|
||||
|
||||
private let fileHandle: FileHandle?
|
||||
private let queue = DispatchQueue(label: "com.oai.filelogger")
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let f = DateFormatter()
|
||||
f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
return f
|
||||
}()
|
||||
|
||||
/// Current minimum log level (read from UserDefaults for thread safety)
|
||||
var minimumLevel: LogLevel {
|
||||
get {
|
||||
let raw = UserDefaults.standard.integer(forKey: "logLevel")
|
||||
return LogLevel(rawValue: raw) ?? .info
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue.rawValue, forKey: "logLevel")
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
let logsDir = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent("Library/Logs")
|
||||
let logFile = logsDir.appendingPathComponent("oAI.log")
|
||||
|
||||
// Ensure file exists
|
||||
if !FileManager.default.fileExists(atPath: logFile.path) {
|
||||
FileManager.default.createFile(atPath: logFile.path, contents: nil)
|
||||
}
|
||||
|
||||
fileHandle = try? FileHandle(forWritingTo: logFile)
|
||||
fileHandle?.seekToEndOfFile()
|
||||
}
|
||||
|
||||
func write(_ level: LogLevel, category: String, message: String) {
|
||||
guard level >= minimumLevel else { return }
|
||||
queue.async { [weak self] in
|
||||
guard let self, let fh = self.fileHandle else { return }
|
||||
let timestamp = self.dateFormatter.string(from: Date())
|
||||
let line = "[\(timestamp)] [\(level.label)] [\(category)] \(message)\n"
|
||||
if let data = line.data(using: .utf8) {
|
||||
fh.write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
fileHandle?.closeFile()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App Logger (wraps os.Logger + file)
|
||||
|
||||
struct AppLogger {
|
||||
let osLogger: Logger
|
||||
let category: String
|
||||
|
||||
func debug(_ message: String) {
|
||||
FileLogger.shared.write(.debug, category: category, message: message)
|
||||
osLogger.debug("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func info(_ message: String) {
|
||||
FileLogger.shared.write(.info, category: category, message: message)
|
||||
osLogger.info("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func warning(_ message: String) {
|
||||
FileLogger.shared.write(.warning, category: category, message: message)
|
||||
osLogger.warning("\(message, privacy: .public)")
|
||||
}
|
||||
|
||||
func error(_ message: String) {
|
||||
FileLogger.shared.write(.error, category: category, message: message)
|
||||
osLogger.error("\(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Log Namespace
|
||||
|
||||
enum Log {
|
||||
private static let subsystem = "com.oai.oAI"
|
||||
|
||||
static let api = AppLogger(osLogger: Logger(subsystem: subsystem, category: "api"), category: "api")
|
||||
static let db = AppLogger(osLogger: Logger(subsystem: subsystem, category: "database"), category: "database")
|
||||
static let mcp = AppLogger(osLogger: Logger(subsystem: subsystem, category: "mcp"), category: "mcp")
|
||||
static let settings = AppLogger(osLogger: Logger(subsystem: subsystem, category: "settings"), category: "settings")
|
||||
static let search = AppLogger(osLogger: Logger(subsystem: subsystem, category: "search"), category: "search")
|
||||
static let ui = AppLogger(osLogger: Logger(subsystem: subsystem, category: "ui"), category: "ui")
|
||||
static let general = AppLogger(osLogger: Logger(subsystem: subsystem, category: "general"), category: "general")
|
||||
}
|
||||
Reference in New Issue
Block a user