diff --git a/README.md b/README.md index 7a85d23..f3d5e2d 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,134 @@ await logger.send(.impletion(screen: "Home")) await logger.send([\.eventName: "tapButton", \.parameters: ["ButtonID": 1]]) ``` + +# Customization + +This section describes how to customize the behavior of the logger. + +## Create a type that conforms to Mutation + +Mutation converts one log into another. + +This is useful if you have parameters that you want to add to all the logs. + +To create the type and set it to logger, write as follows. + +```swift + +// An implementation similar to this can be found in ParchmentDefault + +struct DeviceDataMutation: Mutation { + private let deviceParams = [ + "Model": UIDevice.current.name, + "OS": UIDevice.current.systemName, + "OS Version": UIDevice.current.systemVersion + ] + + public func transform(_ event: Loggable, id: LoggerComponentID) -> Loggable { + let log: LoggableDictonary = [ + \.eventName: event.eventName, + \.parameters: event.parameters.merging(deviceParams) { left, _ in left } + ] + return log + } +} + +logger.mutations.append(DeviceDataMutation()) + +``` + +## Extend LoggerComponentID + +LoggerComponentID is an ID that uniquely recognizes a logger. + +By extending LoggerComponentID, the destination of the log can be controlled as shown below. + + +```swift + +extension LoggerComponentID { + static let firebase: Self = .init("firebase") + static let myBadkend: Self = .init("myBadkend") +} + +await logger.send(.tap, with: .init(scope: .exclude([.firebase, .myBadkend]))) + +await logger.send(.tap, with: .init(scope: .only([.myBadkend]))) + +``` + +## Create a type that conforms to BufferedEventFlushScheduler + +BufferedEventFlushScheduler determines the timing of fetching the log data in the buffer. +To create the type and set it to logger, write as follows. + + +```swift + +// An implementation similar to this can be found in ParchmentDefault +final class RegularlyPollingScheduler: BufferedEventFlushScheduler { + public static let `default` = RegularlyPollingScheduler(timeInterval: 60) + + let timeInterval: TimeInterval + + var lastFlushedDate: Date = Date() + + private weak var timer: Timer? + + public init( + timeInterval: TimeInterval, + ) { + self.timeInterval = timeInterval + } + + public func schedule(with buffer: TrackingEventBufferAdapter) async -> AsyncThrowingStream<[BufferRecord], Error> { + return AsyncThrowingStream { continuation in + let timer = Timer(fire: .init(), interval: 1, repeats: true) { _ in + Task { [weak self] in + await self?.tick(with: buffer) { + continuation.yield($0) + } + } + } + RunLoop.main.add(timer, forMode: .common) + self.timer = timer + } + } + + public func cancel() { + timer?.invalidate() + } + + private func tick(with buffer: TrackingEventBufferAdapter, didFlush: @escaping ([BufferRecord])->()) async { + guard await buffer.count() > 0 else { return } + + let flush = { + let records = await buffer.load() + didFlush(records) + } + + let timeSinceLastFlush = abs(self.lastFlushedDate.timeIntervalSinceNow) + if self.timeInterval < timeSinceLastFlush { + await flush() + self.lastFlushedDate = Date() + return + } + } +} + +let logger = LoggerBundler( + components: [...], + buffer: TrackingEventBuffer = ..., + loggingStrategy: BufferedEventFlushScheduler = RegularlyPollingScheduler.default +) + +``` + +## Create a type that conforms to TrackingEventBuffer + +TrackingEventBuffer is a buffer that saves the log. + +ParchmentDefault defines a class SQLiteBuffer that uses SQLite to store logs. + +This implementation can be replaced by a class that is compatible with TrackingEventBuffer.