ReactiveSwift/Sources/EventLogger.swift

173 lines
6.0 KiB
Swift

//
// EventLogger.swift
// ReactiveSwift
//
// Created by Rui Peres on 30/04/2016.
// Copyright © 2016 GitHub. All rights reserved.
//
import Foundation
/// A namespace for logging event types.
public enum LoggingEvent {
public enum Signal: String, CaseIterable {
case value, completed, failed, terminated, disposed, interrupted
@available(*, deprecated, message:"Use `allCases` instead.")
public static var allEvents: Set<Signal> { Set(allCases) }
}
public enum SignalProducer: String, CaseIterable {
case starting, started, value, completed, failed, terminated, disposed, interrupted
@available(*, deprecated, message:"Use `allCases` instead.")
public static var allEvents: Set<SignalProducer> { Set(allCases) }
}
}
public func defaultEventLog(identifier: String, event: String, fileName: String, functionName: String, lineNumber: Int) {
print("[\(identifier)] \(event) fileName: \(fileName), functionName: \(functionName), lineNumber: \(lineNumber)")
}
/// A type that represents an event logging function.
/// Signature is:
/// - identifier
/// - event
/// - fileName
/// - functionName
/// - lineNumber
public typealias EventLogger = (
_ identifier: String,
_ event: String,
_ fileName: String,
_ functionName: String,
_ lineNumber: Int
) -> Void
// See https://bugs.swift.org/browse/SR-6796.
fileprivate struct LogContext<Event: LoggingEventProtocol> {
let events: Set<Event>
let identifier: String
let fileName: String
let functionName: String
let lineNumber: Int
let logger: EventLogger
func log<T>(_ event: Event) -> ((T) -> Void)? {
return event.logIfNeeded(events: self.events) { event in
self.logger(self.identifier, event, self.fileName, self.functionName, self.lineNumber)
}
}
func log(_ event: Event) -> (() -> Void)? {
return event.logIfNeededNoArg(events: self.events) { event in
self.logger(self.identifier, event, self.fileName, self.functionName, self.lineNumber)
}
}
}
extension Signal {
/// Logs all events that the receiver sends. By default, it will print to
/// the standard output.
///
/// - parameters:
/// - identifier: a string to identify the Signal firing events.
/// - events: Types of events to log.
/// - fileName: Name of the file containing the code which fired the
/// event.
/// - functionName: Function where event was fired.
/// - lineNumber: Line number where event was fired.
/// - logger: Logger that logs the events.
///
/// - returns: Signal that, when observed, logs the fired events.
public func logEvents(identifier: String = "", events: Set<LoggingEvent.Signal> = Set(LoggingEvent.Signal.allCases), fileName: String = #file, functionName: String = #function, lineNumber: Int = #line, logger: @escaping EventLogger = defaultEventLog) -> Signal<Value, Error> {
let logContext = LogContext(events: events,
identifier: identifier,
fileName: fileName,
functionName: functionName,
lineNumber: lineNumber,
logger: logger)
return self.on(
failed: logContext.log(.failed),
completed: logContext.log(.completed),
interrupted: logContext.log(.interrupted),
terminated: logContext.log(.terminated),
disposed: logContext.log(.disposed),
value: logContext.log(.value)
)
}
}
extension SignalProducer {
/// Logs all events that the receiver sends. By default, it will print to
/// the standard output.
///
/// - parameters:
/// - identifier: a string to identify the SignalProducer firing events.
/// - events: Types of events to log.
/// - fileName: Name of the file containing the code which fired the
/// event.
/// - functionName: Function where event was fired.
/// - lineNumber: Line number where event was fired.
/// - logger: Logger that logs the events.
///
/// - returns: Signal producer that, when started, logs the fired events.
public func logEvents(identifier: String = "",
events: Set<LoggingEvent.SignalProducer> = Set(LoggingEvent.SignalProducer.allCases),
fileName: String = #file,
functionName: String = #function,
lineNumber: Int = #line,
logger: @escaping EventLogger = defaultEventLog
) -> SignalProducer<Value, Error> {
let logContext = LogContext(events: events,
identifier: identifier,
fileName: fileName,
functionName: functionName,
lineNumber: lineNumber,
logger: logger)
return self.on(
starting: logContext.log(.starting),
started: logContext.log(.started),
failed: logContext.log(.failed),
completed: logContext.log(.completed),
interrupted: logContext.log(.interrupted),
terminated: logContext.log(.terminated),
disposed: logContext.log(.disposed),
value: logContext.log(.value)
)
}
}
private protocol LoggingEventProtocol: Hashable, RawRepresentable {}
extension LoggingEvent.Signal: LoggingEventProtocol {}
extension LoggingEvent.SignalProducer: LoggingEventProtocol {}
private extension LoggingEventProtocol {
// FIXME: See https://bugs.swift.org/browse/SR-6796 and the discussion in https://github.com/apple/swift/pull/14477.
// Due to differences in the type checker, this method cannot
// overload the generic `logIfNeeded`, or otherwise it would lead to
// infinite recursion with Swift 4.0.x.
func logIfNeededNoArg(events: Set<Self>, logger: @escaping (String) -> Void) -> (() -> Void)? {
return (self.logIfNeeded(events: events, logger: logger) as ((()) -> Void)?)
.map { closure in
{ closure(()) }
}
}
func logIfNeeded<T>(events: Set<Self>, logger: @escaping (String) -> Void) -> ((T) -> Void)? {
guard events.contains(self) else {
return nil
}
return { value in
if value is Void {
logger("\(self.rawValue)")
} else {
logger("\(self.rawValue) \(value)")
}
}
}
}