diff --git a/wire-ios-system/Source/ExpiringActivity.swift b/wire-ios-system/Source/ExpiringActivity.swift index 87cc24bf33a..48228419d28 100644 --- a/wire-ios-system/Source/ExpiringActivity.swift +++ b/wire-ios-system/Source/ExpiringActivity.swift @@ -47,14 +47,14 @@ public func withExpiringActivity(reason: String, block: @escaping () async throw actor ExpiringActivityManager { - let api: ExpiringActivityInterface - var task: Task? + let api: any ExpiringActivityInterface + var task: Task? init() { self.init(api: ProcessInfo.processInfo) } - init(api: ExpiringActivityInterface) { + init(api: any ExpiringActivityInterface) { self.api = api } @@ -90,7 +90,7 @@ actor ExpiringActivityManager { } } - func startWork(block: @escaping () async throws -> Void, semaphore: DispatchSemaphore) -> Task { + func startWork(block: @escaping () async throws -> Void, semaphore: DispatchSemaphore) -> Task { let task = Task { defer { WireLogger.backgroundActivity.debug("Releasing semaphore") diff --git a/wire-ios-system/Source/Logging/Flow.swift b/wire-ios-system/Source/Logging/Flow.swift index eb68daef703..164fcb46f6e 100644 --- a/wire-ios-system/Source/Logging/Flow.swift +++ b/wire-ios-system/Source/Logging/Flow.swift @@ -63,7 +63,7 @@ open class Flow { /// - Parameters: /// - description: A short single line string describing a point of interest. - public func checkpoint(description: LogConvertible) { + public func checkpoint(description: any LogConvertible) { logger.info(FlowLog( name: name, event: .init(type: .checkpoint, description: description.logDescription, outcome: .success) @@ -81,7 +81,7 @@ open class Flow { /// - Parameters: /// - error: The failure reason. - public func fail(_ error: Error) { + public func fail(_ error: any Error) { logger.error(FlowLog( name: name, event: .init(type: .end, description: String(describing: error), outcome: .failure) @@ -93,7 +93,7 @@ open class Flow { /// - Parameters: /// - reason: The failure reason. - public func fail(_ reason: LogConvertible) { + public func fail(_ reason: any LogConvertible) { logger.error(FlowLog( name: name, event: .init(type: .end, description: reason.logDescription, outcome: .failure) diff --git a/wire-ios-system/Source/Logging/LogAttributes.swift b/wire-ios-system/Source/Logging/LogAttributes.swift index 47d876d703e..9b4100a7781 100644 --- a/wire-ios-system/Source/Logging/LogAttributes.swift +++ b/wire-ios-system/Source/Logging/LogAttributes.swift @@ -16,9 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -public typealias LogAttributes = [LogAttributesKey: Encodable] +public typealias LogAttributes = [LogAttributesKey: any Encodable] -public enum LogAttributesKey: String, Comparable { +public enum LogAttributesKey: String, Comparable, Sendable { case selfClientId = "self_client_id" case selfUserId = "self_user_id" @@ -43,5 +43,5 @@ public enum LogAttributesKey: String, Comparable { } public extension LogAttributes { - static var safePublic = [LogAttributesKey.public: true] + static let safePublic = [LogAttributesKey.public: true] } diff --git a/wire-ios-system/Source/Logging/LoggerProtocol.swift b/wire-ios-system/Source/Logging/LoggerProtocol.swift index a746a80891e..2a692fc5794 100644 --- a/wire-ios-system/Source/Logging/LoggerProtocol.swift +++ b/wire-ios-system/Source/Logging/LoggerProtocol.swift @@ -33,7 +33,7 @@ public protocol LoggerProtocol { extension LoggerProtocol { - func attributesDescription(from attributes: LogAttributes) -> String { + public func attributesDescription(from attributes: LogAttributes) -> String { var logAttributes = attributes // drop attributes used for visibility and category diff --git a/wire-ios-system/Source/Logging/SystemLogger.swift b/wire-ios-system/Source/Logging/SystemLogger.swift index ce095d4116d..824a1f90b62 100644 --- a/wire-ios-system/Source/Logging/SystemLogger.swift +++ b/wire-ios-system/Source/Logging/SystemLogger.swift @@ -23,11 +23,11 @@ public protocol FileLoggerDestination { var log: URL? { get } } -struct SystemLogger: LoggerProtocol { +public struct SystemLogger: LoggerProtocol { let persistQueue = DispatchQueue(label: "persistQueue") - var logFiles: [URL] { + public var logFiles: [URL] { [] } @@ -42,35 +42,37 @@ struct SystemLogger: LoggerProtocol { } } - func debug(_ message: any LogConvertible, attributes: LogAttributes...) { + public init() {} + + public func debug(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .debug) } - func info(_ message: any LogConvertible, attributes: LogAttributes...) { + public func info(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .info) } - func notice(_ message: any LogConvertible, attributes: LogAttributes...) { + public func notice(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .default) } - func warn(_ message: any LogConvertible, attributes: LogAttributes...) { + public func warn(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .fault) } - func error(_ message: any LogConvertible, attributes: LogAttributes...) { + public func error(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .error) } - func critical(_ message: any LogConvertible, attributes: LogAttributes...) { + public func critical(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, osLogType: .fault) } - func addTag(_ key: LogAttributesKey, value: String?) { + public func addTag(_ key: LogAttributesKey, value: String?) { // do nothing, as it's only available on datadog } - private func log(_ message: LogConvertible, attributes: [LogAttributes], osLogType: OSLogType) { + private func log(_ message: any LogConvertible, attributes: [LogAttributes], osLogType: OSLogType) { var mergedAttributes: LogAttributes = [:] attributes.forEach { mergedAttributes.merge($0) { _, new in new } @@ -78,7 +80,7 @@ struct SystemLogger: LoggerProtocol { var logger = OSLog.default if let tag = mergedAttributes[.tag] as? String { - logger = loggers[tag] ?? OSLog(subsystem: Bundle.main.bundleIdentifier ?? "main", category: tag) + logger = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "main", category: tag) } let message = "\(message.logDescription)\(attributesDescription(from: mergedAttributes))" @@ -90,5 +92,3 @@ struct SystemLogger: LoggerProtocol { } } } - -private var loggers: [String: OSLog] = [:] diff --git a/wire-ios-system/Source/Logging/WireLogger.swift b/wire-ios-system/Source/Logging/WireLogger.swift index 99e864da933..a8f4cd2f1de 100644 --- a/wire-ios-system/Source/Logging/WireLogger.swift +++ b/wire-ios-system/Source/Logging/WireLogger.swift @@ -16,12 +16,18 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -public struct WireLogger: LoggerProtocol { +public struct WireLogger: LoggerProtocol, Sendable { - private static var provider = AggregatedLogger(loggers: [ - SystemLogger(), - CocoaLumberjackLogger() - ]) + public static func initialize(loggers: [any LoggerProtocol]) { + guard provider == nil else { + assertionFailure("WireLogger.initialize called more than once") + return + } + + provider = AggregatedLogger(loggers: loggers) + } + + private static nonisolated(unsafe) var provider: (any LoggerProtocol)? public let tag: String @@ -34,46 +40,46 @@ public struct WireLogger: LoggerProtocol { // MARK: - LoggerProtocol public var logFiles: [URL] { - Self.provider.logFiles + Self.provider?.logFiles ?? [] } public func addTag(_ key: LogAttributesKey, value: String?) { - Self.provider.addTag(key, value: value) + Self.provider?.addTag(key, value: value) } public func debug(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.debug(message, attributes: finalizedAttributes(attributes)) + Self.provider?.debug(message, attributes: finalizedAttributes(attributes)) } public func info(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.info(message, attributes: finalizedAttributes(attributes)) + Self.provider?.info(message, attributes: finalizedAttributes(attributes)) } public func notice(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.notice(message, attributes: finalizedAttributes(attributes)) + Self.provider?.notice(message, attributes: finalizedAttributes(attributes)) } public func warn(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.warn(message, attributes: finalizedAttributes(attributes)) + Self.provider?.warn(message, attributes: finalizedAttributes(attributes)) } public func error(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.error(message, attributes: finalizedAttributes(attributes)) + Self.provider?.error(message, attributes: finalizedAttributes(attributes)) } public func critical(_ message: any LogConvertible, attributes: LogAttributes...) { guard shouldLogMessage(message) else { return } - Self.provider.critical(message, attributes: finalizedAttributes(attributes)) + Self.provider?.critical(message, attributes: finalizedAttributes(attributes)) } // MARK: - Private Helpers - private func shouldLogMessage(_ message: LogConvertible) -> Bool { + private func shouldLogMessage(_ message: any LogConvertible) -> Bool { !message.logDescription.isEmpty } @@ -90,10 +96,6 @@ public struct WireLogger: LoggerProtocol { // MARK: Static Functions public static var logFiles: [URL] { - provider.logFiles - } - - public static func addLogger(_ logger: LoggerProtocol) { - provider.addLogger(logger) + provider?.logFiles ?? [] } } diff --git a/wire-ios-system/Source/Logging/WireLoggerObjc.swift b/wire-ios-system/Source/Logging/WireLoggerObjc.swift index cc690c7f5e8..09cd6b23959 100644 --- a/wire-ios-system/Source/Logging/WireLoggerObjc.swift +++ b/wire-ios-system/Source/Logging/WireLoggerObjc.swift @@ -32,7 +32,7 @@ public final class WireLoggerObjc: NSObject { } @objc(logSaveCoreDataError:) - static func logSaveCoreData(error: Error) { + static func logSaveCoreData(error: any Error) { WireLogger.localStorage.error("Failed to save: \(error)", attributes: .safePublic) } } diff --git a/wire-ios-system/Source/ZMSLog.swift b/wire-ios-system/Source/ZMSLog.swift index d67e7e0b3be..ce079b2f8b0 100644 --- a/wire-ios-system/Source/ZMSLog.swift +++ b/wire-ios-system/Source/ZMSLog.swift @@ -47,7 +47,7 @@ public final class ZMSLogEntry: NSObject { /// zmLog.warn("A serious warning!") /// @objc -public final class ZMSLog: NSObject { +public final class ZMSLog: NSObject, Sendable { public typealias LogHook = (_ level: ZMLogLevel, _ tag: String?, _ message: String) -> Void public typealias LogEntryHook = ( diff --git a/wire-ios-system/Tests/ZMLogTests.swift b/wire-ios-system/Tests/ZMLogTests.swift index fccae4b1715..d9353efb806 100644 --- a/wire-ios-system/Tests/ZMLogTests.swift +++ b/wire-ios-system/Tests/ZMLogTests.swift @@ -20,10 +20,9 @@ import XCTest @testable import WireSystem -class ZMLogTests: XCTestCase { +final class ZMLogTests: XCTestCase { override func setUp() { - super.setUp() ZMSLog.debug_resetAllLevels() ZMSLog.clearLogs() } @@ -32,7 +31,6 @@ class ZMLogTests: XCTestCase { ZMSLog.debug_resetAllLevels() ZMSLog.stopRecording() ZMSLog.removeAllLogHooks() - super.tearDown() } func testNumberOfPreviousZipLogURLs() { @@ -207,6 +205,7 @@ extension ZMLogTests { extension ZMLogTests { + @MainActor func testThatLogHookIsCalledWithError() { // GIVEN @@ -255,6 +254,7 @@ extension ZMLogTests { ZMSLog.removeLogHook(token: token) } + @MainActor func testThatLogHookIsCalledWithWarning() { // GIVEN @@ -304,6 +304,7 @@ extension ZMLogTests { ZMSLog.removeLogHook(token: token) } + @MainActor func testThatLogHookIsCalledWithDebugIfEnabled() { // GIVEN @@ -362,6 +363,7 @@ extension ZMLogTests { Thread.sleep(forTimeInterval: 0.2) } + @MainActor func testThatCallsMultipleLogHook() { // GIVEN diff --git a/wire-ios/Wire Notification Service Extension/NotificationService.swift b/wire-ios/Wire Notification Service Extension/NotificationService.swift index 5325f79b2ae..02c4cff36b6 100644 --- a/wire-ios/Wire Notification Service Extension/NotificationService.swift +++ b/wire-ios/Wire Notification Service Extension/NotificationService.swift @@ -29,7 +29,7 @@ final class NotificationService: UNNotificationServiceExtension { override init() { super.init() - WireAnalytics.Datadog.enable() + WireAnalytics.setup() } // MARK: - Methods diff --git a/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift b/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift index c6687003260..c710749715d 100644 --- a/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift +++ b/wire-ios/Wire-iOS Share Extension/ShareExtensionViewController.swift @@ -136,7 +136,7 @@ final class ShareExtensionViewController: SLComposeServiceViewController { } private func setUpDatadog() { - WireAnalytics.Datadog.enable() + WireAnalytics.setup() } override func viewDidLoad() { diff --git a/wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift b/wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift deleted file mode 100644 index bf69cddaa01..00000000000 --- a/wire-ios/Wire-iOS Tests/WireAnalytics_DatadogTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import XCTest -@testable import WireCommonComponents - -class WireAnalytics_DatadogTests: XCTestCase { - - func test_enable_isExecutedOnlyOnce() { - // GIVEN - var count = 0 - WireAnalytics.Datadog.enableOnlyOnce = .init { - count += 1 - } - let concurrentQueue = DispatchQueue(label: "test", attributes: .concurrent) - - // WHEN - for i in 1 ... 1000 { - concurrentQueue.async { - WireAnalytics.Datadog.enable() - } - } - - // THEN - XCTAssertEqual(count, 1) - } -} diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index 6aaff409856..64e456a1721 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -39,7 +39,6 @@ 01BC68512CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */; }; 01BC68652CE496A500445243 /* EmptyPlaceholderContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */; }; 01C1A7C72A54C45A0058D578 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 01C1A7C62A54C45A0058D578 /* SnapshotTesting */; }; - 01C488B02C8A45F80066789E /* WireAnalytics_DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */; }; 01D2C78F2CECC41D00F05E5E /* QRCodeScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */; }; 01D8E71A2BA39CE900B71CB7 /* PrivacyWarningChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */; }; 01F5EAE42B712DD7009FD25D /* LogFileDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F5EAE32B712DD6009FD25D /* LogFileDestination.swift */; }; @@ -2014,7 +2013,6 @@ 01A5E048297FDAB500624B65 /* copyExtraAudioNotifications */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = copyExtraAudioNotifications; sourceTree = ""; }; 01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyConversationSearchResultsView.swift; sourceTree = ""; }; 01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyPlaceholderContainerView.swift; sourceTree = ""; }; - 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireAnalytics_DatadogTests.swift; sourceTree = ""; }; 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerViewController.swift; sourceTree = ""; }; 01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyWarningChecker.swift; sourceTree = ""; }; 01F5EAE32B712DD6009FD25D /* LogFileDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFileDestination.swift; sourceTree = ""; }; @@ -3905,6 +3903,7 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 595C04172CDE85E800DF6C57 /* MainController */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MainController; sourceTree = ""; }; + 5983A3EE2CF86F9A002006F6 /* Logging */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Logging; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -7272,7 +7271,6 @@ A923F1C32428C562003F361F /* ZipFile */, 25CCE9D32BA1EA09002AB21F /* OtherUserDeviceDetailsViewTests.swift */, E95D482E2C57974500F342EE /* CharacterInputFieldSnapshotTests.swift */, - 01C488AF2C8A45F80066789E /* WireAnalytics_DatadogTests.swift */, ); path = "Wire-iOS Tests"; sourceTree = ""; @@ -8506,6 +8504,7 @@ E63C65642C1B456900354BB4 /* Analytics */, 594211BD2C90542100576053 /* FilePreviewGenerator */, 594211CE2C906FEC00576053 /* FileMetaDataGenerator */, + 5983A3EE2CF86F9A002006F6 /* Logging */, CE8E4FB21DF066EE0009F437 /* AudioProcessing.swift */, EF1EB850235F008F00575DD9 /* Bundle+InfoDict.swift */, 5922D6D62BB56FA100A60408 /* Bundle+SecurityFlags.swift */, @@ -8720,6 +8719,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 5983A3EE2CF86F9A002006F6 /* Logging */, + ); name = WireCommonComponents; packageProductDependencies = ( 016A141C2CE6BFC4006A7EF5 /* WireDatadog */, @@ -10559,7 +10561,6 @@ A93E6104233B870300CEA65F /* ConversationListHeaderViewSnapshotTests.swift in Sources */, A95C76C2240E915D0047CD29 /* CompositeMessageCellTests.swift in Sources */, EF223F5D2327A1CF004480FA /* ConversationListViewControllerViewModelTests.swift in Sources */, - 01C488B02C8A45F80066789E /* WireAnalytics_DatadogTests.swift in Sources */, 63C4B3BD2C359D9900C09A93 /* SettingsDebugReportViewControllerSnapshotTests.swift in Sources */, E9B439312B0783BD0069DA63 /* SelfProvider.swift in Sources */, EF2FEDC8228E9E42000883C8 /* UserImageViewContainerSnapshotTests.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Sources/AppDelegate.swift b/wire-ios/Wire-iOS/Sources/AppDelegate.swift index 201eb2f663f..3ba910d22f2 100644 --- a/wire-ios/Wire-iOS/Sources/AppDelegate.swift +++ b/wire-ios/Wire-iOS/Sources/AppDelegate.swift @@ -97,8 +97,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil - ) - -> Bool { + ) -> Bool { guard !application.supportsMultipleScenes else { fatalError("Multiple scenes are currently not supported") @@ -115,8 +114,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // switch logs ZMSLog.switchCurrentLogToPrevious() - // Set up Datadog as logger - WireAnalytics.Datadog.enable() + // Set up Datadog and other loggers + WireAnalytics.setup() WireLogger.appDelegate.info( "application:willFinishLaunchingWithOptions \(String(describing: launchOptions)) (applicationState = \(application.applicationState))" diff --git a/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift b/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift index 57863ae1c65..a1781dc552e 100644 --- a/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift +++ b/wire-ios/WireCommonComponents/Analytics/Datadog/WireAnalytics+Datadog.swift @@ -24,7 +24,7 @@ public extension WireAnalytics { /// Namespace for Datadog analytics. enum Datadog { - private static let shared: WireDatadog = { + static let shared: WireDatadog = { let builder = WireDatadogBuilder() return builder.build() }() @@ -33,42 +33,5 @@ public extension WireAnalytics { public static var userIdentifier: String? { shared.userIdentifier } - - /// Enables Datadog analytics instance if available and makes it a global logger. If Datadog is not available, - /// the function just returns. - /// - Note: this should be called early and **has effect only once** - public static func enable() { - enableOnlyOnce.execute() - } - - static var enableOnlyOnce = OnceOnlyThreadSafeFunction { - shared.enable() - WireLogger.addLogger(shared) - - // pass tags to Datadog through WireLogger - WireLogger.system.addTag(.processId, value: "\(ProcessInfo.processInfo.processIdentifier)") - WireLogger.system.addTag(.processName, value: ProcessInfo.processInfo.processName) - } - } -} - -/// Wrapper class to execute a function just once, thread safe -class OnceOnlyThreadSafeFunction { - private let lock = NSLock() - private var executed = false - private let function: () -> Void - - init(_ function: @escaping () -> Void) { - self.function = function - } - - func execute() { - lock.lock() - defer { lock.unlock() } - - if !executed { - executed = true - function() - } } } diff --git a/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift b/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift index 1bc97140913..451abd17c97 100644 --- a/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift +++ b/wire-ios/WireCommonComponents/Analytics/WireAnalytics.swift @@ -16,5 +16,39 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import Foundation +import WireSystem + /// Namespace for analytics tools. -public enum WireAnalytics {} +public enum WireAnalytics { + + private static let isSetUpLock = NSLock() + private static var isSetUp = false + + public static func setup() { + // Adding a lock here since some app extension might execute this setup in the same process as the main app. + // https://stackoverflow.com/a/62674277 + isSetUpLock.lock() + defer { isSetUpLock.unlock() } + + guard !isSetUp else { + assertionFailure("WireAnalytics.setup() called more than once") + return + } + isSetUp = true + + WireAnalytics.Datadog.shared.enable() + + WireLogger.initialize( + loggers: [ + SystemLogger(), + CocoaLumberjackLogger(), + WireAnalytics.Datadog.shared + ] + ) + + // pass tags to Datadog through WireLogger + WireLogger.system.addTag(.processId, value: "\(ProcessInfo.processInfo.processIdentifier)") + WireLogger.system.addTag(.processName, value: ProcessInfo.processInfo.processName) + } +} diff --git a/wire-ios-system/Source/Logging/CocoaLumberjackLogger.swift b/wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift similarity index 82% rename from wire-ios-system/Source/Logging/CocoaLumberjackLogger.swift rename to wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift index 6acfd97fc2b..37902484c7f 100644 --- a/wire-ios-system/Source/Logging/CocoaLumberjackLogger.swift +++ b/wire-ios/WireCommonComponents/Logging/CocoaLumberjackLogger.swift @@ -18,6 +18,7 @@ import CocoaLumberjackSwift import Foundation +import WireSystem /// Logger to write logs to fileSystem via CocoaLumberjack final class CocoaLumberjackLogger: LoggerProtocol { @@ -35,31 +36,31 @@ final class CocoaLumberjackLogger: LoggerProtocol { fileLogger.logFileManager.unsortedLogFilePaths.map { URL(fileURLWithPath: $0) } } - func debug(_ message: LogConvertible, attributes: LogAttributes...) { + func debug(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .debug) } - func info(_ message: LogConvertible, attributes: LogAttributes...) { + func info(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .info) } - func notice(_ message: LogConvertible, attributes: LogAttributes...) { + func notice(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .info) } - func warn(_ message: LogConvertible, attributes: LogAttributes...) { + func warn(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .warning) } - func error(_ message: LogConvertible, attributes: LogAttributes...) { + func error(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .error) } - func critical(_ message: LogConvertible, attributes: LogAttributes...) { + func critical(_ message: any LogConvertible, attributes: LogAttributes...) { log(message, attributes: attributes, level: .error) } - private func log(_ message: LogConvertible, attributes: [LogAttributes], level: DDLogLevel) { + private func log(_ message: any LogConvertible, attributes: [LogAttributes], level: DDLogLevel) { var mergedAttributes: LogAttributes = [:] attributes.forEach { @@ -84,7 +85,7 @@ final class CocoaLumberjackLogger: LoggerProtocol { DDLog.log(asynchronous: true, message: formatedMessage) } - public func addTag(_ key: LogAttributesKey, value: String?) { + func addTag(_ key: LogAttributesKey, value: String?) { // do nothing }