diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift index 4503f4eaf1..86a63bd956 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift @@ -35,7 +35,7 @@ final class AWSCloudWatchLoggingCategoryClient { private var userIdentifier: String? private var authSubscription: AnyCancellable? { willSet { authSubscription?.cancel() } } private let networkMonitor: LoggingNetworkMonitor - + init( enable: Bool, credentialsProvider: CredentialsProviding, @@ -62,7 +62,7 @@ final class AWSCloudWatchLoggingCategoryClient { self?.handle(payload: payload) } } - + func takeUserIdentifierFromCurrentUser() { Task { do { @@ -74,7 +74,7 @@ final class AWSCloudWatchLoggingCategoryClient { self.updateSessionControllers() } } - + private func updateSessionControllers() { lock.execute { for controller in loggersByKey.values { @@ -82,7 +82,7 @@ final class AWSCloudWatchLoggingCategoryClient { } } } - + private func handle(payload: HubPayload) { enum CognitoEventName: String { case signInAPI = "Auth.signInAPI" @@ -98,15 +98,15 @@ final class AWSCloudWatchLoggingCategoryClient { break } } - + /// - Tag: CloudWatchLoggingCategoryClient.reset func reset() async { lock.execute { loggersByKey = [:] } } - - func getLoggerSessionController(forCategory category: String, logLevel: LogLevel) -> AWSCloudWatchLoggingSessionController? { + + func getLoggerSessionController(forCategory category: String, logLevel: LogLevel) -> AWSCloudWatchLoggingSessionController? { let key = LoggerKey(category: category, logLevel: logLevel) if let existing = loggersByKey[key] { return existing @@ -124,7 +124,7 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { } } } - + func disable() { enabled = false lock.execute { @@ -133,19 +133,18 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { } } } - + var `default`: Logger { return self.logger(forCategory: "Amplify") } - + func logger(forCategory category: String, namespace: String?, logLevel: Amplify.LogLevel) -> Logger { return lock.execute { let key = LoggerKey(category: category, logLevel: logLevel) if let existing = loggersByKey[key] { return existing } - - + let controller = AWSCloudWatchLoggingSessionController(credentialsProvider: credentialsProvider, authentication: authentication, logFilter: self.logFilter, @@ -164,25 +163,25 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { return controller } } - + func logger(forCategory category: String, logLevel: LogLevel) -> Logger { return self.logger(forCategory: category, namespace: nil, logLevel: logLevel) } - + func logger(forCategory category: String) -> Logger { let defaultLogLevel = logFilter.getDefaultLogLevel(forCategory: category, userIdentifier: self.userIdentifier) return self.logger(forCategory: category, namespace: nil, logLevel: defaultLogLevel) } - + func logger(forNamespace namespace: String) -> Logger { self.logger(forCategory: namespace) } - + func logger(forCategory category: String, forNamespace namespace: String) -> Logger { let defaultLogLevel = logFilter.getDefaultLogLevel(forCategory: category, userIdentifier: self.userIdentifier) return self.logger(forCategory: category, namespace: namespace, logLevel: defaultLogLevel) } - + func getInternalClient() -> CloudWatchLogsClientProtocol { guard let client = loggersByKey.first(where: { $0.value.client != nil })?.value.client else { return Fatal.preconditionFailure( @@ -194,7 +193,7 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { } return client } - + func flushLogs() async throws { guard enabled else { return } for logger in loggersByKey.values { diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingError.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingError.swift index 7ce738b55c..e10a67b862 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingError.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingError.swift @@ -12,17 +12,17 @@ import Foundation struct AWSCloudWatchLoggingError: AmplifyError { var errorDescription: String - + var recoverySuggestion: String - + var underlyingError: Error? - + init(errorDescription: ErrorDescription, recoverySuggestion: RecoverySuggestion, error: Error) { self.errorDescription = errorDescription self.recoverySuggestion = recoverySuggestion self.underlyingError = error } - + init(errorDescription: ErrorDescription, recoverySuggestion: RecoverySuggestion) { self.errorDescription = errorDescription self.recoverySuggestion = recoverySuggestion diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingPlugin.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingPlugin.swift index e740e0618a..2271e877d8 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingPlugin.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingPlugin.swift @@ -17,21 +17,21 @@ import Foundation /// delegates all calls to the default Console logger implementation. /// /// - Tag: CloudWatchLoggingPlugin -public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { +public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { /// An instance of the authentication service. var loggingClient: AWSCloudWatchLoggingCategoryClient! - + private var loggingPluginConfiguration: AWSCloudWatchLoggingPluginConfiguration? private var remoteLoggingConstraintsProvider: RemoteLoggingConstraintsProvider? - + public var key: PluginKey { return PluginConstants.awsCloudWatchLoggingPluginKey } - + public var `default`: Logger { loggingClient.default } - + public init( loggingPluginConfiguration: AWSCloudWatchLoggingPluginConfiguration? = nil, remoteLoggingConstraintsProvider: RemoteLoggingConstraintsProvider? = nil @@ -66,37 +66,37 @@ public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { public func logger(forCategory category: String) -> Logger { return loggingClient.logger(forCategory: category) } - + public func logger(forNamespace namespace: String) -> Logger { return loggingClient.logger(forCategory: namespace) } - + public func logger(forCategory category: String, forNamespace namespace: String) -> Logger { return loggingClient.logger(forCategory: category, forNamespace: namespace) } - + /// enable plugin public func enable() { loggingClient.enable() } - + /// disable plugin public func disable() { loggingClient.disable() } - + /// send logs on-demand to AWS CloudWatch public func flushLogs() async throws { try await loggingClient.flushLogs() } - + /// Retrieve the escape hatch to perform low level operations on AWSCloudWatch /// /// - Returns: AWS CloudWatch Client public func getEscapeHatch() -> CloudWatchLogsClientProtocol { return loggingClient.getInternalClient() } - + /// Resets the state of the plugin. /// /// Calls the reset methods on the storage service and authentication service to clean up resources. Setting the @@ -104,7 +104,7 @@ public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { public func reset() async { await loggingClient.reset() } - + /// Configures AWSS3StoragePlugin with the specified configuration. /// /// This method will be invoked as part of the Amplify configuration flow. Retrieves the bucket, region, and @@ -117,14 +117,14 @@ public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { if self.loggingPluginConfiguration == nil, let configuration = try? AWSCloudWatchLoggingPluginConfiguration(bundle: Bundle.main) { self.loggingPluginConfiguration = configuration let authService = AWSAuthService() - + if let remoteConfig = configuration.defaultRemoteConfiguration, self.remoteLoggingConstraintsProvider == nil { self.remoteLoggingConstraintsProvider = DefaultRemoteLoggingConstraintsProvider( endpoint: remoteConfig.endpoint, region: configuration.region, refreshIntervalInSeconds: remoteConfig.refreshIntervalInSeconds) } - + self.loggingClient = AWSCloudWatchLoggingCategoryClient( enable: configuration.enable, credentialsProvider: authService.getCredentialsProvider(), @@ -136,7 +136,7 @@ public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { flushIntervalInSeconds: configuration.flushIntervalInSeconds ) } - + if self.loggingPluginConfiguration == nil { throw LoggingError.configuration( """ @@ -149,12 +149,12 @@ public class AWSCloudWatchLoggingPlugin: LoggingCategoryPlugin { """ ) } - + if self.remoteLoggingConstraintsProvider == nil { let localStore: LoggingConstraintsLocalStore = UserDefaults.standard localStore.reset() } - + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { self.loggingClient.takeUserIdentifierFromCurrentUser() } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift index cccc6ba408..cfa0013092 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift @@ -84,5 +84,6 @@ extension AWSCloudWatchLoggingSession: LogBatchProducer { } extension AWSCloudWatchLoggingError { - static let sessionInternalErrorForUserId = AWSCloudWatchLoggingError(errorDescription: "Internal error while attempting to interpret userId", recoverySuggestion: "") + static let sessionInternalErrorForUserId = AWSCloudWatchLoggingError( + errorDescription: "Internal error while attempting to interpret userId", recoverySuggestion: "") } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift index 7cb6c4a74e..76b32228ce 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift @@ -19,7 +19,7 @@ import Network /// /// - Tag: CloudWatchLogSessionController final class AWSCloudWatchLoggingSessionController { - + var client: CloudWatchLogsClientProtocol? let namespace: String? private let logGroupName: String @@ -32,13 +32,13 @@ final class AWSCloudWatchLoggingSessionController { private var consumer: LogBatchConsumer? private let logFilter: AWSCloudWatchLoggingFilterBehavior private let networkMonitor: LoggingNetworkMonitor - + private var batchSubscription: AnyCancellable? { willSet { batchSubscription?.cancel() } } - + private var authSubscription: AnyCancellable? { willSet { authSubscription?.cancel() @@ -83,13 +83,13 @@ final class AWSCloudWatchLoggingSessionController { self.userIdentifier = userIdentifier self.networkMonitor = networkMonitor } - + func enable() { updateSession() updateConsumer() connectProducerAndConsumer() } - + func disable() { self.batchSubscription = nil self.authSubscription = nil @@ -119,7 +119,7 @@ final class AWSCloudWatchLoggingSessionController { logGroupName: self.logGroupName, userIdentifier: self.userIdentifier) } - + private func connectProducerAndConsumer() { guard let consumer = consumer else { self.batchSubscription = nil @@ -143,14 +143,14 @@ final class AWSCloudWatchLoggingSessionController { } } } - + private func userIdentifierDidChange() { resetCurrentLogs() updateSession() updateConsumer() connectProducerAndConsumer() } - + private func updateSession() { do { self.session = try AWSCloudWatchLoggingSession(category: self.category, @@ -163,19 +163,19 @@ final class AWSCloudWatchLoggingSessionController { print(error) } } - + func setCurrentUser(identifier: String?) { self.userIdentifier = identifier } - + func flushLogs() async throws { guard let logBatches = try await session?.logger.getLogBatches() else { return } - + for batch in logBatches { try await consumeLogBatch(batch) } } - + private func consumeLogBatch(_ batch: LogBatch) async throws { do { try await consumer?.consume(batch: batch) @@ -186,7 +186,7 @@ final class AWSCloudWatchLoggingSessionController { try batch.complete() } } - + private func resetCurrentLogs() { Task { do { @@ -203,27 +203,27 @@ extension AWSCloudWatchLoggingSessionController: Logger { guard self.logFilter.canLog(withCategory: self.category, logLevel: .error, userIdentifier: self.userIdentifier) else { return } session?.logger.error(message()) } - + func error(error: Error) { guard self.logFilter.canLog(withCategory: self.category, logLevel: .error, userIdentifier: self.userIdentifier) else { return } session?.logger.error(error: error) } - + func warn(_ message: @autoclosure () -> String) { guard self.logFilter.canLog(withCategory: self.category, logLevel: .warn, userIdentifier: self.userIdentifier) else { return } session?.logger.warn(message()) } - + func info(_ message: @autoclosure () -> String) { guard self.logFilter.canLog(withCategory: self.category, logLevel: .info, userIdentifier: self.userIdentifier) else { return } session?.logger.info(message()) } - + func debug(_ message: @autoclosure () -> String) { guard self.logFilter.canLog(withCategory: self.category, logLevel: .debug, userIdentifier: self.userIdentifier) else { return } session?.logger.debug(message()) } - + func verbose(_ message: @autoclosure () -> String) { guard self.logFilter.canLog(withCategory: self.category, logLevel: .verbose, userIdentifier: self.userIdentifier) else { return } session?.logger.verbose(message()) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/AWSCloudWatchLoggingPluginConfiguration.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/AWSCloudWatchLoggingPluginConfiguration.swift index 19d1cf67b7..5a9ad61361 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/AWSCloudWatchLoggingPluginConfiguration.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/AWSCloudWatchLoggingPluginConfiguration.swift @@ -99,4 +99,3 @@ extension AWSCloudWatchLoggingPluginConfiguration { } } } - diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteConfiguration.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteConfiguration.swift index f833feb98a..79ca6c14d3 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteConfiguration.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteConfiguration.swift @@ -16,7 +16,7 @@ public struct DefaultRemoteConfiguration: Codable { self.endpoint = endpoint self.refreshIntervalInSeconds = refreshIntervalInSeconds } - + public let endpoint: URL public let refreshIntervalInSeconds: Int } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift index 5f8b29c344..64c9863a96 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift @@ -11,23 +11,23 @@ import AWSPluginsCore import AWSClientRuntime import ClientRuntime -public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsProvider { +public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsProvider { public let refreshIntervalInSeconds: Int private let endpoint: URL private let credentialProvider: CredentialsProviding? private let region: String private let loggingConstraintsLocalStore: LoggingConstraintsLocalStore = UserDefaults.standard - + private var loggingConstraint: LoggingConstraints? { return loggingConstraintsLocalStore.getLocalLoggingConstraints() } - + private var refreshTimer: DispatchSourceTimer? { willSet { refreshTimer?.cancel() } } - + public init( endpoint: URL, region: String, @@ -44,26 +44,26 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr self.refreshIntervalInSeconds = refreshIntervalInSeconds self.setupAutomaticRefreshInterval() } - + public func fetchLoggingConstraints() async throws -> LoggingConstraints { var url = URLRequest(url: endpoint) if let etag = loggingConstraintsLocalStore.getLocalLoggingConstraintsEtag() { url.setValue(etag, forHTTPHeaderField: "If-None-Match") } let signedRequest = try await sigV4Sign(url, region: region) - let (data,response) = try await URLSession.shared.data(for: signedRequest) + let (data, response) = try await URLSession.shared.data(for: signedRequest) if (response as? HTTPURLResponse)?.statusCode == 304, let cachedValue = self.loggingConstraint { return cachedValue } let loggingConstraint = try JSONDecoder().decode(LoggingConstraints.self, from: data) loggingConstraintsLocalStore.setLocalLoggingConstraints(loggingConstraints: loggingConstraint) - + if let etag = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "If-None-Match") { loggingConstraintsLocalStore.setLocalLoggingConstraintsEtag(etag: etag) } return loggingConstraint } - + func sigV4Sign(_ request: URLRequest, region: String) async throws -> URLRequest { var request = request guard let url = request.url else { @@ -72,7 +72,7 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr guard let host = url.host else { throw APIError.unknown("Could not get host from mutable request", "") } - + request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(host, forHTTPHeaderField: "host") @@ -92,11 +92,11 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr .withProtocol(.https) .withHeaders(.init(request.allHTTPHeaderFields ?? [:])) .withBody(.data(request.httpBody)) - + guard let credentialProvider = self.credentialProvider else { return request } - + guard let urlRequest = try await AmplifyAWSSignatureV4Signer().sigV4SignedRequest( requestBuilder: requestBuilder, credentialsProvider: credentialProvider, @@ -113,7 +113,7 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr return request } - + func refresh() { Task { do { @@ -123,13 +123,13 @@ public class DefaultRemoteLoggingConstraintsProvider: RemoteLoggingConstraintsPr } } } - + func setupAutomaticRefreshInterval() { guard refreshIntervalInSeconds != .zero else { refreshTimer = nil return } - + refreshTimer = Self.createRepeatingTimer( timeInterval: TimeInterval(self.refreshIntervalInSeconds), eventHandler: { [weak self] in diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/LoggingConstraints.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/LoggingConstraints.swift index 5995194d7a..41feb1ef7f 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/LoggingConstraints.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/LoggingConstraints.swift @@ -18,7 +18,7 @@ public struct LoggingConstraints: Codable { self.categoryLogLevel = categoryLogLevel self.userLogLevel = userLogLevel } - + public let defaultLogLevel: LogLevel public let categoryLogLevel: [String: LogLevel]? public let userLogLevel: [String: UserLogLevel]? diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/UserLogLevel.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/UserLogLevel.swift index 3a2e29e9b7..dc35f4d9fb 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/UserLogLevel.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/UserLogLevel.swift @@ -16,7 +16,7 @@ public struct UserLogLevel: Codable { self.defaultLogLevel = defaultLogLevel self.categoryLogLevel = categoryLogLevel } - + public let defaultLogLevel: LogLevel public let categoryLogLevel: [String: LogLevel] } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift index 4ec0d6aa87..a0cfb45b7d 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift @@ -12,7 +12,7 @@ import Amplify import Foundation class CloudWatchLoggingConsumer { - + private let client: CloudWatchLogsClientProtocol private let logGroupName: String private let logStreamName: String @@ -38,7 +38,7 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { return } await ensureLogStreamExists() - + let batchByteSize = try encoder.encode(entries).count if entries.count > AWSCloudWatchConstants.maxLogEvents { let smallerEntries = entries.chunked(into: AWSCloudWatchConstants.maxLogEvents) @@ -53,7 +53,7 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { try await sendLogEvents(entries) } } - + } else if batchByteSize > AWSCloudWatchConstants.maxBatchByteSize { let smallerEntries = try chunk(entries, into: AWSCloudWatchConstants.maxBatchByteSize) for entries in smallerEntries { @@ -62,19 +62,19 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { } else { try await sendLogEvents(entries) } - + try batch.complete() } - + private func ensureLogStreamExists() async { if ensureLogStreamExistsComplete { return } - + defer { ensureLogStreamExistsComplete = true } - + let stream = try? await self.client.describeLogStreams(input: .init( logGroupName: self.logGroupName, logStreamNamePrefix: self.logStreamName @@ -84,13 +84,13 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { if stream != nil { return } - + _ = try? await self.client.createLogStream(input: .init( logGroupName: self.logGroupName, logStreamName: self.logStreamName )) } - + private func sendLogEvents(_ entries: [LogEntry]) async throws { let events = convertToCloudWatchInputLogEvents(for: entries) let response = try await self.client.putLogEvents(input: PutLogEventsInput( @@ -110,7 +110,7 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { )) } } - + private func convertToCloudWatchInputLogEvents(for entries: [LogEntry]) -> [CloudWatchLogsClientTypes.InputLogEvent] { let formatter = CloudWatchLoggingEntryFormatter() return entries.map { entry in @@ -120,23 +120,24 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { ) } } - + private func retriable(entries: [LogEntry], in response: PutLogEventsOutput) -> [LogEntry] { guard let tooNewLogEventStartIndex = response.rejectedLogEventsInfo?.tooNewLogEventStartIndex else { return [] } let totalEntries = entries.count - if (tooNewLogEventStartIndex < 0 || tooNewLogEventStartIndex >= totalEntries) { + if tooNewLogEventStartIndex < 0 || tooNewLogEventStartIndex >= totalEntries { return [] } - + var retriableEntries: [LogEntry] = [] for index in tooNewLogEventStartIndex.. [[LogEntry]] { var chunks: [[LogEntry]] = [] var chunk: [LogEntry] = [] @@ -152,7 +153,8 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { currentChunkSize = currentChunkSize + entrySize } } - + return chunks } + // swiftlint:enable shorthand_operator } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingEntryFormatter.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingEntryFormatter.swift index a334d4f3ff..d96efc8a64 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingEntryFormatter.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingEntryFormatter.swift @@ -32,6 +32,6 @@ struct CloudWatchLoggingEntryFormatter { return "\(entry.logLevelName)/\(entry.category): \(namespace): \(entry.message)" } else { return "\(entry.logLevelName)/\(entry.category): \(entry.message)" - } + } } } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift index 12b7ec93c0..d40e0eebe9 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift @@ -24,7 +24,7 @@ struct CloudWatchLoggingStreamNameFormatter { let userIdentifier: String? let deviceIdentifier: String? - + init(userIdentifier: String? = nil) { self.userIdentifier = userIdentifier #if canImport(WatchKit) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift index e5d71f28f0..ff5b5b6c75 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift @@ -11,7 +11,7 @@ import Foundation /// [LogFile](x-source-tag://LogFile) /// protocol LogBatchConsumer { - + /// Processes the given [LogBatch](x-source-tag://LogBatch) and ensures to call /// [LogBatch.complete](x-source-tag://LogBatch.complete) on the given `batch` when /// done. diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift index 5647af3d38..91980583de 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift @@ -12,22 +12,22 @@ import Foundation /// Wrapper around a LogRotation to ensure /// thread-safe usage. actor LogActor { - + private let rotation: LogRotation private let rotationSubject: PassthroughSubject - + /// Initialized the actor with the given directory and fileCountLimit. init(directory: URL, fileSizeLimitInBytes: Int) throws { self.rotation = try LogRotation(directory: directory, fileSizeLimitInBytes: fileSizeLimitInBytes) self.rotationSubject = PassthroughSubject() } - + /// Attempts to persist the given log entry. func record(_ entry: LogEntry) throws { let data = try LogEntryCodec().encode(entry: entry) try write(data) } - + private func write(_ data: Data) throws { try rotation.ensureFileExists() if rotation.currentLogFile.hasSpace(for: data) { @@ -39,22 +39,22 @@ actor LogActor { rotationSubject.send(fileURL) } } - + func rotationPublisher() -> AnyPublisher { return rotationSubject.eraseToAnyPublisher() } - + /// Ensures the contents of the underlying file are flushed to disk. /// /// - Tag: LogActor.record func synchronize() throws { try rotation.currentLogFile.synchronize() } - + func getLogs() throws -> [URL] { return try rotation.getAllLogs() } - + func deleteLogs() throws { try rotation.reset() try synchronize() diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift index 1ee0d66ede..9ea55337a2 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift @@ -10,14 +10,14 @@ import Foundation /// Represents an individual row in a log. struct LogEntry: Codable, Hashable { - + /// The timestamp representing the creation time of the log entry or event. let created: Date /// The Amplify category logical tag of the log entry or event. This will likely be a /// Category name. let category: String - + /// Additional logical tag of the log entry or event. This will likely be a /// the namespace of the code module being logged. let namespace: String? @@ -25,10 +25,10 @@ struct LogEntry: Codable, Hashable { /// An integer representation of the [LogLevel](x-source-tag://LogLevel) /// associated with the receiver. This attribute's uses an Int to accomodate coding. private let level: Int - + /// The main payload String associated with the receiver. let message: String - + /// The log level associated with the receiver. var logLevel: LogLevel { if let result = LogLevel(rawValue: self.level) { @@ -36,7 +36,7 @@ struct LogEntry: Codable, Hashable { } return .error } - + /// - Returns: String representation of log level var logLevelName: String { switch logLevel { @@ -48,11 +48,11 @@ struct LogEntry: Codable, Hashable { case .none: return "NONE" } } - + var millisecondsSince1970: Int { Int((created.timeIntervalSince1970 * 1000.0).rounded()) } - + init(category: String, namespace: String?, level: LogLevel, message: String, created: Date = Date()) { self.created = created self.level = level.rawValue @@ -60,5 +60,5 @@ struct LogEntry: Codable, Hashable { self.namespace = namespace self.message = message } - + } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntryCodec.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntryCodec.swift index 43f0349848..8f78e7959b 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntryCodec.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntryCodec.swift @@ -17,7 +17,7 @@ struct LogEntryCodec { case invalidScheme(log: URL) case invalidEncoding(log: URL) } - + func encode(entry: LogEntry) throws -> Data { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .millisecondsSince1970 @@ -26,19 +26,19 @@ struct LogEntryCodec { data.append(Self.lineDelimiter) return data } - + func decode(data: Data) throws -> LogEntry { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .millisecondsSince1970 return try decoder.decode(LogEntry.self, from: data) } - + func decode(string: String) throws -> LogEntry? { let trimmed = string.trim() let data = Data(trimmed.utf8) return try decode(data: data) } - + func decode(from fileURL: URL) throws -> [LogEntry] { guard fileURL.isFileURL else { throw DecodingError.invalidScheme(log: fileURL) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift index f61f310c53..03ee67607a 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift @@ -12,10 +12,10 @@ import Foundation final class LogFile { let fileURL: URL let sizeLimitInBytes: UInt64 - + private let handle: FileHandle private var count: UInt64 - + /// Creates a new file with the given URL and sets its attributes accordingly. init(forWritingTo fileURL: URL, sizeLimitInBytes: UInt64) throws { self.fileURL = fileURL @@ -35,11 +35,11 @@ final class LogFile { self.count = self.handle.offsetInFile } } - + deinit { try? self.handle.close() } - + /// Returns the number of bytes available in the underlying file. var available: UInt64 { if sizeLimitInBytes > count { @@ -48,32 +48,32 @@ final class LogFile { return 0 } } - + /// Attempts to close the underlying log file. func close() throws { try self.handle.close() } - + /// Atempts to flush the receivers contents to disk. func synchronize() throws { try self.handle.synchronize() } - + /// - Returns: true if writing to the underlying log file will keep its size below the limit. func hasSpace(for data: Data) -> Bool { return UInt64(data.count) <= self.available } - + /// Writes the given **single line of text** represented as a /// Data to the underlying log file. - func write(data: Data) throws { + func write(data: Data) throws { if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { try self.handle.write(contentsOf: data) } else { self.handle.write(data) } try self.handle.synchronize() - count = count + UInt64(data.count) + count = count + UInt64(data.count) // swiftlint:disable:this shorthand_operator } - + } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift index 6fb4ad7f32..32552df26c 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift @@ -9,22 +9,22 @@ import Foundation /// Represents a directory that contains a set of log files that are part of a LogRotation. final class LogRotation { - + enum LogRotationError: Error { /// Represents the scenario when a caller attempts to initialize a /// `LogRotation` with an invalid file size limit (minimum is 1KB). case invalidFileSizeLimitInBytes(Int) } - + static let minimumFileSizeLimitInBytes = 1024 /* 1KB */ - + /// The name pattern of files managed by `LogRotation`. private static let filePattern = #"amplify[.]([0-9])[.]log"# let directory: URL let fileCountLimit: Int = 5 let fileSizeLimitInBytes: UInt64 - + private(set) var currentLogFile: LogFile { willSet { try? currentLogFile.synchronize() @@ -33,17 +33,17 @@ final class LogRotation { } init(directory: URL, fileSizeLimitInBytes: Int) throws { - if (fileSizeLimitInBytes < LogRotation.minimumFileSizeLimitInBytes) { + if fileSizeLimitInBytes < LogRotation.minimumFileSizeLimitInBytes { throw LogRotationError.invalidFileSizeLimitInBytes(fileSizeLimitInBytes) } - + self.directory = directory.absoluteURL self.fileSizeLimitInBytes = UInt64(fileSizeLimitInBytes) self.currentLogFile = try Self.selectNextLogFile(from: self.directory, fileCountLimit: fileCountLimit, fileSizeLimitInBytes: self.fileSizeLimitInBytes) } - + /// Selects the most-available log file. /// /// The criteria is roughly as follows: @@ -58,11 +58,11 @@ final class LogRotation { fileCountLimit: self.fileCountLimit, fileSizeLimitInBytes: self.fileSizeLimitInBytes) } - + func getAllLogs() throws -> [URL] { return try Self.listLogFiles(in: directory) } - + func reset() throws { let existingFiles = try Self.listLogFiles(in: directory) for file in existingFiles { @@ -72,7 +72,7 @@ final class LogRotation { index: 0, fileSizeLimitInBytes: fileSizeLimitInBytes) } - + private static func selectNextLogFile(from directory: URL, fileCountLimit: Int, fileSizeLimitInBytes: UInt64) throws -> LogFile { @@ -82,7 +82,7 @@ final class LogRotation { index: index, fileSizeLimitInBytes: fileSizeLimitInBytes) } - + if let underutilized = try Self.oldestUnderutilizedFile(from: existingFiles, sizeLimitInBytes: fileSizeLimitInBytes) { return try LogFile(forAppending: underutilized, @@ -93,12 +93,12 @@ final class LogRotation { return try LogFile(forWritingTo: oldestFileURL, sizeLimitInBytes: fileSizeLimitInBytes) } - + return try createLogFile(in: directory, index: 0, fileSizeLimitInBytes: fileSizeLimitInBytes) } - + func ensureFileExists() throws { if !FileManager.default.fileExists(atPath: currentLogFile.fileURL.relativePath) { try rotate() @@ -124,7 +124,7 @@ final class LogRotation { } return (lastIndex + 1) % fileCountLimit } - + /// - Returns: The URL for the file with the oldest last modified date (assumes that the list of files are presorted by date with oldest last) that /// also is taking up less than half of the size limit. private static func oldestUnderutilizedFile(from existingFiles: [URL], sizeLimitInBytes: UInt64) throws -> URL? { @@ -143,7 +143,7 @@ final class LogRotation { private static func listLogFiles(in directory: URL) throws -> [URL] { let fileManager: FileManager = FileManager.default let propertyKeys: [URLResourceKey] = [.contentModificationDateKey, .nameKey, .fileSizeKey] - return try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys:propertyKeys) + return try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: propertyKeys) .filter { try index(of: $0) != nil } .sorted(by: { lhs, rhs in let lhsAttributes = try fileManager.attributesOfItem(atPath: lhs.path) @@ -185,15 +185,18 @@ final class LogRotation { let fileURL = directory.appendingPathComponent("amplify.\(index).log") fileManager.createFile(atPath: fileURL.path, contents: nil, - attributes: [FileAttributeKey : Any]()) + attributes: [FileAttributeKey: Any]()) if #available(macOS 11.0, *) { - let resourceValues: [URLResourceKey : Any] = [URLResourceKey.fileProtectionKey: URLFileProtection.complete, URLResourceKey.isExcludedFromBackupKey: true] + let resourceValues: [URLResourceKey: Any] = [ + URLResourceKey.fileProtectionKey: URLFileProtection.complete, + URLResourceKey.isExcludedFromBackupKey: true + ] try (fileURL as NSURL).setResourceValues(resourceValues) - } else { - let resourceValues: [URLResourceKey : Any] = [URLResourceKey.isExcludedFromBackupKey: true] + } else { + let resourceValues: [URLResourceKey: Any] = [URLResourceKey.isExcludedFromBackupKey: true] try (fileURL as NSURL).setResourceValues(resourceValues) } - + return try LogFile(forWritingTo: fileURL, sizeLimitInBytes: fileSizeLimitInBytes) } } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift index 3a2810a877..238dbfb7d5 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift @@ -10,9 +10,9 @@ import Combine import Foundation final class RotatingLogger { - + var logLevel: Amplify.LogLevel - + private let category: String private let namespace: String? private let actor: LogActor @@ -37,29 +37,29 @@ final class RotatingLogger { self.batchSubject = PassthroughSubject() self.logLevel = logLevel } - + /// Attempts to flush the contents of the log to disk. func synchronize() async throws { try await actor.synchronize() } - + func getLogBatches() async throws -> [RotatingLogBatch] { let logs = try await actor.getLogs() return logs.map({RotatingLogBatch(url: $0)}) } - + func resetLogs() async throws { try await actor.deleteLogs() } - + func record(level: LogLevel, message: @autoclosure () -> String) async throws { try await setupSubscription() let entry = LogEntry(category: self.category, namespace: self.namespace, level: level, message: message()) try await self.actor.record(entry) } - + private func setupSubscription() async throws { - if (rotationSubscription == nil) { + if rotationSubscription == nil { let rotationPublisher = await self.actor.rotationPublisher() rotationSubscription = rotationPublisher.sink { [weak self] url in guard let self = self else { return } @@ -72,7 +72,7 @@ final class RotatingLogger { let payload = message() Task { do { - try await self.record(level: level, message:payload) + try await self.record(level: level, message: payload) } catch { let payload = HubPayload( eventName: HubPayload.EventName.Logging.writeLogFailure, @@ -91,28 +91,28 @@ extension RotatingLogger: LogBatchProducer { } extension RotatingLogger: Logger { - + func error(_ message: @autoclosure () -> String) { _record(level: .error, message: message()) } - + func error(error: Error) { let message = String(describing: error) _record(level: .error, message: message) } - + func warn(_ message: @autoclosure () -> String) { _record(level: .warn, message: message()) } - + func info(_ message: @autoclosure () -> String) { _record(level: .info, message: message()) } - + func debug(_ message: @autoclosure () -> String) { _record(level: .debug, message: message()) } - + func verbose(_ message: @autoclosure () -> String) { _record(level: .verbose, message: message()) } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingConstraintsResolver.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingConstraintsResolver.swift index 3c71953fb2..7dce65c2d2 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingConstraintsResolver.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingConstraintsResolver.swift @@ -13,13 +13,13 @@ import AWSPluginsCore class AWSCloudWatchLoggingConstraintsResolver { let loggingPluginConfiguration: AWSCloudWatchLoggingPluginConfiguration let loggingConstraintsLocalStore: LoggingConstraintsLocalStore - + init(loggingPluginConfiguration: AWSCloudWatchLoggingPluginConfiguration, loggingConstraintsLocalStore: LoggingConstraintsLocalStore = UserDefaults.standard) { self.loggingPluginConfiguration = loggingPluginConfiguration self.loggingConstraintsLocalStore = loggingConstraintsLocalStore } - + /// Returns the active valid logging constraints /// /// - Returns: the LoggingConstraints diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingFilter.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingFilter.swift index 149690d40a..861e05367c 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingFilter.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingFilter.swift @@ -35,13 +35,13 @@ class AWSCloudWatchLoggingFilter: AWSCloudWatchLoggingFilterBehavior { } return logLevel.rawValue <= userConstraints.defaultLogLevel.rawValue && userConstraints.defaultLogLevel != .none } - + // 2. look for category constraint, is category and log level enabled if let categoryLogLevel = loggingConstraints.categoryLogLevel?.first(where: { $0.key.lowercased() == loweredCasedCategory })?.value { - + return logLevel.rawValue <= categoryLogLevel.rawValue && categoryLogLevel != .none } - + // 3. look for default constraint return logLevel.rawValue <= loggingConstraints.defaultLogLevel.rawValue && loggingConstraints.defaultLogLevel != .none } @@ -51,18 +51,18 @@ class AWSCloudWatchLoggingFilter: AWSCloudWatchLoggingFilterBehavior { /// - Returns: the default LogLevel func getDefaultLogLevel(forCategory category: String, userIdentifier: String?) -> LogLevel { let loweredCasedCategory = category.lowercased() - + if let userConstraints = loggingConstraints.userLogLevel?.first(where: { $0.key == userIdentifier })?.value { if let categoryLogLevel = userConstraints.categoryLogLevel.first(where: { $0.key.lowercased() == loweredCasedCategory })?.value { return categoryLogLevel } return userConstraints.defaultLogLevel } - + if let categoryLogLevel = loggingConstraints.categoryLogLevel?.first(where: { $0.key.lowercased() == loweredCasedCategory })?.value { return categoryLogLevel } - + return loggingConstraints.defaultLogLevel } } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift index 4ab68734f0..295814e8c5 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift @@ -9,10 +9,10 @@ import Foundation /// Defines AWS CloudWatch SDK constants struct AWSCloudWatchConstants { - + /// the max byte size of log events that can be sent is 1 MB static let maxBatchByteSize: Int64 = 1_000_000 - + /// the max number of log events that can be sent is 10,000 static let maxLogEvents = 10_000 }