diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index cc4a61b680f..9b30ce93565 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -53,6 +53,7 @@ import { globals, waitUntil } from '../../../shared' import { telemetry } from '../../../shared/telemetry' import { Auth } from '../../../auth' import { isSsoConnection } from '../../../auth/connection' +import { inspect } from '../../../shared/utilities/collectionUtils' export interface ChatControllerMessagePublishers { readonly processPromptChatMessage: MessagePublisher @@ -656,7 +657,11 @@ export class ChatController { const request = triggerPayloadToChatRequest(triggerPayload) const session = this.sessionStorage.getSession(tabID) - getLogger().info(`request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: %O`, request) + getLogger().info( + `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: ${inspect(request, { + depth: 12, + })}` + ) let response: MessengerResponseType | undefined = undefined session.createNewTokenSource() try { @@ -681,8 +686,7 @@ export class ChatController { getLogger().info( `response to tab: ${tabID} conversationID: ${session.sessionIdentifier} requestID: ${ response.$metadata.requestId - } metadata: %O`, - response.$metadata + } metadata: ${inspect(response.$metadata, { depth: 12 })}` ) await this.messenger.sendAIResponse(response, session, tabID, triggerID, triggerPayload) } catch (e: any) { diff --git a/packages/core/src/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 1a0b16c8499..3776d7fac7b 100644 --- a/packages/core/src/shared/utilities/collectionUtils.ts +++ b/packages/core/src/shared/utilities/collectionUtils.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { isWeb } from '../extensionGlobals' +import { inspect as nodeInspect } from 'util' import { AsyncCollection, toCollection } from './asyncCollection' import { SharedProp, AccumulableKeys, Coalesce, isNonNullable } from './tsUtils' @@ -297,7 +299,6 @@ export function assign, U extends Partial>(data: T * - depth=2 returns `obj` with its children and their children. * - and so on... * - * TODO: node's `util.inspect()` function is better, but doesn't work in web browser? * * @param obj Object to clone. * @param depth @@ -329,6 +330,18 @@ export function partialClone(obj: any, depth: number = 3, omitKeys: string[] = [ return clonedObj } +/** + * Wrapper around nodes inspect function that works on web. Defaults to JSON.stringify on web. + * @param obj object to show + * @param opt options for showing (ex. depth, omitting keys) + */ +export function inspect(obj: any, opt?: { depth: number }): string { + const options = { + depth: opt?.depth ?? 3, + } + return isWeb() ? JSON.stringify(partialClone(obj, options.depth), undefined, 2) : nodeInspect(obj, options) +} + /** Recursively delete undefined key/value pairs */ export function stripUndefined>( obj: T diff --git a/packages/core/src/test/shared/utilities/collectionUtils.test.ts b/packages/core/src/test/shared/utilities/collectionUtils.test.ts index d43f64e173d..1ab5a449034 100644 --- a/packages/core/src/test/shared/utilities/collectionUtils.test.ts +++ b/packages/core/src/test/shared/utilities/collectionUtils.test.ts @@ -31,6 +31,7 @@ import { joinAll, isPresent, partialClone, + inspect, } from '../../../shared/utilities/collectionUtils' import { asyncGenerator } from '../../../shared/utilities/collectionUtils' @@ -511,7 +512,7 @@ describe('CollectionUtils', async function () { const requester = async (request: { next?: string }) => pages[request.next ?? 'page1'] it('creates a new AsyncCollection', async function () { - const collection = pageableToCollection(requester, {}, 'next', 'data') + const collection = pageableToCollection(requester, {}, 'next' as never, 'data') assert.deepStrictEqual(await collection.promise(), [[0, 1, 2], [3, 4], [5], []]) }) @@ -540,7 +541,7 @@ describe('CollectionUtils', async function () { describe('last', function () { it('it persists last element when mapped', async function () { - const collection = pageableToCollection(requester, {}, 'next', 'data') + const collection = pageableToCollection(requester, {}, 'next' as never, 'data') const mapped = collection.map((i) => i[0] ?? -1) assert.strictEqual(await last(mapped), -1) }) @@ -679,6 +680,36 @@ describe('CollectionUtils', async function () { }) }) + describe('inspect', function () { + let testData: any + before(function () { + testData = { + root: { + A: { + B: { + C: { + D: { + E: 'data', + }, + }, + }, + }, + }, + } + }) + + it('defaults to a depth of 3', function () { + assert.strictEqual(inspect(testData), '{\n root: { A: { B: { C: [Object] } } }\n}') + }) + + it('allows depth to be set manually', function () { + assert.strictEqual( + inspect(testData, { depth: 6 }), + "{\n root: {\n A: {\n B: { C: { D: { E: 'data' } } }\n }\n }\n}" + ) + }) + }) + describe('partialClone', function () { it('omits properties by depth', function () { const testObj = { diff --git a/plugins/eslint-plugin-aws-toolkits/lib/rules/no-json-stringify-in-log.ts b/plugins/eslint-plugin-aws-toolkits/lib/rules/no-json-stringify-in-log.ts index 92a9cd75a35..87f183b4b3d 100644 --- a/plugins/eslint-plugin-aws-toolkits/lib/rules/no-json-stringify-in-log.ts +++ b/plugins/eslint-plugin-aws-toolkits/lib/rules/no-json-stringify-in-log.ts @@ -7,7 +7,7 @@ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils' import { Rule } from 'eslint' export const errMsg = - 'Avoid using JSON.stringify within logging and error messages, prefer %O. Note: %O has a depth limit of 2' + 'Avoid using JSON.stringify within logging and error messages, prefer %O in general or inspect from collectionUtils for custom depth formatting' /** * Check if a given expression is a JSON.stringify call.