packages/@aws-cdk/toolkit-lib/lib/api/aws-auth/sdk-logger.ts (78 lines of code) (raw):

import { inspect, format } from 'util'; import type { Logger } from '@smithy/types'; import { replacerBufferWithInfo } from '../../util'; import type { IoHelper } from '../io/private'; import { IO } from '../io/private'; export class SdkToCliLogger implements Logger { private readonly ioHelper: IoHelper; public constructor(ioHelper: IoHelper) { this.ioHelper = ioHelper; } private notify(level: 'info' | 'warn' | 'error', ...content: any[]) { void this.ioHelper.notify(IO.CDK_SDK_I0100.msg(format('[SDK %s] %s', level, formatSdkLoggerContent(content)), { sdkLevel: level, content, })); } public trace(..._content: any[]) { // This is too much detail for our logs // this.notify('trace', ...content); } public debug(..._content: any[]) { // This is too much detail for our logs // this.notify('debug', ...content); } /** * Info is called mostly (exclusively?) for successful API calls * * Payload: * * (Note the input contains entire CFN templates, for example) * * ``` * { * clientName: 'S3Client', * commandName: 'GetBucketLocationCommand', * input: { * Bucket: '.....', * ExpectedBucketOwner: undefined * }, * output: { LocationConstraint: 'eu-central-1' }, * metadata: { * httpStatusCode: 200, * requestId: '....', * extendedRequestId: '...', * cfId: undefined, * attempts: 1, * totalRetryDelay: 0 * } * } * ``` */ public info(...content: any[]) { this.notify('info', ...content); } public warn(...content: any[]) { this.notify('warn', ...content); } /** * Error is called mostly (exclusively?) for failing API calls * * Payload (input would be the entire API call arguments). * * ``` * { * clientName: 'STSClient', * commandName: 'GetCallerIdentityCommand', * input: {}, * error: AggregateError [ECONNREFUSED]: * at internalConnectMultiple (node:net:1121:18) * at afterConnectMultiple (node:net:1688:7) { * code: 'ECONNREFUSED', * '$metadata': { attempts: 3, totalRetryDelay: 600 }, * [errors]: [ [Error], [Error] ] * }, * metadata: { attempts: 3, totalRetryDelay: 600 } * } * ``` */ public error(...content: any[]) { this.notify('error', ...content); } } /** * This can be anything. * * For debug, it seems to be mostly strings. * For info, it seems to be objects. * * Stringify and join without separator. */ export function formatSdkLoggerContent(content: any[]) { if (content.length === 1) { const apiFmt = formatApiCall(content[0]); if (apiFmt) { return apiFmt; } } return content.map((x) => typeof x === 'string' ? x : inspect(x)).join(''); } function formatApiCall(content: any): string | undefined { if (!isSdkApiCallSuccess(content) && !isSdkApiCallError(content)) { return undefined; } const service = content.clientName.replace(/Client$/, ''); const api = content.commandName.replace(/Command$/, ''); const parts = []; if ((content.metadata?.attempts ?? 0) > 1) { parts.push(`[${content.metadata?.attempts} attempts, ${content.metadata?.totalRetryDelay}ms retry]`); } parts.push(`${service}.${api}(${JSON.stringify(content.input, replacerBufferWithInfo)})`); if (isSdkApiCallSuccess(content)) { parts.push('-> OK'); } else { parts.push(`-> ${content.error}`); } return parts.join(' '); } interface SdkApiCallBase { clientName: string; commandName: string; input: Record<string, unknown>; metadata?: { httpStatusCode?: number; requestId?: string; extendedRequestId?: string; cfId?: string; attempts?: number; totalRetryDelay?: number; }; } type SdkApiCallSuccess = SdkApiCallBase & { output: Record<string, unknown> }; type SdkApiCallError = SdkApiCallBase & { error: Error }; function isSdkApiCallSuccess(x: any): x is SdkApiCallSuccess { return x && typeof x === 'object' && x.commandName && x.output; } function isSdkApiCallError(x: any): x is SdkApiCallError { return x && typeof x === 'object' && x.commandName && x.error; }