diff --git a/package-lock.json b/package-lock.json index 16505334ff4..11d464fcc55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "vscode-nls-dev": "^4.0.4" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.258", + "@aws-toolkits/telemetry": "^1.0.267", "@playwright/browser-chromium": "^1.43.1", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", @@ -5192,9 +5192,10 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.258", + "version": "1.0.267", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.267.tgz", + "integrity": "sha512-qVEHuEW6WgqUafJP5oVtlaaWDtn2+6CklzqQgruqH7gxlNLBgi9pM9dpEC8xOYrHN3m1UW0LagUUgRS4ndDOyw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "ajv": "^6.12.6", "fs-extra": "^11.1.0", @@ -9538,9 +9539,10 @@ } }, "node_modules/code-block-writer": { - "version": "13.0.1", - "dev": true, - "license": "MIT" + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.2.tgz", + "integrity": "sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==", + "dev": true }, "node_modules/color": { "version": "3.2.1", diff --git a/package.json b/package.json index 01b33ff6762..c4b768d96e6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.258", + "@aws-toolkits/telemetry": "^1.0.267", "@playwright/browser-chromium": "^1.43.1", "@types/vscode": "^1.68.0", "@types/vscode-webview": "^1.57.1", diff --git a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts index c534b40ffde..e8ecefc171e 100644 --- a/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts +++ b/packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts @@ -11,7 +11,8 @@ import { maxRepoSizeBytes, } from 'aws-core-vscode/amazonqFeatureDev' import { assertTelemetry, createTestWorkspace } from 'aws-core-vscode/test' -import { fs, AmazonqCreateUpload, Metric } from 'aws-core-vscode/shared' +import { fs, AmazonqCreateUpload } from 'aws-core-vscode/shared' +import { Span } from 'aws-core-vscode/telemetry' import sinon from 'sinon' describe('file utils', () => { @@ -28,7 +29,7 @@ describe('file utils', () => { const telemetry = new TelemetryHelper() const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric) + } as unknown as Span) assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true) // checksum is not the same across different test executions because some unique random folder names are generated assert.strictEqual(result.zipFileChecksum.length, 44) @@ -46,7 +47,7 @@ describe('file utils', () => { const telemetry = new TelemetryHelper() const result = await prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric) + } as unknown as Span) assert.strictEqual(Buffer.isBuffer(result.zipFileBuffer), true) // checksum is not the same across different test executions because some unique random folder names are generated @@ -65,7 +66,7 @@ describe('file utils', () => { () => prepareRepoData([workspace.uri.fsPath], [workspace], telemetry, { record: () => {}, - } as unknown as Metric), + } as unknown as Span), ContentLengthError ) }) diff --git a/packages/core/src/amazonqFeatureDev/util/files.ts b/packages/core/src/amazonqFeatureDev/util/files.ts index a5a24139e0f..cffa74af867 100644 --- a/packages/core/src/amazonqFeatureDev/util/files.ts +++ b/packages/core/src/amazonqFeatureDev/util/files.ts @@ -14,7 +14,7 @@ import { maxFileSizeBytes } from '../limits' import { createHash } from 'crypto' import { CurrentWsFolders } from '../types' import { ToolkitError } from '../../shared/errors' -import { AmazonqCreateUpload, Metric, telemetry as amznTelemetry } from '../../shared/telemetry/telemetry' +import { AmazonqCreateUpload, Span, telemetry as amznTelemetry } from '../../shared/telemetry/telemetry' import { TelemetryHelper } from './telemetryHelper' import { maxRepoSizeBytes } from '../constants' import { isCodeFile } from '../../shared/filetypes' @@ -28,7 +28,7 @@ export async function prepareRepoData( repoRootPaths: string[], workspaceFolders: CurrentWsFolders, telemetry: TelemetryHelper, - span: Metric + span: Span ) { try { const files = await collectFiles(repoRootPaths, workspaceFolders, true, maxRepoSizeBytes) diff --git a/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts b/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts index 53ffd8c7065..111dd19f17e 100644 --- a/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts +++ b/packages/core/src/amazonqFeatureDev/util/telemetryHelper.ts @@ -5,7 +5,7 @@ import { globals } from '../../shared' import { getLogger } from '../../shared/logger/logger' -import { AmazonqApproachInvoke, AmazonqCodeGenerationInvoke, Metric } from '../../shared/telemetry/telemetry' +import { AmazonqApproachInvoke, AmazonqCodeGenerationInvoke, Span } from '../../shared/telemetry/telemetry' import { LLMResponseType } from '../types' export class TelemetryHelper { @@ -32,7 +32,7 @@ export class TelemetryHelper { } public recordUserApproachTelemetry( - span: Metric, + span: Span, amazonqConversationId: string, responseType: LLMResponseType ) { @@ -48,7 +48,7 @@ export class TelemetryHelper { span.record(event) } - public recordUserCodeGenerationTelemetry(span: Metric, amazonqConversationId: string) { + public recordUserCodeGenerationTelemetry(span: Span, amazonqConversationId: string) { const event = { amazonqConversationId, amazonqGenerateCodeIteration: this.generateCodeIteration, diff --git a/packages/core/src/auth/secondaryAuth.ts b/packages/core/src/auth/secondaryAuth.ts index e52236987df..add91765833 100644 --- a/packages/core/src/auth/secondaryAuth.ts +++ b/packages/core/src/auth/secondaryAuth.ts @@ -12,7 +12,7 @@ import { isNonNullable } from '../shared/utilities/tsUtils' import { ToolIdStateKey } from '../shared/globalState' import { Connection, getTelemetryMetadataForConn, SsoConnection, StatefulConnection } from './connection' import { indent } from '../shared/utilities/textUtilities' -import { AuthStatus, telemetry } from '../shared/telemetry/telemetry' +import { AuthModifyConnection, AuthStatus, Span, telemetry } from '../shared/telemetry/telemetry' import { asStringifiedStack } from '../shared/telemetry/spans' import { withTelemetryContext } from '../shared/telemetry/util' import { isNetworkError } from '../shared/errors' @@ -307,7 +307,7 @@ export class SecondaryAuth { connectionState: 'undefined', }) await this.auth.tryAutoConnect() - this.#savedConnection = await this._loadSavedConnection() + this.#savedConnection = await this._loadSavedConnection(span) this.#onDidChangeActiveConnection.fire(this.activeConnection) const conn = this.#savedConnection @@ -328,7 +328,7 @@ export class SecondaryAuth { /** * Provides telemetry if called by restoreConnection() (or another auth_modifyConnection context) */ - private async _loadSavedConnection() { + private async _loadSavedConnection(span: Span) { const id = this.getStateConnectionId() if (id === undefined) { return @@ -349,7 +349,7 @@ export class SecondaryAuth { let connectionState = this.auth.getConnectionState(conn) // This function is expected to be called in the context of restoreConnection() - telemetry.auth_modifyConnection.record({ + span.record({ connectionState, authStatus: getAuthStatus(connectionState), }) @@ -358,7 +358,7 @@ export class SecondaryAuth { await this.auth.refreshConnectionState(conn) connectionState = this.auth.getConnectionState(conn) - telemetry.auth_modifyConnection.record({ + span.record({ connectionState, authStatus: getAuthStatus(connectionState), }) @@ -367,7 +367,7 @@ export class SecondaryAuth { // If updating the state fails, then we should delegate downstream to handle getting the proper state. getLogger().error('loadSavedConnection: Failed to refresh connection state: %s', err) if (isNetworkError(err) && connectionState === 'valid') { - telemetry.auth_modifyConnection.record({ + span.record({ authStatus: 'connectedWithNetworkError', }) } diff --git a/packages/core/src/shared/telemetry/index.ts b/packages/core/src/shared/telemetry/index.ts index c31db934680..7e2e615303b 100644 --- a/packages/core/src/shared/telemetry/index.ts +++ b/packages/core/src/shared/telemetry/index.ts @@ -4,5 +4,5 @@ */ export { activate } from './activation' -export { telemetry, AuthStatus } from './telemetry' +export { telemetry, AuthStatus, Span } from './telemetry' export { ExtStartUpSources } from './util' diff --git a/packages/core/src/shared/telemetry/spans.ts b/packages/core/src/shared/telemetry/spans.ts index 1d1cbf7cb2d..08923703be5 100644 --- a/packages/core/src/shared/telemetry/spans.ts +++ b/packages/core/src/shared/telemetry/spans.ts @@ -13,6 +13,7 @@ import { MetricDefinition, MetricName, MetricShapes, + Span, TelemetryBase, } from './telemetry.gen' import { @@ -132,7 +133,7 @@ export type SpanOptions = { * * See also: docs/telemetry.md */ -export class TelemetrySpan { +export class TelemetrySpan implements Span { #startTime?: Date #options: SpanOptions @@ -317,6 +318,9 @@ export class TelemetryTracer extends TelemetryBase { * All changes made to {@link attributes} (via {@link record}) during the execution are * reverted after the execution completes. * + * Runs can be nested within each other. This allows for creating hierarchical spans, + * where child spans inherit the context of their parent spans. + * * This method automatically handles traceId generation and propagation: * - If no traceId exists in the current context, a new one is generated. * - The traceId is attached to all telemetry events created within this span. @@ -325,19 +329,13 @@ export class TelemetryTracer extends TelemetryBase { * * See docs/telemetry.md */ - public run(name: U, fn: (span: Metric) => T, options?: SpanOptions): T { + public run(name: U, fn: (span: Span) => T, options?: SpanOptions): T { return this.withTraceId(() => { const span = this.createSpan(name, options).start() const frame = this.switchContext(span) try { - // - // TODO: Since updating to `@types/node@16`, typescript flags this code with error: - // - // Error: npm ERR! src/shared/telemetry/spans.ts(255,57): error TS2345: Argument of type - // 'TelemetrySpan' is not assignable to parameter of type 'Metric'. - // - const result = this.#context.run(frame, fn, span as any) + const result = this.#context.run(frame, fn, span) if (result instanceof Promise) { return result @@ -444,9 +442,7 @@ export class TelemetryTracer extends TelemetryBase { return { name, emit: (data) => getSpan().emit(data), - record: (data) => getSpan().record(data), run: (fn, options?: SpanOptions) => this.run(name as MetricName, fn, options), - increment: (data) => getSpan().increment(data), } } diff --git a/packages/core/src/shared/telemetry/telemetry.ts b/packages/core/src/shared/telemetry/telemetry.ts index 60a2d13d389..47324c16582 100644 --- a/packages/core/src/shared/telemetry/telemetry.ts +++ b/packages/core/src/shared/telemetry/telemetry.ts @@ -23,7 +23,10 @@ export const telemetry = new TelemetryTracer() */ declare module './telemetry.gen' { interface Metric { + run(fn: (span: Span) => U, options?: SpanOptions): U + } + + interface Span { increment(data: { [P in NumericKeys]+?: number }): void - run(fn: (span: this) => U, options?: SpanOptions): U } } diff --git a/packages/core/src/shared/vscode/commands2.ts b/packages/core/src/shared/vscode/commands2.ts index 736c0b33a9c..9de79d60d24 100644 --- a/packages/core/src/shared/vscode/commands2.ts +++ b/packages/core/src/shared/vscode/commands2.ts @@ -8,7 +8,7 @@ import { toTitleCase } from '../utilities/textUtilities' import { getLogger, NullLogger } from '../logger/logger' import { FunctionKeys, Functions, getFunctions } from '../utilities/classUtils' import { TreeItemContent, TreeNode } from '../treeview/resourceTreeDataProvider' -import { telemetry, MetricName, VscodeExecuteCommand, Metric } from '../telemetry/telemetry' +import { telemetry, MetricName, VscodeExecuteCommand, Metric, Span } from '../telemetry/telemetry' import globals from '../extensionGlobals' import { ToolkitError } from '../errors' import crypto from 'crypto' @@ -498,7 +498,7 @@ function getInstrumenter( return (fn: T, ...args: Parameters) => span.run( (span) => { - ;(span as Metric).record({ + ;(span as Span).record({ command: id.id, debounceCount, ...fields, diff --git a/packages/core/src/test/shared/telemetry/spans.test.ts b/packages/core/src/test/shared/telemetry/spans.test.ts index 4a6efe1c95f..149fe5aa14e 100644 --- a/packages/core/src/test/shared/telemetry/spans.test.ts +++ b/packages/core/src/test/shared/telemetry/spans.test.ts @@ -173,7 +173,7 @@ describe('TelemetryTracer', function () { it('does not change the active span when using a different span', function () { tracer.run(metricName, (span) => { - tracer.vscode_executeCommand.record({ command: 'foo', debounceCount: 1 }) + tracer.vscode_executeCommand.emit({ command: 'foo', debounceCount: 1 }) assert.strictEqual(tracer.activeSpan, span) }) diff --git a/packages/core/src/threatComposer/threatComposerEditor.ts b/packages/core/src/threatComposer/threatComposerEditor.ts index 6193a70db4e..8312113e16b 100644 --- a/packages/core/src/threatComposer/threatComposerEditor.ts +++ b/packages/core/src/threatComposer/threatComposerEditor.ts @@ -57,10 +57,6 @@ export class ThreatComposerEditor { this.defaultTemplateName = path.basename(this.defaultTemplatePath) this.fileId = fileId - telemetry.threatComposer_opened.record({ - id: this.fileId, - }) - this.setupWebviewPanel(textDocument, context) } diff --git a/packages/core/src/threatComposer/threatComposerEditorProvider.ts b/packages/core/src/threatComposer/threatComposerEditorProvider.ts index e8830aa746b..269df1ecd5a 100644 --- a/packages/core/src/threatComposer/threatComposerEditorProvider.ts +++ b/packages/core/src/threatComposer/threatComposerEditorProvider.ts @@ -148,6 +148,7 @@ export class ThreatComposerEditorProvider implements vscode.CustomTextEditorProv this.getWebviewContent ) this.handleNewVisualization(document.uri.fsPath, newVisualization) + span.record({ id: fileId }) } catch (err) { this.handleErr(err as Error) throw new ToolkitError((err as Error).message, { code: 'Failed to Open Threat Composer' })