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

import { inspect } from 'util'; import type { Logger } from '@smithy/types'; import type { IoHelper } from './io-helper'; import { IO } from './messages'; import { replacerBufferWithInfo } from '../../../util'; export function asSdkLogger(ioHost: IoHelper): Logger { return new class implements Logger { // This is too much detail for our logs public trace(..._content: any[]) { } public debug(..._content: any[]) { } /** * 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[]) { void ioHost.notify(IO.CDK_SDK_I0100.msg(`[sdk info] ${formatSdkLoggerContent(content)}`, { sdkLevel: 'info', content, })); } public warn(...content: any[]) { void ioHost.notify(IO.CDK_SDK_I0100.msg(`[sdk warn] ${formatSdkLoggerContent(content)}`, { sdkLevel: '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[]) { void ioHost.notify(IO.CDK_SDK_I0100.msg(`[sdk error] ${formatSdkLoggerContent(content)}`, { sdkLevel: '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. */ 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; }