-
Notifications
You must be signed in to change notification settings - Fork 431
/
EventLogger.swift
179 lines (161 loc) · 6.05 KB
/
EventLogger.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//
// 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) -> (@Sendable (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) -> (@Sendable () -> 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) -> (@Sendable () -> Void)? {
return (self.logIfNeeded(events: events, logger: logger) as ((()) -> Void)?)
.map { closure in
{ @Sendable in closure(()) }
}
}
func logIfNeeded<T>(events: Set<Self>, logger: @escaping (String) -> Void) -> (@Sendable (T) -> Void)? {
guard events.contains(self) else {
return nil
}
return { value in
if value is Void {
logger("\(self.rawValue)")
} else {
logger("\(self.rawValue) \(value)")
}
}
}
}