From 997a50df74c08767172556032cc3095c7988519a Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 4 May 2026 14:16:55 -0700 Subject: [PATCH 1/4] feat(logging): add v3 cloudwatch @spi definitions --- .../Sources/CloudWatchLoggingError.swift | 75 +++++++++++++++++++ .../Configuration/LoggingConstraints.swift | 40 ++++++++++ .../Sources/Domain/FlushStrategy.swift | 15 ++++ .../Sources/Domain/LoggingEvent.swift | 18 +++++ Package.swift | 23 ++++++ 5 files changed, 171 insertions(+) create mode 100644 AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/CloudWatchLoggingError.swift create mode 100644 AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Configuration/LoggingConstraints.swift create mode 100644 AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/FlushStrategy.swift create mode 100644 AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/LoggingEvent.swift diff --git a/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/CloudWatchLoggingError.swift b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/CloudWatchLoggingError.swift new file mode 100644 index 0000000000..acdb358fd1 --- /dev/null +++ b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/CloudWatchLoggingError.swift @@ -0,0 +1,75 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AmplifyFoundation +import Foundation + +/// Represents domain-specific errors within the AmplifyCloudWatchLoggingClient subsystem. +@_spi(AmplifyExperimental) +public enum CloudWatchLoggingError: AmplifyError { + /// Local file I/O or rotation error. + case storage(ErrorDescription, RecoverySuggestion, Error? = nil) + /// CloudWatch API call failed. + case service(ErrorDescription, RecoverySuggestion, Error? = nil) + /// Configuration error. + case configuration(ErrorDescription, RecoverySuggestion, Error? = nil) + /// Catch-all. + case unknown(ErrorDescription, RecoverySuggestion, Error? = nil) + + public var errorDescription: ErrorDescription { + switch self { + case .storage(let description, _, _), + .service(let description, _, _), + .configuration(let description, _, _), + .unknown(let description, _, _): + return description + } + } + + public var recoverySuggestion: RecoverySuggestion { + switch self { + case .storage(_, let suggestion, _), + .service(_, let suggestion, _), + .configuration(_, let suggestion, _), + .unknown(_, let suggestion, _): + return suggestion + } + } + + public var underlyingError: Error? { + switch self { + case .storage(_, _, let error), + .service(_, _, let error), + .configuration(_, _, let error), + .unknown(_, _, let error): + return error + } + } + + public init( + errorDescription: ErrorDescription, + recoverySuggestion: RecoverySuggestion, + error: Error? + ) { + if let error = error as? Self { + self = error + } else { + self = .unknown(errorDescription, recoverySuggestion, error) + } + } + + static func from(_ error: Error) -> CloudWatchLoggingError { + if let loggingError = error as? CloudWatchLoggingError { + return loggingError + } + return .unknown( + "An unknown error occurred", + defaultRecoverySuggestion, + error + ) + } +} diff --git a/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Configuration/LoggingConstraints.swift b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Configuration/LoggingConstraints.swift new file mode 100644 index 0000000000..102d753ea7 --- /dev/null +++ b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Configuration/LoggingConstraints.swift @@ -0,0 +1,40 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AmplifyFoundation +import Foundation + +@_spi(AmplifyExperimental) +public struct LoggingConstraints: Codable, Sendable { + public init( + defaultLogLevel: LogLevel = .error, + namespaceLogLevel: [String: LogLevel] = [:], + userLogLevel: [String: UserLogLevel] = [:] + ) { + self.defaultLogLevel = defaultLogLevel + self.namespaceLogLevel = namespaceLogLevel + self.userLogLevel = userLogLevel + } + + public let defaultLogLevel: LogLevel + public let namespaceLogLevel: [String: LogLevel]? + public let userLogLevel: [String: UserLogLevel]? +} + +@_spi(AmplifyExperimental) +public struct UserLogLevel: Codable, Sendable { + public init( + defaultLogLevel: LogLevel, + namespaceLogLevel: [String: LogLevel] + ) { + self.defaultLogLevel = defaultLogLevel + self.namespaceLogLevel = namespaceLogLevel + } + + public let defaultLogLevel: LogLevel + public let namespaceLogLevel: [String: LogLevel] +} diff --git a/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/FlushStrategy.swift b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/FlushStrategy.swift new file mode 100644 index 0000000000..fb65801c44 --- /dev/null +++ b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/FlushStrategy.swift @@ -0,0 +1,15 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// Strategy for flushing cached log events to CloudWatch. +@_spi(AmplifyExperimental) +public enum FlushStrategy: Sendable { + /// Automatically flush at a regular interval. Default is 60 seconds. + case interval(TimeInterval = 60) +} diff --git a/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/LoggingEvent.swift b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/LoggingEvent.swift new file mode 100644 index 0000000000..e49e6f58ba --- /dev/null +++ b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Domain/LoggingEvent.swift @@ -0,0 +1,18 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// Represents a logging event that can be observed by consumers. +@_spi(AmplifyExperimental) +public enum LoggingEvent: @unchecked Sendable { + /// A log entry failed to be written to local storage. + case writeLogFailure(context: String? = nil, error: Error? = nil) + + /// Log entries failed to be flushed to CloudWatch. + case flushLogFailure(context: String? = nil, error: Error? = nil) +} diff --git a/Package.swift b/Package.swift index b697ef09b4..15f74c13e7 100644 --- a/Package.swift +++ b/Package.swift @@ -550,6 +550,24 @@ let loggingTargets: [Target] = [ ) ] +let cloudWatchLoggingClientTargets: [Target] = [ + .target( + name: "AmplifyCloudWatchLoggingClient", + dependencies: [ + .target(name: "AmplifyFoundation"), + .target(name: "AmplifyFoundationBridge"), + .product(name: "AWSCloudWatchLogs", package: "aws-sdk-swift"), + ], + path: "AmplifyClients/AmplifyCloudWatchLoggingClient/Sources", + resources: [ + .copy("Resources/PrivacyInfo.xcprivacy") + ], + swiftSettings: [ + .enableUpcomingFeature("StrictConcurrency") + ] + ), +] + let foundationTargets: [Target] = [ .target( name: "AmplifyFoundation", @@ -596,6 +614,7 @@ targets.append(contentsOf: pushNotificationsTargets) targets.append(contentsOf: internalPinpointTargets) targets.append(contentsOf: predictionsTargets) targets.append(contentsOf: loggingTargets) +targets.append(contentsOf: cloudWatchLoggingClientTargets) targets.append(contentsOf: foundationTargets) targets.append(contentsOf: foundationBridgeTargets) @@ -659,6 +678,10 @@ let package = Package( name: "AmplifyFirehoseClient", targets: ["AmplifyFirehoseClient"] ), + .library( + name: "AmplifyCloudWatchLoggingClient", + targets: ["AmplifyCloudWatchLoggingClient"] + ), .library( name: "AmplifyFoundation", targets: ["AmplifyFoundation"] From ba5250836d389332fea13bc4b81ed7077910a5d4 Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 4 May 2026 14:24:35 -0700 Subject: [PATCH 2/4] add privacyinfo --- .../Sources/Resources/PrivacyInfo.xcprivacy | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Resources/PrivacyInfo.xcprivacy diff --git a/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Resources/PrivacyInfo.xcprivacy b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..6af16412a0 --- /dev/null +++ b/AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTracking + + NSPrivacyCollectedDataTypes + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + + From 50e27ea0546d6615bc0932636ce62e08bddfcccd Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Mon, 4 May 2026 16:41:13 -0700 Subject: [PATCH 3/4] chore(logging): move shared code to internal module --- ...CloudWatchLoggingStreamNameFormatter.swift | 10 +++---- .../Domain/LogBatch.swift | 6 +--- .../Domain/LogBatchConsumer.swift | 7 ++--- .../Domain/LogBatchProducer.swift | 3 +- .../Persistence/LogActor.swift | 21 +++++--------- .../Persistence/LogFile.swift | 29 +++++++++---------- .../Persistence/LogRotation.swift | 24 +++++++-------- .../Support/CloudWatchConstants.swift} | 6 ++-- .../Support/CloudWatchLoggingMonitor.swift} | 15 +++++----- .../Support/LoggingNetworkMonitor.swift | 10 +++---- .../AWSCloudWatchLoggingCategoryClient.swift | 7 +++-- .../AWSCloudWatchLoggingSession.swift | 1 + ...WSCloudWatchLoggingSessionController.swift | 1 + .../Consumer/CloudWatchLoggingConsumer.swift | 19 +++++++----- .../Producer/RotatingLogBatch.swift | 1 + .../Producer/RotatingLogger.swift | 4 ++- Package.swift | 7 +++++ 17 files changed, 87 insertions(+), 84 deletions(-) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Consumer/CloudWatchLoggingStreamNameFormatter.swift (83%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Domain/LogBatch.swift (74%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Domain/LogBatchConsumer.swift (75%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Domain/LogBatchProducer.swift (68%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Persistence/LogActor.swift (73%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Persistence/LogFile.swift (72%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Persistence/LogRotation.swift (93%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift => Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchConstants.swift} (69%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingMonitor.swift => Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchLoggingMonitor.swift} (75%) rename AmplifyPlugins/{Logging/Sources/AWSCloudWatchLoggingPlugin => Internal/Sources/InternalCloudWatchLogging}/Support/LoggingNetworkMonitor.swift (65%) diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Consumer/CloudWatchLoggingStreamNameFormatter.swift similarity index 83% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Consumer/CloudWatchLoggingStreamNameFormatter.swift index 9b0b81836d..e1720023dd 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingStreamNameFormatter.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Consumer/CloudWatchLoggingStreamNameFormatter.swift @@ -20,10 +20,10 @@ import AppKit #endif /// Responsible for creating pre-formatted CloudWatch stream names. -struct CloudWatchLoggingStreamNameFormatter { +package struct CloudWatchLoggingStreamNameFormatter { - let userIdentifier: String? - var deviceIdentifier: String? { + package let userIdentifier: String? + package var deviceIdentifier: String? { get async { #if canImport(WatchKit) await WKInterfaceDevice.current().identifierForVendor?.uuidString @@ -37,11 +37,11 @@ struct CloudWatchLoggingStreamNameFormatter { } } - init(userIdentifier: String? = nil) { + package init(userIdentifier: String? = nil) { self.userIdentifier = userIdentifier } - func formattedStreamName() async -> String { + package func formattedStreamName() async -> String { return await "\(deviceIdentifier ?? "").\(userIdentifier ?? "guest")" } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatch.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift similarity index 74% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatch.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift index bd0e84c311..6467794661 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatch.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift @@ -8,11 +8,7 @@ import Foundation /// Represents a batch of log that has a series of log entries to produce/consume -protocol LogBatch { - - /// Read the log entries for this log batch - func readEntries() throws -> [LogEntry] - +package protocol LogBatch { /// Log Batches have completed, complete the batch by removing from file system func complete() throws } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchConsumer.swift similarity index 75% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchConsumer.swift index ff5b5b6c75..a5c7545d70 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchConsumer.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchConsumer.swift @@ -7,13 +7,10 @@ import Foundation -/// Represents a general consumer for contents of a -/// [LogFile](x-source-tag://LogFile) -/// -protocol LogBatchConsumer { +package 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. func consume(batch: LogBatch) async throws -} +} \ No newline at end of file diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchProducer.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchProducer.swift similarity index 68% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchProducer.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchProducer.swift index 777af1ccbd..206dbb9a50 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Domain/LogBatchProducer.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchProducer.swift @@ -7,6 +7,7 @@ import Combine -protocol LogBatchProducer { +/// Protocol for a producer of log batches via Combine. +package protocol LogBatchProducer { var logBatchPublisher: AnyPublisher { get } } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogActor.swift similarity index 73% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogActor.swift index 91980583de..9824ea26d7 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogActor.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogActor.swift @@ -5,26 +5,23 @@ // SPDX-License-Identifier: Apache-2.0 // -import Amplify import Combine import Foundation -/// Wrapper around a LogRotation to ensure -/// thread-safe usage. -actor LogActor { +/// Wrapper around a LogRotation to ensure thread-safe usage. +package 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 { + package 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) + package func record(_ data: Data) throws { try write(data) } @@ -40,22 +37,20 @@ actor LogActor { } } - func rotationPublisher() -> AnyPublisher { + package func rotationPublisher() -> AnyPublisher { return rotationSubject.eraseToAnyPublisher() } /// Ensures the contents of the underlying file are flushed to disk. - /// - /// - Tag: LogActor.record - func synchronize() throws { + package func synchronize() throws { try rotation.currentLogFile.synchronize() } - func getLogs() throws -> [URL] { + package func getLogs() throws -> [URL] { return try rotation.getAllLogs() } - func deleteLogs() throws { + package func deleteLogs() throws { try rotation.reset() try synchronize() } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogFile.swift similarity index 72% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogFile.swift index 9f3d35bc81..698b4075b2 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogFile.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogFile.swift @@ -7,17 +7,16 @@ import Foundation -/// Represents an individual log file on disk used as part of a -/// LogRotation -final class LogFile { - let fileURL: URL - let sizeLimitInBytes: UInt64 +/// Represents an individual log file on disk used as part of a LogRotation. +package final class LogFile { + package let fileURL: URL + package 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 { + package init(forWritingTo fileURL: URL, sizeLimitInBytes: UInt64) throws { self.fileURL = fileURL self.sizeLimitInBytes = sizeLimitInBytes self.handle = try FileHandle(forWritingTo: fileURL) @@ -25,7 +24,7 @@ final class LogFile { } /// Opens a file for updating with the given URL and sets its attributes accordingly. - init(forAppending fileURL: URL, sizeLimitInBytes: UInt64) throws { + package init(forAppending fileURL: URL, sizeLimitInBytes: UInt64) throws { self.fileURL = fileURL self.sizeLimitInBytes = sizeLimitInBytes self.handle = try FileHandle(forUpdating: fileURL) @@ -41,7 +40,7 @@ final class LogFile { } /// Returns the number of bytes available in the underlying file. - var available: UInt64 { + package var available: UInt64 { if sizeLimitInBytes > count { return sizeLimitInBytes - count } else { @@ -50,23 +49,22 @@ final class LogFile { } /// Attempts to close the underlying log file. - func close() throws { + package func close() throws { try handle.close() } - /// Atempts to flush the receivers contents to disk. - func synchronize() throws { + /// Attempts to flush the receivers contents to disk. + package func synchronize() throws { try handle.synchronize() } /// - Returns: true if writing to the underlying log file will keep its size below the limit. - func hasSpace(for data: Data) -> Bool { + package func hasSpace(for data: Data) -> Bool { return UInt64(data.count) <= available } - /// Writes the given **single line of text** represented as a - /// Data to the underlying log file. - func write(data: Data) throws { + /// Writes the given data to the underlying log file. + package func write(data: Data) throws { if #available(macOS 12.0, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { try handle.write(contentsOf: data) } else { @@ -75,5 +73,4 @@ final class LogFile { try handle.synchronize() count = count + UInt64(data.count) // swiftlint:disable:this shorthand_operator } - } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogRotation.swift similarity index 93% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogRotation.swift index e56f553be7..7baff5e450 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogRotation.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Persistence/LogRotation.swift @@ -8,32 +8,32 @@ import Foundation /// Represents a directory that contains a set of log files that are part of a LogRotation. -final class LogRotation { +package final class LogRotation { - enum LogRotationError: Error { + package 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 = 1_024 /* 1KB */ - static let fileCountLimit: Int = 5 + package static let minimumFileSizeLimitInBytes = 1_024 /* 1KB */ + package static let fileCountLimit: Int = 5 /// The name pattern of files managed by `LogRotation`. private static let filePattern = #"amplify[.]([0-9])[.]log"# - let directory: URL + package let directory: URL - let fileSizeLimitInBytes: UInt64 + package let fileSizeLimitInBytes: UInt64 - private(set) var currentLogFile: LogFile { + package private(set) var currentLogFile: LogFile { willSet { try? currentLogFile.synchronize() try? currentLogFile.close() } } - init(directory: URL, fileSizeLimitInBytes: Int) throws { + package init(directory: URL, fileSizeLimitInBytes: Int) throws { if fileSizeLimitInBytes < LogRotation.minimumFileSizeLimitInBytes { throw LogRotationError.invalidFileSizeLimitInBytes(fileSizeLimitInBytes) } @@ -56,7 +56,7 @@ final class LogRotation { /// has not been created, it is created and selected. /// 2. Any files containing less than half the limit are filtered, then the one with the oldest last modified date is selected. /// 3. If no files matching #1 are present, the file with the oldest last modified date is cleared and selected. - func rotate() throws { + package func rotate() throws { currentLogFile = try Self.selectNextLogFile( from: directory, fileCountLimit: Self.fileCountLimit, @@ -64,11 +64,11 @@ final class LogRotation { ) } - func getAllLogs() throws -> [URL] { + package func getAllLogs() throws -> [URL] { return try Self.listLogFiles(in: directory) } - func reset() throws { + package func reset() throws { let existingFiles = try Self.listLogFiles(in: directory) for file in existingFiles { try FileManager.default.removeItem(at: file) @@ -118,7 +118,7 @@ final class LogRotation { ) } - func ensureFileExists() throws { + package func ensureFileExists() throws { if !FileManager.default.fileExists(atPath: currentLogFile.fileURL.relativePath) { try rotate() } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchConstants.swift similarity index 69% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchConstants.swift index 28bc0e5e8e..1f045ee28d 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/Constants/AWSCloudWatchConstants.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchConstants.swift @@ -8,11 +8,11 @@ import Foundation /// Defines AWS CloudWatch SDK constants -enum AWSCloudWatchConstants { +package enum CloudWatchConstants { /// the max byte size of log events that can be sent is 1 MB - static let maxBatchByteSize: Int64 = 1_000_000 + package 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 + package static let maxLogEvents = 10_000 } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingMonitor.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchLoggingMonitor.swift similarity index 75% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingMonitor.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchLoggingMonitor.swift index fbcf5ad06b..c1bcde9dcd 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/AWSCloudWatchLoggingMonitor.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/CloudWatchLoggingMonitor.swift @@ -7,8 +7,8 @@ import Foundation -/// Provides a monitor to automatically flush the log at a specific TimeInterval -class AWSCLoudWatchLoggingMonitor { +/// Provides a monitor to automatically flush the log at a specific TimeInterval. +package class CloudWatchLoggingMonitor { private let automaticFlushLogsInterval: TimeInterval private var automaticFlushLogsTimer: DispatchSourceTimer? { willSet { @@ -16,15 +16,14 @@ class AWSCLoudWatchLoggingMonitor { } } - private weak var eventDelegate: AWSCloudWatchLoggingMonitorDelegate? + private weak var eventDelegate: CloudWatchLoggingMonitorDelegate? - init(flushIntervalInSeconds: TimeInterval, eventDelegate: AWSCloudWatchLoggingMonitorDelegate?) { + package init(flushIntervalInSeconds: TimeInterval, eventDelegate: CloudWatchLoggingMonitorDelegate?) { self.automaticFlushLogsInterval = flushIntervalInSeconds self.eventDelegate = eventDelegate } - /// enable the automatic flushing of logs at a specific time interval by calling the monitor delegate - func setAutomaticFlushIntervals() { + package func setAutomaticFlushIntervals() { guard automaticFlushLogsInterval != .zero else { automaticFlushLogsTimer = nil return @@ -35,7 +34,7 @@ class AWSCLoudWatchLoggingMonitor { eventHandler: { [weak self] in guard let self else { return } eventDelegate?.handleAutomaticFlushIntervalEvent() - } + } ) automaticFlushLogsTimer?.resume() } @@ -48,6 +47,6 @@ class AWSCLoudWatchLoggingMonitor { } } -public protocol AWSCloudWatchLoggingMonitorDelegate: AnyObject { +package protocol CloudWatchLoggingMonitorDelegate: AnyObject { func handleAutomaticFlushIntervalEvent() } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/LoggingNetworkMonitor.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/LoggingNetworkMonitor.swift similarity index 65% rename from AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/LoggingNetworkMonitor.swift rename to AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/LoggingNetworkMonitor.swift index 25175a9846..35b8d4f109 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Support/LoggingNetworkMonitor.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Support/LoggingNetworkMonitor.swift @@ -8,23 +8,23 @@ import Foundation import Network -/// Provides a network monitor for logging -protocol LoggingNetworkMonitor: AnyObject { +/// Provides a network monitor for logging. +package protocol LoggingNetworkMonitor: AnyObject { var isOnline: Bool { get } func startMonitoring(using queue: DispatchQueue) func stopMonitoring() } extension NWPathMonitor: LoggingNetworkMonitor { - var isOnline: Bool { + package var isOnline: Bool { currentPath.status == .satisfied } - func startMonitoring(using queue: DispatchQueue) { + package func startMonitoring(using queue: DispatchQueue) { start(queue: queue) } - func stopMonitoring() { + package func stopMonitoring() { cancel() } } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift index cb8c9dfc41..c5a4b83301 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift @@ -11,6 +11,7 @@ import AWSCloudWatchLogs import AWSPluginsCore import Combine import Foundation +import InternalCloudWatchLogging import Network import SmithyIdentity @@ -31,7 +32,7 @@ final class AWSCloudWatchLoggingCategoryClient { private let authentication: AuthCategoryUserBehavior private var loggersByKey: [LoggerKey: AWSCloudWatchLoggingSessionController] = [:] private let localStoreMaxSizeInMB: Int - private var automaticFlushLogMonitor: AWSCLoudWatchLoggingMonitor? + private var automaticFlushLogMonitor: CloudWatchLoggingMonitor? private let logFilter: AWSCloudWatchLoggingFilterBehavior private var userIdentifier: String? private var authSubscription: AnyCancellable? { willSet { authSubscription?.cancel() } } @@ -57,7 +58,7 @@ final class AWSCloudWatchLoggingCategoryClient { self.logFilter = AWSCloudWatchLoggingFilter(loggingConstraintsResolver: loggingConstraintsResolver) self.networkMonitor = networkMonitor self.networkMonitor.startMonitoring(using: DispatchQueue(label: "com.amazonaws.awscloudwatchlogging.networkmonitor")) - self.automaticFlushLogMonitor = AWSCLoudWatchLoggingMonitor(flushIntervalInSeconds: TimeInterval(flushIntervalInSeconds), eventDelegate: self) + self.automaticFlushLogMonitor = CloudWatchLoggingMonitor(flushIntervalInSeconds: TimeInterval(flushIntervalInSeconds), eventDelegate: self) automaticFlushLogMonitor?.setAutomaticFlushIntervals() self.authSubscription = Amplify.Hub.publisher(for: .auth).sink { [weak self] payload in self?.handle(payload: payload) @@ -206,7 +207,7 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { } } -extension AWSCloudWatchLoggingCategoryClient: AWSCloudWatchLoggingMonitorDelegate { +extension AWSCloudWatchLoggingCategoryClient: CloudWatchLoggingMonitorDelegate { func handleAutomaticFlushIntervalEvent() { Task { try await flushLogs() diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift index bfae5e91fb..77522a27fa 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSession.swift @@ -9,6 +9,7 @@ import Amplify import Combine import CryptoKit import Foundation +import InternalCloudWatchLogging /// Responsible for containg the logger of an individual **tag** and **user session** (wheter logged in /// or not) pair. diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift index e730a7ad6a..4115e65fd9 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift @@ -11,6 +11,7 @@ import AWSCloudWatchLogs import AWSPluginsCore import Combine import Foundation +import InternalCloudWatchLogging @_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials import Network import SmithyIdentity diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift index 8c5a7993ce..7fb3e1452a 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift @@ -10,6 +10,7 @@ import AWSClientRuntime import AWSCloudWatchLogs import AWSPluginsCore import Foundation +import InternalCloudWatchLogging class CloudWatchLoggingConsumer { @@ -44,7 +45,11 @@ class CloudWatchLoggingConsumer { extension CloudWatchLoggingConsumer: LogBatchConsumer { func consume(batch: LogBatch) async throws { - let entries = try batch.readEntries() + guard let rotatingBatch = batch as? RotatingLogBatch else { + try batch.complete() + return + } + let entries = try rotatingBatch.readEntries() if entries.isEmpty { try batch.complete() return @@ -70,13 +75,13 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { return } - if entriesCopy.count > AWSCloudWatchConstants.maxLogEvents { - let smallerEntries = entriesCopy.chunked(into: AWSCloudWatchConstants.maxLogEvents) + if entriesCopy.count > CloudWatchConstants.maxLogEvents { + let smallerEntries = entriesCopy.chunked(into: CloudWatchConstants.maxLogEvents) for entries in smallerEntries { do { let entrySize = try safeEncode(entries).count - if entrySize > AWSCloudWatchConstants.maxBatchByteSize { - let chunks = try chunk(entries, into: AWSCloudWatchConstants.maxBatchByteSize) + if entrySize > CloudWatchConstants.maxBatchByteSize { + let chunks = try chunk(entries, into: CloudWatchConstants.maxBatchByteSize) for chunk in chunks { try await sendLogEvents(chunk) } @@ -88,9 +93,9 @@ extension CloudWatchLoggingConsumer: LogBatchConsumer { continue } } - } else if batchByteSize > AWSCloudWatchConstants.maxBatchByteSize { + } else if batchByteSize > CloudWatchConstants.maxBatchByteSize { do { - let smallerEntries = try chunk(entriesCopy, into: AWSCloudWatchConstants.maxBatchByteSize) + let smallerEntries = try chunk(entriesCopy, into: CloudWatchConstants.maxBatchByteSize) for entries in smallerEntries { try await sendLogEvents(entries) } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift index 8b6a2f213a..d7f06c0431 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift @@ -6,6 +6,7 @@ // import Foundation +import InternalCloudWatchLogging /// Concrete implementaiton of LogBatch that reads log file and remove log files. /// LogBatch/RotatingLogBatch are emited subjects of RotatingLogger. diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift index d66f118585..d21d6baa66 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogger.swift @@ -8,6 +8,7 @@ import Amplify @preconcurrency import Combine import Foundation +import InternalCloudWatchLogging final class RotatingLogger { @@ -56,7 +57,8 @@ final class RotatingLogger { func record(level: LogLevel, message: @autoclosure () -> String) async throws { try await setupSubscription() let entry = LogEntry(category: category, namespace: namespace, level: level, message: message()) - try await actor.record(entry) + let data = try LogEntryCodec().encode(entry: entry) + try await actor.record(data) } private func setupSubscription() async throws { diff --git a/Package.swift b/Package.swift index 15f74c13e7..4d968961dc 100644 --- a/Package.swift +++ b/Package.swift @@ -523,12 +523,18 @@ let predictionsTargets: [Target] = [ ] let loggingTargets: [Target] = [ + .target( + name: "InternalCloudWatchLogging", + dependencies: [], + path: "AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging" + ), .target( name: "AWSCloudWatchLoggingPlugin", dependencies: [ .target(name: "Amplify"), .target(name: "AWSPluginsCore"), .target(name: "InternalAmplifyCredentials"), + .target(name: "InternalCloudWatchLogging"), .product(name: "AWSCloudWatchLogs", package: "aws-sdk-swift"), ], path: "AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin", @@ -556,6 +562,7 @@ let cloudWatchLoggingClientTargets: [Target] = [ dependencies: [ .target(name: "AmplifyFoundation"), .target(name: "AmplifyFoundationBridge"), + .target(name: "InternalCloudWatchLogging"), .product(name: "AWSCloudWatchLogs", package: "aws-sdk-swift"), ], path: "AmplifyClients/AmplifyCloudWatchLoggingClient/Sources", From 2f378fb57ca00614270cd65a540d382f8d1d04cc Mon Sep 17 00:00:00 2001 From: Abhash Kumar Singh Date: Tue, 5 May 2026 11:49:37 -0700 Subject: [PATCH 4/4] fix unit tests --- .../Domain/LogBatch.swift | 7 +++++-- .../Domain/LogEntryRepresentable.swift | 16 ++++++++++++++++ .../AWSCloudWatchLoggingCategoryClient.swift | 2 +- .../Consumer/CloudWatchLoggingConsumer.swift | 10 +++------- .../Persistence/LogEntry.swift | 3 ++- .../Producer/RotatingLogBatch.swift | 2 +- .../AWSCloudWatchLoggingMonitorTests.swift | 9 +++++---- ...CloudWatchLoggingSessionControllerTests.swift | 1 + .../CloudWatchLogConsumerTests.swift | 3 ++- .../LogActorTests.swift | 11 ++++++----- .../LogFileTests.swift | 1 + .../LogRotationTests.swift | 1 + .../LoggingNetworkMonitorTests.swift | 1 + .../RotatingLogBatchTests.swift | 4 +++- .../RotatingLoggerTests.swift | 1 + 15 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogEntryRepresentable.swift diff --git a/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift index 6467794661..79f438de07 100644 --- a/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatch.swift @@ -7,8 +7,11 @@ import Foundation -/// Represents a batch of log that has a series of log entries to produce/consume +/// Represents a batch of log entries to produce/consume. package protocol LogBatch { - /// Log Batches have completed, complete the batch by removing from file system + /// Read the log entries for this log batch. + func readEntries() throws -> [LogEntryRepresentable] + + /// Log batch has completed, complete the batch by removing from file system. func complete() throws } diff --git a/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogEntryRepresentable.swift b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogEntryRepresentable.swift new file mode 100644 index 0000000000..ecdbef92ee --- /dev/null +++ b/AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogEntryRepresentable.swift @@ -0,0 +1,16 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +/// Protocol that abstracts a log entry for use in shared logging infrastructure. +/// Each consuming target provides its own concrete `LogEntry` type. +package protocol LogEntryRepresentable { + var created: Date { get } + var message: String { get } + var millisecondsSince1970: Int { get } +} diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift index c5a4b83301..62c3fa6a04 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingCategoryClient.swift @@ -208,7 +208,7 @@ extension AWSCloudWatchLoggingCategoryClient: LoggingCategoryClientBehavior { } extension AWSCloudWatchLoggingCategoryClient: CloudWatchLoggingMonitorDelegate { - func handleAutomaticFlushIntervalEvent() { + package func handleAutomaticFlushIntervalEvent() { Task { try await flushLogs() } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift index 7fb3e1452a..f6a1b6e2f1 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Consumer/CloudWatchLoggingConsumer.swift @@ -44,13 +44,9 @@ class CloudWatchLoggingConsumer { } extension CloudWatchLoggingConsumer: LogBatchConsumer { - func consume(batch: LogBatch) async throws { - guard let rotatingBatch = batch as? RotatingLogBatch else { - try batch.complete() - return - } - let entries = try rotatingBatch.readEntries() - if entries.isEmpty { + func consume(batch: any LogBatch) async throws { + let rawEntries = try batch.readEntries() + guard let entries = rawEntries as? [LogEntry], !entries.isEmpty else { try batch.complete() return } diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift index 76210d29cc..6764783e31 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Persistence/LogEntry.swift @@ -7,9 +7,10 @@ import Amplify import Foundation +import InternalCloudWatchLogging /// Represents an individual row in a log. -struct LogEntry: Codable, Hashable { +struct LogEntry: Codable, Hashable, LogEntryRepresentable { /// The timestamp representing the creation time of the log entry or event. let created: Date diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift index d7f06c0431..dbca7b42fd 100644 --- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift +++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Producer/RotatingLogBatch.swift @@ -23,7 +23,7 @@ struct RotatingLogBatch { } extension RotatingLogBatch: LogBatch { - func readEntries() throws -> [LogEntry] { + func readEntries() throws -> [LogEntryRepresentable] { let codec = LogEntryCodec() let unsorted = try codec.decode(from: url) return unsorted.sorted(by: { lhs, rhs in diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingMonitorTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingMonitorTests.swift index 4139643c39..dee2832215 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingMonitorTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingMonitorTests.swift @@ -10,14 +10,15 @@ import Foundation import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class AWSCloudWatchLoggingMonitorTests: XCTestCase { - var monitor: AWSCLoudWatchLoggingMonitor! + var monitor: CloudWatchLoggingMonitor! var invokedExpectation: XCTestExpectation! override func setUp() async throws { - monitor = AWSCLoudWatchLoggingMonitor(flushIntervalInSeconds: 2, eventDelegate: self) + monitor = CloudWatchLoggingMonitor(flushIntervalInSeconds: 2, eventDelegate: self) invokedExpectation = expectation(description: "Delegate is invoked") } @@ -36,8 +37,8 @@ final class AWSCloudWatchLoggingMonitorTests: XCTestCase { } } -extension AWSCloudWatchLoggingMonitorTests: AWSCloudWatchLoggingMonitorDelegate { - func handleAutomaticFlushIntervalEvent() { +extension AWSCloudWatchLoggingMonitorTests: CloudWatchLoggingMonitorDelegate { + package func handleAutomaticFlushIntervalEvent() { invokedExpectation.fulfill() } } diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingSessionControllerTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingSessionControllerTests.swift index db0af0b1bd..7bf210a259 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingSessionControllerTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/AWSCloudWatchLoggingSessionControllerTests.swift @@ -6,6 +6,7 @@ // @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging import Amplify import Network diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/CloudWatchLogConsumerTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/CloudWatchLogConsumerTests.swift index 13ea0ce61f..5c798ceddc 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/CloudWatchLogConsumerTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/CloudWatchLogConsumerTests.swift @@ -11,6 +11,7 @@ import Foundation import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class CloudWatchLogConsumerTests: XCTestCase { @@ -316,7 +317,7 @@ final class CloudWatchLogConsumerTests: XCTestCase { extension CloudWatchLogConsumerTests: LogBatch { - func readEntries() throws -> [LogEntry] { + func readEntries() throws -> [any LogEntryRepresentable] { interactions.append(#function) return entries } diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift index d58467fe91..af6f269b9a 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogActorTests.swift @@ -10,6 +10,7 @@ import Amplify import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class LogActorTests: XCTestCase { @@ -50,7 +51,7 @@ final class LogActorTests: XCTestCase { XCTAssertEqual(rotations, []) let entry = LogEntry(category: "LogActorTests", namespace: nil, level: .error, message: UUID().uuidString, created: .init(timeIntervalSince1970: 0)) - try await systemUnderTest.record(entry) + try await systemUnderTest.record(LogEntryCodec().encode(entry: entry)) try await systemUnderTest.synchronize() XCTAssertEqual(rotations, []) @@ -71,7 +72,7 @@ final class LogActorTests: XCTestCase { let numberOfEntries = (fileSizeLimitInBytes / size) + 1 let entries = (0 ..< numberOfEntries).map { LogEntry(category: "", namespace: nil, level: .error, message: "\($0)", created: .init(timeIntervalSince1970: Double($0))) } for entry in entries { - try await systemUnderTest.record(entry) + try await systemUnderTest.record(LogEntryCodec().encode(entry: entry)) } try await systemUnderTest.synchronize() @@ -98,7 +99,7 @@ final class LogActorTests: XCTestCase { /// Then: the log file is emptied func testLogActorDeletesEntry() async throws { let entry = LogEntry(category: "LogActorTests", namespace: nil, level: .error, message: UUID().uuidString, created: .init(timeIntervalSince1970: 0)) - try await systemUnderTest.record(entry) + try await systemUnderTest.record(LogEntryCodec().encode(entry: entry)) try await systemUnderTest.synchronize() let files = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) @@ -122,7 +123,7 @@ final class LogActorTests: XCTestCase { let numberOfEntries = (fileSizeLimitInBytes / size) + 1 let entries = (0 ..< numberOfEntries).map { LogEntry(category: "", namespace: nil, level: .error, message: "\($0)", created: .init(timeIntervalSince1970: Double($0))) } for entry in entries { - try await systemUnderTest.record(entry) + try await systemUnderTest.record(LogEntryCodec().encode(entry: entry)) } try await systemUnderTest.synchronize() @@ -141,7 +142,7 @@ final class LogActorTests: XCTestCase { XCTAssertEqual(logs.count, 0) let entry = LogEntry(category: "LogActorTests", namespace: nil, level: .error, message: UUID().uuidString, created: .init(timeIntervalSince1970: 0)) - try await systemUnderTest.record(entry) + try await systemUnderTest.record(LogEntryCodec().encode(entry: entry)) try await systemUnderTest.synchronize() logs = try await systemUnderTest.getLogs() diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogFileTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogFileTests.swift index 5c81e92a05..3ae3e7f978 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogFileTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogFileTests.swift @@ -9,6 +9,7 @@ import Amplify import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class LogFileTests: XCTestCase { diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogRotationTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogRotationTests.swift index 6299098d00..9e9e8f5732 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogRotationTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LogRotationTests.swift @@ -8,6 +8,7 @@ import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class LogRotationTests: XCTestCase { diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LoggingNetworkMonitorTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LoggingNetworkMonitorTests.swift index 6949751a26..16a5ef035d 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LoggingNetworkMonitorTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/LoggingNetworkMonitorTests.swift @@ -12,6 +12,7 @@ import XCTest @testable import AmplifyTestCommon @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class LoggingNetworkMonitorTests: XCTestCase { func testNetworkMonitorEvent() { diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLogBatchTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLogBatchTests.swift index 283d33f6fb..96787d5075 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLogBatchTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLogBatchTests.swift @@ -10,6 +10,7 @@ import Foundation import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class RotatingLogBatchTests: XCTestCase { var fileURL: URL! @@ -41,7 +42,8 @@ final class RotatingLogBatchTests: XCTestCase { /// Then: Log Entries are created from log file func testSuccessfullyyReadEntriesFromDisk() { let rotatingLogBatch = RotatingLogBatch(url: fileURL) - let entries = try? rotatingLogBatch.readEntries() + let rawEntries = try? rotatingLogBatch.readEntries() + let entries = rawEntries as? [LogEntry] XCTAssertEqual(entries?.count, 1) XCTAssertEqual(entries![0].category, "Auth") XCTAssertEqual(entries![0].logLevel.rawValue, LogLevel.error.rawValue) diff --git a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLoggerTests.swift b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLoggerTests.swift index a185ee5b30..b0320fc438 100644 --- a/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLoggerTests.swift +++ b/AmplifyPlugins/Logging/Tests/AWSCloudWatchLoggingPluginTests/RotatingLoggerTests.swift @@ -10,6 +10,7 @@ import Combine import XCTest @testable import AWSCloudWatchLoggingPlugin +@testable import InternalCloudWatchLogging final class RotatingLoggerTests: XCTestCase {