FlurSchnaps-Server/Sources/App/Log.swift
Christoph Hagen 6994a66883 First version
2022-09-04 15:01:35 +02:00

246 lines
6.8 KiB
Swift

import Foundation
// MARK: Public API
func log(_ message: String, file: String = #file, line: Int = #line) {
Log.log(info: message, file: file, line: line)
}
func log(error message: String, file: String = #file, line: Int = #line) {
Log.log(error: message, file: file, line: line)
}
func log(warning message: String, file: String = #file, line: Int = #line) {
Log.log(warning: message, file: file, line: line)
}
func log(info message: String, file: String = #file, line: Int = #line) {
Log.log(info: message, file: file, line: line)
}
func log(debug message: String, file: String = #file, line: Int = #line) {
Log.log(debug: message, file: file, line: line)
}
struct Log {
enum Level: Int, Comparable {
case debug = 1
case info = 2
case warning = 3
case error = 4
case none = 5
var tag: String {
switch self {
case .debug: return "DEBUG"
case .info: return "INFO"
case .warning: return "WARN"
case .error: return "ERROR"
case .none: return ""
}
}
var description: String {
switch self {
case .debug: return "Debug"
case .info: return "Infos"
case .warning: return "Warnungen"
case .error: return "Fehler"
case .none: return "Kein Logging"
}
}
static func < (lhs: Level, rhs: Level) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
private static var logLevels = [String : Level]()
private static var fileHandle: FileHandle?
private(set) static var path: URL?
/// The date formatter for the logging timestamps
private static let df: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .short
df.timeStyle = .short
df.locale = Locale(identifier: "de")
return df
}()
private init() {
// Prevent any initializations
}
// MARK: Public API
static func log(error message: String, file: String = #file, line: Int = #line) {
log(message, level: .error, file: file, line: line)
}
static func log(warning message: String, file: String = #file, line: Int = #line) {
log(message, level: .warning, file: file, line: line)
}
static func log(info message: String, file: String = #file, line: Int = #line) {
log(message, level: .info, file: file, line: line)
}
static func log(debug message: String, file: String = #file, line: Int = #line) {
log(message, level: .debug, file: file, line: line)
}
static func log(raw message: String) {
}
/**
The global log level.
Used when no specific log level is set for a file via `LogLevel.logLevel()`
*/
static var globalLevel: Level = .debug
/**
Remove the custom log level for a file, reverting it to the `globalLevel`
Best called without arguments within the file to modify: `Log.clearLogLevel()`
*/
static func defaultLogLevel(file: String = #file) {
logLevels[name(from: file)] = nil
}
/**
Set a custom log level for a file, ignoring the global default.
Example: `Log.set(logLevel = .debug)`
*/
static func set(logLevel: Level, file: String = #file) {
logLevels[name(from: file)] = logLevel
}
/**
Set the file to log to.
*/
static func set(logFile: URL) {
fileHandle?.closeFile()
if !FileManager.default.fileExists(atPath: logFile.path) {
do {
try "New log started.\n".data(using: .utf8)!.write(to: logFile)
} catch {
print("Failed to start logging to \(logFile.path): \(error)")
}
}
do {
let f = try FileHandle(forWritingTo: logFile)
path = logFile
fileHandle = f
print("Log file set to \(logFile.path)")
} catch {
print("No file handle to write log: \(error)")
fileHandle = nil
path = nil
}
}
/**
Close the log file.
Subsequent logging event will no longer be written to the log file.
Set a new log file by calling `Log.set(logFile:)`
*/
static func close() {
fileHandle?.closeFile()
fileHandle = nil
}
/**
Clear all data from the log file.
The log file will remain active, to close it permanently call `Log.close()`
*/
static func clear() {
guard let f = fileHandle, let p = path else {
return
}
f.closeFile()
try? FileManager.default.removeItem(at: p)
fileHandle = try? FileHandle(forWritingTo: p)
}
/**
Get the full text from the log file.
*/
static func fullText() -> String? {
guard let u = path else {
return nil
}
return try? String(contentsOf: u)
}
/**
Get the full data from the log file.
*/
static func data() -> Data? {
guard let f = fileHandle, let p = path else {
print("No log file set to get data")
return nil
}
f.closeFile()
defer {
do {
fileHandle = try FileHandle(forWritingTo: p)
} catch {
fileHandle = nil
path = nil
print("Failed to create log file handle: \(error)")
}
}
do {
return try Data(contentsOf: p)
} catch {
print("Failed to get log data: \(error)")
return nil
}
}
// MARK: Private helper
/// Get the pure file name for a file path from `#file`
fileprivate static func name(from file: String) -> String {
file.components(separatedBy: "/").last!.replacingOccurrences(of: ".swift", with: "")
}
/// Get the pure file name tag if the specified log level is high enough
fileprivate static func tagIf(logLevel: Level, isSufficientFor file: String) -> String? {
let tag = name(from: file)
let requiredLevel = logLevels[tag] ?? globalLevel
if logLevel >= requiredLevel {
return tag
}
return nil
}
fileprivate static func log(_ message: String, level: Level, file: String, line: Int) {
guard let tag = tagIf(logLevel: level, isSufficientFor: file) else {
return
}
let date = df.string(from: Date())
let msg = "[\(date)][\(level.tag)][\(tag):\(line)] \(message)"
log(msg)
}
private static func log(_ msg: String) {
print(msg)
if let f = fileHandle, let data = (msg + "\n").data(using: .utf8) {
f.write(data)
try? f.synchronize()
}
}
}