From a5814214c07f4882c19b45ac3d91d2facc6e082e Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Mon, 16 Aug 2021 17:52:36 -0700 Subject: [PATCH 1/9] Added first pass on public API methods --- Sources/Segment/Plugins/Logger.swift | 123 +++++++++++++++++++------ Tests/Segment-Tests/Logger_Tests.swift | 63 ++++++++++--- 2 files changed, 144 insertions(+), 42 deletions(-) diff --git a/Sources/Segment/Plugins/Logger.swift b/Sources/Segment/Plugins/Logger.swift index a6ae0f92..bc38e080 100644 --- a/Sources/Segment/Plugins/Logger.swift +++ b/Sources/Segment/Plugins/Logger.swift @@ -7,66 +7,130 @@ import Foundation -public enum LogType: Int { - case error = 0 // Not Verbose - case warning // Semi-verbose - case info // Verbose -} - -class Logger: UtilityPlugin { - public var filterType = LogType.info +internal class Logger: UtilityPlugin { + public var filterKind = LogKind.debug let type = PluginType.utility var analytics: Analytics? private var messages = [LogMessage]() + fileprivate var loggingMediator = [LoggingType: LogTarget]() required init() { } - func log(type: LogType, message: String, event: RawEvent?) { - print("\(type) -- Message: \(message)") - let message = LogMessage(type: type, message: message, event: event) - messages.append(message) + func log(destination: LoggingType.LogDestination, message: String, logKind: LogKind, function: String, line: Int) { + let message = LogMessage(kind: logKind, message: message, event: nil, function: function, line: line) + + for (logType, target) in loggingMediator { + if logType.contains(destination) { + target.parseLog(message) + } + } } func flush() { print("Flushing All Logs") for message in messages { - if message.type.rawValue <= filterType.rawValue { - print("[\(message.type)] \(message.message)") + if message.kind.rawValue <= filterKind.rawValue { + print("[\(message.kind)] \(message.message)") } } messages.removeAll() } } -fileprivate struct LogMessage { - let type: LogType +// MARK: - Types +public protocol LogTarget { + func parseLog(_ log: LogMessage) + +} + +public enum LogKind: Int { + case error = 0 // Not Verbose (fail cases | non-recoverable errors) + case warning // Semi-verbose (deprecations | potential issues) + case debug // Verbose (everything of interest) +} + +public struct LoggingType: Hashable { + private let allTypes: [LogDestination] + + public enum LogDestination { + case log + case metric + case history + } + + public init(types: [LogDestination]) { + self.allTypes = types + } + + public func contains(_ type: LogDestination) -> Bool { + return allTypes.contains(type) + } +} + +public struct LogMessage { + let kind: LogKind let message: String let event: RawEvent? + let function: String? + let line: Int? } + +// MARK: - Public Logging API extension Analytics { + public func log(message: String, kind: LogKind? = nil, function: String = #function, line: Int = #line) { + apply { plugin in + if let loggerPlugin = plugin as? Logger { + var filterKind = loggerPlugin.filterKind + if let logKind = kind { + filterKind = logKind + } + loggerPlugin.log(destination: .log, message: message, logKind: filterKind, function: function, line: line) + } + } + } + + public func metric(_ type: String, name: String, value: Double, tags: [String]? = nil) { + + } + + public func history(event: RawEvent, sender: AnyObject, function: String = #function) { + + } +} + +extension Analytics { + + public func add(_ target: LogTarget, type: LoggingType) { + apply { (potentialLogger) in + if let logger = potentialLogger as? Logger { + logger.loggingMediator[type] = target + } + } + } + /// Log a generic message to the system with a possible log type. If a type is not supplied the system /// will use the current default setting (.info). /// - Parameters: /// - message: The message to be stored in the logging system. /// - event: The event associated with the log (optional). /// - type: The filter type for the message. If nil, defaults to logger setting. - public func log(message: String, event: RawEvent? = nil, type: LogType? = nil) { - apply { (potentialLogger) in - - if let logger = potentialLogger as? Logger { - - var loggingType = logger.filterType - if let type = type { - loggingType = type - } - logger.log(type: loggingType, message: message, event: event) - } - } - } +// public func log(message: String, event: RawEvent? = nil, type: LogType? = nil) { +// apply { (potentialLogger) in +// +// if let logger = potentialLogger as? Logger { +// +// var loggingType = logger.filterType +// if let type = type { +// loggingType = type +// } +// logger.log(type: loggingType, message: message, event: event) +// } +// } +// } public func logFlush() { apply { (potentialLogger) in @@ -76,3 +140,4 @@ extension Analytics { } } } + diff --git a/Tests/Segment-Tests/Logger_Tests.swift b/Tests/Segment-Tests/Logger_Tests.swift index 4e3e7c8e..9d7b0bcb 100644 --- a/Tests/Segment-Tests/Logger_Tests.swift +++ b/Tests/Segment-Tests/Logger_Tests.swift @@ -10,14 +10,26 @@ import XCTest @testable import Segment final class Logger_Tests: XCTestCase { + + var analytics: Analytics? + let mockLogger = LoggerMockPlugin() + + override func setUp() { + analytics = Analytics(configuration: Configuration(writeKey: "test")) + analytics?.add(plugin: mockLogger) + } + + override func tearDown() { + analytics = nil + } - class LoggerMock: Logger { - var logClosure: ((LogType, String) -> Void)? + class LoggerMockPlugin: Logger { + var logClosure: ((LogKind, String) -> Void)? var closure: (() -> Void)? - override func log(type: LogType, message: String, event: RawEvent?) { - super.log(type: type, message: message, event: event) - logClosure?(type, message) + override func log(destination: LoggingType.LogDestination, message: String, logKind: LogKind, function: String, line: Int) { + super.log(destination: .log, message: message, logKind: logKind, function: function, line: line) + logClosure?(logKind, message) } override func flush() { @@ -27,20 +39,45 @@ final class Logger_Tests: XCTestCase { } func testLogging() { - - let analytics = Analytics(configuration: Configuration(writeKey: "test")) - + + // Arrange let expectation = XCTestExpectation(description: "Called") - let mockLogger = LoggerMock() - mockLogger.logClosure = { (type, message) in + // Assert + mockLogger.logClosure = { (kind, message) in expectation.fulfill() - XCTAssertEqual(type, .info, "Type not correctly passed") + XCTAssertEqual(kind, .debug, "Type not correctly passed") XCTAssertEqual(message, "Something Other Than Awesome", "Message not correctly passed") } - analytics.add(plugin: mockLogger) - analytics.log(message: "Something Other Than Awesome") + + // Act + analytics?.log(message: "Something Other Than Awesome") + wait(for: [expectation], timeout: 1.0) + } + + func testTargetSuccess() { + + // Arrange + let expectation = XCTestExpectation(description: "Called") + + struct LogConsoleTarget: LogTarget { + var successClosure: ((String) -> Void) + + func parseLog(_ log: LogMessage) { + print("[Segment Tests - \(log.function ?? ""):\(String(log.line ?? 0))] \(log.message)") + successClosure(log.message) + } + } + + let logConsoleTarget = LogConsoleTarget(successClosure: { (logMessage: String) in + expectation.fulfill() + }) + let loggingType = LoggingType(types: [.log]) + analytics?.add(logConsoleTarget, type: loggingType) + + // Act + analytics?.log(message: "Should hit our proper target") wait(for: [expectation], timeout: 1.0) } } From 5be2b9a6e77077431edfdbc7ac5333a71d509d94 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Tue, 17 Aug 2021 10:14:58 -0700 Subject: [PATCH 2/9] Added flush and other tests --- Sources/Segment/Plugins/Logger.swift | 46 +++++++++++++------------- Tests/Segment-Tests/Logger_Tests.swift | 28 ++++++++++++++-- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Sources/Segment/Plugins/Logger.swift b/Sources/Segment/Plugins/Logger.swift index bc38e080..36aeb0c2 100644 --- a/Sources/Segment/Plugins/Logger.swift +++ b/Sources/Segment/Plugins/Logger.swift @@ -41,8 +41,14 @@ internal class Logger: UtilityPlugin { // MARK: - Types public protocol LogTarget { + func parseLog(_ log: LogMessage) - + func flush() +} + +extension LogTarget { + // Make flush optional with an empty implementation. + func flush() { } } public enum LogKind: Int { @@ -60,6 +66,10 @@ public struct LoggingType: Hashable { case history } + static let log = LoggingType(types: [.log]) + static let metric = LoggingType(types: [.metric]) + static let history = LoggingType(types: [.history]) + public init(types: [LogDestination]) { self.allTypes = types } @@ -94,7 +104,11 @@ extension Analytics { } public func metric(_ type: String, name: String, value: Double, tags: [String]? = nil) { - + apply { plugin in + if let loggerPlugin = plugin as? Logger { +// loggerPlugin.log(destination: .metric, message: message, logKind: filterKind, function: function, line: line) + } + } } public func history(event: RawEvent, sender: AnyObject, function: String = #function) { @@ -104,7 +118,13 @@ extension Analytics { extension Analytics { - public func add(_ target: LogTarget, type: LoggingType) { + /// Add a logging target to the system. These `targets` can handle logs in various ways. Consider + /// sending logs to the console, the OS and a web service. Three targets can handle these scenarios. + /// - Parameters: + /// - target: A `LogTarget` that has logic to parse and handle log messages. + /// - type: The type consists of `log`, `metric` or `history`. These correspond to the + /// public API on Analytics. + public func add(target: LogTarget, type: LoggingType) { apply { (potentialLogger) in if let logger = potentialLogger as? Logger { logger.loggingMediator[type] = target @@ -112,26 +132,6 @@ extension Analytics { } } - /// Log a generic message to the system with a possible log type. If a type is not supplied the system - /// will use the current default setting (.info). - /// - Parameters: - /// - message: The message to be stored in the logging system. - /// - event: The event associated with the log (optional). - /// - type: The filter type for the message. If nil, defaults to logger setting. -// public func log(message: String, event: RawEvent? = nil, type: LogType? = nil) { -// apply { (potentialLogger) in -// -// if let logger = potentialLogger as? Logger { -// -// var loggingType = logger.filterType -// if let type = type { -// loggingType = type -// } -// logger.log(type: loggingType, message: message, event: event) -// } -// } -// } - public func logFlush() { apply { (potentialLogger) in if let logger = potentialLogger as? Logger { diff --git a/Tests/Segment-Tests/Logger_Tests.swift b/Tests/Segment-Tests/Logger_Tests.swift index 9d7b0bcb..0eff14d4 100644 --- a/Tests/Segment-Tests/Logger_Tests.swift +++ b/Tests/Segment-Tests/Logger_Tests.swift @@ -65,7 +65,7 @@ final class Logger_Tests: XCTestCase { var successClosure: ((String) -> Void) func parseLog(_ log: LogMessage) { - print("[Segment Tests - \(log.function ?? ""):\(String(log.line ?? 0))] \(log.message)") + print("[Segment Tests - \(log.function ?? ""):\(String(log.line ?? 0))] \(log.message)\n") successClosure(log.message) } } @@ -73,12 +73,34 @@ final class Logger_Tests: XCTestCase { let logConsoleTarget = LogConsoleTarget(successClosure: { (logMessage: String) in expectation.fulfill() }) - let loggingType = LoggingType(types: [.log]) - analytics?.add(logConsoleTarget, type: loggingType) + let loggingType = LoggingType.log + analytics?.add(target: logConsoleTarget, type: loggingType) // Act analytics?.log(message: "Should hit our proper target") wait(for: [expectation], timeout: 1.0) } + + func testTargetFailure() { + + // Arrange + struct LogConsoleTarget: LogTarget { + var successClosure: ((String) -> Void) + + func parseLog(_ log: LogMessage) { + print("[Segment Tests - \(log.function ?? ""):\(String(log.line ?? 0))] \(log.message)\n") + successClosure(log.message) + } + } + + let logConsoleTarget = LogConsoleTarget(successClosure: { (logMessage: String) in + XCTFail("Should not hit this since it was registered for history") + }) + let loggingType = LoggingType.history + analytics?.add(target: logConsoleTarget, type: loggingType) + + // Act + analytics?.log(message: "Should hit our proper target") + } } From 23e4f59447ee7a9520902aac37d713ce63d1af73 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Tue, 17 Aug 2021 13:49:24 -0700 Subject: [PATCH 3/9] Added public API calls --- Sources/Segment/Plugins/Logger.swift | 119 ++++++++++++++++++++----- Tests/Segment-Tests/Logger_Tests.swift | 8 +- 2 files changed, 99 insertions(+), 28 deletions(-) diff --git a/Sources/Segment/Plugins/Logger.swift b/Sources/Segment/Plugins/Logger.swift index 36aeb0c2..4d254bcd 100644 --- a/Sources/Segment/Plugins/Logger.swift +++ b/Sources/Segment/Plugins/Logger.swift @@ -8,41 +8,43 @@ import Foundation internal class Logger: UtilityPlugin { - public var filterKind = LogKind.debug + public var filterKind = LogFilterKind.debug let type = PluginType.utility var analytics: Analytics? - private var messages = [LogMessage]() fileprivate var loggingMediator = [LoggingType: LogTarget]() required init() { } - func log(destination: LoggingType.LogDestination, message: String, logKind: LogKind, function: String, line: Int) { - let message = LogMessage(kind: logKind, message: message, event: nil, function: function, line: line) + internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { for (logType, target) in loggingMediator { if logType.contains(destination) { - target.parseLog(message) + target.parseLog(logMessage) } } } func flush() { - print("Flushing All Logs") - for message in messages { - if message.kind.rawValue <= filterKind.rawValue { - print("[\(message.kind)] \(message.message)") - } + for (_, target) in loggingMediator { + target.flush() } - messages.removeAll() } + } // MARK: - Types public protocol LogTarget { - + + /// Implement this method to process logging messages. This is where the logic for the target will be + /// added. Feel free to add your own data queueing and offline storage. + /// - important: Use the Segment Network stack for Segment library compatibility and simplicity. func parseLog(_ log: LogMessage) + + /// Optional method to implement. This helps respond to potential queueing events being flushed out. + /// Perhaps responding to backgrounding or networking events, this gives a chance to empty a queue + /// or pump a firehose of logs. func flush() } @@ -51,7 +53,7 @@ extension LogTarget { func flush() { } } -public enum LogKind: Int { +public enum LogFilterKind: Int { case error = 0 // Not Verbose (fail cases | non-recoverable errors) case warning // Semi-verbose (deprecations | potential issues) case debug // Verbose (everything of interest) @@ -79,26 +81,31 @@ public struct LoggingType: Hashable { } } -public struct LogMessage { - let kind: LogKind - let message: String - let event: RawEvent? - let function: String? - let line: Int? +public protocol LogMessage { + var kind: LogFilterKind { get } + var message: String { get } + var event: RawEvent? { get } + var function: String? { get } + var line: Int? { get } } // MARK: - Public Logging API extension Analytics { - public func log(message: String, kind: LogKind? = nil, function: String = #function, line: Int = #line) { + public func log(message: String, kind: LogFilterKind? = nil, function: String = #function, line: Int = #line) { apply { plugin in if let loggerPlugin = plugin as? Logger { var filterKind = loggerPlugin.filterKind if let logKind = kind { filterKind = logKind } - loggerPlugin.log(destination: .log, message: message, logKind: filterKind, function: function, line: line) + do { + let log = try LogFactory.buildLog(destination: .log, title: "", message: message, kind: filterKind, function: function, line: line) + loggerPlugin.log(log, destination: .log) + } catch { + // TODO: LOG TO PRIVATE SEGMENT LOG + } } } } @@ -106,13 +113,27 @@ extension Analytics { public func metric(_ type: String, name: String, value: Double, tags: [String]? = nil) { apply { plugin in if let loggerPlugin = plugin as? Logger { -// loggerPlugin.log(destination: .metric, message: message, logKind: filterKind, function: function, line: line) + do { + let log = try LogFactory.buildLog(destination: .metric, title: type, message: name, value: value, tags: tags) + loggerPlugin.log(log, destination: .metric) + } catch { + // TODO: LOG TO PRIVATE SEGMENT LOG + } } } } - public func history(event: RawEvent, sender: AnyObject, function: String = #function) { - + public func history(event: RawEvent, sender: AnyObject, function: String = #function, line: Int = #line) { + apply { plugin in + if let loggerPlugin = plugin as? Logger { + do { + let log = try LogFactory.buildLog(destination: .history, title: event.toString(), message: "", function: function, line: line, event: event, sender: sender) + loggerPlugin.log(log, destination: .metric) + } catch { + // TODO: LOG TO PRIVATE SEGMENT LOG + } + } + } } } @@ -141,3 +162,53 @@ extension Analytics { } } +struct LogFactory { + static func buildLog(destination: LoggingType.LogDestination, + title: String, + message: String, + kind: LogFilterKind = .debug, + function: String? = nil, + line: Int? = nil, + event: RawEvent? = nil, + sender: Any? = nil, + value: Double? = nil, + tags: [String]? = nil) throws -> LogMessage { + + switch destination { + case .log: + return GenericLog(kind: kind, message: message, function: function, line: line) + case .metric: + return MetricLog(title: title, message: message, event: event, function: function, line: line) + case .history: + return HistoryLog(message: message, event: event, function: function, line: line, sender: sender) + default: + throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) + } + } + + fileprivate struct GenericLog: LogMessage { + var kind: LogFilterKind + var message: String + var event: RawEvent? = nil + var function: String? + var line: Int? + } + + fileprivate struct MetricLog: LogMessage { + var title: String + var kind: LogFilterKind = .debug + var message: String + var event: RawEvent? + var function: String? = nil + var line: Int? = nil + } + + fileprivate struct HistoryLog: LogMessage { + var kind: LogFilterKind = .debug + var message: String + var event: RawEvent? + var function: String? + var line: Int? + var sender: Any? + } +} diff --git a/Tests/Segment-Tests/Logger_Tests.swift b/Tests/Segment-Tests/Logger_Tests.swift index 0eff14d4..6f41d99c 100644 --- a/Tests/Segment-Tests/Logger_Tests.swift +++ b/Tests/Segment-Tests/Logger_Tests.swift @@ -24,12 +24,12 @@ final class Logger_Tests: XCTestCase { } class LoggerMockPlugin: Logger { - var logClosure: ((LogKind, String) -> Void)? + var logClosure: ((LogFilterKind, String) -> Void)? var closure: (() -> Void)? - override func log(destination: LoggingType.LogDestination, message: String, logKind: LogKind, function: String, line: Int) { - super.log(destination: .log, message: message, logKind: logKind, function: function, line: line) - logClosure?(logKind, message) + override func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { + super.log(logMessage, destination: destination) + logClosure?(logMessage.kind, logMessage.message) } override func flush() { From d7da288c66dc1117dc8b9db507b39ca23ca8c725 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Thu, 19 Aug 2021 13:20:58 -0700 Subject: [PATCH 4/9] Updated documentation --- Sources/Segment/Plugins/Logger.swift | 125 +++++++++++++++++++-------- Sources/Segment/Startup.swift | 1 + 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/Sources/Segment/Plugins/Logger.swift b/Sources/Segment/Plugins/Logger.swift index 4d254bcd..2839f9db 100644 --- a/Sources/Segment/Plugins/Logger.swift +++ b/Sources/Segment/Plugins/Logger.swift @@ -7,34 +7,11 @@ import Foundation -internal class Logger: UtilityPlugin { - public var filterKind = LogFilterKind.debug - - let type = PluginType.utility - var analytics: Analytics? - - fileprivate var loggingMediator = [LoggingType: LogTarget]() - - required init() { } - - internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { - - for (logType, target) in loggingMediator { - if logType.contains(destination) { - target.parseLog(logMessage) - } - } - } - - func flush() { - for (_, target) in loggingMediator { - target.flush() - } - } - -} +// MARK: - Logging Types -// MARK: - Types +/// The foundation for building out a special logger. If logs need to be directed to a certain area, this is the +/// interface to start off with. For instance a console logger, a networking logger or offline storage logger +/// would all start off with LogTarget. public protocol LogTarget { /// Implement this method to process logging messages. This is where the logic for the target will be @@ -48,19 +25,17 @@ public protocol LogTarget { func flush() } -extension LogTarget { - // Make flush optional with an empty implementation. - func flush() { } -} - +/// Used for analytics.log() types. This lets the system know what to filter on and how to set priorities. public enum LogFilterKind: Int { case error = 0 // Not Verbose (fail cases | non-recoverable errors) case warning // Semi-verbose (deprecations | potential issues) case debug // Verbose (everything of interest) } +/// The Segment logging system has three types of logs: log, metric and history. When adding a target that +/// responds to logs, it is possible to adhere to 1 to many. In other words, a LoggingType can be .log & +/// .history. This is used to tell which targets logs are directed to. public struct LoggingType: Hashable { - private let allTypes: [LogDestination] public enum LogDestination { case log @@ -68,19 +43,34 @@ public struct LoggingType: Hashable { case history } + /// Convenience .log logging type static let log = LoggingType(types: [.log]) + /// Convenience .metric logging type static let metric = LoggingType(types: [.metric]) + /// Convenience .history logging type static let history = LoggingType(types: [.history]) + + /// Designated initializer for LoggingType. Add all the destinations this LoggingType should support. + /// - Parameter types: The LoggingDestination(s) that this LoggingType will support. public init(types: [LogDestination]) { + // TODO: Failable scenario if types empty self.allTypes = types } - public func contains(_ type: LogDestination) -> Bool { - return allTypes.contains(type) + // - Private Properties and Methods + private let allTypes: [LogDestination] + + /// Convience method to find if the LoggingType supports a particular destination. + /// - Parameter destination: The particular destination being tested for conformance. + /// - Returns: If the destination exists in this LoggingType `true` or `false` will be returned. + fileprivate func contains(_ destination: LogDestination) -> Bool { + return allTypes.contains(destination) } } + +/// The interface to the message being returned to `LogTarget` -> `parseLog()`. public protocol LogMessage { var kind: LogFilterKind { get } var message: String { get } @@ -91,8 +81,16 @@ public protocol LogMessage { // MARK: - Public Logging API + extension Analytics { + /// The public logging method for capturing all general types of log messages related to Segment. + /// - Parameters: + /// - message: The main message of the log to be captured. + /// - kind: Usually .error, .warning or .debug, in order of serverity. This helps filter logs based on + /// this added metadata. + /// - function: The name of the function the log came from. This will be captured automatically. + /// - line: The line number in the function the log came from. This will be captured automatically. public func log(message: String, kind: LogFilterKind? = nil, function: String = #function, line: Int = #line) { apply { plugin in if let loggerPlugin = plugin as? Logger { @@ -110,6 +108,13 @@ extension Analytics { } } + /// The public logging method for capturing metrics related to Segment or other libraries. + /// - Parameters: + /// - type: Metric type, usually .counter or .gauge. Select the one that makes sense for the metric. + /// - name: The title of the metric to track. + /// - value: The value associated with the metric. This would be an incrementing counter or time + /// or pressure gauge. + /// - tags: Any tags that should be associated with the metric. Any extra metadata that may help. public func metric(_ type: String, name: String, value: Double, tags: [String]? = nil) { apply { plugin in if let loggerPlugin = plugin as? Logger { @@ -123,6 +128,14 @@ extension Analytics { } } + /// Used to track the history of events as the event data travels through the Segment Event Timeline. As + /// plugins manipulate the data at the `before`, `enrichment`, `destination`, + /// `destination timeline`, and `after` states, an event can be tracked. Starting with the first one + /// - Parameters: + /// - event: The timeline event that is to be processed. + /// - sender: Where the event came from. + /// - function: The name of the function the log came from. This will be captured automatically. + /// - line: The line number in the function the log came from. This will be captured automatically. public func history(event: RawEvent, sender: AnyObject, function: String = #function, line: Int = #line) { apply { plugin in if let loggerPlugin = plugin as? Logger { @@ -162,7 +175,38 @@ extension Analytics { } } -struct LogFactory { +// MARK: - Plugin Implementation + +internal class Logger: UtilityPlugin { + public var filterKind = LogFilterKind.debug + + let type = PluginType.utility + var analytics: Analytics? + + fileprivate var loggingMediator = [LoggingType: LogTarget]() + + required init() { } + + internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { + + for (logType, target) in loggingMediator { + if logType.contains(destination) { + target.parseLog(logMessage) + } + } + } + + func flush() { + for (_, target) in loggingMediator { + target.flush() + } + } + +} + +// MARK: - Internal Types + +fileprivate struct LogFactory { static func buildLog(destination: LoggingType.LogDestination, title: String, message: String, @@ -181,8 +225,8 @@ struct LogFactory { return MetricLog(title: title, message: message, event: event, function: function, line: line) case .history: return HistoryLog(message: message, event: event, function: function, line: line, sender: sender) - default: - throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) +// default: +// throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) } } @@ -212,3 +256,8 @@ struct LogFactory { var sender: Any? } } + +public extension LogTarget { + // Make flush optional with an empty implementation. + func flush() { } +} diff --git a/Sources/Segment/Startup.swift b/Sources/Segment/Startup.swift index c1164d3f..48061caf 100644 --- a/Sources/Segment/Startup.swift +++ b/Sources/Segment/Startup.swift @@ -11,6 +11,7 @@ import Sovran extension Analytics: Subscriber { internal func platformStartup() { + add(plugin: Logger()) add(plugin: StartupQueue()) // add segment destination plugin unless From 177cc4e2a75a6fae0d9a74f82d16ed8ab13d50d7 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Fri, 20 Aug 2021 11:05:33 -0700 Subject: [PATCH 5/9] Added new console target and replaced all prints --- .../DestinationsExample/ViewController.swift | 2 +- .../LoginViewController.swift | 4 +- .../Supporting Files/KeychainItem.swift | 2 +- .../AppsFlyerDestination.swift | 2 +- Examples/other_plugins/ConsoleLogger.swift | 8 +- .../other_plugins/UIKitScreenTracking.swift | 2 +- Segment.xcodeproj/project.pbxproj | 24 ++- .../Plugins/Logger/ConsoleTarget.swift | 18 ++ .../Segment/Plugins/{ => Logger}/Logger.swift | 106 ++---------- .../Plugins/Logger/SegmentLogger.swift | 158 ++++++++++++++++++ 10 files changed, 221 insertions(+), 105 deletions(-) create mode 100644 Sources/Segment/Plugins/Logger/ConsoleTarget.swift rename Sources/Segment/Plugins/{ => Logger}/Logger.swift (72%) create mode 100644 Sources/Segment/Plugins/Logger/SegmentLogger.swift diff --git a/Examples/apps/DestinationsExample/DestinationsExample/ViewController.swift b/Examples/apps/DestinationsExample/DestinationsExample/ViewController.swift index 2594b482..1b58cd3e 100644 --- a/Examples/apps/DestinationsExample/DestinationsExample/ViewController.swift +++ b/Examples/apps/DestinationsExample/DestinationsExample/ViewController.swift @@ -82,7 +82,7 @@ class ViewController: UIViewController { case .alias: aliasEvent() case .none: - print("Failed to establish event type") + analytics?.log(message: "Failed to establish event type", kind: .error) } clearAll() diff --git a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift index 228e42d6..b5ce4969 100644 --- a/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift +++ b/Examples/apps/SegmentExtensionsExample/SegmentExtensionsExample/LoginViewController.swift @@ -107,7 +107,7 @@ extension LoginViewController: ASAuthorizationControllerDelegate { analytics?.track(name: "Saved to Keychain") } catch { //handle error and optionally track it - print("Unable to save userId to keychain.") + analytics?.log(message: "Unable to save userId to keychain.", kind: .error) } } @@ -140,7 +140,7 @@ extension LoginViewController: ASAuthorizationControllerDelegate { func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { //handle error - print(error) + analytics?.log(message: error, king: .error) } } diff --git a/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift b/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift index 715958a3..3ff5520c 100644 --- a/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift +++ b/Examples/apps/SegmentExtensionsExample/Supporting Files/KeychainItem.swift @@ -143,7 +143,7 @@ struct KeychainItem { do { try KeychainItem(service: "co.alancharles.SegmentExtensionsExample", account: "userIdentifier").deleteItem() } catch { - print("Unable to delete userIdentifier from keychain") + analytics?.log(message: "Unable to delete userIdentifier from keychain", kind: .error) } } } diff --git a/Examples/destination_plugins/AppsFlyerDestination.swift b/Examples/destination_plugins/AppsFlyerDestination.swift index 4e039f55..f7870e70 100644 --- a/Examples/destination_plugins/AppsFlyerDestination.swift +++ b/Examples/destination_plugins/AppsFlyerDestination.swift @@ -250,7 +250,7 @@ extension AppsFlyerDestination: AppsFlyerLibDelegate { extension AppsFlyerDestination: DeepLinkDelegate, UIApplicationDelegate { func didResolveDeepLink(_ result: DeepLinkResult) { - print(result) + analytics?.log(message: result) switch result.status { case .notFound: analytics?.log(message: "AppsFlyer: Deep link not found") diff --git a/Examples/other_plugins/ConsoleLogger.swift b/Examples/other_plugins/ConsoleLogger.swift index 02f03d7f..ce220171 100644 --- a/Examples/other_plugins/ConsoleLogger.swift +++ b/Examples/other_plugins/ConsoleLogger.swift @@ -53,8 +53,8 @@ class ConsoleLogger: Plugin { // we want to log every event, so lets override `execute`. func execute(event: T?) -> T? { if let json = event?.prettyPrint() { - print("event received on instance: \(name)") - print("\(json)\n") + analytics?.log(message: "event received on instance: \(name)") + analytics?.log(message: "\(json)\n") } return event } @@ -62,8 +62,8 @@ class ConsoleLogger: Plugin { // we also want to know when settings are retrieved or changed. func update(settings: Settings) { let json = settings.prettyPrint() - print("settings updated on instance: \(name)") - print("\(json)\n") + analytics?.log(message: "settings updated on instance: \(name)") + analytics?.log(message: "\(json)\n") } } diff --git a/Examples/other_plugins/UIKitScreenTracking.swift b/Examples/other_plugins/UIKitScreenTracking.swift index 185c5dd5..2570c710 100644 --- a/Examples/other_plugins/UIKitScreenTracking.swift +++ b/Examples/other_plugins/UIKitScreenTracking.swift @@ -122,7 +122,7 @@ extension UIViewController { guard let top = Self.seg__visibleViewController(activeController()) else { return } var name = String(describing: top.self.classForCoder).replacingOccurrences(of: "ViewController", with: "") - print(name) + analytics?.log(message: name) // name could've been just "ViewController"... if name.count == 0 { name = top.title ?? "Unknown" diff --git a/Segment.xcodeproj/project.pbxproj b/Segment.xcodeproj/project.pbxproj index 980d71b6..020f4205 100644 --- a/Segment.xcodeproj/project.pbxproj +++ b/Segment.xcodeproj/project.pbxproj @@ -49,12 +49,14 @@ 46FE4D1D25A7A850003A7362 /* Storage_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FE4D1C25A7A850003A7362 /* Storage_Tests.swift */; }; 9620862C2575C0C800314F8D /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9620862B2575C0C800314F8D /* Events.swift */; }; 96208650257AA83E00314F8D /* iOSLifecycleMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9620864F257AA83E00314F8D /* iOSLifecycleMonitor.swift */; }; + 96259F8326CEF526008AE301 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96259F8226CEF526008AE301 /* Logger.swift */; }; + 96259F8626CF1D45008AE301 /* ConsoleTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96259F8526CF1D45008AE301 /* ConsoleTarget.swift */; }; 966945D7259BDCDD00271339 /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40ED259A7311008EB0B6 /* HTTPClient.swift */; }; 967C40DA258D472C008EB0B6 /* Logger_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40D9258D472C008EB0B6 /* Logger_Tests.swift */; }; 967C40E3258D4DAF008EB0B6 /* Metrics_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C40E2258D4DAF008EB0B6 /* Metrics_Tests.swift */; }; 9692724E25A4E5B7009B5298 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692724D25A4E5B7009B5298 /* Startup.swift */; }; 9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9692726725A583A6009B5298 /* SegmentDestination.swift */; }; - 96C33A9C25880A5E00F3D538 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33A9B25880A5E00F3D538 /* Logger.swift */; }; + 96C33A9C25880A5E00F3D538 /* SegmentLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33A9B25880A5E00F3D538 /* SegmentLogger.swift */; }; 96C33AAC25892D6D00F3D538 /* Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33AAB25892D6D00F3D538 /* Metrics.swift */; }; 96C33AB1258961F500F3D538 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C33AB0258961F500F3D538 /* Settings.swift */; }; A31A16262576B6F200C9CDDF /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31A16252576B6F200C9CDDF /* Timeline.swift */; }; @@ -133,12 +135,14 @@ 9620862B2575C0C800314F8D /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; 962086482579CCC200314F8D /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9620864F257AA83E00314F8D /* iOSLifecycleMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSLifecycleMonitor.swift; sourceTree = ""; }; + 96259F8226CEF526008AE301 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 96259F8526CF1D45008AE301 /* ConsoleTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleTarget.swift; sourceTree = ""; }; 967C40D9258D472C008EB0B6 /* Logger_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger_Tests.swift; sourceTree = ""; }; 967C40E2258D4DAF008EB0B6 /* Metrics_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metrics_Tests.swift; sourceTree = ""; }; 967C40ED259A7311008EB0B6 /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 9692724D25A4E5B7009B5298 /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; 9692726725A583A6009B5298 /* SegmentDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentDestination.swift; sourceTree = ""; }; - 96C33A9B25880A5E00F3D538 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 96C33A9B25880A5E00F3D538 /* SegmentLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentLogger.swift; sourceTree = ""; }; 96C33AAB25892D6D00F3D538 /* Metrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metrics.swift; sourceTree = ""; }; 96C33AB0258961F500F3D538 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; A31A16252576B6F200C9CDDF /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = ""; }; @@ -228,8 +232,8 @@ 9620864E257AA82900314F8D /* Plugins */ = { isa = PBXGroup; children = ( + 96259F8426CEF534008AE301 /* Logger */, 969A533D25B0D510009227D9 /* Platforms */, - 96C33A9B25880A5E00F3D538 /* Logger.swift */, 96C33AAB25892D6D00F3D538 /* Metrics.swift */, 46A018C125E5857D00F9CCD8 /* Context.swift */, 9692726725A583A6009B5298 /* SegmentDestination.swift */, @@ -239,6 +243,16 @@ path = Plugins; sourceTree = ""; }; + 96259F8426CEF534008AE301 /* Logger */ = { + isa = PBXGroup; + children = ( + 96259F8226CEF526008AE301 /* Logger.swift */, + 96C33A9B25880A5E00F3D538 /* SegmentLogger.swift */, + 96259F8526CF1D45008AE301 /* ConsoleTarget.swift */, + ); + path = Logger; + sourceTree = ""; + }; 969A533D25B0D510009227D9 /* Platforms */ = { isa = PBXGroup; children = ( @@ -470,6 +484,7 @@ 4621080C2605332D00EBC4A8 /* KeyPath.swift in Sources */, A31A16262576B6F200C9CDDF /* Timeline.swift in Sources */, 96C33AB1258961F500F3D538 /* Settings.swift in Sources */, + 96259F8626CF1D45008AE301 /* ConsoleTarget.swift in Sources */, 46E382E72654429A00BA2502 /* Utils.swift in Sources */, A31A16B225781CB400C9CDDF /* JSON.swift in Sources */, 46022771261F7A4800A9E913 /* Atomic.swift in Sources */, @@ -484,12 +499,13 @@ 4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */, 46FE4C9C25A3F41C003A7362 /* LinuxLifecycleMonitor.swift in Sources */, 460227422612987300A9E913 /* watchOSLifecycleEvents.swift in Sources */, + 96259F8326CEF526008AE301 /* Logger.swift in Sources */, 46F7485E26C718710042798E /* ObjCConfiguration.swift in Sources */, A31A162F2576B73F00C9CDDF /* State.swift in Sources */, 9692726825A583A6009B5298 /* SegmentDestination.swift in Sources */, 4602276C261E7BF900A9E913 /* iOSDelegation.swift in Sources */, 46A018D425E6C9C200F9CCD8 /* LinuxUtils.swift in Sources */, - 96C33A9C25880A5E00F3D538 /* Logger.swift in Sources */, + 96C33A9C25880A5E00F3D538 /* SegmentLogger.swift in Sources */, 46FE4C9725A3F35E003A7362 /* macOSLifecycleMonitor.swift in Sources */, 9620862C2575C0C800314F8D /* Events.swift in Sources */, A3AEE1882581A8F1002386EB /* Deprecations.swift in Sources */, diff --git a/Sources/Segment/Plugins/Logger/ConsoleTarget.swift b/Sources/Segment/Plugins/Logger/ConsoleTarget.swift new file mode 100644 index 00000000..a5940fc8 --- /dev/null +++ b/Sources/Segment/Plugins/Logger/ConsoleTarget.swift @@ -0,0 +1,18 @@ +// +// ConsoleTarget.swift +// ConsoleTarget +// +// Created by Cody Garvin on 8/19/21. +// + +import Foundation + +class ConsoleTarget: LogTarget { + func parseLog(_ log: LogMessage) { + var metadata = "" + if let function = log.function, let line = log.line { + metadata = " - \(function):\(line)" + } + print("[Segment \(log.kind.toString())\(metadata)]\n\(log.message)\n") + } +} diff --git a/Sources/Segment/Plugins/Logger.swift b/Sources/Segment/Plugins/Logger/Logger.swift similarity index 72% rename from Sources/Segment/Plugins/Logger.swift rename to Sources/Segment/Plugins/Logger/Logger.swift index 2839f9db..3d6aafa9 100644 --- a/Sources/Segment/Plugins/Logger.swift +++ b/Sources/Segment/Plugins/Logger/Logger.swift @@ -1,8 +1,8 @@ // // Logger.swift -// Segment +// Logger // -// Created by Cody Garvin on 12/14/20. +// Created by Cody Garvin on 8/19/21. // import Foundation @@ -30,6 +30,17 @@ public enum LogFilterKind: Int { case error = 0 // Not Verbose (fail cases | non-recoverable errors) case warning // Semi-verbose (deprecations | potential issues) case debug // Verbose (everything of interest) + + func toString() -> String { + switch (self) { + case .error: + return "ERROR" + case .warning: + return "Warning" + case .debug: + return "Debug" + } + } } /// The Segment logging system has three types of logs: log, metric and history. When adding a target that @@ -64,7 +75,7 @@ public struct LoggingType: Hashable { /// Convience method to find if the LoggingType supports a particular destination. /// - Parameter destination: The particular destination being tested for conformance. /// - Returns: If the destination exists in this LoggingType `true` or `false` will be returned. - fileprivate func contains(_ destination: LogDestination) -> Bool { + internal func contains(_ destination: LogDestination) -> Bool { return allTypes.contains(destination) } } @@ -161,7 +172,7 @@ extension Analytics { public func add(target: LogTarget, type: LoggingType) { apply { (potentialLogger) in if let logger = potentialLogger as? Logger { - logger.loggingMediator[type] = target + logger.loggingMediator[type] = target // THIS IS A BUG, can't add more than one target to the same type set. } } } @@ -174,90 +185,3 @@ extension Analytics { } } } - -// MARK: - Plugin Implementation - -internal class Logger: UtilityPlugin { - public var filterKind = LogFilterKind.debug - - let type = PluginType.utility - var analytics: Analytics? - - fileprivate var loggingMediator = [LoggingType: LogTarget]() - - required init() { } - - internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { - - for (logType, target) in loggingMediator { - if logType.contains(destination) { - target.parseLog(logMessage) - } - } - } - - func flush() { - for (_, target) in loggingMediator { - target.flush() - } - } - -} - -// MARK: - Internal Types - -fileprivate struct LogFactory { - static func buildLog(destination: LoggingType.LogDestination, - title: String, - message: String, - kind: LogFilterKind = .debug, - function: String? = nil, - line: Int? = nil, - event: RawEvent? = nil, - sender: Any? = nil, - value: Double? = nil, - tags: [String]? = nil) throws -> LogMessage { - - switch destination { - case .log: - return GenericLog(kind: kind, message: message, function: function, line: line) - case .metric: - return MetricLog(title: title, message: message, event: event, function: function, line: line) - case .history: - return HistoryLog(message: message, event: event, function: function, line: line, sender: sender) -// default: -// throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) - } - } - - fileprivate struct GenericLog: LogMessage { - var kind: LogFilterKind - var message: String - var event: RawEvent? = nil - var function: String? - var line: Int? - } - - fileprivate struct MetricLog: LogMessage { - var title: String - var kind: LogFilterKind = .debug - var message: String - var event: RawEvent? - var function: String? = nil - var line: Int? = nil - } - - fileprivate struct HistoryLog: LogMessage { - var kind: LogFilterKind = .debug - var message: String - var event: RawEvent? - var function: String? - var line: Int? - var sender: Any? - } -} - -public extension LogTarget { - // Make flush optional with an empty implementation. - func flush() { } -} diff --git a/Sources/Segment/Plugins/Logger/SegmentLogger.swift b/Sources/Segment/Plugins/Logger/SegmentLogger.swift new file mode 100644 index 00000000..be4874b3 --- /dev/null +++ b/Sources/Segment/Plugins/Logger/SegmentLogger.swift @@ -0,0 +1,158 @@ +// +// Logger.swift +// Segment +// +// Created by Cody Garvin on 12/14/20. +// + +import Foundation + +// MARK: - Plugin Implementation + +internal class Logger: UtilityPlugin { + public var filterKind = LogFilterKind.debug + var analytics: Analytics? + + let type = PluginType.utility + + var loggingMediator = [LoggingType: LogTarget]() + + required init() { } + + func configure(analytics: Analytics) { + self.analytics = analytics + try? add(target: ConsoleTarget(), for: LoggingType.log) + } + + internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { + + for (logType, target) in loggingMediator { + if logType.contains(destination) { + target.parseLog(logMessage) + } + } + } + + internal func add(target: LogTarget, for loggingType: LoggingType) throws { + + // Verify the target does not exist, if it does bail out + let filtered = loggingMediator.filter { (type: LoggingType, existingTarget: LogTarget) in + Swift.type(of: existingTarget) == Swift.type(of: target) + } + if filtered.isEmpty == false { throw NSError(domain: "Target already exists", code: 2002, userInfo: nil) } + + // Finally add the target + loggingMediator[loggingType] = target + } + + internal func flush() { + for (_, target) in loggingMediator { + target.flush() + } + } + +} + +// MARK: - Internal Types + +internal struct LogFactory { + static func buildLog(destination: LoggingType.LogDestination, + title: String, + message: String, + kind: LogFilterKind = .debug, + function: String? = nil, + line: Int? = nil, + event: RawEvent? = nil, + sender: Any? = nil, + value: Double? = nil, + tags: [String]? = nil) throws -> LogMessage { + + switch destination { + case .log: + return GenericLog(kind: kind, message: message, function: function, line: line) + case .metric: + return MetricLog(title: title, message: message, event: event, function: function, line: line) + case .history: + return HistoryLog(message: message, event: event, function: function, line: line, sender: sender) +// default: +// throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) + } + } + + fileprivate struct GenericLog: LogMessage { + var kind: LogFilterKind + var message: String + var event: RawEvent? = nil + var function: String? + var line: Int? + } + + fileprivate struct MetricLog: LogMessage { + var title: String + var kind: LogFilterKind = .debug + var message: String + var event: RawEvent? + var function: String? = nil + var line: Int? = nil + } + + fileprivate struct HistoryLog: LogMessage { + var kind: LogFilterKind = .debug + var message: String + var event: RawEvent? + var function: String? + var line: Int? + var sender: Any? + } +} + +public extension LogTarget { + // Make flush optional with an empty implementation. + func flush() { } +} + +internal extension Analytics { + /// The internal logging method for capturing all general types of log messages related to Segment. + /// - Parameters: + /// - message: The main message of the log to be captured. + /// - kind: Usually .error, .warning or .debug, in order of serverity. This helps filter logs based on + /// this added metadata. + /// - function: The name of the function the log came from. This will be captured automatically. + /// - line: The line number in the function the log came from. This will be captured automatically. + func segmentLog(message: String, kind: LogFilterKind? = nil, function: String = #function, line: Int = #line) { + apply { plugin in + if let loggerPlugin = plugin as? Logger { + var filterKind = loggerPlugin.filterKind + if let logKind = kind { + filterKind = logKind + } + do { + let log = try LogFactory.buildLog(destination: .log, title: "", message: message, kind: filterKind, function: function, line: line) + loggerPlugin.log(log, destination: .log) + } catch { + // TODO: LOG TO PRIVATE SEGMENT LOG + } + } + } + } + + /// The internal logging method for capturing metrics related to Segment or other libraries. + /// - Parameters: + /// - type: Metric type, usually .counter or .gauge. Select the one that makes sense for the metric. + /// - name: The title of the metric to track. + /// - value: The value associated with the metric. This would be an incrementing counter or time + /// or pressure gauge. + /// - tags: Any tags that should be associated with the metric. Any extra metadata that may help. + func segmentMetric(_ type: String, name: String, value: Double, tags: [String]? = nil) { + apply { plugin in + if let loggerPlugin = plugin as? Logger { + do { + let log = try LogFactory.buildLog(destination: .metric, title: type, message: name, value: value, tags: tags) + loggerPlugin.log(log, destination: .metric) + } catch { + // TODO: LOG TO PRIVATE SEGMENT LOG + } + } + } + } +} From f8d255c2b1e0cb528922fb49cdd560486381b63b Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Fri, 20 Aug 2021 11:28:04 -0700 Subject: [PATCH 6/9] Fixed console target bug --- Sources/Segment/Plugins/Logger/Logger.swift | 8 ++++++-- Sources/Segment/Plugins/Logger/SegmentLogger.swift | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/Segment/Plugins/Logger/Logger.swift b/Sources/Segment/Plugins/Logger/Logger.swift index 3d6aafa9..8e59a4c8 100644 --- a/Sources/Segment/Plugins/Logger/Logger.swift +++ b/Sources/Segment/Plugins/Logger/Logger.swift @@ -169,10 +169,14 @@ extension Analytics { /// - target: A `LogTarget` that has logic to parse and handle log messages. /// - type: The type consists of `log`, `metric` or `history`. These correspond to the /// public API on Analytics. - public func add(target: LogTarget, type: LoggingType) { + public func add(target: LogTarget, type: LoggingType) throws { apply { (potentialLogger) in if let logger = potentialLogger as? Logger { - logger.loggingMediator[type] = target // THIS IS A BUG, can't add more than one target to the same type set. + do { + try logger.add(target: target, for: type) + } catch { + log(message: "Could not add target: \(error.localizedDescription)", kind: .error) + } } } } diff --git a/Sources/Segment/Plugins/Logger/SegmentLogger.swift b/Sources/Segment/Plugins/Logger/SegmentLogger.swift index be4874b3..1d4aae3b 100644 --- a/Sources/Segment/Plugins/Logger/SegmentLogger.swift +++ b/Sources/Segment/Plugins/Logger/SegmentLogger.swift @@ -15,7 +15,7 @@ internal class Logger: UtilityPlugin { let type = PluginType.utility - var loggingMediator = [LoggingType: LogTarget]() + fileprivate var loggingMediator = [LoggingType: LogTarget]() required init() { } From 15ceabb61fb2966535173c5162582be802b8e575 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Mon, 23 Aug 2021 15:18:50 -0700 Subject: [PATCH 7/9] Added date and os_log --- Sources/Segment/Plugins/Logger/Logger.swift | 2 + .../Plugins/Logger/SegmentLogger.swift | 8 ++- .../Segment/Plugins/Logger/SystemTarget.swift | 52 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 Sources/Segment/Plugins/Logger/SystemTarget.swift diff --git a/Sources/Segment/Plugins/Logger/Logger.swift b/Sources/Segment/Plugins/Logger/Logger.swift index 8e59a4c8..d1b2bcf5 100644 --- a/Sources/Segment/Plugins/Logger/Logger.swift +++ b/Sources/Segment/Plugins/Logger/Logger.swift @@ -88,6 +88,8 @@ public protocol LogMessage { var event: RawEvent? { get } var function: String? { get } var line: Int? { get } + var logType: LoggingType.LogDestination { get } + var dateTime: Date { get } } diff --git a/Sources/Segment/Plugins/Logger/SegmentLogger.swift b/Sources/Segment/Plugins/Logger/SegmentLogger.swift index 1d4aae3b..d949d581 100644 --- a/Sources/Segment/Plugins/Logger/SegmentLogger.swift +++ b/Sources/Segment/Plugins/Logger/SegmentLogger.swift @@ -21,7 +21,7 @@ internal class Logger: UtilityPlugin { func configure(analytics: Analytics) { self.analytics = analytics - try? add(target: ConsoleTarget(), for: LoggingType.log) + try? add(target: SystemTarget(), for: LoggingType.log) } internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { @@ -85,6 +85,8 @@ internal struct LogFactory { var event: RawEvent? = nil var function: String? var line: Int? + var logType: LoggingType.LogDestination = .log + var dateTime = Date() } fileprivate struct MetricLog: LogMessage { @@ -94,6 +96,8 @@ internal struct LogFactory { var event: RawEvent? var function: String? = nil var line: Int? = nil + var logType: LoggingType.LogDestination = .metric + var dateTime = Date() } fileprivate struct HistoryLog: LogMessage { @@ -103,6 +107,8 @@ internal struct LogFactory { var function: String? var line: Int? var sender: Any? + var logType: LoggingType.LogDestination = .history + var dateTime = Date() } } diff --git a/Sources/Segment/Plugins/Logger/SystemTarget.swift b/Sources/Segment/Plugins/Logger/SystemTarget.swift new file mode 100644 index 00000000..1f5a2db2 --- /dev/null +++ b/Sources/Segment/Plugins/Logger/SystemTarget.swift @@ -0,0 +1,52 @@ +// +// File.swift +// File +// +// Created by Cody Garvin on 8/20/21. +// + +import Foundation +import os.log + +class SystemTarget: LogTarget { + + static let logCategory = OSLog(subsystem: "Segment", category: "Log") + static let metricsCategory = OSLog(subsystem: "Segment", category: "Metrics") + static let historyCategory = OSLog(subsystem: "Segment", category: "History") + + func parseLog(_ log: LogMessage) { + var metadata = "" + if let function = log.function, let line = log.line { + metadata = " - \(function):\(line)" + } + + os_log("[Segment %{public}@ %{public}@]\n%{public}@\n", + log: categoryFor(log: log), + type: osLogTypeFromFilterKind(kind: log.kind), + log.kind.toString(), metadata, log.message) // need to fix type + } + + private func categoryFor(log: LogMessage) -> OSLog { + switch log.logType { + case .log: + return SystemTarget.logCategory + case .metric: + return SystemTarget.metricsCategory + case .history: + return SystemTarget.historyCategory + } + } + + private func osLogTypeFromFilterKind(kind: LogFilterKind) -> OSLogType { + var osLogType: OSLogType + switch kind { + case .debug: + osLogType = .info + case .warning: + osLogType = .debug + case .error: + osLogType = .error + } + return osLogType + } +} From 000b7fe32a3ca641e9ed8379b1f23db68f3912b1 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Mon, 23 Aug 2021 15:57:16 -0700 Subject: [PATCH 8/9] Added flag to disable logging --- Sources/Segment/Plugins/Logger/Logger.swift | 20 ++++++++++++++++--- .../Plugins/Logger/SegmentLogger.swift | 12 +++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Sources/Segment/Plugins/Logger/Logger.swift b/Sources/Segment/Plugins/Logger/Logger.swift index d1b2bcf5..a8ca5a83 100644 --- a/Sources/Segment/Plugins/Logger/Logger.swift +++ b/Sources/Segment/Plugins/Logger/Logger.swift @@ -106,6 +106,10 @@ extension Analytics { /// - line: The line number in the function the log came from. This will be captured automatically. public func log(message: String, kind: LogFilterKind? = nil, function: String = #function, line: Int = #line) { apply { plugin in + // Check if we should send off the event + if Logger.loggingEnabled == false { + return + } if let loggerPlugin = plugin as? Logger { var filterKind = loggerPlugin.filterKind if let logKind = kind { @@ -115,7 +119,7 @@ extension Analytics { let log = try LogFactory.buildLog(destination: .log, title: "", message: message, kind: filterKind, function: function, line: line) loggerPlugin.log(log, destination: .log) } catch { - // TODO: LOG TO PRIVATE SEGMENT LOG + segmentLog(message: "Could not build log: \(error.localizedDescription)", kind: .error) } } } @@ -130,12 +134,17 @@ extension Analytics { /// - tags: Any tags that should be associated with the metric. Any extra metadata that may help. public func metric(_ type: String, name: String, value: Double, tags: [String]? = nil) { apply { plugin in + // Check if we should send off the event + if Logger.loggingEnabled == false { + return + } + if let loggerPlugin = plugin as? Logger { do { let log = try LogFactory.buildLog(destination: .metric, title: type, message: name, value: value, tags: tags) loggerPlugin.log(log, destination: .metric) } catch { - // TODO: LOG TO PRIVATE SEGMENT LOG + segmentLog(message: "Could not build metric: \(error.localizedDescription)", kind: .error) } } } @@ -151,12 +160,17 @@ extension Analytics { /// - line: The line number in the function the log came from. This will be captured automatically. public func history(event: RawEvent, sender: AnyObject, function: String = #function, line: Int = #line) { apply { plugin in + // Check if we should send off the event + if Logger.loggingEnabled == false { + return + } + if let loggerPlugin = plugin as? Logger { do { let log = try LogFactory.buildLog(destination: .history, title: event.toString(), message: "", function: function, line: line, event: event, sender: sender) loggerPlugin.log(log, destination: .metric) } catch { - // TODO: LOG TO PRIVATE SEGMENT LOG + segmentLog(message: "Could not build history: \(error.localizedDescription)", kind: .error) } } } diff --git a/Sources/Segment/Plugins/Logger/SegmentLogger.swift b/Sources/Segment/Plugins/Logger/SegmentLogger.swift index d949d581..c77a9090 100644 --- a/Sources/Segment/Plugins/Logger/SegmentLogger.swift +++ b/Sources/Segment/Plugins/Logger/SegmentLogger.swift @@ -16,6 +16,7 @@ internal class Logger: UtilityPlugin { let type = PluginType.utility fileprivate var loggingMediator = [LoggingType: LogTarget]() + internal static var loggingEnabled = true required init() { } @@ -24,6 +25,15 @@ internal class Logger: UtilityPlugin { try? add(target: SystemTarget(), for: LoggingType.log) } + func update(settings: Settings) { + // Check for the server-side flag + if let settingsDictionary = settings.plan?.dictionaryValue, + let enabled = settingsDictionary["logging_enabled"] as? Bool { + Logger.loggingEnabled = enabled + } + + } + internal func log(_ logMessage: LogMessage, destination: LoggingType.LogDestination) { for (logType, target) in loggingMediator { @@ -74,8 +84,6 @@ internal struct LogFactory { return MetricLog(title: title, message: message, event: event, function: function, line: line) case .history: return HistoryLog(message: message, event: event, function: function, line: line, sender: sender) -// default: -// throw NSError(domain: "Could not parse log", code: 2001, userInfo: nil) } } From 3ca79acd189d76dfd45fd8479d22dc31f0df1fc1 Mon Sep 17 00:00:00 2001 From: Cody Garvin Date: Tue, 7 Sep 2021 10:04:40 -0700 Subject: [PATCH 9/9] Added TODO --- Sources/Segment/Plugins/Logger/SegmentLogger.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Segment/Plugins/Logger/SegmentLogger.swift b/Sources/Segment/Plugins/Logger/SegmentLogger.swift index c77a9090..cbbe6732 100644 --- a/Sources/Segment/Plugins/Logger/SegmentLogger.swift +++ b/Sources/Segment/Plugins/Logger/SegmentLogger.swift @@ -59,8 +59,9 @@ internal class Logger: UtilityPlugin { for (_, target) in loggingMediator { target.flush() } + + // TODO: Clean up history container here } - } // MARK: - Internal Types