From 5ad91cf80d8bff67000229271dd726c2a713032b Mon Sep 17 00:00:00 2001 From: Nika Hassani Date: Wed, 16 Aug 2023 16:27:39 -0700 Subject: [PATCH 01/28] feat(logging): add cloudwatch logger plugin --- .../test/cloudwatch_logger_plugin_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart index 4e4f57f39e..3fe37d8d5b 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart @@ -25,6 +25,7 @@ void main() { localLoggingConstraint: loggingConstraint, enable: false, ); + final errorLog = LogEntry( level: LogLevel.error, message: 'error message', @@ -57,7 +58,7 @@ void main() { id: 4, value: 'forth log message', timestamp: DateTime.timestamp().toIso8601String(), - ), + ) ]; group('enable/disable: ', () { From 53ef002f14d579a8834027e3ac1d4ddd89bc1253 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:51:36 -0700 Subject: [PATCH 02/28] feat(logging): default remote config chore: moved declarations to improve dart syntax chore: use logger instead of print statement chore: added sigv4signer to sign the http request chore: switched Future.delayed to Timer and implemented jsonserializable chore: roll back unnecessary changes from cloudwatch_logger_plugin chore: removed an extra line of fetchAndCacheConstraintFromEndpoint chore: moved saveConstraintLocally out of fetchConstraintFromEndpoint and moved into fetchAndCacheFromEndpoint fix: seeing if moving the queued_item_store_test.dart file into main test directory will fix testing issue Revert "fix: seeing if moving the queued_item_store_test.dart file into main test directory will fix testing issue" This reverts commit f0337131e988eb8e970335dd6a178d5584c9b5d6. chore: added ignore line for try catch block and removed unnecessary method chore: added web implementation of remote_constraint_provider fix: try changing path provider dependency to fix test fix: added flutter sdk to dependencies chore: generated workflows after adding dependencies fix: fixed indentation issue in pubspec chore: generate new workflows chore: changed file and folder names for file_storage chore: fix import for renamed files, added region as a required field to DefaultRemoteConfiguration and fixed scope variable to adjust for that. Also moved scope variable to inside fetchConstraintFromEndpoint method replaced AmplifyLogger with AWSLoggerMixin instead. chore: made signer private and used AWSHttpRequest.get method directly rather than calling it through AWSHttpRequest use stoppable timer instead of while loop chore: updated LoggingConstraint class to match the remote config file chore: added AWSHeaders.accept to the headers for the request chore: added public documentation and trailing comma chore: removed unnecessary time checks in the getter if statement chore: removed initialize() method and put it into the constructor chore: removed unnecessary lastUpdate variable now that it is no longer checked in the getter chore: added a comment and removed flutter plugin files chore: fixed some formatting chore: removed the InSeconds part of the refreshIntervalInSeconds property because it's a Duration, which isn't specific to seconds chore: fixed comment to reflect change in fetchInterval and flushInterval property name change chore: make isRunning variable privatge chore: corrected error handling in the fetchAndCacheConstraintFromEndpoint method chore: add license headers to storage implementations chore: added path for join() method to ensure windows compatibility chore: removed dart:io dependency, changed HttpHeaders.acceptHeader to AWSHeaders.accept, changed getter to just retrieve from cache chore: remove flutter dependency chore: removed http and replaced with AWSHttpClient chore: removed unnecessary stop method chore: remove .flutter-plugins files chore: moved constructor initializers into a separate init function so that I can use async await chore: added local storage check before fetching in the initialize function chore: refactored DefaultRemoteLoggingConstraintProvider to separate createRequest from the rest of the class, allowing for signed and unsigned requests to be sent chore: used private global variable for local storage key chore: refacted fileStore dart implementation --- .github/workflows/aws_logging_cloudwatch.yaml | 23 +-- .../lib/src/cloudwatch_logger_plugin.dart | 6 +- .../lib/src/file_storage/file_storage.dart | 13 ++ .../lib/src/file_storage/file_storage.vm.dart | 34 ++++ .../src/file_storage/file_storage.web.dart | 26 +++ .../lib/src/plugin_config.dart | 52 ++++- .../lib/src/plugin_config.g.dart | 50 +++++ .../lib/src/remote_constraint_provider.dart | 177 ++++++++++++++++-- .../aws_logging_cloudwatch/pubspec.yaml | 4 + .../test/cloudwatch_logger_plugin_test.dart | 3 +- 10 files changed, 344 insertions(+), 44 deletions(-) create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart diff --git a/.github/workflows/aws_logging_cloudwatch.yaml b/.github/workflows/aws_logging_cloudwatch.yaml index 901c6c12fd..4ae5a35606 100644 --- a/.github/workflows/aws_logging_cloudwatch.yaml +++ b/.github/workflows/aws_logging_cloudwatch.yaml @@ -11,8 +11,7 @@ on: - '.github/workflows/aws_logging_cloudwatch.yaml' - '.github/workflows/dart_dart2js.yaml' - '.github/workflows/dart_ddc.yaml' - - '.github/workflows/dart_native.yaml' - - '.github/workflows/dart_vm.yaml' + - '.github/workflows/flutter_vm.yaml' - 'packages/amplify_core/lib/**/*.dart' - 'packages/amplify_core/pubspec.yaml' - 'packages/amplify_lints/lib/**/*.yaml' @@ -38,25 +37,7 @@ permissions: read-all jobs: test: - uses: ./.github/workflows/dart_vm.yaml - with: - package-name: aws_logging_cloudwatch - working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch - native_test: - needs: test - uses: ./.github/workflows/dart_native.yaml - with: - package-name: aws_logging_cloudwatch - working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch - ddc_test: - needs: test - uses: ./.github/workflows/dart_ddc.yaml - with: - package-name: aws_logging_cloudwatch - working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch - dart2js_test: - needs: test - uses: ./.github/workflows/dart_dart2js.yaml + uses: ./.github/workflows/flutter_vm.yaml with: package-name: aws_logging_cloudwatch working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/cloudwatch_logger_plugin.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/cloudwatch_logger_plugin.dart index 22fe2c1cbe..b3eecf74bb 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/cloudwatch_logger_plugin.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/cloudwatch_logger_plugin.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'dart:math'; -import 'package:aws_common/aws_common.dart'; +import 'package:amplify_core/amplify_core.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; import 'package:aws_logging_cloudwatch/src/sdk/cloud_watch_logs.dart'; import 'package:aws_logging_cloudwatch/src/stoppable_timer.dart'; @@ -76,9 +76,9 @@ class CloudWatchLoggerPlugin extends AWSLoggerPlugin region: pluginConfig.region, credentialsProvider: credentialsProvider, ) { - _timer = pluginConfig.flushIntervalInSeconds > Duration.zero + _timer = pluginConfig.flushInterval > Duration.zero ? StoppableTimer( - duration: pluginConfig.flushIntervalInSeconds, + duration: pluginConfig.flushInterval, callback: _startSyncingIfNotInProgress, onError: _onTimerError, ) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart new file mode 100644 index 0000000000..0070be9b5a --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -0,0 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export 'file_storage.vm.dart' if (dart.library.html) 'file_storage.web.dart'; + +/// File storage interface for saving and loading constraint locally +abstract interface class FileStorage { + /// Save constraint locally to file + Future saveConstraintLocally(String filename, String data); + + /// Load constraint from file + Future loadConstraint(String filename); +} diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart new file mode 100644 index 0000000000..05e51fd45c --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart @@ -0,0 +1,34 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:io'; + +import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; +import 'package:path/path.dart' as p; + +/// File storage implementation for saving and loading constraint locally +final class FileStorageImpl implements FileStorage { + /// File storage implementation for saving and loading constraint locally + FileStorageImpl(this.pathProvider); + + /// Path provider to get the application support path + final AppPathProvider pathProvider; + + @override + Future loadConstraint(String fileName) async { + final file = + File(p.join(await pathProvider.getApplicationSupportPath(), fileName)); + if (await file.exists()) { + return file.readAsString(); + } + return null; + } + + @override + Future saveConstraintLocally(String fileName, String content) async { + final file = + File(p.join(await pathProvider.getApplicationSupportPath(), fileName)); + await file.writeAsString(content); + } +} diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart new file mode 100644 index 0000000000..bbed8305b8 --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart @@ -0,0 +1,26 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:html'; + +import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; + +/// File storage implementation for saving and loading constraint locally +final class FileStorageImpl implements FileStorage { + /// File storage implementation for saving and loading constraint locally + // ignore: avoid_unused_constructor_parameters + FileStorageImpl(AppPathProvider pathProvider); + + static const _prefix = 'aws.cloudwatch'; + + @override + Future loadConstraint(String fileName) async { + return window.localStorage['$_prefix.$fileName']; + } + + @override + Future saveConstraintLocally(String fileName, String content) async { + window.localStorage['$_prefix.$fileName'] = content; + } +} diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index 9fd10d18d0..540036ba9f 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -3,6 +3,9 @@ import 'package:aws_common/aws_common.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'plugin_config.g.dart'; /// {@template aws_logging_cloudwatch.cloudwatch_logger_plugin_configuration} /// The configuration for `CloudWatchLoggerPlugin`. @@ -15,7 +18,7 @@ class CloudWatchLoggerPluginConfiguration with AWSDebuggable { required this.localLoggingConstraint, this.enable = true, this.localStoreMaxSizeInMB = 5, - this.flushIntervalInSeconds = const Duration(seconds: 60), + this.flushInterval = const Duration(seconds: 60), this.defaultRemoteConfiguration, }); @@ -31,8 +34,8 @@ class CloudWatchLoggerPluginConfiguration with AWSDebuggable { /// The max size of the local store in MB to be used for storing logs locally. final int localStoreMaxSizeInMB; - /// The duration in seconds for sending locally stored logs to CloudWatch. - final Duration flushIntervalInSeconds; + /// The duration for sending locally stored logs to CloudWatch. + final Duration flushInterval; /// {@macro aws_logging_cloudwatch.logging_constraint} final LoggingConstraint localLoggingConstraint; @@ -47,13 +50,54 @@ class CloudWatchLoggerPluginConfiguration with AWSDebuggable { /// {@template aws_logging_cloudwatch.logging_constraint} /// The logging constraint for sending logs to CloudWatch. /// {@endtemplate} +@JsonSerializable() class LoggingConstraint with AWSDebuggable { /// {@macro aws_logging_cloudwatch.logging_constraint} - const LoggingConstraint({this.defaultLogLevel = LogLevel.error}); + const LoggingConstraint({ + this.defaultLogLevel = LogLevel.error, + this.categoryLogLevel, + this.userLogLevel, + }); + + /// Converts a [Map] to an [LoggingConstraint] instance. + factory LoggingConstraint.fromJson(Map json) => + _$LoggingConstraintFromJson(json); + + /// Converts an [LoggingConstraint] instance to a [Map]. + Map toJson() => _$LoggingConstraintToJson(this); /// The default [LogLevel] for sending logs to CloudWatch. final LogLevel defaultLogLevel; + /// The [LogLevel] for different categories. + final Map? categoryLogLevel; + + /// The [LogLevel] for different users. + final Map? userLogLevel; + @override String get runtimeTypeName => 'LoggingConstraint'; } + +/// The logging constraint for user specific log level. +@JsonSerializable() +class UserLogLevel { + /// The logging constraint for user specific log level. + const UserLogLevel({ + this.defaultLogLevel, + this.categoryLogLevel, + }); + + ///Converts a [Map] to a [UserLogLevel] instance. + factory UserLogLevel.fromJson(Map json) => + _$UserLogLevelFromJson(json); + + /// Converts a [UserLogLevel] instance to a [Map]. + Map toJson() => _$UserLogLevelToJson(this); + + /// The default [LogLevel] for sending logs to CloudWatch. + final LogLevel? defaultLogLevel; + + /// The [LogLevel] for different categories. + final Map? categoryLogLevel; +} diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart new file mode 100644 index 0000000000..6fd6cd2a90 --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'plugin_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LoggingConstraint _$LoggingConstraintFromJson(Map json) => + LoggingConstraint( + defaultLogLevel: + $enumDecodeNullable(_$LogLevelEnumMap, json['defaultLogLevel']) ?? + LogLevel.error, + categoryLogLevel: + (json['categoryLogLevel'] as Map?)?.map( + (k, e) => MapEntry(k, $enumDecode(_$LogLevelEnumMap, e)), + ), + ); + +Map _$LoggingConstraintToJson(LoggingConstraint instance) => + { + 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel]!, + 'categoryLogLevel': instance.categoryLogLevel + ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!)), + }; + +const _$LogLevelEnumMap = { + LogLevel.verbose: 'verbose', + LogLevel.debug: 'debug', + LogLevel.info: 'info', + LogLevel.warn: 'warn', + LogLevel.error: 'error', + LogLevel.none: 'none', +}; + +UserLogLevel _$UserLogLevelFromJson(Map json) => UserLogLevel( + defaultLogLevel: + $enumDecodeNullable(_$LogLevelEnumMap, json['defaultLogLevel']), + categoryLogLevel: + (json['categoryLogLevel'] as Map?)?.map( + (k, e) => MapEntry(k, $enumDecode(_$LogLevelEnumMap, e)), + ), + ); + +Map _$UserLogLevelToJson(UserLogLevel instance) => + { + 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel], + 'categoryLogLevel': instance.categoryLogLevel + ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!)), + }; diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 7a8a36332f..8f7a0e4e4e 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -1,11 +1,18 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import 'dart:async'; +import 'dart:convert'; + +import 'package:amplify_core/amplify_core.dart'; // TODO(nikahsn): remove after implementing the get loggingConstraint. // ignore_for_file: unused_field import 'package:aws_common/aws_common.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; +import 'package:aws_signature_v4/aws_signature_v4.dart'; +import 'package:meta/meta.dart'; /// {@template aws_logging_cloudwatch.remote_logging_constraint_provider} /// An Interface to provide custom implementation for @@ -17,40 +24,182 @@ abstract class RemoteLoggingConstraintProvider { LoggingConstraint? get loggingConstraint; } -/// {@template aws_logging_cloudwatch.default_remote_logging_constraint_provider} -/// Default implementation of [RemoteLoggingConstraintProvider] to fetch -/// [LoggingConstraint] from an http endpoint periodically. +/// {@template aws_logging_cloudwatch.base_remote_constraints_provider} +/// Base class for [RemoteLoggingConstraintProvider] to provide +/// [LoggingConstraint] from a remote location and cache it. /// {@endtemplate} -class DefaultRemoteLoggingConstraintProvider +base class BaseRemoteLoggingConstraintProvider + with AWSDebuggable, AWSLoggerMixin implements RemoteLoggingConstraintProvider { - /// {@macro aws_logging_cloudwatch.default_remote_logging_constraint_provider} - DefaultRemoteLoggingConstraintProvider({ + /// {@macro aws_logging_cloudwatch.base_remote_constraints_provider} + BaseRemoteLoggingConstraintProvider({ required DefaultRemoteConfiguration config, required AWSCredentialsProvider credentialsProvider, - }) : _config = config, - _credentialsProvider = credentialsProvider; + FileStorage? fileStorage, + }) : _fileStorage = fileStorage, + _config = config, + _credentialsProvider = credentialsProvider { + init(); + } + + final FileStorage? _fileStorage; final DefaultRemoteConfiguration _config; final AWSCredentialsProvider _credentialsProvider; + LoggingConstraint? _loggingConstraint; + + final AWSHttpClient _awsHttpClient = AWSHttpClient(); + + // The timer to refresh the constraint periodically. + Timer? _timer; + + /// Whether the periodic fetch is running. + bool _isRunning = false; + + /// Retrives the runtime type name used for logging. + @override + String get runtimeTypeName => 'BaseRemoteConstraintsProvider'; + + /// Initializes the [BaseRemoteLoggingConstraintProvider] by fetching + /// the constraint from the endpoint initially and then + /// starting the refresh timer afterwards. + Future init() async { + // Check local storage first. + if (_fileStorage != null) { + final localConstraint = + await _fileStorage!.loadConstraint('remoteloggingconstraints.json'); + if (localConstraint != null) { + _loggingConstraint = LoggingConstraint.fromJson( + jsonDecode(localConstraint) as Map, + ); + } + } + await _fetchAndCacheConstraintFromEndpoint(); + await _refreshConstraintPeriodically(); + return null; + } + + /// Creates a request to fetch the constraint from the endpoint. + @protected + Future createRequest() async { + final uri = Uri.parse(_config.endpoint); + return AWSHttpRequest( + method: AWSHttpMethod.get, + uri: uri, + headers: const { + AWSHeaders.accept: 'application/json; charset=utf-8', + }, + ); + } + + /// Fetches the constraint from the endpoint and caches it. + Future _fetchAndCacheConstraintFromEndpoint() async { + try { + final request = await createRequest(); + final operation = _awsHttpClient.send(request); + final response = await operation.response; + final body = await response.decodeBody(); + if (response.statusCode == 200) { + final fetchedConstraint = LoggingConstraint.fromJson( + jsonDecode(body) as Map, + ); + _loggingConstraint = fetchedConstraint; + + if (_fileStorage != null) { + await _fileStorage!.saveConstraintLocally( + 'remoteloggingconstraints.json', + jsonEncode(fetchedConstraint.toJson()), + ); + } + } + } on Exception catch (exception) { + throw Exception( + 'Failed to fetch logging constraint from ${_config.endpoint}: $exception', + ); + } on Error catch (error) { + logger.error( + 'Error while fetching logging constraint from ${_config.endpoint}: $error', + ); + } + } + + /// Returns [LoggingConstraint] from cache or `null` if cache is missing. + @override + LoggingConstraint? get loggingConstraint => _loggingConstraint; + + /// Refreshes the constraint from the endpoint periodically. + Future _refreshConstraintPeriodically() async { + if (_isRunning) { + return; + } + + _isRunning = true; + _timer?.cancel(); + + _timer = Timer.periodic( + _config.refreshInterval, + (_) => _fetchAndCacheConstraintFromEndpoint(), + ); + } +} + +/// {@template aws_logging_cloudwatch.default_remote_logging_constraint_provider} +/// Default implementation of [RemoteLoggingConstraintProvider] to fetch +/// [LoggingConstraint] from an http endpoint periodically. +/// {@endtemplate} +final class DefaultRemoteLoggingConstraintProvider + extends BaseRemoteLoggingConstraintProvider { + /// {@macro aws_logging_cloudwatch.default_remote_logging_constraint_provider} + DefaultRemoteLoggingConstraintProvider({ + required super.config, + required this.credentialsProvider, + super.fileStorage, + }) : super(credentialsProvider: credentialsProvider); + + /// The credentials provider to use for signing the request. + final AWSCredentialsProvider credentialsProvider; + + /// The signer to use for signing the request. + final AWSSigV4Signer _signer = const AWSSigV4Signer(); + @override - // TODO(nikahsn): add implementation. - LoggingConstraint get loggingConstraint => throw UnimplementedError(); + Future createRequest() async { + final baseRequest = await super.createRequest(); + final scope = AWSCredentialScope( + region: _config.region, + service: AWSService.apiGatewayManagementApi, + ); + + final signedRequest = await _signer.sign( + baseRequest, + credentialScope: scope, + ); + + final newRequest = + AWSHttpRequest(method: signedRequest.method, uri: signedRequest.uri); + + return newRequest; + } } /// {@template aws_logging_cloudwatch.default_remote_configuration} -/// The configuration for [DefaultRemoteLoggingConstraintProvider] +/// The configuration for [BaseRemoteLoggingConstraintProvider] /// {@endtemplate} class DefaultRemoteConfiguration { /// {@macro aws_logging_cloudwatch.default_remote_configuration} const DefaultRemoteConfiguration({ required this.endpoint, - this.refreshIntervalInSeconds = const Duration(seconds: 1200), + this.refreshInterval = const Duration(seconds: 1200), + required this.region, }); /// The endpoint to fetch the `loggingConstraint`. final String endpoint; - /// The referesh interval in seconds to fetch the `loggingConstraint`. - final Duration refreshIntervalInSeconds; + /// The referesh interval to fetch the `loggingConstraint`. + final Duration refreshInterval; + + /// The region of the endpoint. + final String region; } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml b/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml index 9d2e09f7d3..d14eda80e8 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml @@ -15,8 +15,12 @@ dependencies: built_collection: ^5.1.1 built_value: ">=8.6.0 <8.7.0" fixnum: ^1.1.0 + flutter: + sdk: flutter intl: ">=0.18.0 <1.0.0" + json_annotation: ^4.8.1 meta: ^1.9.1 + path: ^1.8.0 smithy: ^0.5.0+3 smithy_aws: ^0.5.0+3 diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart index 3fe37d8d5b..4e4f57f39e 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/cloudwatch_logger_plugin_test.dart @@ -25,7 +25,6 @@ void main() { localLoggingConstraint: loggingConstraint, enable: false, ); - final errorLog = LogEntry( level: LogLevel.error, message: 'error message', @@ -58,7 +57,7 @@ void main() { id: 4, value: 'forth log message', timestamp: DateTime.timestamp().toIso8601String(), - ) + ), ]; group('enable/disable: ', () { From 0506c771eba92e7b18238148908907e98398b547 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:45:50 -0700 Subject: [PATCH 03/28] chore: dart FileStorage refactor and unit tests added --- .../amplify_logging_cloudwatch/.gitignore | 2 + .../lib/src/file_storage/file_storage.dart | 4 +- .../src/file_storage/file_storage_stub.dart | 24 ++ ...e_storage.vm.dart => file_storage_vm.dart} | 2 +- ...storage.web.dart => file_storage_web.dart} | 2 +- .../lib/src/plugin_config.dart | 34 ++- .../lib/src/plugin_config.g.dart | 4 + .../lib/src/remote_constraint_provider.dart | 54 +++-- .../test/remote_constraint_provider_test.dart | 220 ++++++++++++++++++ 9 files changed, 318 insertions(+), 28 deletions(-) create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart rename packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/{file_storage.vm.dart => file_storage_vm.dart} (95%) rename packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/{file_storage.web.dart => file_storage_web.dart} (94%) create mode 100644 packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore index 65c34dc86e..f7752a753f 100644 --- a/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore +++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore @@ -1,6 +1,8 @@ # Files and directories created by pub. .dart_tool/ .packages +.flutter-plugins +.flutter-plugins-dependencies # Conventional directory for build outputs. build/ diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 0070be9b5a..649e1a7517 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -1,7 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -export 'file_storage.vm.dart' if (dart.library.html) 'file_storage.web.dart'; +export 'file_storage_stub.dart' + if (dart.library.io) 'file_storage_vm.dart' + if (dart.library.html) 'file_storage_web.dart'; /// File storage interface for saving and loading constraint locally abstract interface class FileStorage { diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart new file mode 100644 index 0000000000..8f71d4c5e8 --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; + +/// File storage implementation for saving and loading constraint locally +class FileStorageImpl implements FileStorage { + /// File storage implementation for saving and loading constraint locally + FileStorageImpl(this.pathProvider); + + /// Path provider to get the application support path + final AppPathProvider pathProvider; + + @override + Future loadConstraint(String fileName) async { + throw UnimplementedError(); + } + + @override + Future saveConstraintLocally(String fileName, String content) async { + throw UnimplementedError(); + } +} diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart similarity index 95% rename from packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart rename to packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart index 05e51fd45c..7deb4ec801 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.vm.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart @@ -8,7 +8,7 @@ import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; import 'package:path/path.dart' as p; /// File storage implementation for saving and loading constraint locally -final class FileStorageImpl implements FileStorage { +class FileStorageImpl implements FileStorage { /// File storage implementation for saving and loading constraint locally FileStorageImpl(this.pathProvider); diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart similarity index 94% rename from packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart rename to packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart index bbed8305b8..4bdf07efec 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.web.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart @@ -7,7 +7,7 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; /// File storage implementation for saving and loading constraint locally -final class FileStorageImpl implements FileStorage { +class FileStorageImpl implements FileStorage { /// File storage implementation for saving and loading constraint locally // ignore: avoid_unused_constructor_parameters FileStorageImpl(AppPathProvider pathProvider); diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index 540036ba9f..5f85de6a9c 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -63,8 +63,27 @@ class LoggingConstraint with AWSDebuggable { factory LoggingConstraint.fromJson(Map json) => _$LoggingConstraintFromJson(json); + // /// Converts an [LoggingConstraint] instance to a [Map]. + // Map toJson() => _$LoggingConstraintToJson(this); + /// Converts an [LoggingConstraint] instance to a [Map]. - Map toJson() => _$LoggingConstraintToJson(this); + Map toJson() { + final jsonMap = { + 'defaultLogLevel': + defaultLogLevel.toString().split('.').last, // Convert enum to string + 'categoryLogLevel': categoryLogLevel?.map( + (key, value) => MapEntry(key, value.toString().split('.').last), + ), + }; + + if (userLogLevel != null) { + jsonMap['userLogLevel'] = userLogLevel!.map( + (key, value) => MapEntry(key, value.toJson()), + ); + } + + return jsonMap; + } /// The default [LogLevel] for sending logs to CloudWatch. final LogLevel defaultLogLevel; @@ -93,7 +112,18 @@ class UserLogLevel { _$UserLogLevelFromJson(json); /// Converts a [UserLogLevel] instance to a [Map]. - Map toJson() => _$UserLogLevelToJson(this); + Map toJson() => { + 'defaultLogLevel': defaultLogLevel + ?.toString() + .split('.') + .last, // Convert enum to string + 'categoryLogLevel': categoryLogLevel?.map( + (key, value) => MapEntry( + key, + value.toString().split('.').last, + ), // Convert enum to string + ), + }; /// The default [LogLevel] for sending logs to CloudWatch. final LogLevel? defaultLogLevel; diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart index 6fd6cd2a90..2ec0bf735a 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart @@ -15,6 +15,9 @@ LoggingConstraint _$LoggingConstraintFromJson(Map json) => (json['categoryLogLevel'] as Map?)?.map( (k, e) => MapEntry(k, $enumDecode(_$LogLevelEnumMap, e)), ), + userLogLevel: (json['userLogLevel'] as Map?)?.map( + (k, e) => MapEntry(k, UserLogLevel.fromJson(e as Map)), + ), ); Map _$LoggingConstraintToJson(LoggingConstraint instance) => @@ -22,6 +25,7 @@ Map _$LoggingConstraintToJson(LoggingConstraint instance) => 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel]!, 'categoryLogLevel': instance.categoryLogLevel ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!)), + 'userLogLevel': instance.userLogLevel, }; const _$LogLevelEnumMap = { diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 8f7a0e4e4e..ae9e2a2ccb 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -10,7 +10,9 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:aws_common/aws_common.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; -import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage_stub.dart' + if (dart.library.io) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart' + if (dart.library.html) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_web.dart'; import 'package:aws_signature_v4/aws_signature_v4.dart'; import 'package:meta/meta.dart'; @@ -35,21 +37,23 @@ base class BaseRemoteLoggingConstraintProvider BaseRemoteLoggingConstraintProvider({ required DefaultRemoteConfiguration config, required AWSCredentialsProvider credentialsProvider, - FileStorage? fileStorage, + FileStorageImpl? fileStorage, + AWSHttpClient? awsHttpClient, }) : _fileStorage = fileStorage, _config = config, - _credentialsProvider = credentialsProvider { - init(); + _credentialsProvider = credentialsProvider, + _awsHttpClient = awsHttpClient ?? AWSHttpClient() { + _init(); } - final FileStorage? _fileStorage; + final FileStorageImpl? _fileStorage; final DefaultRemoteConfiguration _config; final AWSCredentialsProvider _credentialsProvider; LoggingConstraint? _loggingConstraint; - final AWSHttpClient _awsHttpClient = AWSHttpClient(); + final AWSHttpClient _awsHttpClient; // The timer to refresh the constraint periodically. Timer? _timer; @@ -64,20 +68,8 @@ base class BaseRemoteLoggingConstraintProvider /// Initializes the [BaseRemoteLoggingConstraintProvider] by fetching /// the constraint from the endpoint initially and then /// starting the refresh timer afterwards. - Future init() async { - // Check local storage first. - if (_fileStorage != null) { - final localConstraint = - await _fileStorage!.loadConstraint('remoteloggingconstraints.json'); - if (localConstraint != null) { - _loggingConstraint = LoggingConstraint.fromJson( - jsonDecode(localConstraint) as Map, - ); - } - } - await _fetchAndCacheConstraintFromEndpoint(); - await _refreshConstraintPeriodically(); - return null; + void _init() { + _refreshConstraintPeriodically(); } /// Creates a request to fetch the constraint from the endpoint. @@ -112,14 +104,18 @@ base class BaseRemoteLoggingConstraintProvider jsonEncode(fetchedConstraint.toJson()), ); } + } else { + await _loadConstraintFromLocalCache(); } } on Exception catch (exception) { - throw Exception( + logger.debug( 'Failed to fetch logging constraint from ${_config.endpoint}: $exception', ); - } on Error catch (error) { + await _loadConstraintFromLocalCache(); + } on Error catch (error, stackTrace) { logger.error( 'Error while fetching logging constraint from ${_config.endpoint}: $error', + stackTrace, ); } } @@ -128,8 +124,18 @@ base class BaseRemoteLoggingConstraintProvider @override LoggingConstraint? get loggingConstraint => _loggingConstraint; + Future _loadConstraintFromLocalCache() async { + final localConstraint = + await _fileStorage!.loadConstraint('remoteloggingconstraints.json'); + if (localConstraint != null) { + _loggingConstraint = LoggingConstraint.fromJson( + jsonDecode(localConstraint) as Map, + ); + } + } + /// Refreshes the constraint from the endpoint periodically. - Future _refreshConstraintPeriodically() async { + void _refreshConstraintPeriodically() { if (_isRunning) { return; } @@ -141,6 +147,7 @@ base class BaseRemoteLoggingConstraintProvider _config.refreshInterval, (_) => _fetchAndCacheConstraintFromEndpoint(), ); + Timer.run(_fetchAndCacheConstraintFromEndpoint); } } @@ -155,6 +162,7 @@ final class DefaultRemoteLoggingConstraintProvider required super.config, required this.credentialsProvider, super.fileStorage, + super.awsHttpClient, }) : super(credentialsProvider: credentialsProvider); /// The credentials provider to use for signing the request. diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart new file mode 100644 index 0000000000..ded004352d --- /dev/null +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -0,0 +1,220 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:convert'; + +import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart' + if (dart.library.io) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart' + if (dart.library.html) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_web.dart'; +// import 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +const sampleJson = ''' + { + "defaultLogLevel": "error", + "categoryLogLevel": { + "API": "debug", + "AUTH": "debug" + }, + "userLogLevel": { + "cognito-sub-xyz-123": { + "defaultLogLevel": "verbose", + "categoryLogLevel": { + "API": "verbose", + "AUTH": "verbose" + } + } + } + } + '''; + +class MockFileStorage extends Mock implements FileStorageImpl { + MockFileStorage(); + + @override + Future saveConstraintLocally(String fileName, String content) async {} +} + +class MockAWSHttpClient extends Mock implements AWSHttpClient {} + +class MockAWSHttpOperation extends Mock + implements AWSHttpOperation {} + +class MockAWSCredentialsProvider extends Mock + implements AWSCredentialsProvider {} + +class PathProvider extends Mock implements AppPathProvider { + PathProvider(); + + @override + Future getApplicationSupportPath() async { + return ''; + } + + @override + Future getTemporaryPath() async { + return ''; + } +} + +final fakeRequest = AWSHttpRequest( + method: AWSHttpMethod.get, + uri: Uri.parse('https://fakewebsite.com'), + headers: const {}, + body: utf8.encode('sample'), +); + +void main() { + group('RemoteLoggingConstraintProvider', () { + late BaseRemoteLoggingConstraintProvider provider; + late FileStorageImpl mockFileStorage; + late MockAWSHttpClient mockAWSHttpClient; + late MockAWSCredentialsProvider mockCredentialsProvider; + late MockAWSHttpOperation mockOperation; + + const sampleJson = ''' + { + "defaultLogLevel": "error", + "categoryLogLevel": { + "API": "debug", + "AUTH": "debug" + }, + "userLogLevel": { + "cognito-sub-xyz-123": { + "defaultLogLevel": "verbose", + "categoryLogLevel": { + "API": "verbose", + "AUTH": "verbose" + } + } + } + } + '''; + + setUp(() { + mockFileStorage = MockFileStorage(); + mockAWSHttpClient = MockAWSHttpClient(); + mockCredentialsProvider = MockAWSCredentialsProvider(); + mockOperation = MockAWSHttpOperation(); + + registerFallbackValue(fakeRequest); + + // mock the response from the endpoint + when(() => mockOperation.response).thenAnswer((_) async { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(sampleJson), + ); + }); + + // mock the call to createRequest + when(() => mockAWSHttpClient.send(any())).thenAnswer((_) { + return mockOperation; + }); + + when(() => mockFileStorage.loadConstraint(any())) + .thenAnswer((_) async => Future.value(sampleJson)); + + provider = BaseRemoteLoggingConstraintProvider( + config: const DefaultRemoteConfiguration( + refreshInterval: Duration(seconds: 10), + endpoint: 'https://example.com', + region: 'us-west-2', + ), + credentialsProvider: mockCredentialsProvider, + fileStorage: mockFileStorage, + awsHttpClient: mockAWSHttpClient, + ); + }); + + test('initializes _loggingConstraint from endpoint', () async { + await Future.delayed(const Duration(seconds: 3)); + + // Verify that _loggingConstraint exists + expect( + provider.loggingConstraint!.toJson(), + equals(json.decode(sampleJson)), + ); + }); + + test( + 'fetches _loggingConstraint from local storage and returns null if there are no constraints in local storage', + () async { + when(() => mockOperation.response).thenAnswer((_) async { + return AWSHttpResponse( + statusCode: 400, + body: utf8.encode('NO RESPONSE'), + ); + }); + + // mock load constraint returns null + when(() => mockFileStorage.loadConstraint(any())) + .thenAnswer((_) async => Future.value(null)); + + await Future.delayed(const Duration(seconds: 3)); + + // Verify that _loggingConstraint is set + expect(provider.loggingConstraint, equals(null)); + }); + + test('uses local storage if endpoint fails', () async { + when(() => mockOperation.response).thenAnswer((_) async { + return AWSHttpResponse( + statusCode: 400, + body: utf8.encode('NO RESPONSE'), + ); + }); + + when(() => mockFileStorage.loadConstraint(any())) + .thenAnswer((_) async => Future.value(sampleJson)); + + await Future.delayed(const Duration(seconds: 3)); + + // Verify that _loggingConstraint uses local storage + expect( + provider.loggingConstraint!.toJson(), + equals(json.decode(sampleJson)), + ); + }); + + test('updates constraints when endpoint returns updated constraints', + () async { + const updatedJson = ''' + { + "defaultLogLevel": "debug", + "categoryLogLevel": { + "API": "debug", + "AUTH": "error" + }, + "userLogLevel": { + "cognito-sub-xyz-123": { + "defaultLogLevel": "verbose", + "categoryLogLevel": { + "API": "error", + "AUTH": "debug" + } + } + } + } + '''; + + when(() => mockOperation.response).thenAnswer((_) async { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(updatedJson), + ); + }); + + await Future.delayed(const Duration(seconds: 3)); + + // Verify that _loggingConstraint got updated + expect( + provider.loggingConstraint!.toJson(), + equals(json.decode(updatedJson)), + ); + }); + }); +} From f379e7ca047ff2ab065cca75f51d26ebebbb8dc7 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:47:25 -0700 Subject: [PATCH 04/28] chore: made the Future.delayed more explicit --- .../test/remote_constraint_provider_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index ded004352d..67f86d93a8 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -131,7 +131,7 @@ void main() { }); test('initializes _loggingConstraint from endpoint', () async { - await Future.delayed(const Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); // Verify that _loggingConstraint exists expect( @@ -154,7 +154,7 @@ void main() { when(() => mockFileStorage.loadConstraint(any())) .thenAnswer((_) async => Future.value(null)); - await Future.delayed(const Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); // Verify that _loggingConstraint is set expect(provider.loggingConstraint, equals(null)); @@ -171,7 +171,7 @@ void main() { when(() => mockFileStorage.loadConstraint(any())) .thenAnswer((_) async => Future.value(sampleJson)); - await Future.delayed(const Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); // Verify that _loggingConstraint uses local storage expect( @@ -208,7 +208,7 @@ void main() { ); }); - await Future.delayed(const Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); // Verify that _loggingConstraint got updated expect( From 8de03091813ed0fc4c17877ac68daefff4b8aa89 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:40:29 -0700 Subject: [PATCH 05/28] chore: added flutter path provider to amplify_logging_cloudwatch --- .../path_provider/flutter_path_provider.dart | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/path_provider/flutter_path_provider.dart diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/path_provider/flutter_path_provider.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/path_provider/flutter_path_provider.dart new file mode 100644 index 0000000000..d19c258c22 --- /dev/null +++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/path_provider/flutter_path_provider.dart @@ -0,0 +1,23 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_core/amplify_core.dart'; +import 'package:path_provider/path_provider.dart'; + +/// {@template flutter_path_provider} +/// A [AppPathProvider] that uses the [path_provider](https://pub.dev/packages/path_provider) +/// package to get the application support and temporary paths. +/// {@endtemplate} +class FlutterPathProvider implements AppPathProvider { + @override + Future getApplicationSupportPath() async { + final directory = await getApplicationSupportDirectory(); + return directory.path; + } + + @override + Future getTemporaryPath() async { + final directory = await getTemporaryDirectory(); + return directory.path; + } +} From 55dfab9e92f7a109bbcab024e07f4067581289f7 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:03:25 -0700 Subject: [PATCH 06/28] chore: removed fluter dependency --- packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml b/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml index d14eda80e8..560eb790c3 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml @@ -15,8 +15,6 @@ dependencies: built_collection: ^5.1.1 built_value: ">=8.6.0 <8.7.0" fixnum: ^1.1.0 - flutter: - sdk: flutter intl: ">=0.18.0 <1.0.0" json_annotation: ^4.8.1 meta: ^1.9.1 From 0ef2b3db0e3ed8eb194646bf945d41bcf893f6f6 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:09:44 -0700 Subject: [PATCH 07/28] chore: remove flutter plugin files from git ignore now that flutter dependency is removed --- .../lib/src/remote_constraint_provider.dart | 19 ++++++++++++++++--- .../test/remote_constraint_provider_test.dart | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index ae9e2a2ccb..18b86511c3 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -38,11 +38,25 @@ base class BaseRemoteLoggingConstraintProvider required DefaultRemoteConfiguration config, required AWSCredentialsProvider credentialsProvider, FileStorageImpl? fileStorage, - AWSHttpClient? awsHttpClient, }) : _fileStorage = fileStorage, _config = config, _credentialsProvider = credentialsProvider, - _awsHttpClient = awsHttpClient ?? AWSHttpClient() { + _awsHttpClient = AWSHttpClient() { + _init(); + } + + /// A [BaseRemoteLoggingConstraintProvider] to use only for testing. + @protected + @visibleForTesting + BaseRemoteLoggingConstraintProvider.forTesting({ + required DefaultRemoteConfiguration config, + required AWSCredentialsProvider credentialsProvider, + required AWSHttpClient awsHttpClient, + FileStorageImpl? fileStorage, + }) : _fileStorage = fileStorage, + _config = config, + _credentialsProvider = credentialsProvider, + _awsHttpClient = awsHttpClient { _init(); } @@ -162,7 +176,6 @@ final class DefaultRemoteLoggingConstraintProvider required super.config, required this.credentialsProvider, super.fileStorage, - super.awsHttpClient, }) : super(credentialsProvider: credentialsProvider); /// The credentials provider to use for signing the request. diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 67f86d93a8..b62e03e5ed 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -118,7 +118,7 @@ void main() { when(() => mockFileStorage.loadConstraint(any())) .thenAnswer((_) async => Future.value(sampleJson)); - provider = BaseRemoteLoggingConstraintProvider( + provider = BaseRemoteLoggingConstraintProvider.forTesting( config: const DefaultRemoteConfiguration( refreshInterval: Duration(seconds: 10), endpoint: 'https://example.com', From 53c66a3638687c8cad8e422ada4c34a14d70453b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:46:26 -0700 Subject: [PATCH 08/28] chore: changed names of file storage functions, logger level, and removed flutter plugin gitignore --- .../lib/src/file_storage/file_storage.dart | 4 ++-- .../lib/src/file_storage/file_storage_stub.dart | 4 ++-- .../lib/src/file_storage/file_storage_vm.dart | 4 ++-- .../lib/src/file_storage/file_storage_web.dart | 4 ++-- .../lib/src/remote_constraint_provider.dart | 6 +++--- .../test/remote_constraint_provider_test.dart | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 649e1a7517..6e4cdde266 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -8,8 +8,8 @@ export 'file_storage_stub.dart' /// File storage interface for saving and loading constraint locally abstract interface class FileStorage { /// Save constraint locally to file - Future saveConstraintLocally(String filename, String data); + Future save(String filename, String data); /// Load constraint from file - Future loadConstraint(String filename); + Future load(String filename); } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart index 8f71d4c5e8..6b8971325c 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_stub.dart @@ -13,12 +13,12 @@ class FileStorageImpl implements FileStorage { final AppPathProvider pathProvider; @override - Future loadConstraint(String fileName) async { + Future load(String fileName) async { throw UnimplementedError(); } @override - Future saveConstraintLocally(String fileName, String content) async { + Future save(String fileName, String content) async { throw UnimplementedError(); } } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart index 7deb4ec801..30c39773e0 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_vm.dart @@ -16,7 +16,7 @@ class FileStorageImpl implements FileStorage { final AppPathProvider pathProvider; @override - Future loadConstraint(String fileName) async { + Future load(String fileName) async { final file = File(p.join(await pathProvider.getApplicationSupportPath(), fileName)); if (await file.exists()) { @@ -26,7 +26,7 @@ class FileStorageImpl implements FileStorage { } @override - Future saveConstraintLocally(String fileName, String content) async { + Future save(String fileName, String content) async { final file = File(p.join(await pathProvider.getApplicationSupportPath(), fileName)); await file.writeAsString(content); diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart index 4bdf07efec..4475a00b62 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage_web.dart @@ -15,12 +15,12 @@ class FileStorageImpl implements FileStorage { static const _prefix = 'aws.cloudwatch'; @override - Future loadConstraint(String fileName) async { + Future load(String fileName) async { return window.localStorage['$_prefix.$fileName']; } @override - Future saveConstraintLocally(String fileName, String content) async { + Future save(String fileName, String content) async { window.localStorage['$_prefix.$fileName'] = content; } } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 18b86511c3..e964e3e49c 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -113,7 +113,7 @@ base class BaseRemoteLoggingConstraintProvider _loggingConstraint = fetchedConstraint; if (_fileStorage != null) { - await _fileStorage!.saveConstraintLocally( + await _fileStorage!.save( 'remoteloggingconstraints.json', jsonEncode(fetchedConstraint.toJson()), ); @@ -122,7 +122,7 @@ base class BaseRemoteLoggingConstraintProvider await _loadConstraintFromLocalCache(); } } on Exception catch (exception) { - logger.debug( + logger.error( 'Failed to fetch logging constraint from ${_config.endpoint}: $exception', ); await _loadConstraintFromLocalCache(); @@ -140,7 +140,7 @@ base class BaseRemoteLoggingConstraintProvider Future _loadConstraintFromLocalCache() async { final localConstraint = - await _fileStorage!.loadConstraint('remoteloggingconstraints.json'); + await _fileStorage!.load('remoteloggingconstraints.json'); if (localConstraint != null) { _loggingConstraint = LoggingConstraint.fromJson( jsonDecode(localConstraint) as Map, diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index b62e03e5ed..b975bd1e68 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -35,7 +35,7 @@ class MockFileStorage extends Mock implements FileStorageImpl { MockFileStorage(); @override - Future saveConstraintLocally(String fileName, String content) async {} + Future save(String fileName, String content) async {} } class MockAWSHttpClient extends Mock implements AWSHttpClient {} @@ -115,7 +115,7 @@ void main() { return mockOperation; }); - when(() => mockFileStorage.loadConstraint(any())) + when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); provider = BaseRemoteLoggingConstraintProvider.forTesting( @@ -151,7 +151,7 @@ void main() { }); // mock load constraint returns null - when(() => mockFileStorage.loadConstraint(any())) + when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(null)); await Future.delayed(const Duration(seconds: 3)); @@ -168,7 +168,7 @@ void main() { ); }); - when(() => mockFileStorage.loadConstraint(any())) + when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); await Future.delayed(const Duration(seconds: 3)); From 507d08664cd59db73a69e3a8097e217b7476d370 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 12:46:44 -0700 Subject: [PATCH 09/28] chore: remove flutter plugin .gitignore --- .../logging_cloudwatch/amplify_logging_cloudwatch/.gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore index f7752a753f..65c34dc86e 100644 --- a/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore +++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore @@ -1,8 +1,6 @@ # Files and directories created by pub. .dart_tool/ .packages -.flutter-plugins -.flutter-plugins-dependencies # Conventional directory for build outputs. build/ From bc635d8211fb56244abffc36f496c66279e6a18f Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:40:14 -0700 Subject: [PATCH 10/28] chore: Added completer, removed conditional import, added closeable, removed credentials provider from base class and only added to defaultconstraintprovider, changed fileStorage to use FileStorage instead of just FileStorageImpl, removed isRunning since it is never false after the first run, added completer so that delay isn't needed for tests, removed the catching of errors, added credentials provider parameter to awssigv4signer, --- .../lib/src/remote_constraint_provider.dart | 97 ++++++++----------- .../test/remote_constraint_provider_test.dart | 68 ++++++------- 2 files changed, 70 insertions(+), 95 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index e964e3e49c..7129d48c39 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -10,9 +10,7 @@ import 'package:amplify_core/amplify_core.dart'; import 'package:aws_common/aws_common.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; -import 'package:aws_logging_cloudwatch/src/file_storage/file_storage_stub.dart' - if (dart.library.io) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart' - if (dart.library.html) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_web.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; import 'package:aws_signature_v4/aws_signature_v4.dart'; import 'package:meta/meta.dart'; @@ -32,15 +30,13 @@ abstract class RemoteLoggingConstraintProvider { /// {@endtemplate} base class BaseRemoteLoggingConstraintProvider with AWSDebuggable, AWSLoggerMixin - implements RemoteLoggingConstraintProvider { + implements RemoteLoggingConstraintProvider, Closeable { /// {@macro aws_logging_cloudwatch.base_remote_constraints_provider} BaseRemoteLoggingConstraintProvider({ required DefaultRemoteConfiguration config, - required AWSCredentialsProvider credentialsProvider, - FileStorageImpl? fileStorage, + FileStorage? fileStorage, }) : _fileStorage = fileStorage, _config = config, - _credentialsProvider = credentialsProvider, _awsHttpClient = AWSHttpClient() { _init(); } @@ -50,31 +46,27 @@ base class BaseRemoteLoggingConstraintProvider @visibleForTesting BaseRemoteLoggingConstraintProvider.forTesting({ required DefaultRemoteConfiguration config, - required AWSCredentialsProvider credentialsProvider, required AWSHttpClient awsHttpClient, - FileStorageImpl? fileStorage, + FileStorage? fileStorage, }) : _fileStorage = fileStorage, _config = config, - _credentialsProvider = credentialsProvider, _awsHttpClient = awsHttpClient { _init(); } - final FileStorageImpl? _fileStorage; + final FileStorage? _fileStorage; final DefaultRemoteConfiguration _config; - final AWSCredentialsProvider _credentialsProvider; LoggingConstraint? _loggingConstraint; final AWSHttpClient _awsHttpClient; + static const _cacheFileName = 'remoteloggingconstraints.json'; + // The timer to refresh the constraint periodically. Timer? _timer; - /// Whether the periodic fetch is running. - bool _isRunning = false; - /// Retrives the runtime type name used for logging. @override String get runtimeTypeName => 'BaseRemoteConstraintsProvider'; @@ -83,12 +75,17 @@ base class BaseRemoteLoggingConstraintProvider /// the constraint from the endpoint initially and then /// starting the refresh timer afterwards. void _init() { - _refreshConstraintPeriodically(); + _readyCompleter.complete(_refreshConstraintPeriodically()); } + final Completer _readyCompleter = Completer(); + + /// A future that completes when the [BaseRemoteLoggingConstraintProvider] + Future get ready => _readyCompleter.future; + /// Creates a request to fetch the constraint from the endpoint. @protected - Future createRequest() async { + Future createRequest() async { final uri = Uri.parse(_config.endpoint); return AWSHttpRequest( method: AWSHttpMethod.get, @@ -106,31 +103,25 @@ base class BaseRemoteLoggingConstraintProvider final operation = _awsHttpClient.send(request); final response = await operation.response; final body = await response.decodeBody(); - if (response.statusCode == 200) { - final fetchedConstraint = LoggingConstraint.fromJson( - jsonDecode(body) as Map, + if (response.statusCode != 200) { + logger.error('Failed to fetch constraints', body); + return; + } + final fetchedConstraint = LoggingConstraint.fromJson( + jsonDecode(body) as Map, + ); + _loggingConstraint = fetchedConstraint; + + if (_fileStorage != null) { + await _fileStorage!.save( + _cacheFileName, + jsonEncode(fetchedConstraint.toJson()), ); - _loggingConstraint = fetchedConstraint; - - if (_fileStorage != null) { - await _fileStorage!.save( - 'remoteloggingconstraints.json', - jsonEncode(fetchedConstraint.toJson()), - ); - } - } else { - await _loadConstraintFromLocalCache(); } } on Exception catch (exception) { logger.error( 'Failed to fetch logging constraint from ${_config.endpoint}: $exception', ); - await _loadConstraintFromLocalCache(); - } on Error catch (error, stackTrace) { - logger.error( - 'Error while fetching logging constraint from ${_config.endpoint}: $error', - stackTrace, - ); } } @@ -139,8 +130,7 @@ base class BaseRemoteLoggingConstraintProvider LoggingConstraint? get loggingConstraint => _loggingConstraint; Future _loadConstraintFromLocalCache() async { - final localConstraint = - await _fileStorage!.load('remoteloggingconstraints.json'); + final localConstraint = await _fileStorage!.load(_cacheFileName); if (localConstraint != null) { _loggingConstraint = LoggingConstraint.fromJson( jsonDecode(localConstraint) as Map, @@ -149,19 +139,19 @@ base class BaseRemoteLoggingConstraintProvider } /// Refreshes the constraint from the endpoint periodically. - void _refreshConstraintPeriodically() { - if (_isRunning) { - return; - } - - _isRunning = true; - _timer?.cancel(); - + Future _refreshConstraintPeriodically() async { + await _loadConstraintFromLocalCache(); _timer = Timer.periodic( _config.refreshInterval, (_) => _fetchAndCacheConstraintFromEndpoint(), ); - Timer.run(_fetchAndCacheConstraintFromEndpoint); + await _fetchAndCacheConstraintFromEndpoint(); + } + + @override + void close() { + _timer?.cancel(); + _timer = null; } } @@ -176,16 +166,18 @@ final class DefaultRemoteLoggingConstraintProvider required super.config, required this.credentialsProvider, super.fileStorage, - }) : super(credentialsProvider: credentialsProvider); + }); /// The credentials provider to use for signing the request. final AWSCredentialsProvider credentialsProvider; /// The signer to use for signing the request. - final AWSSigV4Signer _signer = const AWSSigV4Signer(); + late final AWSSigV4Signer _signer = AWSSigV4Signer( + credentialsProvider: credentialsProvider, + ); @override - Future createRequest() async { + Future createRequest() async { final baseRequest = await super.createRequest(); final scope = AWSCredentialScope( region: _config.region, @@ -197,10 +189,7 @@ final class DefaultRemoteLoggingConstraintProvider credentialScope: scope, ); - final newRequest = - AWSHttpRequest(method: signedRequest.method, uri: signedRequest.uri); - - return newRequest; + return signedRequest; } } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index b975bd1e68..515c135e25 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -4,11 +4,9 @@ import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_common/testing.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; -import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart' - if (dart.library.io) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart' - if (dart.library.html) 'package:aws_logging_cloudwatch/src/file_storage/file_storage_web.dart'; -// import 'package:aws_logging_cloudwatch/src/file_storage/file_storage_vm.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -31,24 +29,15 @@ const sampleJson = ''' } '''; -class MockFileStorage extends Mock implements FileStorageImpl { - MockFileStorage(); - +class MockFileStorage extends Mock implements FileStorage { @override Future save(String fileName, String content) async {} } -class MockAWSHttpClient extends Mock implements AWSHttpClient {} - -class MockAWSHttpOperation extends Mock - implements AWSHttpOperation {} - class MockAWSCredentialsProvider extends Mock implements AWSCredentialsProvider {} -class PathProvider extends Mock implements AppPathProvider { - PathProvider(); - +class PathProvider implements AppPathProvider { @override Future getApplicationSupportPath() async { return ''; @@ -68,12 +57,19 @@ final fakeRequest = AWSHttpRequest( ); void main() { + test('LoggingConstraint', () { + final sampleJsonMap = jsonDecode(sampleJson) as Map; + final loggingConstraint = LoggingConstraint.fromJson(sampleJsonMap); + expect( + loggingConstraint.toJson(), + sampleJsonMap, + ); + }); + group('RemoteLoggingConstraintProvider', () { late BaseRemoteLoggingConstraintProvider provider; - late FileStorageImpl mockFileStorage; + late FileStorage mockFileStorage; late MockAWSHttpClient mockAWSHttpClient; - late MockAWSCredentialsProvider mockCredentialsProvider; - late MockAWSHttpOperation mockOperation; const sampleJson = ''' { @@ -96,25 +92,9 @@ void main() { setUp(() { mockFileStorage = MockFileStorage(); - mockAWSHttpClient = MockAWSHttpClient(); - mockCredentialsProvider = MockAWSCredentialsProvider(); - mockOperation = MockAWSHttpOperation(); registerFallbackValue(fakeRequest); - // mock the response from the endpoint - when(() => mockOperation.response).thenAnswer((_) async { - return AWSHttpResponse( - statusCode: 200, - body: utf8.encode(sampleJson), - ); - }); - - // mock the call to createRequest - when(() => mockAWSHttpClient.send(any())).thenAnswer((_) { - return mockOperation; - }); - when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); @@ -124,13 +104,19 @@ void main() { endpoint: 'https://example.com', region: 'us-west-2', ), - credentialsProvider: mockCredentialsProvider, fileStorage: mockFileStorage, awsHttpClient: mockAWSHttpClient, ); }); test('initializes _loggingConstraint from endpoint', () async { + mockAWSHttpClient = MockAWSHttpClient((request, _) { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(sampleJson), + ); + }); + await Future.delayed(const Duration(seconds: 3)); // Verify that _loggingConstraint exists @@ -143,7 +129,7 @@ void main() { test( 'fetches _loggingConstraint from local storage and returns null if there are no constraints in local storage', () async { - when(() => mockOperation.response).thenAnswer((_) async { + mockAWSHttpClient = MockAWSHttpClient((request, _) { return AWSHttpResponse( statusCode: 400, body: utf8.encode('NO RESPONSE'), @@ -154,14 +140,14 @@ void main() { when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(null)); - await Future.delayed(const Duration(seconds: 3)); + await provider.ready; // Verify that _loggingConstraint is set expect(provider.loggingConstraint, equals(null)); }); test('uses local storage if endpoint fails', () async { - when(() => mockOperation.response).thenAnswer((_) async { + mockAWSHttpClient = MockAWSHttpClient((request, _) { return AWSHttpResponse( statusCode: 400, body: utf8.encode('NO RESPONSE'), @@ -171,7 +157,7 @@ void main() { when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); - await Future.delayed(const Duration(seconds: 3)); + await provider.ready; // Verify that _loggingConstraint uses local storage expect( @@ -201,14 +187,14 @@ void main() { } '''; - when(() => mockOperation.response).thenAnswer((_) async { + mockAWSHttpClient = MockAWSHttpClient((request, _) { return AWSHttpResponse( statusCode: 200, body: utf8.encode(updatedJson), ); }); - await Future.delayed(const Duration(seconds: 3)); + await provider.ready; // Verify that _loggingConstraint got updated expect( From 1e3c6ae805bad7b454e6243bee6c626bfce41dc4 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:42:09 -0700 Subject: [PATCH 11/28] chore: updated aft workflows --- .github/workflows/aws_logging_cloudwatch.yaml | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/aws_logging_cloudwatch.yaml b/.github/workflows/aws_logging_cloudwatch.yaml index 4ae5a35606..901c6c12fd 100644 --- a/.github/workflows/aws_logging_cloudwatch.yaml +++ b/.github/workflows/aws_logging_cloudwatch.yaml @@ -11,7 +11,8 @@ on: - '.github/workflows/aws_logging_cloudwatch.yaml' - '.github/workflows/dart_dart2js.yaml' - '.github/workflows/dart_ddc.yaml' - - '.github/workflows/flutter_vm.yaml' + - '.github/workflows/dart_native.yaml' + - '.github/workflows/dart_vm.yaml' - 'packages/amplify_core/lib/**/*.dart' - 'packages/amplify_core/pubspec.yaml' - 'packages/amplify_lints/lib/**/*.yaml' @@ -37,7 +38,25 @@ permissions: read-all jobs: test: - uses: ./.github/workflows/flutter_vm.yaml + uses: ./.github/workflows/dart_vm.yaml + with: + package-name: aws_logging_cloudwatch + working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch + native_test: + needs: test + uses: ./.github/workflows/dart_native.yaml + with: + package-name: aws_logging_cloudwatch + working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch + ddc_test: + needs: test + uses: ./.github/workflows/dart_ddc.yaml + with: + package-name: aws_logging_cloudwatch + working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch + dart2js_test: + needs: test + uses: ./.github/workflows/dart_dart2js.yaml with: package-name: aws_logging_cloudwatch working-directory: packages/logging_cloudwatch/aws_logging_cloudwatch From 6c03448c2d3676065d4baf37731c2b7fc7c09163 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:05:53 -0700 Subject: [PATCH 12/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../aws_logging_cloudwatch/lib/src/plugin_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index 5f85de6a9c..455f1e8116 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -99,7 +99,7 @@ class LoggingConstraint with AWSDebuggable { } /// The logging constraint for user specific log level. -@JsonSerializable() +@zAmplifySerializable class UserLogLevel { /// The logging constraint for user specific log level. const UserLogLevel({ From 03a8455bf922faf6ad30e24a8567b89e829a1279 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:06:07 -0700 Subject: [PATCH 13/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../aws_logging_cloudwatch/lib/src/plugin_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index 455f1e8116..57d30436d9 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -50,7 +50,7 @@ class CloudWatchLoggerPluginConfiguration with AWSDebuggable { /// {@template aws_logging_cloudwatch.logging_constraint} /// The logging constraint for sending logs to CloudWatch. /// {@endtemplate} -@JsonSerializable() +@zAmplifySerializable class LoggingConstraint with AWSDebuggable { /// {@macro aws_logging_cloudwatch.logging_constraint} const LoggingConstraint({ From 05391d889b72c4869e02fe8bee4dc8fe9848566c Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:17:31 -0700 Subject: [PATCH 14/28] chore: removed manual toJson and replace jsonSerializable with zAmplifySerializable --- .../lib/src/plugin_config.dart | 37 ++------------ .../lib/src/plugin_config.g.dart | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index 57d30436d9..e1b6bac485 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -1,9 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'package:aws_common/aws_common.dart'; +import 'package:amplify_core/amplify_core.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; -import 'package:json_annotation/json_annotation.dart'; part 'plugin_config.g.dart'; @@ -63,27 +62,8 @@ class LoggingConstraint with AWSDebuggable { factory LoggingConstraint.fromJson(Map json) => _$LoggingConstraintFromJson(json); - // /// Converts an [LoggingConstraint] instance to a [Map]. - // Map toJson() => _$LoggingConstraintToJson(this); - /// Converts an [LoggingConstraint] instance to a [Map]. - Map toJson() { - final jsonMap = { - 'defaultLogLevel': - defaultLogLevel.toString().split('.').last, // Convert enum to string - 'categoryLogLevel': categoryLogLevel?.map( - (key, value) => MapEntry(key, value.toString().split('.').last), - ), - }; - - if (userLogLevel != null) { - jsonMap['userLogLevel'] = userLogLevel!.map( - (key, value) => MapEntry(key, value.toJson()), - ); - } - - return jsonMap; - } + Map toJson() => _$LoggingConstraintToJson(this); /// The default [LogLevel] for sending logs to CloudWatch. final LogLevel defaultLogLevel; @@ -112,18 +92,7 @@ class UserLogLevel { _$UserLogLevelFromJson(json); /// Converts a [UserLogLevel] instance to a [Map]. - Map toJson() => { - 'defaultLogLevel': defaultLogLevel - ?.toString() - .split('.') - .last, // Convert enum to string - 'categoryLogLevel': categoryLogLevel?.map( - (key, value) => MapEntry( - key, - value.toString().split('.').last, - ), // Convert enum to string - ), - }; + Map toJson() => _$UserLogLevelToJson(this); /// The default [LogLevel] for sending logs to CloudWatch. final LogLevel? defaultLogLevel; diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart index 2ec0bf735a..64733e29d2 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.g.dart @@ -20,13 +20,25 @@ LoggingConstraint _$LoggingConstraintFromJson(Map json) => ), ); -Map _$LoggingConstraintToJson(LoggingConstraint instance) => - { - 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel]!, - 'categoryLogLevel': instance.categoryLogLevel - ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!)), - 'userLogLevel': instance.userLogLevel, - }; +Map _$LoggingConstraintToJson(LoggingConstraint instance) { + final val = { + 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel]!, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull( + 'categoryLogLevel', + instance.categoryLogLevel + ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!))); + writeNotNull('userLogLevel', + instance.userLogLevel?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} const _$LogLevelEnumMap = { LogLevel.verbose: 'verbose', @@ -46,9 +58,19 @@ UserLogLevel _$UserLogLevelFromJson(Map json) => UserLogLevel( ), ); -Map _$UserLogLevelToJson(UserLogLevel instance) => - { - 'defaultLogLevel': _$LogLevelEnumMap[instance.defaultLogLevel], - 'categoryLogLevel': instance.categoryLogLevel - ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!)), - }; +Map _$UserLogLevelToJson(UserLogLevel instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('defaultLogLevel', _$LogLevelEnumMap[instance.defaultLogLevel]); + writeNotNull( + 'categoryLogLevel', + instance.categoryLogLevel + ?.map((k, e) => MapEntry(k, _$LogLevelEnumMap[e]!))); + return val; +} From 009b0d8e7b6b99ca95908298974b81c3ec675d4d Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:54:32 -0700 Subject: [PATCH 15/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/file_storage/file_storage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 6e4cdde266..7e049bad9c 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -8,7 +8,7 @@ export 'file_storage_stub.dart' /// File storage interface for saving and loading constraint locally abstract interface class FileStorage { /// Save constraint locally to file - Future save(String filename, String data); + Future save(String fileName, String data); /// Load constraint from file Future load(String filename); From 9ecffbf8e002355d0df319524a4dbf27aa2f689f Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:54:47 -0700 Subject: [PATCH 16/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/file_storage/file_storage.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 7e049bad9c..9421e10edc 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -1,12 +1,14 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -export 'file_storage_stub.dart' +import 'file_storage_stub.dart' if (dart.library.io) 'file_storage_vm.dart' if (dart.library.html) 'file_storage_web.dart'; /// File storage interface for saving and loading constraint locally abstract interface class FileStorage { + factory FileStorage(AppPathProvider pathProvider) = FileStorageImpl; + /// Save constraint locally to file Future save(String fileName, String data); From 875048653b2e95b9c0d4268d6b9af4122aa06668 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Fri, 15 Sep 2023 16:00:21 -0700 Subject: [PATCH 17/28] chore: use factory constructor and _fileStorage might be null in remoteConstraint --- .../lib/src/file_storage/file_storage.dart | 4 +++- .../lib/src/remote_constraint_provider.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 9421e10edc..0f257345f5 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -1,12 +1,14 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import 'file_storage_stub.dart' +import 'package:amplify_core/amplify_core.dart'; +import 'package:aws_logging_cloudwatch/src/file_storage/file_storage_stub.dart' if (dart.library.io) 'file_storage_vm.dart' if (dart.library.html) 'file_storage_web.dart'; /// File storage interface for saving and loading constraint locally abstract interface class FileStorage { + /// Default Constructor or FileStorage factory FileStorage(AppPathProvider pathProvider) = FileStorageImpl; /// Save constraint locally to file diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 7129d48c39..72502046ca 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -113,7 +113,7 @@ base class BaseRemoteLoggingConstraintProvider _loggingConstraint = fetchedConstraint; if (_fileStorage != null) { - await _fileStorage!.save( + await _fileStorage?.save( _cacheFileName, jsonEncode(fetchedConstraint.toJson()), ); From f513d0eafe363c64d0578af8aa5f04ab5f7c233b Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:05:05 -0700 Subject: [PATCH 18/28] chore: fixed local storage variable and method name and also added and fixed tests --- .../lib/src/remote_constraint_provider.dart | 6 +- .../test/remote_constraint_provider_test.dart | 158 ++++++++++-------- 2 files changed, 95 insertions(+), 69 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 72502046ca..0668a9d0dd 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -129,8 +129,8 @@ base class BaseRemoteLoggingConstraintProvider @override LoggingConstraint? get loggingConstraint => _loggingConstraint; - Future _loadConstraintFromLocalCache() async { - final localConstraint = await _fileStorage!.load(_cacheFileName); + Future _loadConstraintFromLocalStorage() async { + final localConstraint = await _fileStorage?.load(_cacheFileName); if (localConstraint != null) { _loggingConstraint = LoggingConstraint.fromJson( jsonDecode(localConstraint) as Map, @@ -140,7 +140,7 @@ base class BaseRemoteLoggingConstraintProvider /// Refreshes the constraint from the endpoint periodically. Future _refreshConstraintPeriodically() async { - await _loadConstraintFromLocalCache(); + await _loadConstraintFromLocalStorage(); _timer = Timer.periodic( _config.refreshInterval, (_) => _fetchAndCacheConstraintFromEndpoint(), diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 515c135e25..20cafc87ba 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +@Timeout(Duration(seconds: 300)) + import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; @@ -29,10 +31,7 @@ const sampleJson = ''' } '''; -class MockFileStorage extends Mock implements FileStorage { - @override - Future save(String fileName, String content) async {} -} +class MockFileStorage extends Mock implements FileStorage {} class MockAWSCredentialsProvider extends Mock implements AWSCredentialsProvider {} @@ -57,6 +56,9 @@ final fakeRequest = AWSHttpRequest( ); void main() { + late BaseRemoteLoggingConstraintProvider provider; + late FileStorage mockFileStorage; + late MockAWSHttpClient mockAWSHttpClient; test('LoggingConstraint', () { final sampleJsonMap = jsonDecode(sampleJson) as Map; final loggingConstraint = LoggingConstraint.fromJson(sampleJsonMap); @@ -67,57 +69,34 @@ void main() { }); group('RemoteLoggingConstraintProvider', () { - late BaseRemoteLoggingConstraintProvider provider; - late FileStorage mockFileStorage; - late MockAWSHttpClient mockAWSHttpClient; - - const sampleJson = ''' - { - "defaultLogLevel": "error", - "categoryLogLevel": { - "API": "debug", - "AUTH": "debug" - }, - "userLogLevel": { - "cognito-sub-xyz-123": { - "defaultLogLevel": "verbose", - "categoryLogLevel": { - "API": "verbose", - "AUTH": "verbose" - } - } - } - } - '''; - setUp(() { mockFileStorage = MockFileStorage(); registerFallbackValue(fakeRequest); + }); + test('initializes _loggingConstraint from endpoint', () async { when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); + when(() => mockFileStorage.save(any(), any())).thenAnswer((_) async {}); + mockAWSHttpClient = MockAWSHttpClient((request, _) { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(sampleJson), + ); + }); provider = BaseRemoteLoggingConstraintProvider.forTesting( config: const DefaultRemoteConfiguration( - refreshInterval: Duration(seconds: 10), + refreshInterval: Duration(seconds: 1200), endpoint: 'https://example.com', region: 'us-west-2', ), fileStorage: mockFileStorage, awsHttpClient: mockAWSHttpClient, ); - }); - test('initializes _loggingConstraint from endpoint', () async { - mockAWSHttpClient = MockAWSHttpClient((request, _) { - return AWSHttpResponse( - statusCode: 200, - body: utf8.encode(sampleJson), - ); - }); - - await Future.delayed(const Duration(seconds: 3)); + await provider.ready; // Verify that _loggingConstraint exists expect( @@ -126,36 +105,25 @@ void main() { ); }); - test( - 'fetches _loggingConstraint from local storage and returns null if there are no constraints in local storage', - () async { - mockAWSHttpClient = MockAWSHttpClient((request, _) { - return AWSHttpResponse( - statusCode: 400, - body: utf8.encode('NO RESPONSE'), - ); - }); - - // mock load constraint returns null - when(() => mockFileStorage.load(any())) - .thenAnswer((_) async => Future.value(null)); - - await provider.ready; - - // Verify that _loggingConstraint is set - expect(provider.loggingConstraint, equals(null)); - }); - test('uses local storage if endpoint fails', () async { + when(() => mockFileStorage.load(any())) + .thenAnswer((_) async => Future.value(sampleJson)); + when(() => mockFileStorage.save(any(), any())).thenAnswer((_) async {}); mockAWSHttpClient = MockAWSHttpClient((request, _) { return AWSHttpResponse( statusCode: 400, body: utf8.encode('NO RESPONSE'), ); }); - - when(() => mockFileStorage.load(any())) - .thenAnswer((_) async => Future.value(sampleJson)); + provider = BaseRemoteLoggingConstraintProvider.forTesting( + config: const DefaultRemoteConfiguration( + refreshInterval: Duration(seconds: 10), + endpoint: 'https://example.com', + region: 'us-west-2', + ), + fileStorage: mockFileStorage, + awsHttpClient: mockAWSHttpClient, + ); await provider.ready; @@ -186,13 +154,43 @@ void main() { } } '''; - + // Mocking the endpoint to return updated constraints on the second call + var callCount = 0; + var callCount2 = 0; + when(() => mockFileStorage.load(any())).thenAnswer((_) async { + callCount2++; + if (callCount2 == 1) { + return Future.value(sampleJson); + } else { + return Future.value(updatedJson); + } + }); + when(() => mockFileStorage.save(any(), any())).thenAnswer((_) async {}); mockAWSHttpClient = MockAWSHttpClient((request, _) { - return AWSHttpResponse( - statusCode: 200, - body: utf8.encode(updatedJson), - ); + callCount++; + if (callCount == 1) { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(sampleJson), + ); + } else { + return AWSHttpResponse( + statusCode: 200, + body: utf8.encode(updatedJson), + ); + } }); + provider = BaseRemoteLoggingConstraintProvider.forTesting( + config: const DefaultRemoteConfiguration( + refreshInterval: Duration(seconds: 1), + endpoint: 'https://example.com', + region: 'us-west-2', + ), + fileStorage: mockFileStorage, + awsHttpClient: mockAWSHttpClient, + ); + + await Future.delayed(const Duration(seconds: 2)); await provider.ready; @@ -202,5 +200,33 @@ void main() { equals(json.decode(updatedJson)), ); }); + + test( + 'fetches _loggingConstraint from local storage and returns null if there are no constraints in local storage', + () async { + when(() => mockFileStorage.load(any())) + .thenAnswer((_) async => Future.value(null)); + when(() => mockFileStorage.save(any(), any())).thenAnswer((_) async {}); + mockAWSHttpClient = MockAWSHttpClient((request, _) { + return AWSHttpResponse( + statusCode: 400, + body: utf8.encode('NO RESPONSE'), + ); + }); + provider = BaseRemoteLoggingConstraintProvider.forTesting( + config: const DefaultRemoteConfiguration( + refreshInterval: Duration(seconds: 10), + endpoint: 'https://example.com', + region: 'us-west-2', + ), + fileStorage: mockFileStorage, + awsHttpClient: mockAWSHttpClient, + ); + + await provider.ready; + + // Verify that _loggingConstraint is set + expect(provider.loggingConstraint, equals(null)); + }); }); } From c12af97127f89d1bf4d0be70437f680a60f1933f Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:27:15 -0700 Subject: [PATCH 19/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/remote_constraint_provider.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 0668a9d0dd..9ed5dbab67 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -125,7 +125,8 @@ base class BaseRemoteLoggingConstraintProvider } } - /// Returns [LoggingConstraint] from cache or `null` if cache is missing. + /// Returns [LoggingConstraint] from the cache or `null` if the cache is missing + /// or if the constraints could not be retrieved from the remote server. @override LoggingConstraint? get loggingConstraint => _loggingConstraint; From 2cbb9b7f49751a4a4563d44817980e958db69cc9 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:31:44 -0700 Subject: [PATCH 20/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/remote_constraint_provider.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 9ed5dbab67..af2fa06e54 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -84,6 +84,8 @@ base class BaseRemoteLoggingConstraintProvider Future get ready => _readyCompleter.future; /// Creates a request to fetch the constraint from the endpoint. + /// + /// Can be overridden by subclasses to change how the request is created. @protected Future createRequest() async { final uri = Uri.parse(_config.endpoint); From cad71d6e43b5b5c15bb044a3e0836ba4852e2144 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:31:55 -0700 Subject: [PATCH 21/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/remote_constraint_provider.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index af2fa06e54..6c51adb86e 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -87,6 +87,7 @@ base class BaseRemoteLoggingConstraintProvider /// /// Can be overridden by subclasses to change how the request is created. @protected + @visibleForOverriding Future createRequest() async { final uri = Uri.parse(_config.endpoint); return AWSHttpRequest( From 5f185c4b3436b65c0a5632996b8a58e905ab8565 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:32:03 -0700 Subject: [PATCH 22/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart Co-authored-by: Dillon Nys <24740863+dnys1@users.noreply.github.com> --- .../lib/src/remote_constraint_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index 6c51adb86e..f1b65a0ebb 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -107,7 +107,7 @@ base class BaseRemoteLoggingConstraintProvider final response = await operation.response; final body = await response.decodeBody(); if (response.statusCode != 200) { - logger.error('Failed to fetch constraints', body); + logger.error('Failed to fetch constraints', (response.statusCode, body)); return; } final fetchedConstraint = LoggingConstraint.fromJson( From b77c8ae9702ec8462f95f17edfaf4c7817cbb188 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:41:14 -0700 Subject: [PATCH 23/28] chore: add AWSSerializable and added comment to test to address Future.delayed --- .../aws_logging_cloudwatch/lib/src/plugin_config.dart | 9 +++++++-- .../test/remote_constraint_provider_test.dart | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart index e1b6bac485..3367eb505c 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/plugin_config.dart @@ -50,7 +50,7 @@ class CloudWatchLoggerPluginConfiguration with AWSDebuggable { /// The logging constraint for sending logs to CloudWatch. /// {@endtemplate} @zAmplifySerializable -class LoggingConstraint with AWSDebuggable { +class LoggingConstraint with AWSDebuggable, AWSSerializable { /// {@macro aws_logging_cloudwatch.logging_constraint} const LoggingConstraint({ this.defaultLogLevel = LogLevel.error, @@ -63,6 +63,7 @@ class LoggingConstraint with AWSDebuggable { _$LoggingConstraintFromJson(json); /// Converts an [LoggingConstraint] instance to a [Map]. + @override Map toJson() => _$LoggingConstraintToJson(this); /// The default [LogLevel] for sending logs to CloudWatch. @@ -80,7 +81,7 @@ class LoggingConstraint with AWSDebuggable { /// The logging constraint for user specific log level. @zAmplifySerializable -class UserLogLevel { +class UserLogLevel with AWSDebuggable, AWSSerializable { /// The logging constraint for user specific log level. const UserLogLevel({ this.defaultLogLevel, @@ -92,6 +93,7 @@ class UserLogLevel { _$UserLogLevelFromJson(json); /// Converts a [UserLogLevel] instance to a [Map]. + @override Map toJson() => _$UserLogLevelToJson(this); /// The default [LogLevel] for sending logs to CloudWatch. @@ -99,4 +101,7 @@ class UserLogLevel { /// The [LogLevel] for different categories. final Map? categoryLogLevel; + + @override + String get runtimeTypeName => 'UserLogLevel'; } diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 20cafc87ba..78b3064a28 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -127,7 +127,7 @@ void main() { await provider.ready; - // Verify that _loggingConstraint uses local storage + // Verify that _loggingConstraint uses local storage expect( provider.loggingConstraint!.toJson(), equals(json.decode(sampleJson)), @@ -190,11 +190,12 @@ void main() { awsHttpClient: mockAWSHttpClient, ); + // Wait for the provider to refresh the constraints and make the second call to the endpoint await Future.delayed(const Duration(seconds: 2)); await provider.ready; - // Verify that _loggingConstraint got updated + // Verify that _loggingConstraint got updated expect( provider.loggingConstraint!.toJson(), equals(json.decode(updatedJson)), @@ -225,7 +226,7 @@ void main() { await provider.ready; - // Verify that _loggingConstraint is set + // Verify that _loggingConstraint is set expect(provider.loggingConstraint, equals(null)); }); }); From da94fe3a54a7225c9a2a696fc7de65bc5c24c9e9 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:45:05 -0700 Subject: [PATCH 24/28] chore: fixed some formatting and removed todo comment --- .../lib/src/remote_constraint_provider.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart index f1b65a0ebb..ff6e0b31a1 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/remote_constraint_provider.dart @@ -5,9 +5,6 @@ import 'dart:async'; import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; -// TODO(nikahsn): remove after implementing the get loggingConstraint. -// ignore_for_file: unused_field - import 'package:aws_common/aws_common.dart'; import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart'; import 'package:aws_logging_cloudwatch/src/file_storage/file_storage.dart'; @@ -107,7 +104,8 @@ base class BaseRemoteLoggingConstraintProvider final response = await operation.response; final body = await response.decodeBody(); if (response.statusCode != 200) { - logger.error('Failed to fetch constraints', (response.statusCode, body)); + logger + .error('Failed to fetch constraints', (response.statusCode, body)); return; } final fetchedConstraint = LoggingConstraint.fromJson( From 0f3bae3d39a06154e56d665c79f9cb297aeb5ab4 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:39:29 -0700 Subject: [PATCH 25/28] chore: removed unnecessary comments --- .../test/remote_constraint_provider_test.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 78b3064a28..9bdf4abd2f 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -@Timeout(Duration(seconds: 300)) - import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; @@ -98,7 +96,6 @@ void main() { await provider.ready; - // Verify that _loggingConstraint exists expect( provider.loggingConstraint!.toJson(), equals(json.decode(sampleJson)), @@ -127,7 +124,6 @@ void main() { await provider.ready; - // Verify that _loggingConstraint uses local storage expect( provider.loggingConstraint!.toJson(), equals(json.decode(sampleJson)), @@ -195,7 +191,6 @@ void main() { await provider.ready; - // Verify that _loggingConstraint got updated expect( provider.loggingConstraint!.toJson(), equals(json.decode(updatedJson)), @@ -226,7 +221,6 @@ void main() { await provider.ready; - // Verify that _loggingConstraint is set expect(provider.loggingConstraint, equals(null)); }); }); From 04b78d41addaa39a57c56f0fd7c38d5b492c4cf7 Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:45:12 -0700 Subject: [PATCH 26/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart Co-authored-by: NikaHsn --- .../test/remote_constraint_provider_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 9bdf4abd2f..20af6e9f88 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -73,7 +73,7 @@ void main() { registerFallbackValue(fakeRequest); }); - test('initializes _loggingConstraint from endpoint', () async { + test('initializes loggingConstraint from endpoint', () async { when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(sampleJson)); From 0cba479f0158a14904d6333d23479fc122860f0d Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:46:14 -0700 Subject: [PATCH 27/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart Co-authored-by: NikaHsn --- .../test/remote_constraint_provider_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart index 20af6e9f88..cd1e4ba6ce 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/remote_constraint_provider_test.dart @@ -198,7 +198,7 @@ void main() { }); test( - 'fetches _loggingConstraint from local storage and returns null if there are no constraints in local storage', + 'fetches loggingConstraint from local storage and returns null if there are no constraints in local storage', () async { when(() => mockFileStorage.load(any())) .thenAnswer((_) async => Future.value(null)); From 3c2b6668b10094ff733dcf5b2fae03b5854010da Mon Sep 17 00:00:00 2001 From: Kha Truong <64438356+khatruong2009@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:46:43 -0700 Subject: [PATCH 28/28] Update packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart Co-authored-by: NikaHsn --- .../lib/src/file_storage/file_storage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart index 0f257345f5..94627270bc 100644 --- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart +++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/file_storage/file_storage.dart @@ -15,5 +15,5 @@ abstract interface class FileStorage { Future save(String fileName, String data); /// Load constraint from file - Future load(String filename); + Future load(String fileName); }