-
Notifications
You must be signed in to change notification settings - Fork 9
Log enhancements to allow for instances #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
628c9b1
8a3ff8a
4b91620
8585a6b
c06f4d9
832fd88
4bb2367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,26 +7,30 @@ | |
| // | ||
|
|
||
| import Foundation | ||
| import OSLog | ||
|
|
||
| /** | ||
| * A simple log that outputs to the console via ```print()```` | ||
| * A simple log that outputs to the console via ```print()```` and also logs to the console with OSLog (if available) | ||
| */ | ||
| open class Log { | ||
|
|
||
| // MARK: Configuration | ||
| // MARK: Types | ||
|
|
||
| public typealias InstanceLogHandler = ((Level, String) -> Void) | ||
| public typealias GlobalLogHandler = ((Log, Level, String) -> Void) | ||
|
|
||
| /** | ||
| Represents a level of detail to be logged. | ||
| */ | ||
| public enum Level: Int { | ||
| public enum Level: Int, CaseIterable { | ||
| case verbose | ||
| case debug | ||
| case info | ||
| case warn | ||
| case error | ||
| case off | ||
|
|
||
| var name: String { | ||
| public var name: String { | ||
| switch self { | ||
| case .verbose: return "Verbose" | ||
| case .debug: return "Debug" | ||
|
|
@@ -37,7 +41,7 @@ open class Log { | |
| } | ||
| } | ||
|
|
||
| var emoji: String { | ||
| public var emoji: String { | ||
| switch self { | ||
| case .verbose: return "📖" | ||
| case .debug: return "🐝" | ||
|
|
@@ -49,11 +53,38 @@ open class Log { | |
| } | ||
| } | ||
|
|
||
| public static private(set) var standard = Log("", logLevel: .off) | ||
|
|
||
| /// Static instance used for helper methods. | ||
| open class var instance: Log { | ||
| return standard | ||
| } | ||
|
||
|
|
||
| /// Subsystem of the OSLog message when running on iOS 14 or later. | ||
| open var subsystem: String { | ||
| let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String | ||
| return (bundleName ?? "com.rightpoint.swiftilities") + ".log" | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Given the definition from Apple. this seems like it should default to the bundle id of the parent application. |
||
| } | ||
|
|
||
| /// If this is non-nil, we will call it with the same string that we | ||
| /// are going to print to the console. You can use this to pass log | ||
| /// messages along to your crash reporter, analytics service, etc. | ||
| /// - warning: Be mindful of private user data that might end up in | ||
| /// your log statements! Use log levels appropriately | ||
| /// to keep private data out of logs that are sent over | ||
| /// the Internet. | ||
| public static var globalHandler: GlobalLogHandler? | ||
|
|
||
| // MARK: Configuration | ||
|
|
||
| /// Displayed name of the Log instance, also used as the Category in OSLog messages | ||
| public var name: String | ||
|
|
||
| /// The log level, defaults to .Off | ||
| public static var logLevel: Level = .off | ||
| public var logLevel: Level = .off | ||
|
|
||
| /// If true, prints emojis to signify log type, defaults to off | ||
| public static var useEmoji: Bool = false | ||
| public var useEmoji: Bool = false | ||
|
|
||
| /// If this is non-nil, we will call it with the same string that we | ||
| /// are going to print to the console. You can use this to pass log | ||
|
|
@@ -62,7 +93,15 @@ open class Log { | |
| /// your log statements! Use log levels appropriately | ||
| /// to keep private data out of logs that are sent over | ||
| /// the Internet. | ||
| public static var handler: ((Level, String) -> Void)? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be kept around and marked with a deprecated attribute, and shimmed with overridden
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's being kept as an Instance level handler. This allow for more selective handling if there are a bunch of different Log instances floating around. |
||
| public var handler: InstanceLogHandler? | ||
|
|
||
| // MARK: Initializer | ||
|
|
||
| required public init(_ name: String, logLevel: Level = .off, useEmoji: Bool = false) { | ||
| self.name = name | ||
| self.logLevel = logLevel | ||
| self.useEmoji = useEmoji | ||
| } | ||
|
|
||
| // MARK: Private | ||
|
|
||
|
|
@@ -74,37 +113,100 @@ open class Log { | |
| }() | ||
|
|
||
| /// Generic log method | ||
| fileprivate static func log<T>(_ object: @autoclosure () -> T, level: Log.Level, _ fileName: String, _ functionName: String, _ line: Int) { | ||
| if logLevel.rawValue <= level.rawValue { | ||
| let date = Log.dateformatter.string(from: Date()) | ||
| let components: [String] = fileName.components(separatedBy: "/") | ||
| let objectName = components.last ?? "Unknown Object" | ||
| let levelString = Log.useEmoji ? level.emoji : "|" + level.name.uppercased() + "|" | ||
| let logString = "\(levelString)\(date) \(objectName) \(functionName) line \(line):\n\(object())" | ||
| print(logString + "\n") | ||
| handler?(level, logString) | ||
| internal func log<T>(_ object: @autoclosure () -> T, level: Log.Level, _ fileName: String, _ functionName: String, _ line: Int) { | ||
| guard logLevel.rawValue <= level.rawValue else {return} | ||
|
|
||
| let date = Log.dateformatter.string(from: Date()) | ||
| let components: [String] = fileName.components(separatedBy: "/") | ||
| let objectName = components.last ?? "Unknown Object" | ||
| let levelString = useEmoji ? level.emoji : "|" + level.name.uppercased() + "|" | ||
| let logString = "\(levelString)\(date) \(objectName) \(functionName) line \(line):\n\(object())" | ||
|
|
||
| if #available(iOS 14.0, *) { | ||
| let logger = Logger(subsystem: subsystem, category: name) | ||
| let objectString = "\(object())" | ||
| let logMessage = "\(objectName) \(functionName) line \(line)" | ||
| switch level { | ||
| case .verbose: | ||
| logger.trace("\(logMessage):\n\(objectString, privacy: .private)") | ||
| case .debug: | ||
| logger.debug("\(logMessage):\n\(objectString, privacy: .private)") | ||
| case .info: | ||
| logger.info("\(logMessage):\n\(objectString, privacy: .private)") | ||
| case .warn: | ||
| logger.warning("\(logMessage):\n\(objectString, privacy: .private)") | ||
| case .error: | ||
| logger.error("\(logMessage):\n\(objectString, privacy: .private)") | ||
| case .off: | ||
| break | ||
| } | ||
| } | ||
| else { | ||
| let nameString = name.count > 0 ? "[\(name)]]" : "" | ||
| print(nameString + logString + "\n") | ||
| } | ||
| self.handler?(level, logString) | ||
| Log.globalHandler?(self, level, logString) | ||
| } | ||
|
|
||
| // MARK: Log Methods | ||
| // MARK: Public | ||
|
|
||
| public static func error<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| public func error<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| log(object(), level:.error, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func warn<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| public func warn<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| log(object(), level:.warn, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func info<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| public func info<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| log(object(), level:.info, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func debug<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| public func debug<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| log(object(), level:.debug, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func verbose<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| public func verbose<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| log(object(), level:.verbose, fileName, functionName, line) | ||
| } | ||
| } | ||
|
|
||
| // MARK: Static Helper Methods | ||
| extension Log { | ||
|
|
||
| public static var logLevel: Level { | ||
| get { instance.logLevel } | ||
| set { instance.logLevel = newValue } | ||
| } | ||
|
|
||
| public static var useEmoji: Bool { | ||
| get { instance.useEmoji } | ||
| set { instance.useEmoji = newValue } | ||
| } | ||
|
|
||
| public static var handler: InstanceLogHandler? { | ||
| get { instance.handler } | ||
| set { instance.handler = newValue } | ||
| } | ||
|
Comment on lines
+223
to
+226
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! I see the compatibility shim is down here. Might be good to mark some of these as deprecated/renamed
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want them deprecated? For apps where a single Log instance is sufficient, I can still see some value keeping these shorthand versions around
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah perhaps not deprecated, but specifically the change related to the renaming of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's applied to the shared instance, but not necessarily globally. I agree the name is a little confusing though 🤔 |
||
|
|
||
| public static func error<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| instance.log(object(), level:.error, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func warn<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| instance.log(object(), level:.warn, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func info<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| instance.log(object(), level:.info, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func debug<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| instance.log(object(), level:.debug, fileName, functionName, line) | ||
| } | ||
|
|
||
| public static func verbose<T>(_ object: @autoclosure () -> T, _ fileName: String = #file, _ functionName: String = #function, _ line: Int = #line) { | ||
| instance.log(object(), level:.verbose, fileName, functionName, line) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.