Last active
August 24, 2023 06:17
-
-
Save ostholz/fd635e63b31dea5c31bbec9ae98b47db to your computer and use it in GitHub Desktop.
Log function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Logger.swift | |
// | |
// Created by on 24.01.20. | |
// | |
import Foundation | |
import os.log | |
public struct Logger { | |
public enum Constants { | |
public static let logFileName = "YOURAPP.log" | |
public static let logEnabledKey = "YOUAPP_debug_log" | |
} | |
public enum Level: Int { | |
case debug = 0 | |
case info | |
case warn | |
case flow | |
case error | |
case none | |
} | |
/** | |
* Sets or returns the lowest level that is still reported (defaults to `Level.info`). | |
*/ | |
#if RELEASE | |
public static var logLevel = Level.none | |
#elseif DEBUG | |
public static var logLevel = Level.debug | |
#else | |
public static var logLevel = Level.info | |
#endif | |
/// true: use system logger NSLog or os_log | |
/// false: Swift.print() method. this will only logged in the Xcode console | |
public static var useSystemLog = false | |
private static var dateFormatter: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.dateFormat = "HH:mm:ss.SSSS" | |
return formatter | |
}() | |
/** | |
* Logs a message, if given level is greater-or-equal to `logLevel` | |
* | |
* - parameter message: the message to log; parameter will only be evaluated if actually logged | |
* - parameter level: the log level which is compared to `logLevel` | |
*/ | |
static func log(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line, level: Level) { | |
#if !RELEASE | |
if level.rawValue >= logLevel.rawValue { | |
let msg = buildmessage(message(), flag: flag, file: file, function: function, line: line) | |
let time = dateFormatter.string(from: Date()) | |
// Swift.print() logs only in Xcode Console, not in System/Device Console. though OS_ACTIVITY_MODE not active is. | |
// so os_log() or NSLog should be used. | |
// Swift.print("\(String(describing: level).uppercased()): \(time): \(msg)") | |
// internal NSLog() calls os_log() | |
if useSystemLog { | |
if #available(iOS 10.0, *) { | |
let bundleID: String = Bundle.main.bundleIdentifier ?? "unknown" | |
let log: OSLog | |
if flag.isEmpty { | |
log = OSLog(subsystem: bundleID, category: "YOURAPP") | |
} else { | |
log = OSLog(subsystem: bundleID, category: flag) | |
} | |
os_log("%{public}@: %{public}@\n", log: log, type: OSLogType.error, String(describing: level).uppercased(), msg) | |
} else { | |
NSLog("%@: %@\n\n", String(describing: level).uppercased(), msg) | |
} | |
} else { | |
Swift.print("\(String(describing: level).uppercased()): \(time): \(msg)") | |
} | |
} | |
#endif | |
} | |
/** | |
* Logs a message at level `Level.debug`. | |
* The message is only evaluated if actually logged. | |
*/ | |
public static func debug(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line) { | |
log(message(), flag: flag, file: file, function: function, line: line, level: .debug) | |
} | |
/** | |
* Logs a message at level `Level.info`. | |
* The message is only evaluated if actually logged. | |
*/ | |
public static func info(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line) { | |
log(message(), flag: flag, file: file, function: function, line: line, level: .info) | |
} | |
/** | |
* Logs a message at level `Level.warn`. | |
* The message is only evaluated if actually logged. | |
*/ | |
public static func warn(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line) { | |
log(message(), flag: flag, file: file, function: function, line: line, level: .warn) | |
} | |
/** | |
* Logs a message at level `Level.error`. | |
* The message is only evaluated if actually logged. | |
*/ | |
public static func error(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line) { | |
log(message(), flag: flag, file: file, function: function, line: line, level: .error) | |
} | |
/** | |
* Logs a message at level `Level.flow`. | |
* The message is only evaluated if actually logged. | |
*/ | |
public static func flow(_ message: @autoclosure () -> String, flag: String = "", file: String = #file, | |
function: String = #function, line: Int = #line) { | |
log(message(), flag: flag, file: file, function: function, line: line, level: .flow) | |
} | |
// MARK: - Private | |
static private func buildmessage(_ message: @autoclosure () -> String, flag: String, file: String, | |
function: String, line: Int) -> String { | |
let file = (file as NSString).lastPathComponent | |
return "\(file):\(line) \(function)\t\(flag) \(message())\n" | |
} | |
} | |
// MARK: - Log to file | |
extension Logger { | |
private static var logFile: URL? { | |
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil } | |
return documentsDirectory.appendingPathComponent(Constants.logFileName) | |
} | |
private static var fileLogDateFormatter: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.dateFormat = "dd-MMM | HH:mm:ss.SSS" | |
return formatter | |
}() | |
public static func logToFile(message: String, categories: [String]) { | |
DispatchQueue.global(qos: .background).async { | |
if Environment.isAppStoreBuild { | |
return | |
} | |
let logEnabled = UserDefaults.standard.bool(forKey: Constants.logEnabledKey) ?? false | |
if !logEnabled { return } | |
guard let logFile = logFile else { return } | |
let timestamp = fileLogDateFormatter.string(from: Date()) | |
let joinedCategory = categories.joined(separator: ", ") | |
guard let data = "\(timestamp) | \(message) | [\(joinedCategory)]\n".data(using: .utf8) else { return } | |
do { | |
if FileManager.default.fileExists(atPath: logFile.path) { | |
if let fileHandle = try? FileHandle(forWritingTo: logFile) { | |
if #available(iOS 13.4, *) { | |
try fileHandle.seekToEnd() | |
try fileHandle.write(contentsOf: data) | |
try fileHandle.close() | |
} else { | |
try fileHandle.seekToEndOfFile() | |
try fileHandle.write(data) | |
try fileHandle.closeFile() | |
} | |
} | |
} else { | |
try data.write(to: logFile) | |
} | |
} catch { | |
NSLog("can't log to file %@", logFile.path) | |
} | |
} | |
} | |
public static func logToFile(message: String, logCategories: LoggingCategory) { | |
let categories = logCategories.getCategories() | |
Self.logToFile(message: message, categories: categories) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment