-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(cli): log SDK calls when using -vvv
#32308
Changes from 5 commits
da2c65a
751c525
14de19c
f3bdd3f
7fc7c9a
d2c3b45
146c489
d94db0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { inspect } from 'util'; | ||
import { Logger } from '@smithy/types'; | ||
import { trace } from '../../logging'; | ||
|
||
export class SdkToCliLogger implements Logger { | ||
public trace(...content: any[]) { | ||
// This is too much detail for our logs | ||
// trace('[SDK trace] %s', this.fmtContent(content)); | ||
Array.isArray(content); | ||
} | ||
|
||
public debug(...content: any[]) { | ||
// This is too much detail for our logs | ||
// trace('[SDK debug] %s', this.fmtContent(content)); | ||
Array.isArray(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[]) { | ||
trace('[sdk info] %s', this.fmtContent(content)); | ||
} | ||
|
||
public warn(...content: any[]) { | ||
trace('[sdk warn] %s', this.fmtContent(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[]) { | ||
trace('[sdk error] %s', this.fmtContent(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. | ||
*/ | ||
private fmtContent(content: any[]) { | ||
if (content.length === 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If so we won't do the wrong thing, we just won't be doing a nice thing. We'll see that in the logs at some point and fix it then. |
||
const apiFmt = formatApiCall(content[0]); | ||
if (apiFmt) { | ||
return apiFmt; | ||
} | ||
} | ||
return content.map((x) => typeof x === 'string' ? x : inspect(x)).join(''); | ||
} | ||
} | ||
|
||
export 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 > 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so but who knows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll code more defensively |
||
parts.push(`[${content.metadata.attempts} attempts, ${content.metadata.totalRetryDelay}ms retry]`); | ||
} | ||
|
||
parts.push(`${service}.${api}(${JSON.stringify(content.input)})`); | ||
|
||
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this line is doing.