Skip to content

Commit 56a706d

Browse files
authored
telemetry(amazonqFeatureDev): generateApproach api error metrics, minor fixes #4149
Problem featureDev errors return a generic failure. Understanding the specific error (403 or 409) with telemetry is hard. Solution Add status code to api errors to track in telemetry.
1 parent 7fd3082 commit 56a706d

File tree

4 files changed

+89
-25
lines changed

4 files changed

+89
-25
lines changed

src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import { ConfiguredRetryStrategy } from '@aws-sdk/util-retry'
99
import { omit } from 'lodash'
1010
import { AuthUtil } from '../../codewhisperer/util/authUtil'
1111
import { ServiceOptions } from '../../shared/awsClientBuilder'
12-
import { ToolkitError } from '../../shared/errors'
1312
import globals from '../../shared/extensionGlobals'
1413
import { getLogger } from '../../shared/logger'
1514
import * as FeatureDevProxyClient from './featuredevproxyclient'
1615
import apiConfig = require('./codewhispererruntime-2022-11-11.json')
1716
import { featureName } from '../constants'
18-
import { ContentLengthError } from '../errors'
17+
import { ApiError, ContentLengthError, UnknownApiError } from '../errors'
1918
import { endpoint, region } from '../../codewhisperer/models/constants'
19+
import { isAwsError, isCodeWhispererStreamingServiceException } from '../../shared/errors'
2020

2121
// Create a client for featureDev proxy client based off of aws sdk v2
2222
export async function createFeatureDevProxyClient(): Promise<FeatureDevProxyClient> {
@@ -53,6 +53,15 @@ async function createFeatureDevStreamingClient(): Promise<CodeWhispererStreaming
5353
return streamingClient
5454
}
5555

56+
const streamResponseErrors: Record<string, number> = {
57+
ValidationException: 400,
58+
AccessDeniedException: 403,
59+
ResourceNotFoundException: 404,
60+
ConflictException: 409,
61+
ThrottlingException: 429,
62+
InternalServerException: 500,
63+
}
64+
5665
export class FeatureDevClient {
5766
private async getClient() {
5867
// Should not be stored for the whole session.
@@ -77,12 +86,14 @@ export class FeatureDevClient {
7786
})
7887
return conversationId
7988
} catch (e) {
80-
getLogger().error(
81-
`${featureName}: failed to start conversation: ${(e as Error).message} RequestId: ${
82-
(e as any).requestId
83-
}`
84-
)
85-
throw new ToolkitError((e as Error).message, { code: 'CreateConversationFailed' })
89+
if (isAwsError(e)) {
90+
getLogger().error(
91+
`${featureName}: failed to start conversation: ${e.message} RequestId: ${e.requestId}`
92+
)
93+
throw new ApiError(e.message, 'CreateConversation', e.code, e.statusCode ?? 400)
94+
}
95+
96+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateConversation')
8697
}
8798
}
8899

@@ -108,16 +119,18 @@ export class FeatureDevClient {
108119
requestId: response.$response.requestId,
109120
})
110121
return response
111-
} catch (e: any) {
112-
getLogger().error(
113-
`${featureName}: failed to generate presigned url: ${(e as Error).message} RequestId: ${
114-
(e as any).requestId
115-
}`
116-
)
117-
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
118-
throw new ContentLengthError()
122+
} catch (e) {
123+
if (isAwsError(e)) {
124+
getLogger().error(
125+
`${featureName}: failed to generate presigned url: ${e.message} RequestId: ${e.requestId}`
126+
)
127+
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
128+
throw new ContentLengthError()
129+
}
130+
throw new ApiError(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 400)
119131
}
120-
throw new ToolkitError((e as Error).message, { code: 'CreateUploadUrlFailed' })
132+
133+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateUploadUrl')
121134
}
122135
}
123136

@@ -153,10 +166,21 @@ export class FeatureDevClient {
153166
}
154167
return assistantResponse.join(' ')
155168
} catch (e) {
156-
getLogger().error(
157-
`${featureName}: failed to execute planning: ${(e as Error).message} RequestId: ${(e as any).requestId}`
158-
)
159-
throw new ToolkitError((e as Error).message, { code: 'GeneratePlanFailed' })
169+
if (isCodeWhispererStreamingServiceException(e)) {
170+
getLogger().error(
171+
`${featureName}: failed to execute planning: ${e.message} RequestId: ${
172+
e.$metadata.requestId ?? 'unknown'
173+
}`
174+
)
175+
throw new ApiError(
176+
e.message,
177+
'GeneratePlan',
178+
e.name,
179+
e.$metadata?.httpStatusCode ?? streamResponseErrors[e.name] ?? 500
180+
)
181+
}
182+
183+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'GeneratePlan')
160184
}
161185
}
162186
}

src/amazonqFeatureDev/errors.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ export class PrepareRepoFailedError extends ToolkitError {
5555
}
5656
}
5757

58+
export class IllegalStateTransition extends ToolkitError {
59+
constructor() {
60+
super('Illegal transition between states, restart the conversation', { code: 'IllegalStateTransition' })
61+
}
62+
}
63+
5864
export class ContentLengthError extends ToolkitError {
5965
constructor() {
6066
super(
@@ -64,6 +70,18 @@ export class ContentLengthError extends ToolkitError {
6470
}
6571
}
6672

73+
export class UnknownApiError extends ToolkitError {
74+
constructor(message: string, api: string) {
75+
super(message, { code: `${api}-Unknown` })
76+
}
77+
}
78+
79+
export class ApiError extends ToolkitError {
80+
constructor(message: string, api: string, errorName: string, errorCode: number) {
81+
super(message, { code: `${api}-${errorName}-${errorCode}` })
82+
}
83+
}
84+
6785
const denyListedErrors: string[] = ['Deserialization error', 'Inaccessible host']
6886

6987
export function createUserFacingErrorMessage(message: string) {

src/amazonqFeatureDev/session/sessionState.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as vscode from 'vscode'
88
import { ToolkitError } from '../../shared/errors'
99
import { getLogger } from '../../shared/logger'
1010
import { telemetry } from '../../shared/telemetry/telemetry'
11-
import { UserMessageNotFoundError } from '../errors'
11+
import { IllegalStateTransition, UserMessageNotFoundError } from '../errors'
1212
import { SessionState, SessionStateAction, SessionStateConfig, SessionStateInteraction } from '../types'
1313
import { prepareRepoData } from '../util/files'
1414
import { uploadCode } from '../util/upload'
@@ -23,9 +23,7 @@ export class ConversationNotStartedState implements Omit<SessionState, 'uploadId
2323
}
2424

2525
async interact(_action: SessionStateAction): Promise<SessionStateInteraction> {
26-
throw new ToolkitError('Illegal transition between states, restart the conversation', {
27-
code: 'IllegalStateTransition',
28-
})
26+
throw new IllegalStateTransition()
2927
}
3028
}
3129

src/shared/errors.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CancellationError } from './utilities/timeoutUtils'
1212
import { isNonNullable } from './utilities/tsUtils'
1313
import type * as fs from 'fs'
1414
import type * as os from 'os'
15+
import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming'
1516

1617
export const errorCode = {
1718
invalidConnection: 'InvalidConnection',
@@ -370,6 +371,29 @@ export function findAwsErrorInCausalChain(error: unknown): AWSError | undefined
370371
return undefined
371372
}
372373

374+
export function isCodeWhispererStreamingServiceException(
375+
error: unknown
376+
): error is CodeWhispererStreamingServiceException {
377+
if (error === undefined) {
378+
return false
379+
}
380+
381+
return error instanceof Error && hasFault(error) && hasMetadata(error) && hasName(error)
382+
}
383+
384+
function hasFault<T>(error: T): error is T & { $fault: 'client' | 'server' } {
385+
const fault = (error as { $fault?: unknown }).$fault
386+
return typeof fault === 'string' && (fault === 'client' || fault === 'server')
387+
}
388+
389+
function hasMetadata<T>(error: T): error is T & Pick<CodeWhispererStreamingServiceException, '$metadata'> {
390+
return typeof (error as { $metadata?: unknown }).$metadata === 'object'
391+
}
392+
393+
function hasName<T>(error: T): error is T & { name: string } {
394+
return typeof (error as { name?: unknown }).name === 'string'
395+
}
396+
373397
export function isAwsError(error: unknown): error is AWSError {
374398
if (error === undefined) {
375399
return false

0 commit comments

Comments
 (0)