Lightweight logging class supporting different topics
import Foundation | |
final class Log: NSObject { | |
enum Topic: String { | |
case network, app | |
} | |
private enum Level: String { | |
case info = "" | |
case warn = "❌" | |
} | |
var isEnabled: Bool { | |
return Environment.current.logTopics.contains(topic) | |
} | |
static var dateFormatter: DateFormatter = { | |
let formatter = DateFormatter() | |
formatter.timeStyle = .medium | |
formatter.dateStyle = .short | |
return formatter | |
}() | |
private let topic: Topic | |
init(topic: Topic) { | |
self.topic = topic | |
} | |
private func log( | |
level: Level, | |
message: String, | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line | |
) { | |
guard isEnabled else { return } | |
let fileUrl = URL(fileURLWithPath: file) | |
let fileExtension = fileUrl.pathExtension | |
let fileName = fileUrl.deletingPathExtension().lastPathComponent | |
let date = Log.dateFormatter.string(from: .init()) | |
let source = "\(fileName).\(fileExtension):\(line) \(function)" | |
var output = "\(date) [\(topic.rawValue.uppercased())] \(source): \(message)" | |
if !level.rawValue.isEmpty { | |
output = "\(level.rawValue) \(output)" | |
} | |
// FIXME: Using `print` is probably not the best choice here, in theory it would be best to | |
// use os_log as seen here: https://github.com/jaredsinclair/etcetera/blob/master/OSLog.swift. | |
print(output) | |
} | |
func info( | |
_ message: String, | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line | |
) { | |
log(level: .info, message: message, file: file, function: function, line: line) | |
} | |
func warn( | |
_ message: String, | |
file: String = #file, | |
function: String = #function, | |
line: Int = #line | |
) { | |
log(level: .warn, message: message, file: file, function: function, line: line) | |
} | |
} | |
// MARK: - Environment | |
struct Environment { | |
static let current = Environment() | |
/// Add cases here to use them in Scheme (⌘ ⇧ ,) → Run → Environment Variables | |
private enum Keys: String { | |
case logTopics = "LogTopics" | |
case runningTests = "XCTestConfigurationFilePath" | |
} | |
enum Configuration: String { | |
case debug, release | |
} | |
let isRunningTests: Bool | |
let logTopics: Set<Log.Topic> | |
private init() { | |
let env = ProcessInfo.processInfo.environment | |
isRunningTests = nil != env[Keys.runningTests] | |
switch (Environment.buildConfiguration, isRunningTests) { | |
case (.release, _), (.debug, true): | |
// Disable logs when running in release configuration or when running tests. | |
logTopics = [] | |
case (.debug, false): | |
let topics = env[Keys.logTopics]?.components(separatedBy: ",") ?? [] | |
logTopics = Set(topics.compactMap { Log.Topic(rawValue: $0.lowercased()) }) | |
} | |
} | |
static var buildConfiguration: Configuration { | |
#if RELEASE | |
return .release | |
#else | |
return .debug | |
#endif | |
} | |
} | |
// MARK: - CustomStringConvertible | |
extension Environment: CustomStringConvertible { | |
var description: String { | |
let configuration = Environment.buildConfiguration.rawValue.uppercased() | |
let logs = "Enabled log topics: \(logTopics.map { "\"\($0)\"" }.joined(separator: ", "))" | |
return "Configuration: \(configuration). \(logs).\n" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment