Skip to content

Commit f7ac080

Browse files
committed
chore(logging): refactor code to use shared sdk client and resolve build warnings (#4215)
* feat(logging): add v3 cloudwatch @SPI definitions * add privacyinfo * chore(logging): move shared code to internal module * fix unit tests * feat(logging): add implementation for cloudwatch client * chore(logging): add unit tests for cloudwatch client * chore(logging): refactor code to use shared sdk client * update code * chore(logging): add integration tests for cloudwatch client (#4216)
1 parent 96ec94b commit f7ac080

21 files changed

Lines changed: 1382 additions & 77 deletions

File tree

AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/AmplifyCloudWatchLoggingClient.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,22 @@ public typealias AmplifyCloudWatchLoggingClientConfigurationProvider = (
4646
/// ```
4747
@available(iOS 13.0, macOS 12.0, tvOS 13.0, watchOS 9.0, *)
4848
@_spi(AmplifyExperimental)
49-
public class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior {
49+
public final class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior, @unchecked Sendable {
5050

5151
private var enabled: Bool = true
5252

5353
private let lock = NSLock()
5454
private let logGroupName: String
5555
private let region: String
56-
private let credentialIdentityResolver: any AWSCredentialIdentityResolver
5756
private var loggersByKey: [LoggerKey: CloudWatchLoggingSessionController] = [:]
5857
private let localStoreMaxSizeInMB: Int
5958
private var automaticFlushLogMonitor: CloudWatchLoggingMonitor?
6059
private let logFilter: CloudWatchLoggingFilter
6160
private var userIdentifier: String?
6261
private let networkMonitor: LoggingNetworkMonitor
63-
private let configureClient: AmplifyCloudWatchLoggingClientConfigurationProvider?
6462
private let logger = AmplifyFoundation.AmplifyLogging.logger(for: AmplifyCloudWatchLoggingClient.self)
6563
private let eventSubject = PassthroughSubject<LoggingEvent, Never>()
64+
private let cloudWatchClient: CloudWatchLogsClientProtocol
6665

6766
private let sinkId = "AmplifyCloudWatchLoggingSink-\(UUID().uuidString)"
6867

@@ -112,19 +111,33 @@ public class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior {
112111
region: String,
113112
credentialsProvider: any AmplifyFoundation.AWSCredentialsProvider,
114113
options: Options
115-
) {
114+
) throws {
116115
self.region = region
117116
self.logGroupName = options.logGroupName
118117
self.localStoreMaxSizeInMB = options.localStoreMaxSizeInMB
119-
self.credentialIdentityResolver = FoundationToSDKCredentialsAdapter(provider: credentialsProvider)
118+
let credentialIdentityResolver = FoundationToSDKCredentialsAdapter(provider: credentialsProvider)
120119
self.networkMonitor = NWPathMonitor()
121120
self.networkMonitor.startMonitoring(
122121
using: DispatchQueue(label: "com.amazonaws.amplify.cloudwatchlogging.networkmonitor")
123122
)
124-
self.configureClient = options.configureClient
125123

126124
self.logFilter = CloudWatchLoggingFilter(loggingConstraints: options.loggingConstraints)
127125

126+
var configuration = try CloudWatchLogsClient.CloudWatchLogsClientConfig(
127+
awsCredentialIdentityResolver: credentialIdentityResolver,
128+
region: region,
129+
signingRegion: region
130+
)
131+
132+
options.configureClient?(&configuration)
133+
134+
configuration.httpClientEngine = UserAgentClientEngine(
135+
target: configuration.httpClientEngine,
136+
additionalMetadata: ["md/amplify-cloudwatch-logging"]
137+
)
138+
139+
self.cloudWatchClient = CloudWatchLogsClient(config: configuration)
140+
128141
if case .interval(let interval) = options.flushStrategy {
129142
self.automaticFlushLogMonitor = CloudWatchLoggingMonitor(
130143
flushIntervalInSeconds: interval,
@@ -166,11 +179,10 @@ public class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior {
166179

167180
/// Returns the underlying AWS CloudWatch Logs SDK client.
168181
public func getCloudWatchLogsClient() throws -> AWSCloudWatchLogs.CloudWatchLogsClient {
169-
guard let controller = loggersByKey.first(where: { $0.value.client != nil })?.value,
170-
let client = controller.client as? AWSCloudWatchLogs.CloudWatchLogsClient else {
182+
guard let client = cloudWatchClient as? AWSCloudWatchLogs.CloudWatchLogsClient else {
171183
throw CloudWatchLoggingError.configuration(
172-
"No CloudWatch Logs client found",
173-
"Ensure a logger has been created and the client is configured."
184+
"CloudWatch Logs client is not the expected type",
185+
"This is an internal error. Please file a bug report."
174186
)
175187
}
176188
return client
@@ -188,7 +200,7 @@ public class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior {
188200
return existing
189201
}
190202
let controller = CloudWatchLoggingSessionController(
191-
credentialIdentityResolver: credentialIdentityResolver,
203+
client: self.cloudWatchClient,
192204
logFilter: self.logFilter,
193205
namespace: namespace,
194206
logLevel: logLevel,
@@ -197,8 +209,7 @@ public class AmplifyCloudWatchLoggingClient: AmplifyFoundation.LogSinkBehavior {
197209
localStoreMaxSizeInMB: self.localStoreMaxSizeInMB,
198210
userIdentifier: self.userIdentifier,
199211
networkMonitor: self.networkMonitor,
200-
eventSubject: self.eventSubject,
201-
configureClient: self.configureClient
212+
eventSubject: self.eventSubject
202213
)
203214
if enabled {
204215
controller.enable()
@@ -253,8 +264,8 @@ private extension NSLock {
253264
@available(iOS 13.0, macOS 12.0, tvOS 13.0, watchOS 9.0, *)
254265
extension AmplifyCloudWatchLoggingClient: CloudWatchLoggingMonitorDelegate {
255266
package func handleAutomaticFlushIntervalEvent() {
256-
Task {
257-
try await flushLogs()
267+
Task { [weak self] in
268+
try await self?.flushLogs()
258269
}
259270
}
260271
}

AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/CloudWatchLoggingSessionController.swift

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@ import SmithyIdentity
1616

1717
/// Responsible for setting up and tearing-down log sessions for a given namespace
1818
/// according to changes in user authentication sessions.
19-
final class CloudWatchLoggingSessionController {
19+
final class CloudWatchLoggingSessionController: @unchecked Sendable {
2020

21-
var client: CloudWatchLogsClientProtocol?
21+
let client: CloudWatchLogsClientProtocol
2222
let namespace: String
2323
private let logGroupName: String
2424
private let region: String
2525
private let localStoreMaxSizeInMB: Int
26-
private let credentialIdentityResolver: any AWSCredentialIdentityResolver
2726
private var session: CloudWatchLoggingSession?
2827
private var consumer: CloudWatchLoggingConsumer?
2928
private let logFilter: CloudWatchLoggingFilterBehavior
3029
private let networkMonitor: LoggingNetworkMonitor
3130
private let eventSubject: PassthroughSubject<LoggingEvent, Never>
32-
private let configureClient: AmplifyCloudWatchLoggingClientConfigurationProvider?
3331
private let internalLogger = AmplifyFoundation.AmplifyLogging.logger(for: CloudWatchLoggingSessionController.self)
3432

3533
private var batchSubscription: AnyCancellable? {
@@ -51,7 +49,7 @@ final class CloudWatchLoggingSessionController {
5149
}
5250

5351
init(
54-
credentialIdentityResolver: some AWSCredentialIdentityResolver,
52+
client: CloudWatchLogsClientProtocol,
5553
logFilter: CloudWatchLoggingFilterBehavior,
5654
namespace: String,
5755
logLevel: LogLevel,
@@ -60,10 +58,9 @@ final class CloudWatchLoggingSessionController {
6058
localStoreMaxSizeInMB: Int,
6159
userIdentifier: String?,
6260
networkMonitor: LoggingNetworkMonitor,
63-
eventSubject: PassthroughSubject<LoggingEvent, Never>,
64-
configureClient: AmplifyCloudWatchLoggingClientConfigurationProvider? = nil
61+
eventSubject: PassthroughSubject<LoggingEvent, Never>
6562
) {
66-
self.credentialIdentityResolver = credentialIdentityResolver
63+
self.client = client
6764
self.logFilter = logFilter
6865
self.namespace = namespace
6966
self.logLevel = logLevel
@@ -73,7 +70,6 @@ final class CloudWatchLoggingSessionController {
7370
self.userIdentifier = userIdentifier
7471
self.networkMonitor = networkMonitor
7572
self.eventSubject = eventSubject
76-
self.configureClient = configureClient
7773
}
7874

7975
func enable() {
@@ -89,7 +85,7 @@ final class CloudWatchLoggingSessionController {
8985
}
9086

9187
private func updateConsumer() {
92-
consumer = try? createConsumer()
88+
consumer = createConsumer()
9389
}
9490

9591
private func connectProducerAndConsumer() {
@@ -103,41 +99,21 @@ final class CloudWatchLoggingSessionController {
10399
}
104100
batchSubscription = producer.logBatchPublisher.sink { [weak self] batch in
105101
guard self?.networkMonitor.isOnline == true else { return }
106-
let strongConsumer = consumer
107-
let strongBatch = batch
108-
Task {
102+
Task { [weak self] in
109103
do {
110-
try await strongConsumer.consume(batch: strongBatch)
104+
try await consumer.consume(batch: batch)
111105
} catch {
112106
self?.internalLogger.error("Error flushing logs: \(error.localizedDescription)")
113107
self?.eventSubject.send(.flushLogFailure(context: error.localizedDescription, error: error))
114-
try strongBatch.complete()
108+
try batch.complete()
115109
}
116110
}
117111
}
118112
}
119113

120-
private func createConsumer() throws -> CloudWatchLoggingConsumer? {
121-
if client == nil {
122-
var configuration = try CloudWatchLogsClient.CloudWatchLogsClientConfig(
123-
awsCredentialIdentityResolver: credentialIdentityResolver,
124-
region: region,
125-
signingRegion: region
126-
)
127-
128-
configureClient?(&configuration)
129-
130-
configuration.httpClientEngine = UserAgentClientEngine(
131-
target: configuration.httpClientEngine,
132-
additionalMetadata: ["md/amplify-cloudwatch-logging"]
133-
)
134-
135-
client = CloudWatchLogsClient(config: configuration)
136-
}
137-
138-
guard let cloudWatchClient = client else { return nil }
114+
private func createConsumer() -> CloudWatchLoggingConsumer {
139115
return CloudWatchLoggingConsumer(
140-
client: cloudWatchClient,
116+
client: client,
141117
logGroupName: logGroupName,
142118
userIdentifier: userIdentifier
143119
)
@@ -193,11 +169,11 @@ final class CloudWatchLoggingSessionController {
193169
}
194170

195171
private func resetCurrentLogs() {
196-
Task {
172+
Task { [weak self] in
197173
do {
198-
try await session?.logger.resetLogs()
174+
try await self?.session?.logger.resetLogs()
199175
} catch {
200-
internalLogger.error("Error resetting logs: \(error)")
176+
self?.internalLogger.error("Error resetting logs: \(error)")
201177
}
202178
}
203179
}

AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Consumer/CloudWatchLoggingConsumer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import AWSCloudWatchLogs
1010
import Foundation
1111
import InternalCloudWatchLogging
1212

13-
class CloudWatchLoggingConsumer {
13+
class CloudWatchLoggingConsumer: @unchecked Sendable {
1414

1515
private let client: CloudWatchLogsClientProtocol
1616
private let formatter: CloudWatchLoggingStreamNameFormatter

AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Producer/RotatingLogger.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Combine
1010
import Foundation
1111
import InternalCloudWatchLogging
1212

13-
final class RotatingLogger {
13+
final class RotatingLogger: @unchecked Sendable {
1414

1515
var logLevel: LogLevel
1616

@@ -58,7 +58,7 @@ final class RotatingLogger {
5858

5959
private func setupSubscription() async throws {
6060
if rotationSubscription == nil {
61-
let rotationPublisher = await logActor.rotationPublisher()
61+
let rotationPublisher = logActor.rotationPublisher()
6262
rotationSubscription = rotationPublisher.sink { [weak self] url in
6363
guard let self else { return }
6464
batchSubject.send(RotatingLogBatch(url: url))
@@ -68,11 +68,11 @@ final class RotatingLogger {
6868

6969
func _record(level: LogLevel, message: @autoclosure () -> String) {
7070
let payload = message()
71-
Task {
71+
Task { [weak self] in
7272
do {
73-
try await self.record(level: level, message: payload)
73+
try await self?.record(level: level, message: payload)
7474
} catch {
75-
eventSubject?.send(.writeLogFailure(context: error.localizedDescription, error: error))
75+
self?.eventSubject?.send(.writeLogFailure(context: error.localizedDescription, error: error))
7676
}
7777
}
7878
}

AmplifyClients/AmplifyCloudWatchLoggingClient/Sources/Support/CloudWatchLoggingFilter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import AmplifyFoundation
99
import Foundation
1010

1111
/// Provides the concrete implementation for the CloudWatchLoggingFilterBehavior.
12-
final class CloudWatchLoggingFilter: CloudWatchLoggingFilterBehavior {
12+
final class CloudWatchLoggingFilter: CloudWatchLoggingFilterBehavior, @unchecked Sendable {
1313
private let lock = NSLock()
1414
private var _loggingConstraints: LoggingConstraints
1515

0 commit comments

Comments
 (0)