Логирование
При разработке любого приложения возникает потребность выводить отладочные логи в консоль. Это могут быть ошибки или просто детали работы приложения.
Часто в коде используют просто NSLog
/print()
, но такое решение далеко от идеального. Есть популярная библиотека CocoaLumberjack. Долгое время я использовал её, но всё логирование завязывается на статические методы или макросы, которые дергаются из любого метода/класса.
Это распространенный подход, но совсем не объектно-ориентированный и не расширяемый. Чтобы разделить потоки логов от разных подсистем - придется както завязывать на константы в лог-сообщениях. Либо создавать новые макросы/статические методы.
Напрашивается решение - передавать некоторый объект через который будут логироваться все сообщения. Например, вот такой:
public protocol WlgLogEvent {
var level: WlgLogLevel {get}
var message: String {get}
}
public protocol WlgLog {
func log<E: WlgLogEvent>(_ event: E)
}
Теперь некий абстрактный WldLog
принимает абстрактные WldLogEvent
. Подклассов для реализации конкретных ивентов можно придумать большое кол-во.
Самый простой - объект просто хранящий level
и message
public class WlgLogEventDefault: WlgLogEvent {
public let level: WlgLogLevel
public let message: String
public init(level: WlgLogLevel, message: String) {
self.level = level
self.message = message
}
}
Теперь, чтобы залогировать некоторые операции можно просто передавать инстанс класса реализующего WlgLog
:
class DownloadOperation {
private let log: WldLog
init(url: URL, log: WldLog) {
...
}
func start() {
self.log.log(WlgLogEventDefault(.debug, "Starting download operation"))
}
}
Чтобы залогировать события можно создать класс WlgOsLog
, который внутри себя будет использовать os_log
, примерно вот так:
class WlgOsLog: WlgLog {
private let subsystem: String
private let category: String
init(subsystem: String = "", category: String = "") {
self.subsystem = subsystem
self.category = category
}
func log<E: WlgLogEventI>(_ event: E) {
if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
var osLogType = OSLogType.default
var levelStr = ""
switch event.level {
case .debug:
osLogType = .debug
levelStr = "[DEBUG]"
case .info:
osLogType = .info
levelStr = "[INFO]"
case .error:
osLogType = .error
levelStr = "[ERROR]"
default:
osLogType = .default
}
let osLog = OSLog(subsystem: self.subsystem,
category: self.category)
os_log("%{public}@ %{public}@",
log: osLog,
type: osLogType,
levelStr,
event.message)
}
}
}
В этом коде можно инстанцировать WlgOsLog
с конкретной подсистемой и категорией. Тем самым упростив анализ логов в Console.app. При этом ничего не мешает использовать декорирование логгеров. И например, написать логгер отправляющий ошибки в Sentry/Firebase.
Чтобы не выводить ошибки в лог потребуется WlgNullLog
, который не пишет никуда. Тогда в конструкторе объекта можно указывать дефолтную реализацию:
class DownloadOperation {
private let log: WldLog
init(url: URL, log: WldLog = WlgNullLog()) {
...
}
}
Такой подход реализован в библиотеке Wlog