diff --git a/package-lock.json b/package-lock.json index db823c798b4..d26459f32e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1091,6 +1091,917 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.682.0.tgz", + "integrity": "sha512-fodsKdamwVpc9wLRrly4Ccur9TZdfDjrlDY2V0pZT+B4XS9PKhPaQ/8JY4/5ysJoUCe3NxFEpQxkediKEbu2uQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/client-sts": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/eventstream-serde-browser": "^3.0.10", + "@smithy/eventstream-serde-config-resolver": "^3.0.7", + "@smithy/eventstream-serde-node": "^3.0.9", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.682.0.tgz", + "integrity": "sha512-PYH9RFUMYLFl66HSBq4tIx6fHViMLkhJHTYJoJONpBs+Td+NwVJ895AdLtDsBIhMS0YseCbPpuyjUCJgsUrwUw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.682.0.tgz", + "integrity": "sha512-ZPZ7Y/r/w3nx/xpPzGSqSQsB090Xk5aZZOH+WBhTDn/pBEuim09BYXCLzvvxb7R7NnuoQdrTJiwimdJAhHl7ZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sts": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.682.0.tgz", + "integrity": "sha512-xKuo4HksZ+F8m9DOfx/ZuWNhaPuqZFPwwy0xqcBT6sWH7OAuBjv/fnpOTzyQhpVTWddlf+ECtMAMrxjxuOExGQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-node": "3.682.0", + "@aws-sdk/middleware-host-header": "3.679.0", + "@aws-sdk/middleware-logger": "3.679.0", + "@aws-sdk/middleware-recursion-detection": "3.679.0", + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/region-config-resolver": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@aws-sdk/util-user-agent-browser": "3.679.0", + "@aws-sdk/util-user-agent-node": "3.682.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.8", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.23", + "@smithy/util-defaults-mode-node": "^3.0.23", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/core": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.679.0.tgz", + "integrity": "sha512-CS6PWGX8l4v/xyvX8RtXnBisdCa5+URzKd0L6GvHChype9qKUVxO/Gg6N/y43Hvg7MNWJt9FBPNWIxUB+byJwg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/signature-v4": "^4.2.0", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-middleware": "^3.0.7", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.679.0.tgz", + "integrity": "sha512-EdlTYbzMm3G7VUNAMxr9S1nC1qUNqhKlAxFU8E7cKsAe8Bp29CD5HAs3POc56AVo9GC4yRIS+/mtlZSmrckzUA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.679.0.tgz", + "integrity": "sha512-ZoKLubW5DqqV1/2a3TSn+9sSKg0T8SsYMt1JeirnuLJF0mCoYFUaWMyvxxKuxPoqvUsaycxKru4GkpJ10ltNBw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-stream": "^3.1.9", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.682.0.tgz", + "integrity": "sha512-6eqWeHdK6EegAxqDdiCi215nT3QZPwukgWAYuVxNfJ/5m0/P7fAzF+D5kKVgByUvGJEbq/FEL8Fw7OBe64AA+g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.682.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.682.0.tgz", + "integrity": "sha512-HSmDqZcBVZrTctHCT9m++vdlDfJ1ARI218qmZa+TZzzOFNpKWy6QyHMEra45GB9GnkkMmV6unoDSPMuN0AqcMg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.679.0", + "@aws-sdk/credential-provider-http": "3.679.0", + "@aws-sdk/credential-provider-ini": "3.682.0", + "@aws-sdk/credential-provider-process": "3.679.0", + "@aws-sdk/credential-provider-sso": "3.682.0", + "@aws-sdk/credential-provider-web-identity": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.679.0.tgz", + "integrity": "sha512-u/p4TV8kQ0zJWDdZD4+vdQFTMhkDEJFws040Gm113VHa/Xo1SYOjbpvqeuFoz6VmM0bLvoOWjxB9MxnSQbwKpQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.682.0.tgz", + "integrity": "sha512-h7IH1VsWgV6YAJSWWV6y8uaRjGqLY3iBpGZlXuTH/c236NMLaNv+WqCBLeBxkFGUb2WeQ+FUPEJDCD69rgLIkg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.682.0", + "@aws-sdk/core": "3.679.0", + "@aws-sdk/token-providers": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.679.0.tgz", + "integrity": "sha512-a74tLccVznXCaBefWPSysUcLXYJiSkeUmQGtalNgJ1vGkE36W5l/8czFiiowdWdKWz7+x6xf0w+Kjkjlj42Ung==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.679.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.679.0.tgz", + "integrity": "sha512-y176HuQ8JRY3hGX8rQzHDSbCl9P5Ny9l16z4xmaiLo+Qfte7ee4Yr3yaAKd7GFoJ3/Mhud2XZ37fR015MfYl2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-logger": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.679.0.tgz", + "integrity": "sha512-0vet8InEj7nvIvGKk+ch7bEF5SyZ7Us9U7YTEgXPrBNStKeRUsgwRm0ijPWWd0a3oz2okaEwXsFl7G/vI0XiEA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.679.0.tgz", + "integrity": "sha512-sQoAZFsQiW/LL3DfKMYwBoGjYDEnMbA9WslWN8xneCmBAwKo6IcSksvYs23PP8XMIoBGe2I2J9BSr654XWygTQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.682.0.tgz", + "integrity": "sha512-7TyvYR9HdGH1/Nq0eeApUTM4izB6rExiw87khVYuJwZHr6FmvIL1FsOVFro/4WlXa0lg4LiYOm/8H8dHv+fXTg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.679.0", + "@aws-sdk/types": "3.679.0", + "@aws-sdk/util-endpoints": "3.679.0", + "@smithy/core": "^2.4.8", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.679.0.tgz", + "integrity": "sha512-Ybx54P8Tg6KKq5ck7uwdjiKif7n/8g1x+V0V9uTjBjRWqaIgiqzXwKWoPj6NCNkE7tJNtqI4JrNxp/3S3HvmRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.679.0.tgz", + "integrity": "sha512-1/+Zso/x2jqgutKixYFQEGli0FELTgah6bm7aB+m2FAWH4Hz7+iMUsazg6nSWm714sG9G3h5u42Dmpvi9X6/hA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.679.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/types": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", + "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.679.0.tgz", + "integrity": "sha512-YL6s4Y/1zC45OvddvgE139fjeWSKKPgLlnfrvhVL7alNyY9n7beR4uhoDpNrt5mI6sn9qiBF17790o+xLAXjjg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "@smithy/util-endpoints": "^2.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.679.0.tgz", + "integrity": "sha512-CusSm2bTBG1kFypcsqU8COhnYc6zltobsqs3nRrvYqYaOqtMnuE46K4XTWpnzKgwDejgZGOE+WYyprtAxrPvmQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.679.0", + "@smithy/types": "^3.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.682.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.682.0.tgz", + "integrity": "sha512-so5s+j0gPoTS0HM4HPL+G0ajk0T6cQAg8JXzRgvyiQAxqie+zGCZAV3VuVeMNWMVbzsgZl0pYZaatPFTLG/AxA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.682.0", + "@aws-sdk/types": "3.679.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/abort-controller": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.6.tgz", + "integrity": "sha512-0XuhuHQlEqbNQZp7QxxrFTdVWdwxch4vjxYgfInF91hZFkPxf9QDrdQka0KfxFMPqLNzSw0b95uGTrLliQUavQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-endpoint": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.1.tgz", + "integrity": "sha512-wWO3xYmFm6WRW8VsEJ5oU6h7aosFXfszlz3Dj176pTij6o21oZnzkCLzShfmRaaCHDkBXWBdO0c4sQAvLFP6zA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-serde": "^3.0.8", + "@smithy/node-config-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "@smithy/url-parser": "^3.0.8", + "@smithy/util-middleware": "^3.0.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-retry": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.25.tgz", + "integrity": "sha512-m1F70cPaMBML4HiTgCw5I+jFNtjgz5z5UdGnUbG37vw6kh4UvizFYjqJGHvicfgKMkDL6mXwyPp5mhZg02g5sg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.5", + "@smithy/service-error-classification": "^3.0.8", + "@smithy/smithy-client": "^3.4.2", + "@smithy/types": "^3.6.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-retry": "^3.0.8", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-serde": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.8.tgz", + "integrity": "sha512-Xg2jK9Wc/1g/MBMP/EUn2DLspN8LNt+GMe7cgF+Ty3vl+Zvu+VeZU5nmhveU+H8pxyTsjrAkci8NqY6OuvZnjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-stack": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.8.tgz", + "integrity": "sha512-d7ZuwvYgp1+3682Nx0MD3D/HtkmZd49N3JUndYWQXfRZrYEnCWYc8BHcNmVsPAp9gKvlurdg/mubE6b/rPS9MA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-config-provider": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.9.tgz", + "integrity": "sha512-qRHoah49QJ71eemjuS/WhUXB+mpNtwHRWQr77J/m40ewBVVwvo52kYAmb7iuaECgGTTcYxHS4Wmewfwy++ueew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.9", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-http-handler": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.5.tgz", + "integrity": "sha512-PkOwPNeKdvX/jCpn0A8n9/TyoxjGZB8WVoJmm9YzsnAgggTj4CrjpRHlTQw7dlLZ320n1mY1y+nTRUDViKi/3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^3.1.6", + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/property-provider": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.8.tgz", + "integrity": "sha512-ukNUyo6rHmusG64lmkjFeXemwYuKge1BJ8CtpVKmrxQxc6rhUX0vebcptFA9MmrGsnLhwnnqeH83VTU9hwOpjA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-builder": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.8.tgz", + "integrity": "sha512-btYxGVqFUARbUrN6VhL9c3dnSviIwBYD9Rz1jHuN1hgh28Fpv2xjU1HeCeDJX68xctz7r4l1PBnFhGg1WBBPuA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.8.tgz", + "integrity": "sha512-BtEk3FG7Ks64GAbt+JnKqwuobJNX8VmFLBsKIwWr1D60T426fGrV2L3YS5siOcUhhp6/Y6yhBw1PSPxA5p7qGg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/service-error-classification": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.8.tgz", + "integrity": "sha512-uEC/kCCFto83bz5ZzapcrgGqHOh/0r69sZ2ZuHlgoD5kYgXJEThCoTuw/y1Ub3cE7aaKdznb+jD9xRPIfIwD7g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.9.tgz", + "integrity": "sha512-/+OsJRNtoRbtsX0UpSgWVxFZLsJHo/4sTr+kBg/J78sr7iC+tHeOvOJrS5hCpVQ6sWBbhWLp1UNiuMyZhE6pmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/smithy-client": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.2.tgz", + "integrity": "sha512-dxw1BDxJiY9/zI3cBqfVrInij6ShjpV4fmGHesGZZUiP9OSE/EVfdwdRz0PgvkEvrZHpsj2htRaHJfftE8giBA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^2.5.1", + "@smithy/middleware-endpoint": "^3.2.1", + "@smithy/middleware-stack": "^3.0.8", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-stream": "^3.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/url-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.8.tgz", + "integrity": "sha512-4FdOhwpTW7jtSFWm7SpfLGKIBC9ZaTKG5nBF0wK24aoQKQyDIKUw3+KFWCQ9maMzrgTJIuOvOnsV2lLGW5XjTg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-middleware": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-retry": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.8.tgz", + "integrity": "sha512-TCEhLnY581YJ+g1x0hapPz13JFqzmh/pMWL2KEFASC51qCfw3+Y47MrTmea4bUE5vsdxQ4F6/KFbUeSz22Q1ow==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^3.0.8", + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.2.1.tgz", + "integrity": "sha512-R3ufuzJRxSJbE58K9AEnL/uSZyVdHzud9wLS8tIbXclxKzoe09CRohj2xV8wpx5tj7ZbiJaKYcutMm1eYgz/0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^4.0.0", + "@smithy/node-http-handler": "^3.2.5", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.0.0.tgz", + "integrity": "sha512-MLb1f5tbBO2X6K4lMEKJvxeLooyg7guq48C2zKr4qM7F2Gpkz4dc+hdSgu77pCJ76jVqFBjZczHYAs6dp15N+g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.5", + "@smithy/querystring-builder": "^3.0.8", + "@smithy/types": "^3.6.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.637.0", "license": "Apache-2.0", @@ -20241,6 +21152,7 @@ "@amzn/amazon-q-developer-streaming-client": "file:../../src.gen/@amzn/amazon-q-developer-streaming-client", "@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming", "@aws-sdk/client-cloudformation": "^3.667.0", + "@aws-sdk/client-cloudwatch-logs": "^3.666.0", "@aws-sdk/client-cognito-identity": "^3.637.0", "@aws-sdk/client-lambda": "^3.637.0", "@aws-sdk/client-sso": "^3.342.0", diff --git a/packages/core/package.json b/packages/core/package.json index 41fb9e1c962..88173eeea15 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -495,6 +495,7 @@ "dependencies": { "@amzn/amazon-q-developer-streaming-client": "file:../../src.gen/@amzn/amazon-q-developer-streaming-client", "@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming", + "@aws-sdk/client-cloudwatch-logs": "^3.666.0", "@aws-sdk/client-cloudformation": "^3.667.0", "@aws-sdk/client-cognito-identity": "^3.637.0", "@aws-sdk/client-lambda": "^3.637.0", diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json index e07958a856f..2f92a23b5ac 100644 --- a/packages/core/package.nls.json +++ b/packages/core/package.nls.json @@ -177,6 +177,7 @@ "AWS.command.downloadSchemaItemCode": "Download Code Bindings", "AWS.command.viewLogs": "View Logs", "AWS.command.cloudWatchLogs.searchLogGroup": "Search Log Group", + "AWS.command.cloudWatchLogs.tailLogGroup": "Tail Log Group", "AWS.command.sam.newTemplate": "Create new SAM Template", "AWS.command.cloudFormation.newTemplate": "Create new CloudFormation Template", "AWS.command.quickStart": "View Quick Start", @@ -253,7 +254,7 @@ "AWS.appcomposer.explorerTitle": "Infrastructure Composer", "AWS.cdk.explorerTitle": "CDK", "AWS.codecatalyst.explorerTitle": "CodeCatalyst", - "AWS.cwl.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs (max 10000)", + "AWS.cwl.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs. For LiveTail, when the limit is reached, the oldest events will be removed to accomodate new events. (max 10000)", "AWS.samcli.deploy.bucket.recentlyUsed": "Buckets recently used for SAM deployments", "AWS.submenu.amazonqEditorContextSubmenu.title": "Amazon Q", "AWS.submenu.auth.title": "Authentication", diff --git a/packages/core/src/awsService/cloudWatchLogs/activation.ts b/packages/core/src/awsService/cloudWatchLogs/activation.ts index 5c3f4ac91c8..bfcaaf1d0e2 100644 --- a/packages/core/src/awsService/cloudWatchLogs/activation.ts +++ b/packages/core/src/awsService/cloudWatchLogs/activation.ts @@ -4,7 +4,7 @@ */ import * as vscode from 'vscode' -import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' +import { cloudwatchLogsLiveTailScheme, CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' import { Settings } from '../../shared/settings' import { addLogEvents } from './commands/addLogEvents' import { copyLogResource } from './commands/copyLogResource' @@ -19,16 +19,22 @@ import { searchLogGroup } from './commands/searchLogGroup' import { changeLogSearchParams } from './changeLogSearch' import { CloudWatchLogsNode } from './explorer/cloudWatchLogsNode' import { loadAndOpenInitialLogStreamFile, LogStreamCodeLensProvider } from './document/logStreamsCodeLensProvider' +import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGroup' +import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider' +import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry' import { DeployedResourceNode } from '../appBuilder/explorer/nodes/deployedNode' import { isTreeNode } from '../../shared/treeview/resourceTreeDataProvider' import { getLogger } from '../../shared/logger/logger' import { ToolkitError } from '../../shared' +import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider' export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise { const registry = LogDataRegistry.instance + const liveTailRegistry = LiveTailSessionRegistry.instance const documentProvider = new LogDataDocumentProvider(registry) - + const liveTailDocumentProvider = new LiveTailDocumentProvider() + const liveTailCodeLensProvider = new LiveTailCodeLensProvider(liveTailRegistry) context.subscriptions.push( vscode.languages.registerCodeLensProvider( { @@ -43,6 +49,20 @@ export async function activate(context: vscode.ExtensionContext, configuration: vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider) ) + context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + { + language: 'log', + scheme: cloudwatchLogsLiveTailScheme, + }, + liveTailCodeLensProvider + ) + ) + + context.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider(cloudwatchLogsLiveTailScheme, liveTailDocumentProvider) + ) + context.subscriptions.push( vscode.workspace.onDidCloseTextDocument((doc) => { if (doc.isClosed && doc.uri.scheme === CLOUDWATCH_LOGS_SCHEME) { @@ -95,6 +115,23 @@ export async function activate(context: vscode.ExtensionContext, configuration: Commands.register('aws.cwl.changeTimeFilter', async () => changeLogSearchParams(registry, 'timeFilter')), + Commands.register('aws.cwl.tailLogGroup', async (node: LogGroupNode | CloudWatchLogsNode) => { + const logGroupInfo = + node instanceof LogGroupNode + ? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! } + : undefined + const source = node ? (logGroupInfo ? 'ExplorerLogGroupNode' : 'ExplorerServiceNode') : 'Command' + await tailLogGroup(liveTailRegistry, source, liveTailCodeLensProvider, logGroupInfo) + }), + + Commands.register('aws.cwl.stopTailingLogGroup', async (document: vscode.TextDocument, source: string) => { + closeSession(document.uri, liveTailRegistry, source, liveTailCodeLensProvider) + }), + + Commands.register('aws.cwl.clearDocument', async (document: vscode.TextDocument) => { + await clearDocument(document) + }), + Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode) => { try { const logGroupInfo = isTreeNode(node) @@ -112,6 +149,7 @@ export async function activate(context: vscode.ExtensionContext, configuration: }) ) } + function getFunctionLogGroupName(configuration: any) { const logGroupPrefix = '/aws/lambda/' return configuration.logGroupName || logGroupPrefix + configuration.FunctionName diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts new file mode 100644 index 00000000000..4aa1feb272a --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -0,0 +1,283 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { telemetry } from '../../../shared/telemetry/telemetry' +import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard' +import { CancellationError } from '../../../shared/utilities/timeoutUtils' +import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession' +import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' +import { + LiveTailSessionLogEvent, + LiveTailSessionUpdate, + StartLiveTailResponseStream, +} from '@aws-sdk/client-cloudwatch-logs' +import { getLogger, globals, ToolkitError } from '../../../shared' +import { uriToKey } from '../cloudWatchLogsUtils' +import { LiveTailCodeLensProvider } from '../document/liveTailCodeLensProvider' + +export async function tailLogGroup( + registry: LiveTailSessionRegistry, + source: string, + codeLensProvider: LiveTailCodeLensProvider, + logData?: { regionName: string; groupName: string } +): Promise { + await telemetry.cloudwatchlogs_startLiveTail.run(async (span) => { + const wizard = new TailLogGroupWizard(logData) + const wizardResponse = await wizard.run() + if (!wizardResponse) { + throw new CancellationError('user') + } + if (wizardResponse.logStreamFilter.type === 'menu' || wizardResponse.logStreamFilter.type === undefined) { + // logstream filter wizard uses type to determine which submenu to show. 'menu' is set when no type is selected + // and to show the 'menu' of selecting a type. This should not be reachable due to the picker logic, but validating in case. + throw new ToolkitError(`Invalid Log Stream filter type: ${wizardResponse.logStreamFilter.type}`) + } + const awsCredentials = await globals.awsContext.getCredentials() + if (awsCredentials === undefined) { + throw new ToolkitError('Failed to start LiveTail session: credentials are undefined.') + } + const liveTailSessionConfig: LiveTailSessionConfiguration = { + logGroupArn: wizardResponse.regionLogGroupSubmenuResponse.data, + logStreamFilter: wizardResponse.logStreamFilter, + logEventFilterPattern: wizardResponse.filterPattern, + region: wizardResponse.regionLogGroupSubmenuResponse.region, + awsCredentials: awsCredentials, + } + const session = new LiveTailSession(liveTailSessionConfig) + if (registry.has(uriToKey(session.uri))) { + await vscode.window.showTextDocument(session.uri, { preview: false }) + void vscode.window.showInformationMessage(`Switching editor to an existing session that matches request.`) + span.record({ + result: 'Succeeded', + sessionAlreadyStarted: true, + source: source, + }) + return + } + const document = await prepareDocument(session) + + const disposables: vscode.Disposable[] = [] + disposables.push(hideShowStatusBarItemsOnActiveEditor(session, document)) + disposables.push(closeSessionWhenAllEditorsClosed(session, registry, document, codeLensProvider)) + + try { + const stream = await session.startLiveTailSession() + registry.set(uriToKey(session.uri), session) + codeLensProvider.refresh() + getLogger().info(`LiveTail session started: ${uriToKey(session.uri)}`) + span.record({ + source: source, + result: 'Succeeded', + sessionAlreadyStarted: false, + hasTextFilter: Boolean(wizardResponse.filterPattern), + filterType: wizardResponse.logStreamFilter.type, + }) + await handleSessionStream(stream, document, session) + } finally { + disposables.forEach((disposable) => disposable.dispose()) + } + }) +} + +export function closeSession( + sessionUri: vscode.Uri, + registry: LiveTailSessionRegistry, + source: string, + codeLensProvider: LiveTailCodeLensProvider +) { + telemetry.cloudwatchlogs_stopLiveTail.run((span) => { + const session = registry.get(uriToKey(sessionUri)) + if (session === undefined) { + throw new ToolkitError(`No LiveTail session found for URI: ${uriToKey(sessionUri)}`) + } + session.stopLiveTailSession() + registry.delete(uriToKey(sessionUri)) + void vscode.window.showInformationMessage(`Stopped LiveTail session: ${uriToKey(sessionUri)}`) + codeLensProvider.refresh() + span.record({ + result: 'Succeeded', + source: source, + duration: session.getLiveTailSessionDuration(), + }) + }) +} + +export async function clearDocument(textDocument: vscode.TextDocument) { + const edit = new vscode.WorkspaceEdit() + const startPosition = new vscode.Position(0, 0) + const endPosition = new vscode.Position(textDocument.lineCount, 0) + edit.delete(textDocument.uri, new vscode.Range(startPosition, endPosition)) + await vscode.workspace.applyEdit(edit) +} + +async function prepareDocument(session: LiveTailSession): Promise { + const textDocument = await vscode.workspace.openTextDocument(session.uri) + await clearDocument(textDocument) + await vscode.window.showTextDocument(textDocument, { preview: false }) + await vscode.languages.setTextDocumentLanguage(textDocument, 'log') + session.showStatusBarItem(true) + return textDocument +} + +async function handleSessionStream( + stream: AsyncIterable, + document: vscode.TextDocument, + session: LiveTailSession +) { + try { + for await (const event of stream) { + if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) { + const formattedLogEvents = event.sessionUpdate.sessionResults.map((logEvent) => + formatLogEvent(logEvent) + ) + if (formattedLogEvents.length !== 0) { + // Determine should scroll before adding new lines to doc because adding large + // amount of new lines can push bottom of file out of view before scrolling. + const editorsToScroll = getTextEditorsToScroll(document) + await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines) + editorsToScroll.forEach(scrollTextEditorToBottom) + } + session.eventRate = eventRate(event.sessionUpdate) + session.isSampled = isSampled(event.sessionUpdate) + } + } + } catch (e) { + if (session.isAborted) { + // Expected case. User action cancelled stream (CodeLens, Close Editor, etc.). + // AbortSignal interrupts the LiveTail stream, causing error to be thrown here. + // Can assume that stopLiveTailSession() has already been called - AbortSignal is only + // exposed through that method. + getLogger().info(`LiveTail session stopped: ${uriToKey(session.uri)}`) + } else { + // Unexpected exception. + session.stopLiveTailSession() + throw ToolkitError.chain( + e, + `Unexpected on-stream exception while tailing session: ${session.uri.toString()}` + ) + } + } +} + +function formatLogEvent(logEvent: LiveTailSessionLogEvent): string { + if (!logEvent.timestamp || !logEvent.message) { + return '' + } + const timestamp = new Date(logEvent.timestamp).toLocaleTimeString('en', { + timeStyle: 'medium', + hour12: false, + timeZone: 'UTC', + }) + let line = timestamp.concat('\t', logEvent.message) + if (!line.endsWith('\n')) { + line = line.concat('\n') + } + return line +} + +// Auto scroll visible LiveTail session editors if the end-of-file is in view. +// This allows for newly added log events to stay in view. +function getTextEditorsToScroll(document: vscode.TextDocument): vscode.TextEditor[] { + return vscode.window.visibleTextEditors.filter((editor) => { + if (editor.document !== document) { + return false + } + return editor.visibleRanges[0].contains(new vscode.Position(document.lineCount - 1, 0)) + }) +} + +function scrollTextEditorToBottom(editor: vscode.TextEditor) { + const position = new vscode.Position(Math.max(editor.document.lineCount - 2, 0), 0) + editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.Default) +} + +async function updateTextDocumentWithNewLogEvents( + formattedLogEvents: string[], + document: vscode.TextDocument, + maxLines: number +) { + const edit = new vscode.WorkspaceEdit() + formattedLogEvents.forEach((formattedLogEvent) => + edit.insert(document.uri, new vscode.Position(document.lineCount, 0), formattedLogEvent) + ) + if (document.lineCount + formattedLogEvents.length > maxLines) { + trimOldestLines(formattedLogEvents.length, maxLines, document, edit) + } + await vscode.workspace.applyEdit(edit) +} + +function trimOldestLines( + numNewLines: number, + maxLines: number, + document: vscode.TextDocument, + edit: vscode.WorkspaceEdit +) { + const numLinesToTrim = document.lineCount + numNewLines - maxLines + const startPosition = new vscode.Position(0, 0) + const endPosition = new vscode.Position(numLinesToTrim, 0) + const range = new vscode.Range(startPosition, endPosition) + edit.delete(document.uri, range) +} + +function isSampled(event: LiveTailSessionUpdate): boolean { + return event.sessionMetadata === undefined || event.sessionMetadata.sampled === undefined + ? false + : event.sessionMetadata.sampled +} + +function eventRate(event: LiveTailSessionUpdate): number { + return event.sessionResults === undefined ? 0 : event.sessionResults.length +} + +function hideShowStatusBarItemsOnActiveEditor( + session: LiveTailSession, + document: vscode.TextDocument +): vscode.Disposable { + return vscode.window.onDidChangeActiveTextEditor((editor) => { + session.showStatusBarItem(editor?.document === document) + }) +} + +/** + * The LiveTail session should be automatically closed if the user does not have the session's + * document in any Tab in their editor. + * + * `onDidCloseTextDocument` doesn't work for our case because the tailLogGroup command will keep the stream + * writing to the doc even when all its tabs/editors are closed, seemingly keeping the doc 'open'. + * Also there is no guarantee that this event fires when an editor tab is closed + * + * `onDidChangeVisibleTextEditors` returns editors that the user can see its contents. An editor that is open, but hidden + * from view, will not be returned. Meaning a Tab that is created (shown in top bar), but not open, will not be returned. Even if + * the tab isn't visible, we want to continue writing to the doc, and keep the session alive. + */ +function closeSessionWhenAllEditorsClosed( + session: LiveTailSession, + registry: LiveTailSessionRegistry, + document: vscode.TextDocument, + codeLensProvider: LiveTailCodeLensProvider +): vscode.Disposable { + return vscode.window.tabGroups.onDidChangeTabs((tabEvent) => { + const isOpen = isLiveTailSessionOpenInAnyTab(session) + if (!isOpen) { + closeSession(session.uri, registry, 'ClosedEditors', codeLensProvider) + void clearDocument(document) + } + }) +} + +function isLiveTailSessionOpenInAnyTab(liveTailSession: LiveTailSession) { + let isOpen = false + vscode.window.tabGroups.all.forEach(async (tabGroup) => { + tabGroup.tabs.forEach((tab) => { + if (tab.input instanceof vscode.TabInputText) { + if (liveTailSession.uri.toString() === tab.input.uri.toString()) { + isOpen = true + } + } + }) + }) + return isOpen +} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts new file mode 100644 index 00000000000..dde0ddbbe28 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts @@ -0,0 +1,62 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' +import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' +import { uriToKey } from '../cloudWatchLogsUtils' + +export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { + private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter() + public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event + + public constructor(private readonly registry: LiveTailSessionRegistry) {} + + public provideCodeLenses( + document: vscode.TextDocument, + token: vscode.CancellationToken + ): vscode.ProviderResult { + const uri = document.uri + // if registry does not contain session, it is assumed to have been stopped, thus, hide lenses. + if (uri.scheme !== cloudwatchLogsLiveTailScheme || !this.registry.has(uriToKey(uri))) { + return [] + } + const codeLenses: vscode.CodeLens[] = [] + codeLenses.push(this.buildClearDocumentCodeLens(document)) + codeLenses.push(this.buildStopTailingCodeLens(document)) + return codeLenses + } + + public refresh() { + this._onDidChangeCodeLenses.fire() + } + + private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = this.getBottomOfDocumentRange(document) + const command: vscode.Command = { + title: 'Clear document', + command: 'aws.cwl.clearDocument', + arguments: [document], + } + return new vscode.CodeLens(range, command) + } + + private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = this.getBottomOfDocumentRange(document) + const command: vscode.Command = { + title: 'Stop tailing', + command: 'aws.cwl.stopTailingLogGroup', + arguments: [document, 'codeLens'], + } + return new vscode.CodeLens(range, command) + } + + private getBottomOfDocumentRange(document: vscode.TextDocument): vscode.Range { + return new vscode.Range( + new vscode.Position(document.lineCount - 1, 0), + new vscode.Position(document.lineCount - 1, 0) + ) + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts new file mode 100644 index 00000000000..7d662890dcf --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts @@ -0,0 +1,13 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' + +export class LiveTailDocumentProvider implements vscode.TextDocumentContentProvider { + provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { + // Content will be written to the document via handling a LiveTail response stream in the TailLogGroup command. + return '' + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts new file mode 100644 index 00000000000..87eba4d4079 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts @@ -0,0 +1,159 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import * as AWS from '@aws-sdk/types' +import { + CloudWatchLogsClient, + StartLiveTailCommand, + StartLiveTailResponseStream, +} from '@aws-sdk/client-cloudwatch-logs' +import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu' +import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils' +import { globals, Settings, ToolkitError } from '../../../shared' +import { createLiveTailURIFromArgs } from './liveTailSessionRegistry' +import { getUserAgent } from '../../../shared/telemetry/util' +import { convertToTimeString } from '../../../shared/datetime' + +export type LiveTailSessionConfiguration = { + logGroupArn: string + logStreamFilter?: LogStreamFilterResponse + logEventFilterPattern?: string + region: string + awsCredentials: AWS.Credentials +} + +export type LiveTailSessionClient = { + cwlClient: CloudWatchLogsClient + abortController: AbortController +} + +export class LiveTailSession { + private liveTailClient: LiveTailSessionClient + private _logGroupArn: string + private logStreamFilter?: LogStreamFilterResponse + private logEventFilterPattern?: string + private _maxLines: number + private _uri: vscode.Uri + private statusBarItem: vscode.StatusBarItem + private startTime: number | undefined + private endTime: number | undefined + private _eventRate: number + private _isSampled: boolean + + // While session is running, used to update the StatusBar each half second. + private statusBarUpdateTimer: NodeJS.Timer | undefined + + static settings = new CloudWatchLogsSettings(Settings.instance) + + public constructor(configuration: LiveTailSessionConfiguration) { + this._logGroupArn = configuration.logGroupArn + this.logStreamFilter = configuration.logStreamFilter + this.logEventFilterPattern = configuration.logEventFilterPattern + this.liveTailClient = { + cwlClient: new CloudWatchLogsClient({ + credentials: configuration.awsCredentials, + region: configuration.region, + customUserAgent: getUserAgent(), + }), + abortController: new AbortController(), + } + this._maxLines = LiveTailSession.settings.get('limit', 10000) + this._uri = createLiveTailURIFromArgs(configuration) + this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0) + this._eventRate = 0 + this._isSampled = false + } + + public get maxLines() { + return this._maxLines + } + + public get uri() { + return this._uri + } + + public get logGroupArn() { + return this._logGroupArn + } + + public set eventRate(rate: number) { + this._eventRate = rate + } + + public set isSampled(isSampled: boolean) { + this._isSampled = isSampled + } + + public async startLiveTailSession(): Promise> { + const commandOutput = await this.liveTailClient.cwlClient.send(this.buildStartLiveTailCommand(), { + abortSignal: this.liveTailClient.abortController.signal, + }) + if (!commandOutput.responseStream) { + throw new ToolkitError('LiveTail session response stream is undefined.') + } + this.startTime = globals.clock.Date.now() + this.endTime = undefined + this.statusBarUpdateTimer = globals.clock.setInterval(() => { + this.updateStatusBarItemText() + }, 500) + return commandOutput.responseStream + } + + public stopLiveTailSession() { + this.endTime = globals.clock.Date.now() + this.statusBarItem.dispose() + globals.clock.clearInterval(this.statusBarUpdateTimer) + this.liveTailClient.abortController.abort() + this.liveTailClient.cwlClient.destroy() + } + + public getLiveTailSessionDuration(): number { + // Never started + if (this.startTime === undefined) { + return 0 + } + // Currently running + if (this.endTime === undefined) { + return globals.clock.Date.now() - this.startTime + } + return this.endTime - this.startTime + } + + public buildStartLiveTailCommand(): StartLiveTailCommand { + let logStreamNamePrefix = undefined + let logStreamName = undefined + if (this.logStreamFilter) { + if (this.logStreamFilter.type === 'prefix') { + logStreamNamePrefix = this.logStreamFilter.filter + logStreamName = undefined + } else if (this.logStreamFilter.type === 'specific') { + logStreamName = this.logStreamFilter.filter + logStreamNamePrefix = undefined + } + } + + return new StartLiveTailCommand({ + logGroupIdentifiers: [this.logGroupArn], + logStreamNamePrefixes: logStreamNamePrefix ? [logStreamNamePrefix] : undefined, + logStreamNames: logStreamName ? [logStreamName] : undefined, + logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined, + }) + } + + public showStatusBarItem(shouldShow: boolean) { + shouldShow ? this.statusBarItem.show() : this.statusBarItem.hide() + } + + public updateStatusBarItemText() { + const elapsedTime = this.getLiveTailSessionDuration() + const timeString = convertToTimeString(elapsedTime) + const sampledString = this._isSampled ? 'Yes' : 'No' + this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}` + } + + public get isAborted() { + return this.liveTailClient.abortController.signal.aborted + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts new file mode 100644 index 00000000000..988725edc87 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts @@ -0,0 +1,34 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' +import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession' + +export class LiveTailSessionRegistry extends Map { + static #instance: LiveTailSessionRegistry + + public static get instance() { + return (this.#instance ??= new this()) + } + + public constructor() { + super() + } +} + +export function createLiveTailURIFromArgs(sessionData: LiveTailSessionConfiguration): vscode.Uri { + let uriStr = `${cloudwatchLogsLiveTailScheme}:${sessionData.region}:${sessionData.logGroupArn}` + + if (sessionData.logStreamFilter) { + if (sessionData.logStreamFilter.type !== 'all') { + uriStr += `:${sessionData.logStreamFilter.type}:${sessionData.logStreamFilter.filter}` + } else { + uriStr += `:${sessionData.logStreamFilter.type}` + } + } + uriStr += sessionData.logEventFilterPattern ? `:${sessionData.logEventFilterPattern}` : '' + + return vscode.Uri.parse(uriStr) +} diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts new file mode 100644 index 00000000000..c94259684bb --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts @@ -0,0 +1,163 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { Prompter, PromptResult } from '../../../shared/ui/prompter' +import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' +import { createCommonButtons } from '../../../shared/ui/buttons' +import { createInputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter' +import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../../shared/ui/pickerPrompter' +import { pageableToCollection } from '../../../shared/utilities/collectionUtils' +import { CloudWatchLogs } from 'aws-sdk' +import { isValidResponse, StepEstimator } from '../../../shared/wizards/wizard' +import { isNonNullable } from '../../../shared/utilities/tsUtils' +import { + startLiveTailHelpUrl, + startLiveTailLogStreamNamesHelpUrl, + startLiveTailLogStreamPrefixHelpUrl, +} from '../../../shared/constants' + +export type LogStreamFilterType = 'menu' | 'prefix' | 'specific' | 'all' + +export interface LogStreamFilterResponse { + readonly filter?: string + readonly type: LogStreamFilterType +} + +export class LogStreamFilterSubmenu extends Prompter { + private logStreamPrefixRegEx = /^[^:*]*$/ + private currentState: LogStreamFilterType = 'menu' + private steps?: [current: number, total: number] + private region: string + private logGroupArn: string + public defaultPrompter: QuickPickPrompter = this.createMenuPrompter() + + public constructor(logGroupArn: string, region: string) { + super() + this.region = region + this.logGroupArn = logGroupArn + } + + public createMenuPrompter() { + const helpUri = startLiveTailHelpUrl + const prompter = createQuickPick(this.menuOptions, { + title: 'Include log events from...', + buttons: createCommonButtons(helpUri), + }) + return prompter + } + + private get menuOptions(): DataQuickPickItem[] { + const options: DataQuickPickItem[] = [] + options.push({ + label: 'All Log Streams', + data: 'all', + }) + options.push({ + label: 'Specific Log Stream', + data: 'specific', + }) + options.push({ + label: 'Log Streams matching prefix', + data: 'prefix', + }) + return options + } + + public createLogStreamPrefixBox(): InputBoxPrompter { + const helpUri = startLiveTailLogStreamPrefixHelpUrl + return createInputBox({ + title: 'Enter Log Stream prefix', + placeholder: 'log stream prefix (case sensitive; empty matches all)', + prompt: 'Only log events in Log Streams whose name starts with the supplied prefix will be included.', + validateInput: (input) => this.validateLogStreamPrefix(input), + buttons: createCommonButtons(helpUri), + }) + } + + public validateLogStreamPrefix(prefix: string) { + if (prefix.length > 512) { + return 'Log Stream prefix cannot be longer than 512 characters' + } + + if (!this.logStreamPrefixRegEx.test(prefix)) { + return 'Log Stream prefix must match pattern: [^:*]*' + } + } + + public createLogStreamSelector(): QuickPickPrompter { + const helpUri = startLiveTailLogStreamNamesHelpUrl + const client = new DefaultCloudWatchLogsClient(this.region) + const request: CloudWatchLogs.DescribeLogStreamsRequest = { + logGroupIdentifier: this.logGroupArn, + orderBy: 'LastEventTime', + descending: true, + } + const requester = (request: CloudWatchLogs.DescribeLogStreamsRequest) => client.describeLogStreams(request) + const collection = pageableToCollection(requester, request, 'nextToken', 'logStreams') + + const items = collection + .filter(isNonNullable) + .map((streams) => streams!.map((stream) => ({ data: stream.logStreamName!, label: stream.logStreamName! }))) + + return createQuickPick(items, { + title: 'Select Log Stream', + buttons: createCommonButtons(helpUri), + }) + } + + private switchState(newState: LogStreamFilterType) { + this.currentState = newState + } + + protected async promptUser(): Promise> { + while (true) { + switch (this.currentState) { + case 'menu': { + const prompter = this.createMenuPrompter() + this.steps && prompter.setSteps(this.steps[0], this.steps[1]) + + const resp = await prompter.prompt() + if (resp === 'prefix') { + this.switchState('prefix') + } else if (resp === 'specific') { + this.switchState('specific') + } else if (resp === 'all') { + return { filter: undefined, type: resp } + } else { + return undefined + } + + break + } + case 'prefix': { + const resp = await this.createLogStreamPrefixBox().prompt() + if (isValidResponse(resp)) { + return { filter: resp, type: 'prefix' } + } + this.switchState('menu') + break + } + case 'specific': { + const resp = await this.createLogStreamSelector().prompt() + if (isValidResponse(resp)) { + return { filter: resp, type: 'specific' } + } + this.switchState('menu') + break + } + } + } + } + + public setSteps(current: number, total: number): void { + this.steps = [current, total] + } + + // Unused + public get recentItem(): any { + return + } + public set recentItem(response: any) {} + public setStepEstimator(estimator: StepEstimator): void {} +} diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts new file mode 100644 index 00000000000..025820df792 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts @@ -0,0 +1,109 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as nls from 'vscode-nls' +import { globals, ToolkitError } from '../../../shared' +import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient' +import { cwlFilterPatternHelpUrl } from '../../../shared/constants' +import { createBackButton, createExitButton, createHelpButton } from '../../../shared/ui/buttons' +import { RegionSubmenu, RegionSubmenuResponse } from '../../../shared/ui/common/regionSubmenu' +import { createInputBox } from '../../../shared/ui/inputPrompter' +import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' +import { Wizard } from '../../../shared/wizards/wizard' +import { CloudWatchLogsGroupInfo } from '../registry/logDataRegistry' +import { LogStreamFilterResponse, LogStreamFilterSubmenu } from './liveTailLogStreamSubmenu' + +const localize = nls.loadMessageBundle() + +export interface TailLogGroupWizardResponse { + regionLogGroupSubmenuResponse: RegionSubmenuResponse + logStreamFilter: LogStreamFilterResponse + filterPattern: string +} + +export class TailLogGroupWizard extends Wizard { + public constructor(logGroupInfo?: CloudWatchLogsGroupInfo) { + super({ + initState: { + regionLogGroupSubmenuResponse: logGroupInfo + ? { + data: buildLogGroupArn(logGroupInfo.groupName, logGroupInfo.regionName), + region: logGroupInfo.regionName, + } + : undefined, + }, + }) + this.form.regionLogGroupSubmenuResponse.bindPrompter(createRegionLogGroupSubmenu) + this.form.logStreamFilter.bindPrompter((state) => { + if (!state.regionLogGroupSubmenuResponse?.data) { + throw new ToolkitError('Log Group name is null') + } + return new LogStreamFilterSubmenu( + state.regionLogGroupSubmenuResponse.data, + state.regionLogGroupSubmenuResponse.region + ) + }) + this.form.filterPattern.bindPrompter((state) => createFilterPatternPrompter()) + } +} + +export function createRegionLogGroupSubmenu(): RegionSubmenu { + return new RegionSubmenu( + getLogGroupQuickPickOptions, + { + title: localize('AWS.cwl.tailLogGroup.logGroupPromptTitle', 'Select Log Group to tail'), + buttons: [createExitButton()], + }, + { title: localize('AWS.cwl.tailLogGroup.regionPromptTitle', 'Select Region for Log Group') }, + 'Log Groups' + ) +} + +async function getLogGroupQuickPickOptions(regionCode: string): Promise[]> { + const client = new DefaultCloudWatchLogsClient(regionCode) + const logGroups = client.describeLogGroups() + + const logGroupsOptions: DataQuickPickItem[] = [] + + for await (const logGroupObject of logGroups) { + if (!logGroupObject.arn || !logGroupObject.logGroupName) { + throw new ToolkitError('Log Group name or arn is undefined') + } + + logGroupsOptions.push({ + label: logGroupObject.logGroupName, + data: formatLogGroupArn(logGroupObject.arn), + }) + } + + return logGroupsOptions +} + +export function buildLogGroupArn(logGroupName: string, region: string): string { + if (logGroupName.startsWith('arn:')) { + return logGroupName + } + const awsAccountId = globals.awsContext.getCredentialAccountId() + if (awsAccountId === undefined) { + throw new ToolkitError( + `Failed to construct Arn for Log Group because awsAccountId is undefined. Log Group: ${logGroupName}` + ) + } + return `arn:aws:logs:${region}:${awsAccountId}:log-group:${logGroupName}` +} + +function formatLogGroupArn(logGroupArn: string): string { + return logGroupArn.endsWith(':*') ? logGroupArn.substring(0, logGroupArn.length - 2) : logGroupArn +} + +export function createFilterPatternPrompter() { + const helpUri = cwlFilterPatternHelpUrl + return createInputBox({ + title: 'Provide log event filter pattern', + placeholder: 'filter pattern (case sensitive; empty matches all)', + prompt: 'Optional filter to include only log events that match the supplied pattern.', + buttons: [createHelpButton(helpUri), createBackButton(), createExitButton()], + }) +} diff --git a/packages/core/src/shared/constants.ts b/packages/core/src/shared/constants.ts index 0b095e23df6..48e64342e57 100644 --- a/packages/core/src/shared/constants.ts +++ b/packages/core/src/shared/constants.ts @@ -146,10 +146,20 @@ export const ecsIamPermissionsUrl = vscode.Uri.parse( * URI scheme for CloudWatch Logs Virtual Documents */ export const CLOUDWATCH_LOGS_SCHEME = 'aws-cwl' // eslint-disable-line @typescript-eslint/naming-convention +export const cloudwatchLogsLiveTailScheme = 'aws-cwl-lt' + export const AWS_SCHEME = 'aws' // eslint-disable-line @typescript-eslint/naming-convention export const ec2LogsScheme = 'aws-ec2' export const amazonQDiffScheme = 'amazon-q-diff' +export const startLiveTailHelpUrl = + 'https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_StartLiveTail.html' +export const startLiveTailLogStreamPrefixHelpUrl = `${startLiveTailHelpUrl}#CWL-StartLiveTail-request-logStreamNamePrefixes` +export const startLiveTailLogStreamNamesHelpUrl = `${startLiveTailHelpUrl}#CWL-StartLiveTail-request-logStreamNames` + +export const cwlFilterPatternHelpUrl = + 'https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html' + export const lambdaPackageTypeImage = 'Image' // URLs for App Runner diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts new file mode 100644 index 00000000000..f2b085b2f2c --- /dev/null +++ b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts @@ -0,0 +1,217 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as sinon from 'sinon' +import * as vscode from 'vscode' + +import assert from 'assert' +import { clearDocument, closeSession, tailLogGroup } from '../../../../awsService/cloudWatchLogs/commands/tailLogGroup' +import { StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs' +import { LiveTailSessionRegistry } from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry' +import { LiveTailSession } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' +import { + TailLogGroupWizard, + TailLogGroupWizardResponse, +} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard' +import { getTestWindow } from '../../../shared/vscode/window' +import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' +import { DefaultAwsContext, ToolkitError, waitUntil } from '../../../../shared' +import { LiveTailCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/liveTailCodeLensProvider' + +describe('TailLogGroup', function () { + const testLogGroup = 'test-log-group' + const testRegion = 'test-region' + const testMessage = 'test-message' + const testAwsAccountId = '1234' + const testSource = 'test-source' + const testAwsCredentials = {} as any as AWS.Credentials + + let sandbox: sinon.SinonSandbox + let registry: LiveTailSessionRegistry + let codeLensProvider: LiveTailCodeLensProvider + let startLiveTailSessionSpy: sinon.SinonSpy + let stopLiveTailSessionSpy: sinon.SinonSpy + let cloudwatchSettingsSpy: sinon.SinonSpy + let wizardSpy: sinon.SinonSpy + + beforeEach(function () { + sandbox = sinon.createSandbox() + registry = new LiveTailSessionRegistry() + codeLensProvider = new LiveTailCodeLensProvider(registry) + }) + + afterEach(function () { + sandbox.restore() + }) + + it('starts LiveTailSession and writes to document. Closes tab and asserts session gets closed.', async function () { + sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) + sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(testAwsCredentials)) + + const startTimestamp = 1732276800000 // 11-22-2024 12:00:00PM GMT + const updateFrames: StartLiveTailResponseStream[] = [ + getSessionUpdateFrame(false, `${testMessage}-1`, startTimestamp + 1000), + getSessionUpdateFrame(false, `${testMessage}-2`, startTimestamp + 2000), + getSessionUpdateFrame(false, `${testMessage}-3`, startTimestamp + 3000), + ] + // Returns the configured update frames and then indefinitely blocks. + // This keeps the stream 'open', simulating an open network stream waiting for new events. + // If the stream were to close, the event listeners in the TailLogGroup command would dispose, + // breaking the 'closes tab closes session' assertions this test makes. + async function* generator(): AsyncIterable { + for (const frame of updateFrames) { + yield frame + } + await new Promise(() => {}) + } + + startLiveTailSessionSpy = sandbox + .stub(LiveTailSession.prototype, 'startLiveTailSession') + .returns(Promise.resolve(generator())) + stopLiveTailSessionSpy = sandbox + .stub(LiveTailSession.prototype, 'stopLiveTailSession') + .callsFake(async function () { + return + }) + wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () { + return getTestWizardResponse() + }) + // Set maxLines to 1. + cloudwatchSettingsSpy = sandbox.stub(CloudWatchLogsSettings.prototype, 'get').callsFake(() => { + return 1 + }) + + // The mock stream doesn't 'close', causing tailLogGroup to not return. If we `await`, it will never resolve. + // Run it in the background and use waitUntil to poll its state. + void tailLogGroup(registry, testSource, codeLensProvider, { + groupName: testLogGroup, + regionName: testRegion, + }) + await waitUntil(async () => registry.size !== 0, { interval: 100, timeout: 1000 }) + + // registry is asserted to have only one entry, so this is assumed to be the session that was + // started in this test. + let sessionUri: vscode.Uri | undefined + registry.forEach((session) => (sessionUri = session.uri)) + if (sessionUri === undefined) { + throw Error + } + + assert.strictEqual(wizardSpy.calledOnce, true) + assert.strictEqual(cloudwatchSettingsSpy.calledOnce, true) + assert.strictEqual(startLiveTailSessionSpy.calledOnce, true) + assert.strictEqual(registry.size, 1) + + // Validate writing to the document. + // MaxLines is set to 1, and "testMessage3" is the last event in the stream, its contents should be the only thing in the doc. + const window = getTestWindow() + const document = window.activeTextEditor?.document + assert.strictEqual(sessionUri.toString(), document?.uri.toString()) + const doesDocumentContainExpectedContent = await waitUntil( + async () => document?.getText().trim() === `12:00:03\t${testMessage}-3`, + { interval: 100, timeout: 1000 } + ) + assert.strictEqual(doesDocumentContainExpectedContent, true) + + // Test that closing all tabs the session's document is open in will cause the session to close + let tabs: vscode.Tab[] = [] + window.tabGroups.all.forEach((tabGroup) => { + tabs = tabs.concat(getLiveTailSessionTabsFromTabGroup(tabGroup, sessionUri!)) + }) + await Promise.all(tabs.map((tab) => window.tabGroups.close(tab))) + assert.strictEqual(registry.size, 0) + assert.strictEqual(stopLiveTailSessionSpy.calledOnce, true) + }) + + it('throws if crendentials are undefined', async function () { + sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(undefined)) + wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () { + return getTestWizardResponse() + }) + await assert.rejects(async () => { + await tailLogGroup(registry, testSource, codeLensProvider, { + groupName: testLogGroup, + regionName: testRegion, + }) + }, ToolkitError) + }) + + it('closeSession removes session from registry and calls underlying stopLiveTailSession function.', function () { + stopLiveTailSessionSpy = sandbox + .stub(LiveTailSession.prototype, 'stopLiveTailSession') + .callsFake(async function () { + return + }) + + const session = new LiveTailSession({ + logGroupArn: testLogGroup, + region: testRegion, + awsCredentials: testAwsCredentials, + }) + registry.set(uriToKey(session.uri), session) + + closeSession(session.uri, registry, testSource, codeLensProvider) + assert.strictEqual(0, registry.size) + assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce) + }) + + it('clearDocument clears all text from document', async function () { + const session = new LiveTailSession({ + logGroupArn: testLogGroup, + region: testRegion, + awsCredentials: testAwsCredentials, + }) + const testData = 'blah blah blah' + const document = await vscode.workspace.openTextDocument(session.uri) + const edit = new vscode.WorkspaceEdit() + edit.insert(document.uri, new vscode.Position(0, 0), testData) + await vscode.workspace.applyEdit(edit) + assert.strictEqual(document.getText(), testData) + + await clearDocument(document) + assert.strictEqual(document.getText(), '') + }) + + function getLiveTailSessionTabsFromTabGroup(tabGroup: vscode.TabGroup, sessionUri: vscode.Uri): vscode.Tab[] { + return tabGroup.tabs.filter((tab) => { + if (tab.input instanceof vscode.TabInputText) { + return sessionUri!.toString() === tab.input.uri.toString() + } + }) + } + + function getTestWizardResponse(): TailLogGroupWizardResponse { + return { + regionLogGroupSubmenuResponse: { + region: testRegion, + data: testLogGroup, + }, + filterPattern: '', + logStreamFilter: { + type: 'all', + }, + } + } + + function getSessionUpdateFrame( + isSampled: boolean, + message: string, + timestamp: number + ): StartLiveTailResponseStream { + return { + sessionUpdate: { + sessionMetadata: { + sampled: isSampled, + }, + sessionResults: [ + { + message: message, + timestamp: timestamp, + }, + ], + }, + } + } +}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts new file mode 100644 index 00000000000..1aeeec9e6ae --- /dev/null +++ b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts @@ -0,0 +1,99 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import assert from 'assert' +import { LiveTailSessionConfiguration } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' +import { createLiveTailURIFromArgs } from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry' +import { cloudwatchLogsLiveTailScheme } from '../../../../shared/constants' + +describe('LiveTailSession URI', async function () { + const testLogGroupName = 'test-log-group' + const testRegion = 'test-region' + const testAwsCredentials = {} as any as AWS.Credentials + const expectedUriBase = `${cloudwatchLogsLiveTailScheme}:${testRegion}:${testLogGroupName}` + + it('is correct with no logStream filter, no filter pattern', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(expectedUriBase) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) + + it('is correct with no logStream filter, with filter pattern', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + logEventFilterPattern: 'test-filter', + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(`${expectedUriBase}:test-filter`) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) + + it('is correct with ALL logStream filter', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + logStreamFilter: { + type: 'all', + }, + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(`${expectedUriBase}:all`) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) + + it('is correct with prefix logStream filter', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + logStreamFilter: { + type: 'prefix', + filter: 'test-prefix', + }, + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(`${expectedUriBase}:prefix:test-prefix`) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) + + it('is correct with specific logStream filter', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + logStreamFilter: { + type: 'specific', + filter: 'test-stream', + }, + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(`${expectedUriBase}:specific:test-stream`) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) + + it('is correct with specific logStream filter and filter pattern', function () { + const config: LiveTailSessionConfiguration = { + logGroupArn: testLogGroupName, + region: testRegion, + logStreamFilter: { + type: 'specific', + filter: 'test-stream', + }, + logEventFilterPattern: 'test-filter', + awsCredentials: testAwsCredentials, + } + const expectedUri = vscode.Uri.parse(`${expectedUriBase}:specific:test-stream:test-filter`) + const uri = createLiveTailURIFromArgs(config) + assert.deepEqual(uri, expectedUri) + }) +}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts new file mode 100644 index 00000000000..514114750b4 --- /dev/null +++ b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts @@ -0,0 +1,149 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as sinon from 'sinon' +import * as FakeTimers from '@sinonjs/fake-timers' +import assert from 'assert' +import { LiveTailSession } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' +import { + CloudWatchLogsClient, + StartLiveTailCommand, + StartLiveTailResponseStream, +} from '@aws-sdk/client-cloudwatch-logs' +import { LogStreamFilterResponse } from '../../../../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu' +import { installFakeClock } from '../../../testUtil' + +describe('LiveTailSession', async function () { + const testLogGroupArn = 'arn:aws:test-log-group' + const testRegion = 'test-region' + const testFilter = 'test-filter' + const testAwsCredentials = {} as any as AWS.Credentials + + let sandbox: sinon.SinonSandbox + let clock: FakeTimers.InstalledClock + + before(function () { + clock = installFakeClock() + }) + + beforeEach(function () { + clock.reset() + sandbox = sinon.createSandbox() + }) + + after(function () { + clock.uninstall() + }) + + afterEach(function () { + sandbox.restore() + }) + + it('builds StartLiveTailCommand: no stream Filter, no event filter.', function () { + const session = buildLiveTailSession({ type: 'all' }, undefined) + assert.deepStrictEqual( + session.buildStartLiveTailCommand().input, + new StartLiveTailCommand({ + logGroupIdentifiers: [testLogGroupArn], + logEventFilterPattern: undefined, + logStreamNamePrefixes: undefined, + logStreamNames: undefined, + }).input + ) + }) + + it('builds StartLiveTailCommand: with prefix stream Filter', function () { + const session = buildLiveTailSession({ type: 'prefix', filter: testFilter }, undefined) + assert.deepStrictEqual( + session.buildStartLiveTailCommand().input, + new StartLiveTailCommand({ + logGroupIdentifiers: [testLogGroupArn], + logEventFilterPattern: undefined, + logStreamNamePrefixes: [testFilter], + logStreamNames: undefined, + }).input + ) + }) + + it('builds StartLiveTailCommand: with specific stream Filter', function () { + const session = buildLiveTailSession({ type: 'specific', filter: testFilter }, undefined) + assert.deepStrictEqual( + session.buildStartLiveTailCommand().input, + new StartLiveTailCommand({ + logGroupIdentifiers: [testLogGroupArn], + logEventFilterPattern: undefined, + logStreamNamePrefixes: undefined, + logStreamNames: [testFilter], + }).input + ) + }) + + it('builds StartLiveTailCommand: with log event Filter', function () { + const session = buildLiveTailSession({ type: 'all' }, testFilter) + assert.deepStrictEqual( + session.buildStartLiveTailCommand().input, + new StartLiveTailCommand({ + logGroupIdentifiers: [testLogGroupArn], + logEventFilterPattern: testFilter, + logStreamNamePrefixes: undefined, + logStreamNames: undefined, + }).input + ) + }) + + it('closes a started session', async function () { + const startLiveTailStub = sinon.stub(CloudWatchLogsClient.prototype, 'send').callsFake(function () { + return { + responseStream: mockResponseStream(), + } + }) + const session = buildLiveTailSession({ type: 'all' }, testFilter) + assert.strictEqual(session.getLiveTailSessionDuration(), 0) + + const returnedResponseStream = await session.startLiveTailSession() + assert.strictEqual(startLiveTailStub.calledOnce, true) + const requestArgs = startLiveTailStub.getCall(0).args + assert.deepEqual(requestArgs[0].input, session.buildStartLiveTailCommand().input) + assert.strictEqual(requestArgs[1].abortSignal !== undefined && !requestArgs[1].abortSignal.aborted, true) + assert.strictEqual(session.isAborted, false) + assert.strictEqual(clock.countTimers(), 1) + assert.deepStrictEqual(returnedResponseStream, mockResponseStream()) + + clock.tick(1000) + assert.strictEqual(session.getLiveTailSessionDuration(), 1000) + + session.stopLiveTailSession() + assert.strictEqual(session.isAborted, true) + assert.strictEqual(clock.countTimers(), 0) + + // Session is stopped; ticking the clock forward should not change the session duration + clock.tick(1000) + assert.strictEqual(session.getLiveTailSessionDuration(), 1000) + }) + + function buildLiveTailSession( + logStreamFilter: LogStreamFilterResponse, + logEventFilterPattern: string | undefined + ): LiveTailSession { + return new LiveTailSession({ + logGroupArn: testLogGroupArn, + logStreamFilter: logStreamFilter, + logEventFilterPattern: logEventFilterPattern, + region: testRegion, + awsCredentials: testAwsCredentials, + }) + } + + async function* mockResponseStream(): AsyncIterable { + const frame: StartLiveTailResponseStream = { + sessionUpdate: { + sessionMetadata: { + sampled: false, + }, + sessionResults: [], + }, + } + yield frame + } +}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts new file mode 100644 index 00000000000..32946c38056 --- /dev/null +++ b/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts @@ -0,0 +1,78 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { LogStreamFilterSubmenu } from '../../../../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu' +import { createQuickPickPrompterTester, QuickPickPrompterTester } from '../../../shared/ui/testUtils' +import { getTestWindow } from '../../../shared/vscode/window' + +describe('liveTailLogStreamSubmenu', async function () { + let logStreamFilterSubmenu: LogStreamFilterSubmenu + let logStreamMenuPrompter: QuickPickPrompterTester + const testRegion = 'us-east-1' + const testLogGroupArn = 'my-log-group-arn' + + beforeEach(async function () { + logStreamFilterSubmenu = new LogStreamFilterSubmenu(testRegion, testLogGroupArn) + logStreamMenuPrompter = createQuickPickPrompterTester(logStreamFilterSubmenu.defaultPrompter) + }) + + describe('Menu prompter', async function () { + it('gives option for each filter type', async function () { + logStreamMenuPrompter.assertContainsItems( + 'All Log Streams', + 'Specific Log Stream', + 'Log Streams matching prefix' + ) + logStreamMenuPrompter.acceptItem('All Log Streams') + await logStreamMenuPrompter.result() + }) + }) + + describe('LogStream Prefix Submenu', function () { + it('accepts valid input', async function () { + const validInput = 'my-log-stream' + getTestWindow().onDidShowInputBox((input) => { + input.acceptValue(validInput) + }) + const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() + const result = inputBox.prompt() + assert.strictEqual(await result, validInput) + }) + + it('rejects invalid input (:)', async function () { + const invalidInput = 'my-log-stream:' + getTestWindow().onDidShowInputBox((input) => { + input.acceptValue(invalidInput) + assert.deepEqual(input.validationMessage, 'Log Stream prefix must match pattern: [^:*]*') + input.hide() + }) + const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() + await inputBox.prompt() + }) + + it('rejects invalid input (*)', async function () { + const invalidInput = 'my-log-stream*' + getTestWindow().onDidShowInputBox((input) => { + input.acceptValue(invalidInput) + assert.deepEqual(input.validationMessage, 'Log Stream prefix must match pattern: [^:*]*') + input.hide() + }) + const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() + await inputBox.prompt() + }) + + it('rejects invalid input (length)', async function () { + const invalidInput = 'a'.repeat(520) + getTestWindow().onDidShowInputBox((input) => { + input.acceptValue(invalidInput) + assert.deepEqual(input.validationMessage, 'Log Stream prefix cannot be longer than 512 characters') + input.hide() + }) + const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() + await inputBox.prompt() + }) + }) +}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts new file mode 100644 index 00000000000..e3dde5f96cf --- /dev/null +++ b/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts @@ -0,0 +1,52 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as sinon from 'sinon' + +import assert from 'assert' +import { buildLogGroupArn, TailLogGroupWizard } from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard' +import { createWizardTester } from '../../../shared/wizards/wizardTestUtils' +import { DefaultAwsContext } from '../../../../shared' + +describe('TailLogGroupWizard', async function () { + let sandbox: sinon.SinonSandbox + + const testLogGroupName = 'testLogGroup' + const testRegion = 'testRegion' + const testAwsAccountId = '1234' + + beforeEach(function () { + sandbox = sinon.createSandbox() + }) + + afterEach(function () { + sandbox.restore() + }) + + it('prompts regionLogGroup submenu first if context not provided', async function () { + const wizard = new TailLogGroupWizard() + const tester = await createWizardTester(wizard) + tester.regionLogGroupSubmenuResponse.assertShowFirst() + tester.logStreamFilter.assertShowSecond() + tester.filterPattern.assertShowThird() + }) + + it('skips regionLogGroup submenu if context provided', async function () { + sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) + const wizard = new TailLogGroupWizard({ + groupName: testLogGroupName, + regionName: testRegion, + }) + const tester = await createWizardTester(wizard) + tester.regionLogGroupSubmenuResponse.assertDoesNotShow() + tester.logStreamFilter.assertShowFirst() + tester.filterPattern.assertShowSecond() + }) + + it('builds LogGroup Arn properly', async function () { + sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) + const arn = buildLogGroupArn(testLogGroupName, testRegion) + assert.strictEqual(arn, `arn:aws:logs:${testRegion}:${testAwsAccountId}:log-group:${testLogGroupName}`) + }) +}) diff --git a/packages/toolkit/.changes/next-release/Feature-eb088612-3f07-4410-86f2-6e91131d3ffe.json b/packages/toolkit/.changes/next-release/Feature-eb088612-3f07-4410-86f2-6e91131d3ffe.json new file mode 100644 index 00000000000..547ae687098 --- /dev/null +++ b/packages/toolkit/.changes/next-release/Feature-eb088612-3f07-4410-86f2-6e91131d3ffe.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "CloudWatch Logs: Added support for Live Tailing LogGroups. Start using LiveTail by: selecting 'Tail Log Group' in the command palette, or, right clicking/pressing the 'Play' icon on a Log Group in the Explorer menu. See [Troubleshoot with CloudWatch Logs Live Tail](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogs_LiveTail.html) for more information. LiveTail is a paid feature - for more information about pricing, see the Logs tab at [Amazon CloudWatch Pricing](https://aws.amazon.com/cloudwatch/pricing/)." +} diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index ab1fb3c1bed..311f3d02d36 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -1830,6 +1830,16 @@ "group": "inline@1", "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" }, + { + "command": "aws.cwl.tailLogGroup", + "group": "0@1", + "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" + }, + { + "command": "aws.cwl.tailLogGroup", + "group": "inline@1", + "when": "view == aws.explorer && viewItem =~ /^awsCloudWatchLogNode|awsCloudWatchLogParentNode$/" + }, { "command": "aws.apig.copyUrl", "when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsApiGatewayNode)$/", @@ -3390,6 +3400,18 @@ } } }, + { + "command": "aws.cwl.tailLogGroup", + "title": "%AWS.command.cloudWatchLogs.tailLogGroup%", + "category": "%AWS.title%", + "enablement": "isCloud9 || !aws.isWebExtHost", + "icon": "$(notebook-execute)", + "cloud9": { + "cn": { + "category": "%AWS.title.cn%" + } + } + }, { "command": "aws.saveCurrentLogDataContent", "title": "%AWS.command.saveCurrentLogDataContent%",