Skip to content

Instantly share code, notes, and snippets.

@ostholz
Last active August 24, 2023 06:17
Show Gist options
  • Save ostholz/fd635e63b31dea5c31bbec9ae98b47db to your computer and use it in GitHub Desktop.
Save ostholz/fd635e63b31dea5c31bbec9ae98b47db to your computer and use it in GitHub Desktop.
Log function
//
// 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