Skip to content

Commit d82ca88

Browse files
authored
refactor(cli): localize SDK tracing (#33273)
### Reason for this change The CLI has a global method call tracing feature that is currently only used for SDK calls. Eventually this feature needs to support logging through the IoHost. While this PR doesn't achieve this, it co-locates the tracing feature with SDK logs and explicitly calls out its limitations. We will need a better, different approach once we are looking at the SDK Provider in more detail. ### Description of changes Move the feature to `aws-auth` module. Limit exports and renames for clarity. Limit tracing to member methods as these can add a class logger. Add a manual trace to the one static method this was currently tracig ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Existing unit & integ tests, manual verification ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 6a77e4f commit d82ca88

File tree

8 files changed

+109
-78
lines changed

8 files changed

+109
-78
lines changed

packages/aws-cdk/lib/api/aws-auth/sdk-logger.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
1-
import { inspect } from 'util';
1+
import { inspect, format } from 'util';
22
import { Logger } from '@smithy/types';
3-
import { trace } from '../../logging';
43
import { replacerBufferWithInfo } from '../../serialize';
4+
import type { IIoHost } from '../../toolkit/cli-io-host';
55

66
export class SdkToCliLogger implements Logger {
7+
private readonly ioHost: IIoHost;
8+
9+
public constructor(ioHost: IIoHost) {
10+
this.ioHost = ioHost;
11+
}
12+
13+
private notify(level: 'debug' | 'info' | 'warn' | 'error', ...content: any[]) {
14+
void this.ioHost.notify({
15+
time: new Date(),
16+
level,
17+
action: 'none' as any,
18+
code: 'CDK_SDK_I0000',
19+
message: format('[SDK %s] %s', level, formatSdkLoggerContent(content)),
20+
});
21+
}
22+
723
public trace(..._content: any[]) {
824
// This is too much detail for our logs
9-
// trace('[SDK trace] %s', fmtContent(content));
25+
// this.notify('trace', ...content);
1026
}
1127

1228
public debug(..._content: any[]) {
1329
// This is too much detail for our logs
14-
// trace('[SDK debug] %s', fmtContent(content));
30+
// this.notify('debug', ...content);
1531
}
1632

1733
/**
@@ -42,11 +58,11 @@ export class SdkToCliLogger implements Logger {
4258
* ```
4359
*/
4460
public info(...content: any[]) {
45-
trace('[sdk info] %s', formatSdkLoggerContent(content));
61+
this.notify('info', ...content);
4662
}
4763

4864
public warn(...content: any[]) {
49-
trace('[sdk warn] %s', formatSdkLoggerContent(content));
65+
this.notify('warn', ...content);
5066
}
5167

5268
/**
@@ -71,7 +87,7 @@ export class SdkToCliLogger implements Logger {
7187
* ```
7288
*/
7389
public error(...content: any[]) {
74-
trace('[sdk error] %s', formatSdkLoggerContent(content));
90+
this.notify('info', ...content);
7591
}
7692
}
7793

packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { cached } from './cached';
1010
import { CredentialPlugins } from './credential-plugins';
1111
import { makeCachingProvider } from './provider-caching';
1212
import { SDK } from './sdk';
13+
import { callTrace, traceMemberMethods } from './tracing';
1314
import { debug, warning } from '../../logging';
1415
import { AuthenticationError } from '../../toolkit/error';
1516
import { formatErrorMessage } from '../../util/error';
16-
import { traceMethods } from '../../util/tracing';
1717
import { Mode } from '../plugin/mode';
1818

1919
export type AssumeRoleAdditionalOptions = Partial<Omit<AssumeRoleCommandInput, 'ExternalId' | 'RoleArn'>>;
@@ -111,7 +111,7 @@ export interface SdkForEnvironment {
111111
* - Seeded terminal with `ReadOnly` credentials in order to do `cdk diff`--the `ReadOnly`
112112
* role doesn't have `sts:AssumeRole` and will fail for no real good reason.
113113
*/
114-
@traceMethods
114+
@traceMemberMethods
115115
export class SdkProvider {
116116
/**
117117
* Create a new SdkProvider which gets its defaults in a way that behaves like the AWS CLI does
@@ -120,6 +120,7 @@ export class SdkProvider {
120120
* class `AwsCliCompatible` for the details.
121121
*/
122122
public static async withAwsCliCompatibleDefaults(options: SdkProviderOptions = {}) {
123+
callTrace(SdkProvider.withAwsCliCompatibleDefaults.name, SdkProvider.constructor.name, options.logger);
123124
const credentialProvider = await AwsCliCompatible.credentialChainBuilder({
124125
profile: options.profile,
125126
httpOptions: options.httpOptions,

packages/aws-cdk/lib/api/aws-auth/sdk.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,11 +317,11 @@ import { WaiterResult } from '@smithy/util-waiter';
317317
import { AccountAccessKeyCache } from './account-cache';
318318
import { cachedAsync } from './cached';
319319
import { Account } from './sdk-provider';
320+
import { traceMemberMethods } from './tracing';
320321
import { defaultCliUserAgent } from './user-agent';
321322
import { debug } from '../../logging';
322323
import { AuthenticationError } from '../../toolkit/error';
323324
import { formatErrorMessage } from '../../util/error';
324-
import { traceMethods } from '../../util/tracing';
325325

326326
export interface S3ClientOptions {
327327
/**
@@ -521,14 +521,16 @@ export interface IStepFunctionsClient {
521521
/**
522522
* Base functionality of SDK without credential fetching
523523
*/
524-
@traceMethods
524+
@traceMemberMethods
525525
export class SDK {
526526
private static readonly accountCache = new AccountAccessKeyCache();
527527

528528
public readonly currentRegion: string;
529529

530530
public readonly config: ConfigurationOptions;
531531

532+
protected readonly logger?: Logger;
533+
532534
/**
533535
* STS is used to check credential validity, don't do too many retries.
534536
*/
@@ -557,6 +559,7 @@ export class SDK {
557559
customUserAgent: defaultCliUserAgent(),
558560
logger,
559561
};
562+
this.logger = logger;
560563
this.currentRegion = region;
561564
}
562565

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { Logger } from '@smithy/types';
2+
3+
let ENABLED = false;
4+
let INDENT = 0;
5+
6+
export function setSdkTracing(enabled: boolean) {
7+
ENABLED = enabled;
8+
}
9+
10+
/**
11+
* Method decorator to trace a single static or member method, any time it's called
12+
*/
13+
export function callTrace(fn: string, className?: string, logger?: Logger) {
14+
if (!ENABLED || !logger) {
15+
return;
16+
}
17+
18+
logger.info(`[trace] ${' '.repeat(INDENT)}${className || '(anonymous)'}#${fn}()`);
19+
}
20+
21+
/**
22+
* Method decorator to trace a single member method any time it's called
23+
*/
24+
function traceCall(receiver: object, _propertyKey: string, descriptor: PropertyDescriptor, parentClassName?: string) {
25+
const fn = descriptor.value;
26+
const className = typeof receiver === 'function' ? receiver.name : parentClassName;
27+
28+
descriptor.value = function (...args: any[]) {
29+
const logger = (this as any).logger;
30+
if (!ENABLED || typeof logger?.info !== 'function') { return fn.apply(this, args); }
31+
32+
logger.info.apply(logger, [`[trace] ${' '.repeat(INDENT)}${className || this.constructor.name || '(anonymous)'}#${fn.name}()`]);
33+
INDENT += 2;
34+
35+
const ret = fn.apply(this, args);
36+
if (ret instanceof Promise) {
37+
return ret.finally(() => {
38+
INDENT -= 2;
39+
});
40+
} else {
41+
INDENT -= 2;
42+
return ret;
43+
}
44+
};
45+
return descriptor;
46+
}
47+
48+
/**
49+
* Class decorator, enable tracing for all member methods on this class
50+
* @deprecated this doesn't work well with localized logging instances, don't use
51+
*/
52+
export function traceMemberMethods(constructor: Function) {
53+
// Instance members
54+
for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(constructor.prototype))) {
55+
if (typeof descriptor.value !== 'function') { continue; }
56+
const newDescriptor = traceCall(constructor.prototype, name, descriptor, constructor.name) ?? descriptor;
57+
Object.defineProperty(constructor.prototype, name, newDescriptor);
58+
}
59+
}

packages/aws-cdk/lib/cli/cli.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { checkForPlatformWarnings } from './platform-warnings';
88
import * as version from './version';
99
import { SdkProvider } from '../api/aws-auth';
1010
import { SdkToCliLogger } from '../api/aws-auth/sdk-logger';
11+
import { setSdkTracing } from '../api/aws-auth/tracing';
1112
import { BootstrapSource, Bootstrapper } from '../api/bootstrap';
1213
import { StackSelector } from '../api/cxapp/cloud-assembly';
1314
import { CloudExecutable, Synthesizer } from '../api/cxapp/cloud-executable';
@@ -28,7 +29,6 @@ import { Notices } from '../notices';
2829
import { Command, Configuration } from './user-configuration';
2930
import { IoMessageLevel, CliIoHost } from '../toolkit/cli-io-host';
3031
import { ToolkitError } from '../toolkit/error';
31-
import { enableTracing } from '../util/tracing';
3232

3333
/* eslint-disable max-len */
3434
/* eslint-disable @typescript-eslint/no-shadow */ // yargs
@@ -66,7 +66,10 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
6666

6767
// Debug should always imply tracing
6868
if (argv.debug || argv.verbose > 2) {
69-
enableTracing(true);
69+
setSdkTracing(true);
70+
} else {
71+
// cli-lib-alpha needs to explicitly set in case it was enabled before
72+
setSdkTracing(false);
7073
}
7174

7275
try {
@@ -104,7 +107,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
104107
proxyAddress: argv.proxy,
105108
caBundlePath: argv['ca-bundle-path'],
106109
},
107-
logger: new SdkToCliLogger(),
110+
logger: new SdkToCliLogger(ioHost),
108111
});
109112

110113
let outDirLock: ILock | undefined;

packages/aws-cdk/lib/legacy-exports-source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export { execProgram } from './api/cxapp/exec';
1919
export { RequireApproval } from './diff';
2020
export { leftPad } from './api/util/string-manipulation';
2121
export { formatAsBanner } from './cli/util/console-formatters';
22-
export { enableTracing } from './util/tracing';
22+
export { setSdkTracing as enableTracing } from './api/aws-auth/tracing';
2323
export { aliases, command, describe } from './commands/docs';
2424
export { lowerCaseFirstCharacter } from './api/hotswap/common';
2525
export { deepMerge } from './util/objects';

packages/aws-cdk/lib/util/tracing.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

packages/aws-cdk/test/api/aws-auth/sdk-logger.test.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import { formatSdkLoggerContent, SdkToCliLogger } from '../../../lib/api/aws-auth/sdk-logger';
2-
import * as logging from '../../../lib/logging';
32

43
describe(SdkToCliLogger, () => {
5-
const logger = new SdkToCliLogger();
6-
const trace = jest.spyOn(logging, 'trace');
4+
const ioHost = {
5+
notify: jest.fn(),
6+
requestResponse: jest.fn(),
7+
};
8+
const logger = new SdkToCliLogger(ioHost);
79

810
beforeEach(() => {
9-
trace.mockReset();
11+
ioHost.notify.mockReset();
1012
});
1113

12-
test.each(['trace', 'debug'] as Array<keyof SdkToCliLogger>)('%s method does not call trace', (meth) => {
13-
logger[meth]('test');
14-
expect(trace).not.toHaveBeenCalled();
14+
test.each(['trace', 'debug'] as Array<keyof SdkToCliLogger>)('%s method does not call notify', (method) => {
15+
logger[method]('test');
16+
expect(ioHost.notify).not.toHaveBeenCalled();
1517
});
1618

17-
test.each(['info', 'warn', 'error'] as Array<keyof SdkToCliLogger>)('%s method logs to trace', (meth) => {
18-
logger[meth]('test');
19-
expect(trace).toHaveBeenCalled();
19+
test.each(['info', 'warn', 'error'] as Array<keyof SdkToCliLogger>)('%s method logs to notify', (method) => {
20+
logger[method]('test');
21+
expect(ioHost.notify).toHaveBeenCalled();
2022
});
2123
});
2224

0 commit comments

Comments
 (0)