diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index ab687d9801..b687c0e31c 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -785,6 +785,33 @@ updates:
- "version-update:semver-patch"
- dependency-name: "example_common"
- dependency-name: "amplify_lints"
+ - package-ecosystem: "pub"
+ directory: "packages/logging_cloudwatch/amplify_logging_cloudwatch"
+ schedule:
+ interval: "daily"
+ ignore:
+ # Ignore patch version bumps
+ - dependency-name: "*"
+ update-types:
+ - "version-update:semver-patch"
+ - dependency-name: "amplify_core"
+ - dependency-name: "aws_common"
+ - dependency-name: "amplify_lints"
+ - dependency-name: "aws_signature_v4"
+ - dependency-name: "amplify_db_common_dart"
+ - dependency-name: "aws_logging_cloudwatch"
+ - dependency-name: "smithy"
+ - dependency-name: "smithy_aws"
+ - package-ecosystem: "pub"
+ directory: "packages/logging_cloudwatch/amplify_logging_cloudwatch/example"
+ schedule:
+ interval: "daily"
+ ignore:
+ # Ignore patch version bumps
+ - dependency-name: "*"
+ update-types:
+ - "version-update:semver-patch"
+ - dependency-name: "amplify_lints"
- package-ecosystem: "pub"
directory: "packages/logging_cloudwatch/aws_logging_cloudwatch"
schedule:
diff --git a/.github/workflows/amplify_logging_cloudwatch.yaml b/.github/workflows/amplify_logging_cloudwatch.yaml
new file mode 100644
index 0000000000..d89927c36c
--- /dev/null
+++ b/.github/workflows/amplify_logging_cloudwatch.yaml
@@ -0,0 +1,66 @@
+# Generated with aft. To update, run: `aft generate workflows`
+name: amplify_logging_cloudwatch
+on:
+ push:
+ branches:
+ - main
+ - stable
+ pull_request:
+ paths:
+ - '.github/composite_actions/setup_firefox/action.yaml'
+ - '.github/workflows/amplify_logging_cloudwatch.yaml'
+ - '.github/workflows/dart_dart2js.yaml'
+ - '.github/workflows/dart_ddc.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'
+ - 'packages/amplify_lints/pubspec.yaml'
+ - 'packages/aws_common/lib/**/*.dart'
+ - 'packages/aws_common/pubspec.yaml'
+ - 'packages/aws_signature_v4/lib/**/*.dart'
+ - 'packages/aws_signature_v4/pubspec.yaml'
+ - 'packages/common/amplify_db_common_dart/lib/**/*.dart'
+ - 'packages/common/amplify_db_common_dart/pubspec.yaml'
+ - 'packages/logging_cloudwatch/amplify_logging_cloudwatch/**/*.dart'
+ - 'packages/logging_cloudwatch/amplify_logging_cloudwatch/**/*.yaml'
+ - 'packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/**/*'
+ - 'packages/logging_cloudwatch/amplify_logging_cloudwatch/test/**/*'
+ - 'packages/logging_cloudwatch/aws_logging_cloudwatch/lib/**/*.dart'
+ - 'packages/logging_cloudwatch/aws_logging_cloudwatch/pubspec.yaml'
+ - 'packages/smithy/smithy/lib/**/*.dart'
+ - 'packages/smithy/smithy/pubspec.yaml'
+ - 'packages/smithy/smithy_aws/lib/**/*.dart'
+ - 'packages/smithy/smithy_aws/pubspec.yaml'
+ schedule:
+ - cron: "0 0 * * 0" # Every Sunday at 00:00
+defaults:
+ run:
+ shell: bash
+permissions: read-all
+
+jobs:
+ test:
+ uses: ./.github/workflows/dart_vm.yaml
+ with:
+ package-name: amplify_logging_cloudwatch
+ working-directory: packages/logging_cloudwatch/amplify_logging_cloudwatch
+ native_test:
+ needs: test
+ uses: ./.github/workflows/dart_native.yaml
+ with:
+ package-name: amplify_logging_cloudwatch
+ working-directory: packages/logging_cloudwatch/amplify_logging_cloudwatch
+ ddc_test:
+ needs: test
+ uses: ./.github/workflows/dart_ddc.yaml
+ with:
+ package-name: amplify_logging_cloudwatch
+ working-directory: packages/logging_cloudwatch/amplify_logging_cloudwatch
+ dart2js_test:
+ needs: test
+ uses: ./.github/workflows/dart_dart2js.yaml
+ with:
+ package-name: amplify_logging_cloudwatch
+ working-directory: packages/logging_cloudwatch/amplify_logging_cloudwatch
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore
new file mode 100644
index 0000000000..65c34dc86e
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/.gitignore
@@ -0,0 +1,10 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+
+# Conventional directory for build outputs.
+build/
+
+# Omit committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/CHANGELOG.md b/packages/logging_cloudwatch/amplify_logging_cloudwatch/CHANGELOG.md
new file mode 100644
index 0000000000..a0712a79e7
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.1.0
+
+- Initial version.
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/LICENSE b/packages/logging_cloudwatch/amplify_logging_cloudwatch/LICENSE
new file mode 100644
index 0000000000..19dc35b243
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/LICENSE
@@ -0,0 +1,175 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
\ No newline at end of file
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/README.md b/packages/logging_cloudwatch/amplify_logging_cloudwatch/README.md
new file mode 100644
index 0000000000..945070b34b
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/README.md
@@ -0,0 +1 @@
+# amplify_logging_cloudwatch
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/analysis_options.yaml b/packages/logging_cloudwatch/amplify_logging_cloudwatch/analysis_options.yaml
new file mode 100644
index 0000000000..7f605f74d4
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:amplify_lints/library.yaml
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/.gitignore b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/.gitignore
new file mode 100644
index 0000000000..f1d87ba3fd
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/.gitignore
@@ -0,0 +1,9 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+
+# Conventional directory for build output.
+build/
+
+# Amplify
+amplifyconfiguration.dart
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/LICENSE b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/LICENSE
new file mode 100644
index 0000000000..19dc35b243
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/LICENSE
@@ -0,0 +1,175 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
\ No newline at end of file
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/README.md b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/README.md
new file mode 100644
index 0000000000..a79b5136a7
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/README.md
@@ -0,0 +1 @@
+# amplify_logging_cloudwatch_example
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/analysis_options.yaml b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/analysis_options.yaml
new file mode 100644
index 0000000000..49f9f5a9a4
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:amplify_lints/app.yaml
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/bin/main.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/bin/main.dart
new file mode 100644
index 0000000000..d02def190f
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/bin/main.dart
@@ -0,0 +1,2 @@
+// TODO(amplify_logging_cloudwatch): Write example
+void main() {}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/pubspec.yaml b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/pubspec.yaml
new file mode 100644
index 0000000000..99323c4dd0
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/pubspec.yaml
@@ -0,0 +1,13 @@
+name: amplify_logging_cloudwatch_example
+description: An example app showcasing the amplify_logging_cloudwatch package.
+version: 1.0.0
+publish_to: none
+environment:
+ sdk: "^3.0.0"
+
+dependencies:
+
+dev_dependencies:
+ amplify_lints: ^2.0.0
+ build_runner: ^2.4.0
+ build_web_compilers: ^4.0.0
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon-180x180.png b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon-180x180.png
new file mode 100644
index 0000000000..abeda3c2d1
Binary files /dev/null and b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon-180x180.png differ
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon.png b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon.png
new file mode 100644
index 0000000000..55f80b0a46
Binary files /dev/null and b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/favicon.png differ
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/index.html b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/index.html
new file mode 100644
index 0000000000..246bf3dc40
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ Amplify Logging Cloudwatch Example
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/main.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/main.dart
new file mode 100644
index 0000000000..87a3d61bde
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/main.dart
@@ -0,0 +1,5 @@
+import 'dart:html';
+
+void main() {
+ querySelector('#output')?.text = 'Your Amplify app is running.';
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/styles.css b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/styles.css
new file mode 100644
index 0000000000..cc035c95c9
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/example/web/styles.css
@@ -0,0 +1,14 @@
+@import url(https://fonts.googleapis.com/css?family=Roboto);
+
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: 'Roboto', sans-serif;
+}
+
+#output {
+ padding: 20px;
+ text-align: center;
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/amplify_logging_cloudwatch.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/amplify_logging_cloudwatch.dart
new file mode 100644
index 0000000000..c309d9866d
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/amplify_logging_cloudwatch.dart
@@ -0,0 +1,2 @@
+/// amplify_logging_cloudwatch
+library amplify_logging_cloudwatch;
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.dart
new file mode 100644
index 0000000000..716a6b83ea
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.dart
@@ -0,0 +1,6 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+export 'dart_queued_item_store.stub.dart'
+ if (dart.library.html) 'dart_queued_item_store.web.dart'
+ if (dart.library.io) 'dart_queued_item_store.vm.dart';
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.stub.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.stub.dart
new file mode 100644
index 0000000000..659be2c439
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.stub.dart
@@ -0,0 +1,55 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+
+import 'package:aws_common/aws_common.dart';
+import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';
+import 'package:meta/meta.dart';
+
+/// {@template amplify_logging_cloudwatch.dart_queued_item_store}
+/// Stores strings using IndexedDB for web and Drift for all other platforms.
+/// {@endtemplate}
+class DartQueuedItemStore implements QueuedItemStore, Closeable {
+ /// {@macro amplify_logging_cloudwatch.dart_queued_item_store}
+ // ignore: avoid_unused_constructor_parameters
+ DartQueuedItemStore(String? storagePath) {
+ throw UnimplementedError('constructor has not been implemented.');
+ }
+
+ @override
+ FutureOr> getCount(int count) {
+ throw UnimplementedError('getCount() has not been implemented.');
+ }
+
+ @override
+ FutureOr addItem(String string, String timestamp) {
+ throw UnimplementedError('addItem() has not been implemented.');
+ }
+
+ @override
+ FutureOr deleteItems(Iterable items) {
+ throw UnimplementedError('deleteItems() has not been implemented.');
+ }
+
+ @override
+ FutureOr close() async {
+ throw UnimplementedError('close() has not been implemented.');
+ }
+
+ @override
+ FutureOr isFull(int maxSizeInMB) {
+ throw UnimplementedError('isFull() has not been implemented.');
+ }
+
+ @override
+ FutureOr> getAll() {
+ throw UnimplementedError('getAll() has not been implemented.');
+ }
+
+ @override
+ @visibleForTesting
+ FutureOr clear() async {
+ throw UnimplementedError('clear() has not been implemented.');
+ }
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.vm.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.vm.dart
new file mode 100644
index 0000000000..17de9554c1
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.vm.dart
@@ -0,0 +1,62 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+
+import 'package:amplify_logging_cloudwatch/src/queued_item_store/drift/drift_queued_item_store.dart';
+import 'package:aws_common/aws_common.dart';
+import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';
+import 'package:meta/meta.dart';
+
+/// {@macro amplify_logging_cloudwatch.dart_queued_item_store}
+class DartQueuedItemStore implements QueuedItemStore, Closeable {
+ /// {@macro amplify_logging_cloudwatch.dart_queued_item_store}
+ factory DartQueuedItemStore(String? storagePath) {
+ assert(
+ storagePath != null,
+ 'A storage path is required on VM for locating the DB',
+ );
+ final database = DriftQueuedItemStore(storagePath!);
+ return DartQueuedItemStore._(database);
+ }
+
+ DartQueuedItemStore._(this._database);
+
+ final DriftQueuedItemStore _database;
+
+ @override
+ Future addItem(String string, String timestamp) {
+ return _database.addItem(string, timestamp);
+ }
+
+ @override
+ Future deleteItems(Iterable items) {
+ return _database.deleteItems(items);
+ }
+
+ @override
+ Future> getCount(int count) {
+ return _database.getCount(count);
+ }
+
+ @override
+ Future> getAll() {
+ return _database.getAll();
+ }
+
+ @override
+ Future isFull(int maxSizeInMB) {
+ return _database.isFull(maxSizeInMB);
+ }
+
+ @override
+ @visibleForTesting
+ Future clear() async {
+ return _database.clear();
+ }
+
+ @override
+ Future close() async {
+ return _database.close();
+ }
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.web.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.web.dart
new file mode 100644
index 0000000000..7f8b79f397
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/dart_queued_item_store.web.dart
@@ -0,0 +1,71 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'package:amplify_logging_cloudwatch/src/queued_item_store/index_db/indexed_db_adapter.dart';
+import 'package:aws_common/aws_common.dart';
+import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';
+import 'package:meta/meta.dart';
+
+/// {@macro amplify_logging_cloudwatch.dart_queued_item_store}
+class DartQueuedItemStore
+ with AWSDebuggable, AWSLoggerMixin
+ implements QueuedItemStore, Closeable {
+ /// {@macro amplify_logging_cloudwatch.index_db_queued_item_store}
+ // ignore: avoid_unused_constructor_parameters
+ DartQueuedItemStore(String? storagePath);
+
+ late final Future _database = () async {
+ if (await IndexedDbAdapter.checkIsIndexedDBSupported()) {
+ return IndexedDbAdapter();
+ }
+ logger.warn(
+ 'IndexedDB is not available. '
+ 'Falling back to in-memory storage.',
+ );
+ return InMemoryQueuedItemStore();
+ }();
+
+ @override
+ String get runtimeTypeName => 'DartQueuedItemStore';
+
+ @override
+ Future addItem(String string, String timestamp) async {
+ final db = await _database;
+ await db.addItem(string, timestamp);
+ }
+
+ @override
+ Future deleteItems(Iterable items) async {
+ final db = await _database;
+ await db.deleteItems(items);
+ }
+
+ @override
+ Future> getCount(int count) async {
+ final db = await _database;
+ return db.getCount(count);
+ }
+
+ @override
+ Future> getAll() async {
+ final db = await _database;
+ return db.getAll();
+ }
+
+ @override
+ Future isFull(int maxSizeInMB) async {
+ final db = await _database;
+ return db.isFull(maxSizeInMB);
+ }
+
+ /// Clear IndexedDB data.
+ @override
+ @visibleForTesting
+ Future clear() async {
+ final db = await _database;
+ return db.clear();
+ }
+
+ @override
+ void close() {}
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.dart
new file mode 100644
index 0000000000..c4e35c683a
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.dart
@@ -0,0 +1,120 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+
+import 'package:amplify_db_common_dart/amplify_db_common_dart.dart';
+import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';
+import 'package:drift/drift.dart';
+
+part 'drift_queued_item_store.g.dart';
+
+/// SQL schema of data stored in DriftDatabase.
+class DriftQueuedItems extends Table {
+ /// Identifies object in the SQL database.
+ IntColumn get id => integer().autoIncrement()();
+
+ /// The string value stored for this object.
+ TextColumn get value => text()();
+
+ /// The timestamp of the item.
+ TextColumn get timestamp => text()();
+}
+
+/// {@template amplify_logging_cloudwatch.drift_queued_item_store}
+/// Drift class for managing stored [DriftQueuedItems].
+/// {@endtemplate}
+@DriftDatabase(tables: [DriftQueuedItems])
+class DriftQueuedItemStore extends _$DriftQueuedItemStore
+ implements QueuedItemStore {
+ /// {@macro amplify_logging_cloudwatch.drift_queued_item_store}
+ factory DriftQueuedItemStore(String storagePath) {
+ final driftQueryExecutor = connect(
+ name: 'logging_cached_logs',
+ path: storagePath,
+ );
+ return DriftQueuedItemStore._(driftQueryExecutor);
+ }
+
+ DriftQueuedItemStore._(super.driftQueryExecutor);
+
+ // Reminder: Bump this number whenever you change or add a table definition.
+ @override
+ int get schemaVersion => 1;
+ int _currentTotalByteSize = 0;
+
+ @override
+ MigrationStrategy get migration {
+ return MigrationStrategy(
+ onCreate: (Migrator m) async {
+ await m.createAll();
+ },
+ onUpgrade: (Migrator m, int from, int to) async {
+ // Note: From schemaVersion 1->2 we renamed DriftJsonStrings to DriftQueuedItems before the GA release
+ await m.createAll();
+ },
+ );
+ }
+
+ @override
+ Future addItem(String value, String timestamp) async {
+ await into(driftQueuedItems).insert(
+ DriftQueuedItemsCompanion(
+ value: Value(value),
+ timestamp: Value(timestamp),
+ ),
+ );
+ _currentTotalByteSize += QueuedItem.getByteSize(value, timestamp);
+ }
+
+ @override
+ Future> getCount(int count) async {
+ final statement = (select(driftQueuedItems)
+ ..orderBy([(v) => OrderingTerm.asc(v.id)])
+ ..limit(count));
+
+ final retrievedItems = await statement.get();
+ return retrievedItems.map(
+ (item) => QueuedItem(
+ id: item.id,
+ value: item.value,
+ timestamp: item.timestamp,
+ ),
+ );
+ }
+
+ @override
+ Future> getAll() async {
+ final retrievedItems = await select(driftQueuedItems).get();
+ return retrievedItems.map(
+ (item) => QueuedItem(
+ id: item.id,
+ value: item.value,
+ timestamp: item.timestamp,
+ ),
+ );
+ }
+
+ @override
+ Future isFull(int maxSizeInMB) async {
+ final maxBytes = maxSizeInMB * 1024 * 1024;
+ return _currentTotalByteSize >= maxBytes;
+ }
+
+ @override
+ Future deleteItems(Iterable items) async {
+ final idsToDelete = items.map((item) => item.id);
+ final statement = delete(driftQueuedItems)
+ ..where((t) => t.id.isIn(idsToDelete));
+ await statement.go();
+ for (final item in items) {
+ _currentTotalByteSize -= item.byteSize;
+ }
+ }
+
+ @override
+ Future clear() async {
+ await delete(driftQueuedItems).go();
+ _currentTotalByteSize = 0;
+ }
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.g.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.g.dart
new file mode 100644
index 0000000000..691a278bd9
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/drift/drift_queued_item_store.g.dart
@@ -0,0 +1,227 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'drift_queued_item_store.dart';
+
+// ignore_for_file: type=lint
+class $DriftQueuedItemsTable extends DriftQueuedItems
+ with TableInfo<$DriftQueuedItemsTable, DriftQueuedItem> {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ $DriftQueuedItemsTable(this.attachedDatabase, [this._alias]);
+ static const VerificationMeta _idMeta = const VerificationMeta('id');
+ @override
+ late final GeneratedColumn id = GeneratedColumn(
+ 'id', aliasedName, false,
+ hasAutoIncrement: true,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ defaultConstraints:
+ GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
+ static const VerificationMeta _valueMeta = const VerificationMeta('value');
+ @override
+ late final GeneratedColumn value = GeneratedColumn(
+ 'value', aliasedName, false,
+ type: DriftSqlType.string, requiredDuringInsert: true);
+ static const VerificationMeta _timestampMeta =
+ const VerificationMeta('timestamp');
+ @override
+ late final GeneratedColumn timestamp = GeneratedColumn(
+ 'timestamp', aliasedName, false,
+ type: DriftSqlType.string, requiredDuringInsert: true);
+ @override
+ List get $columns => [id, value, timestamp];
+ @override
+ String get aliasedName => _alias ?? 'drift_queued_items';
+ @override
+ String get actualTableName => 'drift_queued_items';
+ @override
+ VerificationContext validateIntegrity(Insertable instance,
+ {bool isInserting = false}) {
+ final context = VerificationContext();
+ final data = instance.toColumns(true);
+ if (data.containsKey('id')) {
+ context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
+ }
+ if (data.containsKey('value')) {
+ context.handle(
+ _valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta));
+ } else if (isInserting) {
+ context.missing(_valueMeta);
+ }
+ if (data.containsKey('timestamp')) {
+ context.handle(_timestampMeta,
+ timestamp.isAcceptableOrUnknown(data['timestamp']!, _timestampMeta));
+ } else if (isInserting) {
+ context.missing(_timestampMeta);
+ }
+ return context;
+ }
+
+ @override
+ Set get $primaryKey => {id};
+ @override
+ DriftQueuedItem map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return DriftQueuedItem(
+ id: attachedDatabase.typeMapping
+ .read(DriftSqlType.int, data['${effectivePrefix}id'])!,
+ value: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}value'])!,
+ timestamp: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}timestamp'])!,
+ );
+ }
+
+ @override
+ $DriftQueuedItemsTable createAlias(String alias) {
+ return $DriftQueuedItemsTable(attachedDatabase, alias);
+ }
+}
+
+class DriftQueuedItem extends DataClass implements Insertable {
+ /// Identifies object in the SQL database.
+ final int id;
+
+ /// The string value stored for this object.
+ final String value;
+
+ /// The timestamp of the item.
+ final String timestamp;
+ const DriftQueuedItem(
+ {required this.id, required this.value, required this.timestamp});
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['id'] = Variable(id);
+ map['value'] = Variable(value);
+ map['timestamp'] = Variable(timestamp);
+ return map;
+ }
+
+ DriftQueuedItemsCompanion toCompanion(bool nullToAbsent) {
+ return DriftQueuedItemsCompanion(
+ id: Value(id),
+ value: Value(value),
+ timestamp: Value(timestamp),
+ );
+ }
+
+ factory DriftQueuedItem.fromJson(Map json,
+ {ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return DriftQueuedItem(
+ id: serializer.fromJson(json['id']),
+ value: serializer.fromJson(json['value']),
+ timestamp: serializer.fromJson(json['timestamp']),
+ );
+ }
+ @override
+ Map toJson({ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return {
+ 'id': serializer.toJson(id),
+ 'value': serializer.toJson(value),
+ 'timestamp': serializer.toJson(timestamp),
+ };
+ }
+
+ DriftQueuedItem copyWith({int? id, String? value, String? timestamp}) =>
+ DriftQueuedItem(
+ id: id ?? this.id,
+ value: value ?? this.value,
+ timestamp: timestamp ?? this.timestamp,
+ );
+ @override
+ String toString() {
+ return (StringBuffer('DriftQueuedItem(')
+ ..write('id: $id, ')
+ ..write('value: $value, ')
+ ..write('timestamp: $timestamp')
+ ..write(')'))
+ .toString();
+ }
+
+ @override
+ int get hashCode => Object.hash(id, value, timestamp);
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ (other is DriftQueuedItem &&
+ other.id == this.id &&
+ other.value == this.value &&
+ other.timestamp == this.timestamp);
+}
+
+class DriftQueuedItemsCompanion extends UpdateCompanion {
+ final Value id;
+ final Value value;
+ final Value timestamp;
+ const DriftQueuedItemsCompanion({
+ this.id = const Value.absent(),
+ this.value = const Value.absent(),
+ this.timestamp = const Value.absent(),
+ });
+ DriftQueuedItemsCompanion.insert({
+ this.id = const Value.absent(),
+ required String value,
+ required String timestamp,
+ }) : value = Value(value),
+ timestamp = Value(timestamp);
+ static Insertable custom({
+ Expression? id,
+ Expression? value,
+ Expression? timestamp,
+ }) {
+ return RawValuesInsertable({
+ if (id != null) 'id': id,
+ if (value != null) 'value': value,
+ if (timestamp != null) 'timestamp': timestamp,
+ });
+ }
+
+ DriftQueuedItemsCompanion copyWith(
+ {Value? id, Value? value, Value? timestamp}) {
+ return DriftQueuedItemsCompanion(
+ id: id ?? this.id,
+ value: value ?? this.value,
+ timestamp: timestamp ?? this.timestamp,
+ );
+ }
+
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ if (id.present) {
+ map['id'] = Variable(id.value);
+ }
+ if (value.present) {
+ map['value'] = Variable(value.value);
+ }
+ if (timestamp.present) {
+ map['timestamp'] = Variable(timestamp.value);
+ }
+ return map;
+ }
+
+ @override
+ String toString() {
+ return (StringBuffer('DriftQueuedItemsCompanion(')
+ ..write('id: $id, ')
+ ..write('value: $value, ')
+ ..write('timestamp: $timestamp')
+ ..write(')'))
+ .toString();
+ }
+}
+
+abstract class _$DriftQueuedItemStore extends GeneratedDatabase {
+ _$DriftQueuedItemStore(QueryExecutor e) : super(e);
+ late final $DriftQueuedItemsTable driftQueuedItems =
+ $DriftQueuedItemsTable(this);
+ @override
+ Iterable> get allTables =>
+ allSchemaEntities.whereType>();
+ @override
+ List get allSchemaEntities => [driftQueuedItems];
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/index_db/indexed_db_adapter.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/index_db/indexed_db_adapter.dart
new file mode 100644
index 0000000000..a14508d926
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/lib/src/queued_item_store/index_db/indexed_db_adapter.dart
@@ -0,0 +1,175 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+import 'dart:js_util';
+
+import 'package:amplify_core/amplify_core.dart';
+// ignore: implementation_imports
+import 'package:aws_common/src/js/indexed_db.dart';
+import 'package:aws_logging_cloudwatch/aws_logging_cloudwatch.dart';
+import 'package:collection/collection.dart';
+
+// TODO(kylechen): Consider merging/refactoring with existing 'amplify_secure_storage_web - _IndexedDBStorage' class
+/// {@template amplify_logging_cloudwatch.indexed_db_adapter}
+/// Provide indexDB methods for storing/retrieving Strings.
+/// {@endtemplate}
+class IndexedDbAdapter implements QueuedItemStore {
+ /// {@macro amplify_logging_cloudwatch.indexed_db_adapter}
+ IndexedDbAdapter();
+
+ /// The name of the database.
+ ///
+ /// Reference: https://www.w3.org/TR/IndexedDB/#name
+ String get databaseName => 'amplify_logging_cloudwatch';
+
+ /// The name of the object store.
+ ///
+ /// Reference: https://www.w3.org/TR/IndexedDB/#object-store-name
+ final storeName = 'logging_cached_logs';
+
+ late final Future _databaseOpenEvent = _openDatabase();
+
+ late final IDBDatabase _database;
+
+ int _currentTotalByteSize = 0;
+
+ /// Checks for IDB availability and attempts to open the database.
+ Future _openDatabase() async {
+ final db = indexedDB;
+ if (db == null) {
+ throw const InvalidStateException('IndexedDB is not available');
+ }
+ final openRequest = db.open(databaseName, 1)
+ ..onupgradeneeded = (event) {
+ final database = event.target.result;
+ final objectStoreNames = database.objectStoreNames;
+ if (!objectStoreNames.contains(storeName)) {
+ database.createObjectStore(
+ storeName,
+ keyPath: 'id',
+ autoIncrement: true,
+ );
+ }
+ };
+ _database = await openRequest.future;
+ }
+
+ /// Returns a new [IDBObjectStore] instance after waiting for initialization
+ /// to complete.
+ IDBObjectStore _getObjectStore() {
+ final transaction = _database.transaction(
+ storeName,
+ mode: IDBTransactionMode.readwrite,
+ );
+ final store = transaction.objectStore(storeName);
+ return store;
+ }
+
+ @override
+ Future addItem(String string, String timestamp) async {
+ await _databaseOpenEvent;
+ await _getObjectStore().add(string, timestamp).future;
+
+ _currentTotalByteSize += QueuedItem.getByteSize(string, timestamp);
+ }
+
+ @override
+ Future> getCount([int? count]) async {
+ final readValues = [];
+
+ await _databaseOpenEvent;
+ final store = _getObjectStore();
+ final request = store.getAll(null, count);
+
+ await request.future;
+
+ for (final elem in request.result) {
+ final value = elem as Object;
+ final id = getProperty(value, 'id');
+ final string = getProperty(value, 'value');
+ final timestamp = getProperty(value, 'timestamp');
+ readValues.add(
+ QueuedItem(id: id, value: string, timestamp: timestamp),
+ );
+ }
+ return readValues;
+ }
+
+ @override
+ Future deleteItems(Iterable items) async {
+ if (items.isEmpty) return;
+
+ final idsToDelete = items.map((item) => item.id);
+
+ await _databaseOpenEvent;
+ final store = _getObjectStore();
+
+ final ranges = idsToDelete.splitBetween((a, b) => b != a + 1).map(
+ (range) => IDBKeyRange.bound(range.first, range.last),
+ );
+ await Future.wait(
+ ranges.map((range) => store.deleteByKeyRange(range).future),
+ );
+
+ for (final item in items) {
+ _currentTotalByteSize -= item.byteSize;
+ }
+ }
+
+ @override
+ Future> getAll() async {
+ final readValues = [];
+
+ await _databaseOpenEvent;
+ final store = _getObjectStore();
+ final request = store.getAll(null, null);
+
+ await request.future;
+
+ for (final elem in request.result) {
+ final value = elem as Map;
+ final id = value['id'] as int;
+ final itemValue = value['value'] as String;
+ final timestamp = value['timestamp'] as String;
+ readValues.add(
+ QueuedItem(id: id, value: itemValue, timestamp: timestamp),
+ );
+ }
+
+ return readValues;
+ }
+
+ @override
+ Future isFull(int maxSizeInMB) async {
+ final maxBytes = maxSizeInMB * 1024 * 1024;
+ return _currentTotalByteSize >= maxBytes;
+ }
+
+ /// Clear the database.
+ @override
+ Future clear() async {
+ await _databaseOpenEvent;
+ await _getObjectStore().clear().future;
+ _currentTotalByteSize = 0;
+ }
+
+ @override
+ void close() {}
+
+ /// Check that IndexDB will work on this device.
+ static Future checkIsIndexedDBSupported() async {
+ if (indexedDB == null) {
+ return false;
+ }
+ // indexedDB will be non-null in Firefox private browsing,
+ // but will fail to open.
+ try {
+ final openRequest = indexedDB!.open('test', 1);
+ await openRequest.future;
+ return true;
+ } on Object {
+ return false;
+ }
+ }
+}
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/pubspec.yaml b/packages/logging_cloudwatch/amplify_logging_cloudwatch/pubspec.yaml
new file mode 100644
index 0000000000..4b919f6b48
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/pubspec.yaml
@@ -0,0 +1,26 @@
+name: amplify_logging_cloudwatch
+description: Implementation of Amplify logger plugin with CloudWatch Logs.
+version: 0.1.0
+homepage: https://docs.amplify.aws/lib/q/platform/flutter/
+repository: https://github.com/aws-amplify/amplify-flutter/tree/main/amplify_logging_cloudwatch
+issue_tracker: https://github.com/aws-amplify/amplify-flutter/issues
+
+environment:
+ sdk: "^3.0.0"
+
+dependencies:
+ amplify_core: ">=1.2.0 <1.3.0"
+ amplify_db_common_dart: ">=0.3.0+1 <0.4.0"
+ aws_common: ">=0.5.0+2 <0.6.0"
+ aws_logging_cloudwatch: ^0.1.0
+ collection: ^1.15.0
+ drift: ">=2.10.0 <2.11.0"
+ meta: ^1.7.0
+
+dev_dependencies:
+ amplify_lints: ^2.0.0
+ build_runner: ^2.0.0
+ build_test: ^2.0.0
+ build_web_compilers: ^4.0.0
+ drift_dev: ^2.2.0+1
+ test: ^1.16.0
diff --git a/packages/logging_cloudwatch/amplify_logging_cloudwatch/test/queued_item_store_test.dart b/packages/logging_cloudwatch/amplify_logging_cloudwatch/test/queued_item_store_test.dart
new file mode 100644
index 0000000000..9ad3ba46f3
--- /dev/null
+++ b/packages/logging_cloudwatch/amplify_logging_cloudwatch/test/queued_item_store_test.dart
@@ -0,0 +1,232 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import 'package:amplify_logging_cloudwatch/src/queued_item_store/dart_queued_item_store.dart';
+import 'package:aws_logging_cloudwatch/src/queued_item_store/queued_item_store.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late DartQueuedItemStore db;
+
+ group('DartQueuedItemStore ', () {
+ setUpAll(() {
+ db = DartQueuedItemStore('/tmp');
+ });
+
+ tearDownAll(() async {
+ await db.clear();
+ });
+
+ setUp(() async {
+ await db.clear();
+ });
+
+ Future> getAll() async {
+ return db.getCount(100);
+ }
+
+ test('writes values to storage', () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final readItems = await db.getCount(values.length);
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, equals(values));
+ });
+
+ test('returns first n items in storage', () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final readItems = await db.getCount(3);
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, equals(values.sublist(0, 3)));
+ });
+
+ test(
+ 'returns all stored items when get request size exceeds stored item count',
+ () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final readItems = await db.getCount(100);
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values);
+ });
+
+ test('deletes all items in storage', () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ await db.deleteItems(await getAll());
+
+ final readItems = await getAll();
+ expect(readItems, isEmpty);
+ });
+
+ test('deletes first subset of stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ await db.deleteItems(await db.getCount(3));
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values.sublist(3));
+ });
+
+ test('deletes middle subset of stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final itemsToDelete = (await getAll()).toList().sublist(3, 7);
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, const ['0', '1', '2', '7', '8', '9']);
+ });
+
+ test('deletes last subset of stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final itemsToDelete = (await getAll()).toList().sublist(7);
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values.sublist(0, 7));
+ });
+
+ test('deletes first, middle, and last subsets of stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ const valuesToDelete = ['0', '1', '4', '5', '8', '9'];
+ final itemsToDelete = (await getAll()).where((item) {
+ return valuesToDelete.contains(item.value);
+ });
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, const ['2', '3', '6', '7']);
+ });
+
+ test('deletes inner left and right subsets of stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ const valuesToDelete = ['1', '2', '4', '5', '7', '8'];
+ final itemsToDelete = (await getAll()).where((item) {
+ return valuesToDelete.contains(item.value);
+ });
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, const ['0', '3', '6', '9']);
+ });
+
+ test('deletes the first stored item', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final itemsToDelete = (await getAll()).toList().sublist(0, 1);
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values.sublist(1));
+ });
+
+ test('deletes the last stored item', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final itemsToDelete = (await getAll()).toList().sublist(9);
+ await db.deleteItems(itemsToDelete);
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values.sublist(0, 9));
+ });
+
+ test('throws no error when deleting all items twice', () async {
+ const values = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ var readValues = await getAll();
+
+ await db.deleteItems(readValues);
+ await db.deleteItems(readValues);
+
+ readValues = await getAll();
+
+ expect(readValues, isEmpty);
+ });
+
+ test('returns all stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values);
+ });
+
+ test('returns empty list when no items are stored', () async {
+ final readItems = await getAll();
+ expect(readItems, isEmpty);
+ });
+
+ test(
+ 'checks if storage is full',
+ () async {
+ const capacityLimit = 1;
+
+ for (var i = 0; i < 100; i++) {
+ await db.addItem('0', DateTime.now().toIso8601String());
+ }
+
+ var result = await db.isFull(capacityLimit);
+ expect(result, isFalse);
+
+ // add enough items to exceed capacity limit of 1mb
+ for (var i = 0; i < 50000; i++) {
+ await db.addItem('0', DateTime.now().toIso8601String());
+ }
+
+ result = await db.isFull(capacityLimit);
+ expect(result, isTrue);
+ },
+ );
+ });
+}
diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/in_memory_queued_item_store.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/in_memory_queued_item_store.dart
index a335d5a9c9..e07cc6f617 100644
--- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/in_memory_queued_item_store.dart
+++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/in_memory_queued_item_store.dart
@@ -14,17 +14,20 @@ class InMemoryQueuedItemStore implements QueuedItemStore {
/// The next ID that should be used when adding an item in the DB.
int _nextId = 0;
+ int _currentTotalByteSize = 0;
final LinkedHashMap _database =
LinkedHashMap();
@override
void addItem(String string, String timestamp) {
- _database[_nextId] = QueuedItem(
+ final queuedItem = QueuedItem(
id: _nextId,
value: string,
timestamp: timestamp,
);
+ _database[_nextId] = queuedItem;
_nextId++;
+ _currentTotalByteSize += queuedItem.byteSize;
}
@override
@@ -36,12 +39,14 @@ class InMemoryQueuedItemStore implements QueuedItemStore {
void deleteItems(Iterable items) {
for (final item in items) {
_database.remove(item.id);
+ _currentTotalByteSize -= item.byteSize;
}
}
@override
void clear() {
_database.clear();
+ _currentTotalByteSize = 0;
}
@override
@@ -54,6 +59,7 @@ class InMemoryQueuedItemStore implements QueuedItemStore {
@override
bool isFull(int maxSizeInMB) {
- throw UnimplementedError();
+ final maxBytes = maxSizeInMB * 1024 * 1024;
+ return _currentTotalByteSize >= maxBytes;
}
}
diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/queued_item_store.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/queued_item_store.dart
index 8c189b73f1..7a44684f10 100644
--- a/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/queued_item_store.dart
+++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/lib/src/queued_item_store/queued_item_store.dart
@@ -32,11 +32,22 @@ abstract interface class QueuedItemStore {
/// {@endtemplate}
class QueuedItem {
/// {@macro aws_logging_cloudwatch.string_database_element}
+
+ /// The size of the item, in bytes, when stored in the database.
const QueuedItem({
required this.id,
required this.value,
required this.timestamp,
- });
+ }) : _byteSize = value.length + timestamp.length + 8;
+ final int _byteSize;
+
+ /// The size of the item, in bytes, when stored in the database.
+ int get byteSize => _byteSize;
+
+ /// Gets the size of the item, in bytes, without creating the item.
+ static int getByteSize(String value, String timestamp) {
+ return value.length + timestamp.length + 8;
+ }
/// The ID in the item.
final int id;
diff --git a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/queued_item_store/queued_item_store_test.dart b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/queued_item_store/queued_item_store_test.dart
index a2ccab9746..48e73ca64e 100644
--- a/packages/logging_cloudwatch/aws_logging_cloudwatch/test/queued_item_store/queued_item_store_test.dart
+++ b/packages/logging_cloudwatch/aws_logging_cloudwatch/test/queued_item_store/queued_item_store_test.dart
@@ -190,5 +190,43 @@ void main() {
expect(readValues, isEmpty);
});
+
+ test('returns all stored items', () async {
+ const values = ['0', '1', '2', '3', '4', '5'];
+ for (final value in values) {
+ await db.addItem(value, DateTime.now().toIso8601String());
+ }
+
+ final readItems = await getAll();
+ final readValues = readItems.map((e) => e.value);
+ expect(readValues, values);
+ });
+
+ test('returns empty list when no items are stored', () async {
+ final readItems = await getAll();
+ expect(readItems, isEmpty);
+ });
+
+ test(
+ 'checks if storage is full',
+ () async {
+ const capacityLimit = 1;
+
+ for (var i = 0; i < 100; i++) {
+ await db.addItem('0', DateTime.now().toIso8601String());
+ }
+
+ var result = await db.isFull(capacityLimit);
+ expect(result, isFalse);
+
+ // add enough items to exceed capacity limit of 1mb
+ for (var i = 0; i < 50000; i++) {
+ await db.addItem('0', DateTime.now().toIso8601String());
+ }
+
+ result = await db.isFull(capacityLimit);
+ expect(result, isTrue);
+ },
+ );
});
}