Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
#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

Check warning on line 29 in AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Consumer/CloudWatchLoggingStreamNameFormatter.swift

View workflow job for this annotation

GitHub Actions / Build Amplify Swift for watchOS / Build Amplify-Package | watchOS

no 'async' operations occur within 'await' expression

Check warning on line 29 in AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Consumer/CloudWatchLoggingStreamNameFormatter.swift

View workflow job for this annotation

GitHub Actions / AWSCloudWatchLoggingPlugin Unit Tests / watchOS Tests | AWSCloudWatchLoggingPlugin / watchOS Tests | AWSCloudWatchLoggingPlugin

no 'async' operations occur within 'await' expression
#elseif canImport(UIKit)
await UIDevice.current.identifierForVendor?.uuidString
#elseif canImport(AppKit)
Expand All @@ -37,11 +37,11 @@
}
}

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")"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

/// Represents a batch of log entries to produce/consume.
package protocol LogBatch {
/// 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Check warning on line 16 in AmplifyPlugins/Internal/Sources/InternalCloudWatchLogging/Domain/LogBatchConsumer.swift

View workflow job for this annotation

GitHub Actions / run-swiftformat

Add empty blank line at end of file. (linebreakAtEndOfFile)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Combine

protocol LogBatchProducer {
/// Protocol for a producer of log batches via Combine.
package protocol LogBatchProducer {
var logBatchPublisher: AnyPublisher<LogBatch, Never> { get }
}
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<URL, Never>

/// 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)
}

Expand All @@ -40,22 +37,20 @@ actor LogActor {
}
}

func rotationPublisher() -> AnyPublisher<URL, Never> {
package func rotationPublisher() -> AnyPublisher<URL, Never> {
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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,24 @@

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)
self.count = 0
}

/// 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)
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -75,5 +73,4 @@ final class LogFile {
try handle.synchronize()
count = count + UInt64(data.count) // swiftlint:disable:this shorthand_operator
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -56,19 +56,19 @@ 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,
fileSizeLimitInBytes: fileSizeLimitInBytes
)
}

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)
Expand Down Expand Up @@ -118,7 +118,7 @@ final class LogRotation {
)
}

func ensureFileExists() throws {
package func ensureFileExists() throws {
if !FileManager.default.fileExists(atPath: currentLogFile.fileURL.relativePath) {
try rotate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,23 @@

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 {
automaticFlushLogsTimer?.cancel()
}
}

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
Expand All @@ -35,7 +34,7 @@ class AWSCLoudWatchLoggingMonitor {
eventHandler: { [weak self] in
guard let self else { return }
eventDelegate?.handleAutomaticFlushIntervalEvent()
}
}
)
automaticFlushLogsTimer?.resume()
}
Expand All @@ -48,6 +47,6 @@ class AWSCLoudWatchLoggingMonitor {
}
}

public protocol AWSCloudWatchLoggingMonitorDelegate: AnyObject {
package protocol CloudWatchLoggingMonitorDelegate: AnyObject {
func handleAutomaticFlushIntervalEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Loading
Loading