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); + }, + ); }); }