diff --git a/src/schema/GetSchemaTask.ts b/src/schema/GetSchemaTask.ts index dcb8d580..26c09ab0 100644 --- a/src/schema/GetSchemaTask.ts +++ b/src/schema/GetSchemaTask.ts @@ -5,6 +5,7 @@ import { CfnService } from '../services/CfnService'; import { LoggerFactory } from '../telemetry/LoggerFactory'; import { ScopedTelemetry } from '../telemetry/ScopedTelemetry'; import { Measure, Telemetry } from '../telemetry/TelemetryDecorator'; +import { classifyAwsError } from '../utils/AwsErrorMapper'; import { AwsRegion } from '../utils/Region'; import { downloadFile } from '../utils/RemoteDownload'; import { PrivateSchemas, PrivateSchemasType, PrivateStoreKey } from './PrivateSchemas'; @@ -73,6 +74,9 @@ export class GetPublicSchemaTask extends GetSchemaTask { export class GetPrivateSchemasTask extends GetSchemaTask { private readonly logger = LoggerFactory.getLogger(GetPrivateSchemasTask); + @Telemetry() + private readonly telemetry!: ScopedTelemetry; + constructor(private readonly getSchemas: () => Promise) { super(); } @@ -94,6 +98,10 @@ export class GetPrivateSchemasTask extends GetSchemaTask { this.logger.info(`${schemas.length} private schemas retrieved`); } catch (error) { + const { category, httpStatus } = classifyAwsError(error); + this.telemetry.count('getSchemas.error', 1, { + attributes: { category, httpStatus }, + }); this.logger.error(error, 'Failed to get private schemas'); throw error; } diff --git a/src/utils/AwsErrorMapper.ts b/src/utils/AwsErrorMapper.ts index 3f6de06f..2ba9f6d6 100644 --- a/src/utils/AwsErrorMapper.ts +++ b/src/utils/AwsErrorMapper.ts @@ -60,6 +60,34 @@ function isRetryableAwsError(error: AwsError): boolean { return statusCode !== undefined && RETRYABLE_STATUS_CODES.has(statusCode); } +export type AwsErrorCategory = 'credentials' | 'network' | 'permissions' | 'throttling' | 'service' | 'unknown'; + +export function classifyAwsError(error: unknown): { category: AwsErrorCategory; httpStatus?: number } { + if (!isAwsError(error)) { + return { category: 'unknown' }; + } + + const httpStatus = error.$metadata?.httpStatusCode; + + if (isCredentialError(error)) { + return { category: 'credentials', httpStatus }; + } + if (isNetworkError(error)) { + return { category: 'network', httpStatus }; + } + if (error.name === 'AccessDeniedException' || httpStatus === 403) { + return { category: 'permissions', httpStatus }; + } + if (error.name === 'ThrottlingException' || httpStatus === 429) { + return { category: 'throttling', httpStatus }; + } + if (httpStatus !== undefined) { + return { category: 'service', httpStatus }; + } + + return { category: 'unknown' }; +} + export function mapAwsErrorToLspError(error: unknown): ResponseError { if (error instanceof ResponseError) { return error;