diff --git a/packages/accessmanagement/src/v1/client.ts b/packages/accessmanagement/src/v1/client.ts index 8a3b803e..8eebc989 100755 --- a/packages/accessmanagement/src/v1/client.ts +++ b/packages/accessmanagement/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -76,6 +77,9 @@ export class AccessManagementClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -89,6 +93,9 @@ export class AccessManagementClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -112,6 +119,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -140,6 +149,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -168,6 +179,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -200,6 +213,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -240,6 +255,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRuleSetSchema); }; @@ -277,6 +294,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRuleSetSchema); }; @@ -311,6 +330,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -348,6 +369,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -380,6 +403,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRuleSetSchema); }; @@ -409,6 +434,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRuleSetSchema); }; @@ -437,6 +464,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPermissionsResponseSchema); }; @@ -465,6 +494,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -497,6 +528,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPermissionsResponseSchema); }; @@ -529,6 +562,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPermissionsResponseSchema); }; @@ -586,6 +621,8 @@ export class AccessManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCheckPolicyResponseSchema); }; diff --git a/packages/accessmanagement/src/v1/utils.ts b/packages/accessmanagement/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/accessmanagement/src/v1/utils.ts +++ b/packages/accessmanagement/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/alerts/src/v1/client.ts b/packages/alerts/src/v1/client.ts index f17a66d9..36b9439d 100644 --- a/packages/alerts/src/v1/client.ts +++ b/packages/alerts/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class AlertsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class AlertsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -88,6 +95,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; @@ -113,6 +122,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; @@ -150,6 +161,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAlertsResponseSchema); }; @@ -195,6 +208,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEmptySchema); }; @@ -224,6 +239,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; diff --git a/packages/alerts/src/v1/utils.ts b/packages/alerts/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/alerts/src/v1/utils.ts +++ b/packages/alerts/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/alerts/src/v2/client.ts b/packages/alerts/src/v2/client.ts index 91ea40f6..9230913e 100644 --- a/packages/alerts/src/v2/client.ts +++ b/packages/alerts/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -47,6 +48,9 @@ export class AlertsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -59,6 +63,9 @@ export class AlertsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -86,6 +93,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; @@ -111,6 +120,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; @@ -148,6 +159,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAlertsResponseSchema); }; @@ -199,6 +212,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEmptySchema); }; @@ -240,6 +255,8 @@ export class AlertsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAlertSchema); }; diff --git a/packages/alerts/src/v2/utils.ts b/packages/alerts/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/alerts/src/v2/utils.ts +++ b/packages/alerts/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/apps/src/v1/client.ts b/packages/apps/src/v1/client.ts index a4000b7d..84bfdf57 100644 --- a/packages/apps/src/v1/client.ts +++ b/packages/apps/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -99,6 +100,9 @@ export class AppsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -111,6 +115,9 @@ export class AppsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -138,6 +145,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppUpdateSchema); }; @@ -190,6 +199,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -230,6 +241,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppDeploymentSchema); }; @@ -275,6 +288,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomTemplateSchema); }; @@ -304,6 +319,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -337,6 +354,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -364,6 +383,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -387,6 +408,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomTemplateSchema); }; @@ -415,6 +438,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -448,6 +473,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -476,6 +503,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppDeploymentSchema); }; @@ -504,6 +533,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppUpdateSchema); }; @@ -532,6 +563,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomTemplateSchema); }; @@ -557,6 +590,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSpaceSchema); }; @@ -585,6 +620,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -622,6 +659,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAppDeploymentsResponseSchema); }; @@ -679,6 +718,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAppsResponseSchema); }; @@ -733,6 +774,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -790,6 +833,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSpacesResponseSchema); }; @@ -836,6 +881,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -876,6 +923,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -913,6 +962,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppSchema); }; @@ -942,6 +993,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAppThumbnailSchema); }; @@ -971,6 +1024,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomTemplateSchema); }; @@ -1012,6 +1067,8 @@ export class AppsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; diff --git a/packages/apps/src/v1/utils.ts b/packages/apps/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/apps/src/v1/utils.ts +++ b/packages/apps/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/authentication/src/v1/client.ts b/packages/authentication/src/v1/client.ts index 105f8061..1b190850 100755 --- a/packages/authentication/src/v1/client.ts +++ b/packages/authentication/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -60,6 +61,9 @@ export class AuthenticationClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -72,6 +76,9 @@ export class AuthenticationClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -111,6 +118,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -149,6 +158,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -179,6 +190,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -198,6 +211,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -224,6 +239,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -249,6 +266,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -286,6 +305,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -340,6 +361,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -404,6 +427,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -442,6 +467,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -471,6 +498,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -503,6 +532,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -531,6 +562,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -559,6 +592,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -596,6 +631,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -650,6 +687,8 @@ export class AuthenticationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/authentication/src/v1/utils.ts b/packages/authentication/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/authentication/src/v1/utils.ts +++ b/packages/authentication/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/budgetpolicy/src/v1/client.ts b/packages/budgetpolicy/src/v1/client.ts index 7dae4c52..49b983ba 100644 --- a/packages/budgetpolicy/src/v1/client.ts +++ b/packages/budgetpolicy/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class BudgetPolicyClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class BudgetPolicyClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -85,6 +92,8 @@ export class BudgetPolicyClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBudgetPolicySchema); }; @@ -109,6 +118,8 @@ export class BudgetPolicyClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -129,6 +140,8 @@ export class BudgetPolicyClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBudgetPolicySchema); }; @@ -177,6 +190,8 @@ export class BudgetPolicyClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListBudgetPoliciesResponseSchema); }; @@ -236,6 +251,8 @@ export class BudgetPolicyClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBudgetPolicySchema); }; diff --git a/packages/budgetpolicy/src/v1/utils.ts b/packages/budgetpolicy/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/budgetpolicy/src/v1/utils.ts +++ b/packages/budgetpolicy/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/budgets/src/v1/client.ts b/packages/budgets/src/v1/client.ts index 67f9b0d9..6d542589 100755 --- a/packages/budgets/src/v1/client.ts +++ b/packages/budgets/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -52,6 +53,9 @@ export class BudgetsClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -64,6 +68,9 @@ export class BudgetsClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -91,6 +98,8 @@ export class BudgetsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -119,6 +128,8 @@ export class BudgetsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -153,6 +164,8 @@ export class BudgetsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -196,6 +209,8 @@ export class BudgetsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -245,6 +260,8 @@ export class BudgetsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/budgets/src/v1/utils.ts b/packages/budgets/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/budgets/src/v1/utils.ts +++ b/packages/budgets/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/bundledeployments/src/v1/client.ts b/packages/bundledeployments/src/v1/client.ts index f04ab20c..612d5a1a 100755 --- a/packages/bundledeployments/src/v1/client.ts +++ b/packages/bundledeployments/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -72,6 +73,9 @@ export class BundleDeploymentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -84,6 +88,9 @@ export class BundleDeploymentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -119,6 +126,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVersionSchema); }; @@ -166,6 +175,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeploymentSchema); }; @@ -217,6 +228,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -265,6 +278,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVersionSchema); }; @@ -299,6 +314,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -322,6 +339,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeploymentSchema); }; @@ -350,6 +369,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -378,6 +399,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalResourceSchema); }; @@ -406,6 +429,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVersionSchema); }; @@ -442,6 +467,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalHeartbeatResponseSchema); }; @@ -479,6 +506,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListDeploymentsResponseSchema); }; @@ -533,6 +562,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListOperationsResponseSchema); }; @@ -587,6 +618,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListResourcesResponseSchema); }; @@ -644,6 +677,8 @@ export class BundleDeploymentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListVersionsResponseSchema); }; diff --git a/packages/bundledeployments/src/v1/utils.ts b/packages/bundledeployments/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/bundledeployments/src/v1/utils.ts +++ b/packages/bundledeployments/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/cleanrooms/src/v1/client.ts b/packages/cleanrooms/src/v1/client.ts index e287fc76..e4fd74d1 100644 --- a/packages/cleanrooms/src/v1/client.ts +++ b/packages/cleanrooms/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -89,6 +90,9 @@ export class CleanRoomsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -101,6 +105,9 @@ export class CleanRoomsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -137,6 +144,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomSchema); }; @@ -183,6 +192,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAssetSchema); }; @@ -215,6 +226,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -250,6 +263,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAutoApprovalRuleSchema); }; @@ -282,6 +297,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -317,6 +334,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -340,6 +359,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -370,6 +391,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -393,6 +416,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomSchema); }; @@ -421,6 +446,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAssetSchema); }; @@ -449,6 +476,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAssetSchema); }; @@ -477,6 +506,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAutoApprovalRuleSchema); }; @@ -514,6 +545,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -568,6 +601,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -625,6 +660,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -685,6 +722,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -748,6 +787,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListCleanRoomsResponseSchema); }; @@ -800,6 +841,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomSchema); }; @@ -832,6 +875,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAssetSchema); }; @@ -864,6 +909,8 @@ export class CleanRoomsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCleanRoomAutoApprovalRuleSchema); }; diff --git a/packages/cleanrooms/src/v1/utils.ts b/packages/cleanrooms/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/cleanrooms/src/v1/utils.ts +++ b/packages/cleanrooms/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/clusterlibraries/src/v2/client.ts b/packages/clusterlibraries/src/v2/client.ts index c5721e24..d5175a03 100755 --- a/packages/clusterlibraries/src/v2/client.ts +++ b/packages/clusterlibraries/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class ClusterLibrariesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class ClusterLibrariesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -90,6 +97,8 @@ export class ClusterLibrariesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -135,6 +144,8 @@ export class ClusterLibrariesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalClusterLibraryStatusesSchema); }; @@ -167,6 +178,8 @@ export class ClusterLibrariesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInstallLibrariesResponseSchema); }; @@ -199,6 +212,8 @@ export class ClusterLibrariesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUninstallLibrariesResponseSchema); }; diff --git a/packages/clusterlibraries/src/v2/utils.ts b/packages/clusterlibraries/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/clusterlibraries/src/v2/utils.ts +++ b/packages/clusterlibraries/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/clusterpolicies/src/v2/client.ts b/packages/clusterpolicies/src/v2/client.ts index f93a3b5b..9eb9dc43 100755 --- a/packages/clusterpolicies/src/v2/client.ts +++ b/packages/clusterpolicies/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -53,6 +54,9 @@ export class ClusterPoliciesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -65,6 +69,9 @@ export class ClusterPoliciesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -92,6 +99,8 @@ export class ClusterPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreatePolicyResponseSchema); }; @@ -121,6 +130,8 @@ export class ClusterPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeletePolicyResponseSchema); }; @@ -150,6 +161,8 @@ export class ClusterPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEditPolicyResponseSchema); }; @@ -184,6 +197,8 @@ export class ClusterPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPolicySchema); }; @@ -221,6 +236,8 @@ export class ClusterPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListPoliciesResponseSchema); }; diff --git a/packages/clusterpolicies/src/v2/utils.ts b/packages/clusterpolicies/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/clusterpolicies/src/v2/utils.ts +++ b/packages/clusterpolicies/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/clusters/src/v2/client.ts b/packages/clusters/src/v2/client.ts index 536e0f70..f7d6b76a 100755 --- a/packages/clusters/src/v2/client.ts +++ b/packages/clusters/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -114,6 +115,9 @@ export class ClustersClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -126,6 +130,9 @@ export class ClustersClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -157,6 +164,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetEventsResponseSchema); }; @@ -203,6 +212,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalChangeClusterOwnerResponseSchema); }; @@ -245,6 +256,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateClusterResponseSchema); }; @@ -291,6 +304,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteClusterResponseSchema); }; @@ -344,6 +359,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEditClusterResponseSchema); }; @@ -394,6 +411,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalClusterInfoSchema); }; @@ -425,6 +444,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAvailableZonesResponseSchema); }; @@ -462,6 +483,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListClustersResponseSchema); }; @@ -507,6 +530,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListNodeTypesResponseSchema); }; @@ -535,6 +560,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetSparkVersionsResponseSchema); }; @@ -572,6 +599,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -608,6 +637,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPinClusterResponseSchema); }; @@ -637,6 +668,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalResizeClusterResponseSchema); }; @@ -679,6 +712,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRestartClusterResponseSchema); }; @@ -729,6 +764,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStartClusterResponseSchema); }; @@ -775,6 +812,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUnpinClusterResponseSchema); }; @@ -813,6 +852,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateClusterResponseSchema); }; @@ -869,6 +910,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -909,6 +952,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -956,6 +1001,8 @@ export class ClustersClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/clusters/src/v2/utils.ts b/packages/clusters/src/v2/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/clusters/src/v2/utils.ts +++ b/packages/clusters/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/commandexecution/src/v2/client.ts b/packages/commandexecution/src/v2/client.ts index 436085e8..e76cb761 100644 --- a/packages/commandexecution/src/v2/client.ts +++ b/packages/commandexecution/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -60,6 +61,9 @@ export class CommandExecutionClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -72,6 +76,9 @@ export class CommandExecutionClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -103,6 +110,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCancelResponseSchema); }; @@ -170,6 +179,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetCommandStatusResponseSchema); }; @@ -207,6 +218,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetContextStatusResponseSchema); }; @@ -240,6 +253,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateResponseSchema); }; @@ -285,6 +300,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDestroyResponseSchema); }; @@ -318,6 +335,8 @@ export class CommandExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateResponseSchema); }; diff --git a/packages/commandexecution/src/v2/utils.ts b/packages/commandexecution/src/v2/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/commandexecution/src/v2/utils.ts +++ b/packages/commandexecution/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/core/package.json b/packages/core/package.json index 840f1737..f70a5260 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -53,6 +53,11 @@ "import": "./dist/logger/index.js", "default": "./dist/logger/index.js" }, + "./logger/debug": { + "types": "./dist/logger/debug.d.ts", + "import": "./dist/logger/debug.js", + "default": "./dist/logger/debug.js" + }, "./ops": { "types": "./dist/ops/index.d.ts", "import": "./dist/ops/index.js", diff --git a/packages/core/src/logger/debug.ts b/packages/core/src/logger/debug.ts new file mode 100644 index 00000000..d45e1167 --- /dev/null +++ b/packages/core/src/logger/debug.ts @@ -0,0 +1,154 @@ +/** + * Redaction and truncation helpers for debug-level HTTP body and header + * logging. Ported from the Go SDK's logger/httplog package so debug logs stay + * secret-safe. + */ + +/** Default per-value byte budget for debug HTTP logs. Matches the Go SDK. */ +export const DEFAULT_DEBUG_TRUNCATE_BYTES = 96; + +// Body field names whose values are replaced wholesale. Ported from the Go +// SDK's body_logger.go redactKeys. +const REDACT_BODY_KEYS = new Set([ + 'string_value', + 'token_value', + 'content', + 'access_token', + 'refresh_token', + 'id_token', + 'token', + 'password', +]); + +// Token written in place of a redacted body field value. +const BODY_REDACTED = '**REDACTED**'; + +// Header names redacted regardless of value. Ported from the Go SDK's +// round_trip_stringer.go authorizationHeaders; compared case-insensitively. +const REDACT_HEADER_NAMES = new Set([ + 'authorization', + 'x-databricks-azure-sp-management-token', + 'x-databricks-gcp-sa-access-token', +]); + +// Token written in place of a redacted header value. +const HEADER_REDACTED = 'REDACTED'; + +/** Truncates a string to numBytes UTF-8 bytes, appending a byte-count note. */ +export function onlyNBytes(s: string, numBytes: number): string { + const bytes = new TextEncoder().encode(s); + const diff = bytes.length - numBytes; + if (diff > 0) { + // Decode the byte prefix back to text so multi-byte sequences stay intact. + const head = new TextDecoder().decode(bytes.subarray(0, numBytes)); + return `${head}... (${String(diff)} more bytes)`; + } + return s; +} + +// Recursively rebuilds a parsed JSON value with secret fields redacted, string +// values truncated, and arrays cut off once the budget is spent. +function recursiveMarshal( + value: unknown, + debugTruncateBytes: number, + budget: {remaining: number} +): unknown { + if (Array.isArray(value)) { + return recursiveMarshalSlice(value, debugTruncateBytes, budget); + } + if (value !== null && typeof value === 'object') { + return recursiveMarshalMap( + value as Record, + debugTruncateBytes, + budget + ); + } + if (typeof value === 'string') { + const truncated = onlyNBytes(value, debugTruncateBytes); + budget.remaining -= JSON.stringify(truncated).length; + return truncated; + } + budget.remaining -= JSON.stringify(value ?? null).length; + return value; +} + +// Every key is emitted regardless of budget; secret keys have their value +// replaced. Mirrors the Go SDK's recursiveMarshalMap + mask. +function recursiveMarshalMap( + m: Record, + debugTruncateBytes: number, + budget: {remaining: number} +): Record { + const out: Record = {}; + for (const key of Object.keys(m).sort()) { + if (REDACT_BODY_KEYS.has(key)) { + out[key] = BODY_REDACTED; + budget.remaining -= JSON.stringify(BODY_REDACTED).length; + continue; + } + out[key] = recursiveMarshal(m[key], debugTruncateBytes, budget); + } + return out; +} + +// The first element is always emitted; later elements are dropped with a +// trailer once the budget is spent. Mirrors the Go SDK's recursiveMarshalSlice. +function recursiveMarshalSlice( + s: unknown[], + debugTruncateBytes: number, + budget: {remaining: number} +): unknown[] { + const out: unknown[] = []; + for (let i = 0; i < s.length; i++) { + if (i > 0 && budget.remaining <= 0) { + out.push(`... (${String(s.length - out.length)} additional elements)`); + break; + } + out.push(recursiveMarshal(s[i], debugTruncateBytes, budget)); + } + return out; +} + +/** Redacts secret fields and truncates a JSON or plaintext body for logging. */ +export function redactedDumpBody( + text: string, + debugTruncateBytes: number +): string { + if (text === '') { + return ''; + } + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + // Not JSON: truncate the whole string, matching the Go SDK's fallback. + return onlyNBytes(text, debugTruncateBytes); + } + // Budget defaults to 1024, raised to debugTruncateBytes when larger. + const budget = {remaining: Math.max(1024, debugTruncateBytes)}; + const redacted = recursiveMarshal(parsed, debugTruncateBytes, budget); + return JSON.stringify(redacted, null, 2); +} + +/** Redacts auth headers and truncates all header values for logging. */ +export function redactHeaders( + headers: Headers, + debugTruncateBytes: number +): Record { + const out: Record = {}; + for (const [name, value] of headers.entries()) { + if (REDACT_HEADER_NAMES.has(name.toLowerCase())) { + out[name] = HEADER_REDACTED; + continue; + } + // Strip CR/LF to prevent log injection (CWE-117). + const sanitized = value.replace(/[\r\n]/g, ''); + out[name] = onlyNBytes(sanitized, debugTruncateBytes); + } + // Sort keys so the logged object is deterministic. + const sorted: Record = {}; + for (const name of Object.keys(out).sort()) { + sorted[name] = out[name]; + } + return sorted; +} diff --git a/packages/core/src/logger/index.ts b/packages/core/src/logger/index.ts index b99ee56d..a95e9e3b 100644 --- a/packages/core/src/logger/index.ts +++ b/packages/core/src/logger/index.ts @@ -6,3 +6,9 @@ export type {Level, Logger} from './logger'; export {NoOpLogger, LogLevel} from './logger'; +export { + DEFAULT_DEBUG_TRUNCATE_BYTES, + onlyNBytes, + redactedDumpBody, + redactHeaders, +} from './debug'; diff --git a/packages/core/tests/logger/debug.test.ts b/packages/core/tests/logger/debug.test.ts new file mode 100644 index 00000000..b1f4cfd3 --- /dev/null +++ b/packages/core/tests/logger/debug.test.ts @@ -0,0 +1,106 @@ +import {describe, it, expect} from 'vitest'; +import { + DEFAULT_DEBUG_TRUNCATE_BYTES, + onlyNBytes, + redactedDumpBody, + redactHeaders, +} from '../../src/logger/debug'; + +describe('DEFAULT_DEBUG_TRUNCATE_BYTES', () => { + it('matches the Go SDK default of 96', () => { + expect(DEFAULT_DEBUG_TRUNCATE_BYTES).toBe(96); + }); +}); + +describe('onlyNBytes', () => { + it('returns the string unchanged when within the budget', () => { + expect(onlyNBytes('hello', 96)).toBe('hello'); + }); + + it('truncates and appends the remaining byte count', () => { + expect(onlyNBytes('abcdef', 3)).toBe('abc... (3 more bytes)'); + }); + + it('truncates on a UTF-8 byte boundary without splitting code points', () => { + // "é" is two UTF-8 bytes; a budget of 2 keeps exactly one character. + const result = onlyNBytes('éé', 2); + expect(result).toBe('é... (2 more bytes)'); + }); +}); + +describe('redactedDumpBody', () => { + it('returns an empty string for an empty body', () => { + expect(redactedDumpBody('', 96)).toBe(''); + }); + + it('redacts every known secret field by key', () => { + const keys = [ + 'string_value', + 'token_value', + 'content', + 'access_token', + 'refresh_token', + 'id_token', + 'token', + 'password', + ]; + for (const key of keys) { + const out = redactedDumpBody(JSON.stringify({[key]: 'super-secret'}), 96); + expect(out).toContain('**REDACTED**'); + expect(out).not.toContain('super-secret'); + } + }); + + it('keeps non-secret fields and truncates long string values', () => { + const text = JSON.stringify({name: 'a'.repeat(200)}); + const out = redactedDumpBody(text, 10); + expect(out).toContain('more bytes'); + expect(out).not.toContain('a'.repeat(200)); + }); + + it('redacts secrets in nested objects', () => { + const text = JSON.stringify({outer: {password: 'p'}}); + const out = redactedDumpBody(text, 96); + expect(out).toContain('**REDACTED**'); + expect(out).not.toContain('"p"'); + }); + + it('emits an array trailer once the budget is spent', () => { + const big = Array.from({length: 50}, (_, i) => 'x'.repeat(40) + String(i)); + const out = redactedDumpBody(JSON.stringify(big), 8); + expect(out).toContain('additional elements'); + }); + + it('truncates a non-JSON body wholesale', () => { + const out = redactedDumpBody('not json '.repeat(50), 10); + expect(out).toContain('more bytes'); + }); +}); + +describe('redactHeaders', () => { + it('redacts authorization-family headers regardless of case', () => { + const headers = new Headers({ + Authorization: 'Bearer secret-token', + 'X-Databricks-Azure-SP-Management-Token': 'azure-secret', + 'X-Databricks-GCP-SA-Access-Token': 'gcp-secret', + }); + const out = redactHeaders(headers, 96); + expect(out.authorization).toBe('REDACTED'); + expect(out['x-databricks-azure-sp-management-token']).toBe('REDACTED'); + expect(out['x-databricks-gcp-sa-access-token']).toBe('REDACTED'); + const serialized = JSON.stringify(out); + expect(serialized).not.toContain('secret-token'); + expect(serialized).not.toContain('azure-secret'); + }); + + it('truncates non-secret header values', () => { + const headers = new Headers({'X-Custom': 'v'.repeat(200)}); + const out = redactHeaders(headers, 10); + expect(out['x-custom']).toContain('more bytes'); + }); + + it('returns header names sorted for deterministic output', () => { + const headers = new Headers({'X-Zed': '1', 'X-Abc': '2'}); + expect(Object.keys(redactHeaders(headers, 96))).toEqual(['x-abc', 'x-zed']); + }); +}); diff --git a/packages/customllms/src/v1/client.ts b/packages/customllms/src/v1/client.ts index 347eb25b..b184ebf7 100644 --- a/packages/customllms/src/v1/client.ts +++ b/packages/customllms/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -47,6 +48,9 @@ export class CustomLlmsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -59,6 +63,9 @@ export class CustomLlmsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -88,6 +95,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -112,6 +121,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomLlmSchema); }; @@ -139,6 +150,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -162,6 +175,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomLlmSchema); }; @@ -194,6 +209,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomLlmSchema); }; @@ -223,6 +240,8 @@ export class CustomLlmsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomLlmSchema); }; diff --git a/packages/customllms/src/v1/utils.ts b/packages/customllms/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/customllms/src/v1/utils.ts +++ b/packages/customllms/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/database/src/v1/client.ts b/packages/database/src/v1/client.ts index 57d86ca1..22005750 100644 --- a/packages/database/src/v1/client.ts +++ b/packages/database/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -89,6 +90,9 @@ export class DatabaseClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -101,6 +105,9 @@ export class DatabaseClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -128,6 +135,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseCatalogSchema); }; @@ -160,6 +169,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceSchema); }; @@ -215,6 +226,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceRoleSchema); }; @@ -247,6 +260,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseTableSchema); }; @@ -279,6 +294,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSyncedDatabaseTableSchema); }; @@ -306,6 +323,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -337,6 +356,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -368,6 +389,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -390,6 +413,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -418,6 +443,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -447,6 +474,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceSchema); }; @@ -479,6 +508,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseCredentialSchema); }; @@ -507,6 +538,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseCatalogSchema); }; @@ -535,6 +568,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceSchema); }; @@ -563,6 +598,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceRoleSchema); }; @@ -591,6 +628,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseTableSchema); }; @@ -619,6 +658,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSyncedDatabaseTableSchema); }; @@ -656,6 +697,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -718,6 +761,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -775,6 +820,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -832,6 +879,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -896,6 +945,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseCatalogSchema); }; @@ -940,6 +991,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseInstanceSchema); }; @@ -984,6 +1037,8 @@ export class DatabaseClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSyncedDatabaseTableSchema); }; diff --git a/packages/database/src/v1/utils.ts b/packages/database/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/database/src/v1/utils.ts +++ b/packages/database/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/dataclassification/src/v1/client.ts b/packages/dataclassification/src/v1/client.ts index ccbc7639..14a8e761 100644 --- a/packages/dataclassification/src/v1/client.ts +++ b/packages/dataclassification/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -42,6 +43,9 @@ export class DataClassificationClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -54,6 +58,9 @@ export class DataClassificationClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -87,6 +94,8 @@ export class DataClassificationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogConfigSchema); }; @@ -114,6 +123,8 @@ export class DataClassificationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -137,6 +148,8 @@ export class DataClassificationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogConfigSchema); }; @@ -182,6 +195,8 @@ export class DataClassificationClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogConfigSchema); }; diff --git a/packages/dataclassification/src/v1/utils.ts b/packages/dataclassification/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/dataclassification/src/v1/utils.ts +++ b/packages/dataclassification/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/dataquality/src/v1/client.ts b/packages/dataquality/src/v1/client.ts index 1d0c4114..d1929152 100644 --- a/packages/dataquality/src/v1/client.ts +++ b/packages/dataquality/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -59,6 +60,9 @@ export class DataQualityClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -71,6 +75,9 @@ export class DataQualityClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -106,6 +113,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCancelRefreshResponseSchema); }; @@ -148,6 +157,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMonitorSchema); }; @@ -185,6 +196,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRefreshSchema); }; @@ -226,6 +239,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -248,6 +263,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -286,6 +303,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMonitorSchema); }; @@ -325,6 +344,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRefreshSchema); }; @@ -362,6 +383,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListMonitorResponseSchema); }; @@ -427,6 +450,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListRefreshResponseSchema); }; @@ -496,6 +521,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMonitorSchema); }; @@ -537,6 +564,8 @@ export class DataQualityClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRefreshSchema); }; diff --git a/packages/dataquality/src/v1/utils.ts b/packages/dataquality/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/dataquality/src/v1/utils.ts +++ b/packages/dataquality/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/disasterrecovery/src/v1/client.ts b/packages/disasterrecovery/src/v1/client.ts index 163ed782..7d38c160 100644 --- a/packages/disasterrecovery/src/v1/client.ts +++ b/packages/disasterrecovery/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -56,6 +57,9 @@ export class DisasterRecoveryClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -68,6 +72,9 @@ export class DisasterRecoveryClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -110,6 +117,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFailoverGroupSchema); }; @@ -154,6 +163,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStableUrlSchema); }; @@ -187,6 +198,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -209,6 +222,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -233,6 +248,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFailoverGroupSchema); }; @@ -261,6 +278,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFailoverGroupSchema); }; @@ -289,6 +308,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStableUrlSchema); }; @@ -326,6 +347,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListFailoverGroupsResponseSchema); }; @@ -380,6 +403,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListStableUrlsResponseSchema); }; @@ -438,6 +463,8 @@ export class DisasterRecoveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFailoverGroupSchema); }; diff --git a/packages/disasterrecovery/src/v1/utils.ts b/packages/disasterrecovery/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/disasterrecovery/src/v1/utils.ts +++ b/packages/disasterrecovery/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/environments/src/v1/client.ts b/packages/environments/src/v1/client.ts index 72ffe729..530b2008 100644 --- a/packages/environments/src/v1/client.ts +++ b/packages/environments/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -61,6 +62,9 @@ export class EnvironmentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -73,6 +77,9 @@ export class EnvironmentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -126,6 +133,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -165,6 +174,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -191,6 +202,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -225,6 +238,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -253,6 +268,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceBaseEnvironmentSchema); }; @@ -305,6 +322,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -362,6 +381,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -417,6 +438,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -457,6 +480,8 @@ export class EnvironmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; diff --git a/packages/environments/src/v1/utils.ts b/packages/environments/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/environments/src/v1/utils.ts +++ b/packages/environments/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/examples/tests/debug-logging.test.ts b/packages/examples/tests/debug-logging.test.ts new file mode 100644 index 00000000..935dc3fc --- /dev/null +++ b/packages/examples/tests/debug-logging.test.ts @@ -0,0 +1,106 @@ +import {describe, expect, it, vi} from 'vitest'; + +import type { + HttpClient, + HttpRequest, + HttpResponse, +} from '@databricks/sdk-core/http'; +import type {Logger} from '@databricks/sdk-core/logger'; +import {PostgresClient} from '@databricks/sdk-postgres/v1'; + +const HOST = 'https://test.cloud.databricks.com'; + +// A project body that also carries a secret-bearing field. Unknown keys are +// stripped by the response schema, but the raw body is logged before parsing, +// so the redaction path still sees the secret. +const PROJECT_WIRE = { + name: 'projects/demo', + password: 'super-secret-value', +}; + +// Wraps a JS value as a 200 HttpResponse with a streamed JSON body. The +// response also sets an Authorization header to exercise header redaction. +function jsonResponse(body: unknown): HttpResponse { + return { + statusCode: 200, + headers: new Headers({ + 'content-type': 'application/json', + authorization: 'Bearer server-secret', + }), + body: new Response(JSON.stringify(body)).body, + }; +} + +function stubClient(): HttpClient { + return { + send(_request: HttpRequest): Promise { + return Promise.resolve(jsonResponse(PROJECT_WIRE)); + }, + }; +} + +function mockLogger(): Logger { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }; +} + +// Pulls the object logged alongside the 'HTTP response' debug message. +function responseLog(logger: Logger): Record { + const calls = (logger.debug as ReturnType).mock.calls; + const call = calls.find(c => c[0] === 'HTTP response'); + if (call === undefined) { + throw new Error('no HTTP response debug log was emitted'); + } + return call[1] as Record; +} + +describe('debug HTTP body logging', () => { + it('does not log anything with the default logger', async () => { + const client = new PostgresClient({host: HOST, httpClient: stubClient()}); + // The default NoOpLogger silently discards everything; nothing to assert + // beyond the call completing without surfacing the body anywhere. + await expect( + client.getProject({name: 'projects/demo'}) + ).resolves.toBeDefined(); + }); + + it('logs the response body redacted when a debug logger is supplied', async () => { + const logger = mockLogger(); + const client = new PostgresClient({ + host: HOST, + httpClient: stubClient(), + logger, + }); + + await client.getProject({name: 'projects/demo'}); + + const log = responseLog(logger); + const body = log.body as string; + // The body IS logged, with the secret field redacted, not in plaintext. + expect(body).toContain('**REDACTED**'); + expect(body).not.toContain('super-secret-value'); + // Headers are omitted unless debugHeaders is set. + expect(log.headers).toBeUndefined(); + }); + + it('logs redacted headers only when debugHeaders is enabled', async () => { + const logger = mockLogger(); + const client = new PostgresClient({ + host: HOST, + httpClient: stubClient(), + logger, + debugHeaders: true, + }); + + await client.getProject({name: 'projects/demo'}); + + const headers = responseLog(logger).headers as Record; + // The Authorization header is always redacted, never shown in plaintext. + expect(headers.authorization).toBe('REDACTED'); + expect(JSON.stringify(headers)).not.toContain('server-secret'); + }); +}); diff --git a/packages/experiments/src/v1/client.ts b/packages/experiments/src/v1/client.ts index 3a649555..29b2664a 100755 --- a/packages/experiments/src/v1/client.ts +++ b/packages/experiments/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -170,6 +171,9 @@ export class ExperimentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -182,6 +186,9 @@ export class ExperimentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -218,6 +225,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateExperimentResponseSchema); }; @@ -247,6 +256,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateLoggedModelResponseSchema); }; @@ -280,6 +291,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateRunResponseSchema); }; @@ -312,6 +325,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteExperimentResponseSchema); }; @@ -340,6 +355,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteLoggedModelResponseSchema); }; @@ -368,6 +385,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -400,6 +419,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteRunResponseSchema); }; @@ -432,6 +453,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteRunsResponseSchema); }; @@ -464,6 +487,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteTagResponseSchema); }; @@ -493,6 +518,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -530,6 +557,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetExperimentResponseSchema); }; @@ -572,6 +601,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -603,6 +634,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetLoggedModelResponseSchema); }; @@ -645,6 +678,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetRunResponseSchema); }; @@ -694,6 +729,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListArtifactsResponseSchema); }; @@ -751,6 +788,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListExperimentsResponseSchema); }; @@ -814,6 +853,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetMetricHistoryResponseSchema); }; @@ -901,6 +942,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogBatchResponseSchema); }; @@ -930,6 +973,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogInputsResponseSchema); }; @@ -963,6 +1008,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -999,6 +1046,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogMetricResponseSchema); }; @@ -1032,6 +1081,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogModelResponseSchema); }; @@ -1061,6 +1112,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogOutputsResponseSchema); }; @@ -1094,6 +1147,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalLogParamResponseSchema); }; @@ -1129,6 +1184,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRestoreExperimentResponseSchema); }; @@ -1162,6 +1219,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRestoreRunResponseSchema); }; @@ -1194,6 +1253,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRestoreRunsResponseSchema); }; @@ -1223,6 +1284,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSearchExperimentsResponseSchema); }; @@ -1269,6 +1332,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSearchLoggedModelsResponseSchema); }; @@ -1302,6 +1367,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSearchRunsResponseSchema); }; @@ -1348,6 +1415,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSetExperimentTagResponseSchema); }; @@ -1377,6 +1446,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSetLoggedModelTagsResponseSchema); }; @@ -1409,6 +1480,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSetTagResponseSchema); }; @@ -1438,6 +1511,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateExperimentResponseSchema); }; @@ -1467,6 +1542,8 @@ export class ExperimentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateRunResponseSchema); }; diff --git a/packages/experiments/src/v1/utils.ts b/packages/experiments/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/experiments/src/v1/utils.ts +++ b/packages/experiments/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/features/src/v1/client.ts b/packages/features/src/v1/client.ts index a173cc3c..2c05b6ce 100644 --- a/packages/features/src/v1/client.ts +++ b/packages/features/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -79,6 +80,9 @@ export class FeaturesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -91,6 +95,9 @@ export class FeaturesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -121,6 +128,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -153,6 +162,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFeatureSchema); }; @@ -186,6 +197,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKafkaConfigSchema); }; @@ -218,6 +231,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMaterializedFeatureSchema); }; @@ -247,6 +262,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStreamSchema); }; @@ -274,6 +291,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -300,6 +319,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -322,6 +343,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -344,6 +367,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -367,6 +392,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFeatureSchema); }; @@ -399,6 +426,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKafkaConfigSchema); }; @@ -427,6 +456,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMaterializedFeatureSchema); }; @@ -455,6 +486,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStreamSchema); }; @@ -498,6 +531,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListFeaturesResponseSchema); }; @@ -556,6 +591,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListKafkaConfigsResponseSchema); }; @@ -613,6 +650,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -673,6 +712,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListStreamsResponseSchema); }; @@ -731,6 +772,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFeatureSchema); }; @@ -776,6 +819,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKafkaConfigSchema); }; @@ -820,6 +865,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMaterializedFeatureSchema); }; @@ -861,6 +908,8 @@ export class FeaturesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStreamSchema); }; diff --git a/packages/features/src/v1/utils.ts b/packages/features/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/features/src/v1/utils.ts +++ b/packages/features/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/featurestore/src/v1/client.ts b/packages/featurestore/src/v1/client.ts index 1847debf..5bffc269 100644 --- a/packages/featurestore/src/v1/client.ts +++ b/packages/featurestore/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -50,6 +51,9 @@ export class FeatureStoreClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -62,6 +66,9 @@ export class FeatureStoreClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -89,6 +96,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOnlineStoreSchema); }; @@ -116,6 +125,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -138,6 +149,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -161,6 +174,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOnlineStoreSchema); }; @@ -198,6 +213,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListOnlineStoresResponseSchema); }; @@ -244,6 +261,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPublishTableResponseSchema); }; @@ -285,6 +304,8 @@ export class FeatureStoreClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOnlineStoreSchema); }; diff --git a/packages/featurestore/src/v1/utils.ts b/packages/featurestore/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/featurestore/src/v1/utils.ts +++ b/packages/featurestore/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/files/src/v2/client.ts b/packages/files/src/v2/client.ts index 4798ddad..6f09e3a4 100755 --- a/packages/files/src/v2/client.ts +++ b/packages/files/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -98,6 +99,9 @@ export class FilesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -110,6 +114,9 @@ export class FilesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -142,6 +149,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAddBlockResponseSchema); }; @@ -174,6 +183,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCloseResponseSchema); }; @@ -213,6 +224,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateResponseSchema); }; @@ -257,6 +270,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteResponseSchema); }; @@ -294,6 +309,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetStatusResponseSchema); }; @@ -338,6 +355,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListStatusResponseSchema); }; @@ -371,6 +390,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMkDirsResponseSchema); }; @@ -402,6 +423,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMoveResponseSchema); }; @@ -439,6 +462,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPutResponseSchema); }; @@ -483,6 +508,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalReadResponseSchema); }; @@ -516,6 +543,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateDirectoryResponseSchema); }; @@ -549,6 +578,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteDirectoryResponseSchema); }; @@ -577,6 +608,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteFileResponseSchema); }; @@ -615,6 +648,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); const contentLengthHeader = httpResp.headers.get('content-length'); resp = { @@ -661,6 +696,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -701,6 +738,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetFileMetadataResponseSchema); }; @@ -741,6 +780,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListDirectoryResponseSchema); }; @@ -801,6 +842,8 @@ export class FilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); return parseResponse(respBody, unmarshalUploadFileResponseSchema); } diff --git a/packages/files/src/v2/utils.ts b/packages/files/src/v2/utils.ts index 69b8340d..dc424c00 100755 --- a/packages/files/src/v2/utils.ts +++ b/packages/files/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { @@ -175,10 +209,21 @@ export function encodeMultiSegmentPath(path: string): string { export async function sendAndCheckError( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // These methods stream the request body, so it is never drained for logging. + if (opts.request.body !== undefined && opts.request.body !== null) { + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -188,10 +233,22 @@ export async function sendAndCheckError( throw e; } - opts.logger.debug('HTTP response', {statusCode: resp.statusCode}); - if (resp.statusCode < 200 || resp.statusCode >= 300) { const body = await readAll(resp.body); + const responseLog: Record = { + statusCode: resp.statusCode, + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders( + resp.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { throw apiErr; @@ -199,5 +256,15 @@ export async function sendAndCheckError( throw new Error(`unexpected HTTP status ${String(resp.statusCode)}`); } + // The success body is a stream returned to the caller, so it is not drained. + const responseLog: Record = { + statusCode: resp.statusCode, + body: '', + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); + return resp; } diff --git a/packages/forecasting/src/v1/client.ts b/packages/forecasting/src/v1/client.ts index 65c8a280..e9a757bd 100644 --- a/packages/forecasting/src/v1/client.ts +++ b/packages/forecasting/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -46,6 +47,9 @@ export class ForecastingClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -58,6 +62,9 @@ export class ForecastingClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -88,6 +95,8 @@ export class ForecastingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -132,6 +141,8 @@ export class ForecastingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalForecastingExperimentSchema); }; diff --git a/packages/forecasting/src/v1/utils.ts b/packages/forecasting/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/forecasting/src/v1/utils.ts +++ b/packages/forecasting/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/genie/src/v1/client.ts b/packages/genie/src/v1/client.ts index b1bb9a38..e47ef649 100644 --- a/packages/genie/src/v1/client.ts +++ b/packages/genie/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -109,6 +110,9 @@ export class GenieClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -121,6 +125,9 @@ export class GenieClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -148,6 +155,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieSpaceSchema); }; @@ -183,6 +192,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieMessageSchema); }; @@ -238,6 +249,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieEvalRunResponseSchema); }; @@ -270,6 +283,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieMessageCommentSchema); }; @@ -297,6 +312,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -319,6 +336,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -346,6 +365,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -381,6 +402,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -440,6 +463,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -471,6 +496,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieMessageSchema); }; @@ -529,6 +556,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -560,6 +589,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieEvalResultDetailsSchema); }; @@ -588,6 +619,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieEvalRunResponseSchema); }; @@ -619,6 +652,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -650,6 +685,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -681,6 +718,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -721,6 +760,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieSpaceSchema); }; @@ -758,6 +799,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -798,6 +841,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -841,6 +886,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -881,6 +928,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -921,6 +970,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieListEvalRunsResponseSchema); }; @@ -958,6 +1009,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -998,6 +1051,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieListSpacesResponseSchema); }; @@ -1029,6 +1084,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1056,6 +1113,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1112,6 +1171,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1136,6 +1197,8 @@ export class GenieClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGenieSpaceSchema); }; diff --git a/packages/genie/src/v1/utils.ts b/packages/genie/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/genie/src/v1/utils.ts +++ b/packages/genie/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/gitcredentials/src/v1/client.ts b/packages/gitcredentials/src/v1/client.ts index ad02744a..555397cc 100755 --- a/packages/gitcredentials/src/v1/client.ts +++ b/packages/gitcredentials/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -52,6 +53,9 @@ export class GitCredentialsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -64,6 +68,9 @@ export class GitCredentialsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -95,6 +102,8 @@ export class GitCredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateCredentialsResponseSchema); }; @@ -129,6 +138,8 @@ export class GitCredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteCredentialsResponseSchema); }; @@ -163,6 +174,8 @@ export class GitCredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetCredentialsResponseSchema); }; @@ -197,6 +210,8 @@ export class GitCredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListCredentialsResponseSchema); }; @@ -226,6 +241,8 @@ export class GitCredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateCredentialsResponseSchema); }; diff --git a/packages/gitcredentials/src/v1/utils.ts b/packages/gitcredentials/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/gitcredentials/src/v1/utils.ts +++ b/packages/gitcredentials/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/globalinitscripts/src/v2/client.ts b/packages/globalinitscripts/src/v2/client.ts index 4ff7e63d..a55eb227 100755 --- a/packages/globalinitscripts/src/v2/client.ts +++ b/packages/globalinitscripts/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -52,6 +53,9 @@ export class GlobalInitScriptsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -64,6 +68,9 @@ export class GlobalInitScriptsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -94,6 +101,8 @@ export class GlobalInitScriptsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -125,6 +134,8 @@ export class GlobalInitScriptsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -156,6 +167,8 @@ export class GlobalInitScriptsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGlobalInitScriptDetailsSchema); }; @@ -187,6 +200,8 @@ export class GlobalInitScriptsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -225,6 +240,8 @@ export class GlobalInitScriptsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/globalinitscripts/src/v2/utils.ts b/packages/globalinitscripts/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/globalinitscripts/src/v2/utils.ts +++ b/packages/globalinitscripts/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/instancepools/src/v2/client.ts b/packages/instancepools/src/v2/client.ts index ac268da8..8bbcc7ad 100755 --- a/packages/instancepools/src/v2/client.ts +++ b/packages/instancepools/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -53,6 +54,9 @@ export class InstancePoolsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -65,6 +69,9 @@ export class InstancePoolsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -92,6 +99,8 @@ export class InstancePoolsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateInstancePoolResponseSchema); }; @@ -121,6 +130,8 @@ export class InstancePoolsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteInstancePoolResponseSchema); }; @@ -150,6 +161,8 @@ export class InstancePoolsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEditInstancePoolResponseSchema); }; @@ -184,6 +197,8 @@ export class InstancePoolsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetInstancePoolResponseSchema); }; @@ -212,6 +227,8 @@ export class InstancePoolsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListInstancePoolsResponseSchema); }; diff --git a/packages/instancepools/src/v2/utils.ts b/packages/instancepools/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/instancepools/src/v2/utils.ts +++ b/packages/instancepools/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/instanceprofiles/src/v2/client.ts b/packages/instanceprofiles/src/v2/client.ts index 737adc13..c6f77a78 100755 --- a/packages/instanceprofiles/src/v2/client.ts +++ b/packages/instanceprofiles/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -50,6 +51,9 @@ export class InstanceProfilesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -62,6 +66,9 @@ export class InstanceProfilesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -94,6 +101,8 @@ export class InstanceProfilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAddInstanceProfileResponseSchema); }; @@ -136,6 +145,8 @@ export class InstanceProfilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -171,6 +182,8 @@ export class InstanceProfilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -208,6 +221,8 @@ export class InstanceProfilesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/instanceprofiles/src/v2/utils.ts b/packages/instanceprofiles/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/instanceprofiles/src/v2/utils.ts +++ b/packages/instanceprofiles/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/jobs/src/v2/client.ts b/packages/jobs/src/v2/client.ts index 4fc0d0f5..37c4e8f3 100755 --- a/packages/jobs/src/v2/client.ts +++ b/packages/jobs/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -110,6 +111,9 @@ export class JobsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -122,6 +126,9 @@ export class JobsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -157,6 +164,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -199,6 +208,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -247,6 +258,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListJobComplianceResponseSchema); }; @@ -296,6 +309,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCancelAllRunsResponseSchema); }; @@ -328,6 +343,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCancelRunResponseSchema); }; @@ -368,6 +385,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateJobResponseSchema); }; @@ -397,6 +416,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteJobResponseSchema); }; @@ -426,6 +447,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteRunResponseSchema); }; @@ -463,6 +486,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExportRunResponseSchema); }; @@ -510,6 +535,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetJobResponseSchema); }; @@ -563,6 +590,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetRunResponseSchema); }; @@ -607,6 +636,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetRunOutputResponseSchema); }; @@ -653,6 +684,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListJobsResponseSchema); }; @@ -734,6 +767,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListRunsResponseSchema); }; @@ -784,6 +819,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRepairRunResponseSchema); }; @@ -824,6 +861,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalResetJobResponseSchema); }; @@ -853,6 +892,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRunNowResponseSchema); }; @@ -905,6 +946,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSubmitRunResponseSchema); }; @@ -945,6 +988,8 @@ export class JobsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateJobResponseSchema); }; diff --git a/packages/jobs/src/v2/utils.ts b/packages/jobs/src/v2/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/jobs/src/v2/utils.ts +++ b/packages/jobs/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/keyconfigurations/src/v1/client.ts b/packages/keyconfigurations/src/v1/client.ts index ffdaca71..b38ab658 100644 --- a/packages/keyconfigurations/src/v1/client.ts +++ b/packages/keyconfigurations/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -43,6 +44,9 @@ export class KeyConfigurationsClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -55,6 +59,9 @@ export class KeyConfigurationsClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -104,6 +111,8 @@ export class KeyConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerManagedKeySchema); }; @@ -129,6 +138,8 @@ export class KeyConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerManagedKeySchema); }; @@ -167,6 +178,8 @@ export class KeyConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerManagedKeySchema); }; @@ -192,6 +205,8 @@ export class KeyConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { customerManagedKeys: parseResponse( diff --git a/packages/keyconfigurations/src/v1/utils.ts b/packages/keyconfigurations/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/keyconfigurations/src/v1/utils.ts +++ b/packages/keyconfigurations/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/knowledgeassistants/src/v1/client.ts b/packages/knowledgeassistants/src/v1/client.ts index 9ee7fe05..f7b2f19a 100644 --- a/packages/knowledgeassistants/src/v1/client.ts +++ b/packages/knowledgeassistants/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -67,6 +68,9 @@ export class KnowledgeAssistantsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -79,6 +83,9 @@ export class KnowledgeAssistantsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -106,6 +113,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -138,6 +147,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeAssistantSchema); }; @@ -170,6 +181,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeSourceSchema); }; @@ -197,6 +210,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -219,6 +234,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -241,6 +258,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -264,6 +283,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -292,6 +313,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeAssistantSchema); }; @@ -320,6 +343,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeSourceSchema); }; @@ -357,6 +382,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListExamplesResponseSchema); }; @@ -411,6 +438,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -468,6 +497,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -516,6 +547,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -552,6 +585,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -596,6 +631,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeAssistantSchema); }; @@ -640,6 +677,8 @@ export class KnowledgeAssistantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalKnowledgeSourceSchema); }; diff --git a/packages/knowledgeassistants/src/v1/utils.ts b/packages/knowledgeassistants/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/knowledgeassistants/src/v1/utils.ts +++ b/packages/knowledgeassistants/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/lakeview/src/v1/client.ts b/packages/lakeview/src/v1/client.ts index 1fc64d6e..e6e5c3a3 100644 --- a/packages/lakeview/src/v1/client.ts +++ b/packages/lakeview/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -83,6 +84,9 @@ export class LakeviewClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -95,6 +99,9 @@ export class LakeviewClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -137,6 +144,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDashboardSchema); }; @@ -166,6 +175,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalScheduleSchema); }; @@ -195,6 +206,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSubscriptionSchema); }; @@ -228,6 +241,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -256,6 +271,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -279,6 +296,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDashboardSchema); }; @@ -307,6 +326,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPublishedDashboardSchema); }; @@ -344,6 +365,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -375,6 +398,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalScheduleSchema); }; @@ -403,6 +428,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSubscriptionSchema); }; @@ -446,6 +473,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListDashboardsResponseSchema); }; @@ -500,6 +529,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSchedulesResponseSchema); }; @@ -554,6 +585,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSubscriptionsResponseSchema); }; @@ -600,6 +633,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDashboardSchema); }; @@ -629,6 +664,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPublishedDashboardSchema); }; @@ -658,6 +695,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRevertDashboardResponseSchema); }; @@ -686,6 +725,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTrashDashboardResponseSchema); }; @@ -714,6 +755,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUnpublishDashboardResponseSchema); }; @@ -758,6 +801,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDashboardSchema); }; @@ -787,6 +832,8 @@ export class LakeviewClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalScheduleSchema); }; diff --git a/packages/lakeview/src/v1/utils.ts b/packages/lakeview/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/lakeview/src/v1/utils.ts +++ b/packages/lakeview/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/logdelivery/src/v1/client.ts b/packages/logdelivery/src/v1/client.ts index 3032f4d9..13c11134 100755 --- a/packages/logdelivery/src/v1/client.ts +++ b/packages/logdelivery/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class LogDeliveryClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class LogDeliveryClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -102,6 +109,8 @@ export class LogDeliveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -130,6 +139,8 @@ export class LogDeliveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -173,6 +184,8 @@ export class LogDeliveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -226,6 +239,8 @@ export class LogDeliveryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/logdelivery/src/v1/utils.ts b/packages/logdelivery/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/logdelivery/src/v1/utils.ts +++ b/packages/logdelivery/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/marketplaces/src/v1/client.ts b/packages/marketplaces/src/v1/client.ts index 56a9d07d..c98b5f79 100755 --- a/packages/marketplaces/src/v1/client.ts +++ b/packages/marketplaces/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -214,6 +215,9 @@ export class MarketplacesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -226,6 +230,9 @@ export class MarketplacesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -258,6 +265,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBatchGetListingsResponseSchema); }; @@ -292,6 +301,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBatchGetProvidersResponseSchema); }; @@ -321,6 +332,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -361,6 +374,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListInstallationsResponseSchema); }; @@ -415,6 +430,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -463,6 +480,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -494,6 +513,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -525,6 +546,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -557,6 +580,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateInstallationResponseSchema); }; @@ -594,6 +619,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -655,6 +682,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListFulfillmentsResponseSchema); }; @@ -709,6 +738,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -794,6 +825,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -857,6 +890,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -938,6 +973,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -989,6 +1026,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteInstallationResponseSchema); }; @@ -1023,6 +1062,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateInstallationResponseSchema); }; @@ -1052,6 +1093,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1084,6 +1127,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateExchangeResponseSchema); }; @@ -1113,6 +1158,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1145,6 +1192,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateFileResponseSchema); }; @@ -1174,6 +1223,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateListingResponseSchema); }; @@ -1203,6 +1254,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateProviderResponseSchema); }; @@ -1235,6 +1288,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1266,6 +1321,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteExchangeResponseSchema); }; @@ -1294,6 +1351,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1325,6 +1384,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteFileResponseSchema); }; @@ -1353,6 +1414,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteListingResponseSchema); }; @@ -1381,6 +1444,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteProviderResponseSchema); }; @@ -1409,6 +1474,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetExchangeResponseSchema); }; @@ -1437,6 +1504,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetFileResponseSchema); }; @@ -1465,6 +1534,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1496,6 +1567,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetListingResponseSchema); }; @@ -1536,6 +1609,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1587,6 +1662,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetProviderResponseSchema); }; @@ -1627,6 +1704,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1684,6 +1763,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListExchangesResponseSchema); }; @@ -1741,6 +1822,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1805,6 +1888,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListFilesResponseSchema); }; @@ -1859,6 +1944,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetListingsResponseSchema); }; @@ -1916,6 +2003,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1964,6 +2053,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -2004,6 +2095,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListProvidersResponseSchema); }; @@ -2049,6 +2142,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -2081,6 +2176,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateExchangeResponseSchema); }; @@ -2110,6 +2207,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -2142,6 +2241,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateListingResponseSchema); }; @@ -2174,6 +2275,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -2206,6 +2309,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateProviderResponseSchema); }; @@ -2238,6 +2343,8 @@ export class MarketplacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/marketplaces/src/v1/utils.ts b/packages/marketplaces/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/marketplaces/src/v1/utils.ts +++ b/packages/marketplaces/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/modelregistry/src/v1/client.ts b/packages/modelregistry/src/v1/client.ts index d6598e9d..9e9ce029 100755 --- a/packages/modelregistry/src/v1/client.ts +++ b/packages/modelregistry/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -151,6 +152,9 @@ export class ModelRegistryClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -163,6 +167,9 @@ export class ModelRegistryClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -190,6 +197,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalApproveTransitionResponseSchema); }; @@ -222,6 +231,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateCommentResponseSchema); }; @@ -254,6 +265,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -286,6 +299,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateTransitionResponseSchema); }; @@ -320,6 +335,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteCommentResponseSchema); }; @@ -357,6 +374,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -406,6 +425,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteTransitionResponseSchema); }; @@ -444,6 +465,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -493,6 +516,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -550,6 +575,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTransitionResponseSchema); }; @@ -579,6 +606,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRejectTransitionResponseSchema); }; @@ -611,6 +640,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -650,6 +681,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -682,6 +715,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateCommentResponseSchema); }; @@ -714,6 +749,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -746,6 +783,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateModelVersionResponseSchema); }; @@ -778,6 +817,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -818,6 +859,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteModelVersionResponseSchema); }; @@ -858,6 +901,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -895,6 +940,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -935,6 +982,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -975,6 +1024,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetModelVersionResponseSchema); }; @@ -1012,6 +1063,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1044,6 +1097,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetLatestVersionsResponseSchema); }; @@ -1081,6 +1136,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1130,6 +1187,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1176,6 +1235,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1239,6 +1300,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1288,6 +1351,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSetModelVersionTagResponseSchema); }; @@ -1317,6 +1382,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1349,6 +1416,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateModelVersionResponseSchema); }; @@ -1378,6 +1447,8 @@ export class ModelRegistryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/modelregistry/src/v1/utils.ts b/packages/modelregistry/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/modelregistry/src/v1/utils.ts +++ b/packages/modelregistry/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/modelserving/src/v1/client.ts b/packages/modelserving/src/v1/client.ts index 17357d7d..10868ece 100755 --- a/packages/modelserving/src/v1/client.ts +++ b/packages/modelserving/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -86,6 +87,9 @@ export class ModelServingClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -98,6 +102,9 @@ export class ModelServingClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -128,6 +135,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInferenceEndpointDetailedSchema); }; @@ -168,6 +177,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInferenceEndpointDetailedSchema); }; @@ -210,6 +221,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -241,6 +254,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { contents: httpResp.body ?? undefined, @@ -271,6 +286,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInferenceEndpointDetailedSchema); }; @@ -299,6 +316,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { contents: httpResp.body ?? undefined, @@ -329,6 +348,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -360,6 +381,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetServedModelLogsResponseSchema); }; @@ -388,6 +411,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -423,6 +448,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -458,6 +485,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -493,6 +522,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInferenceEndpointDetailedSchema); }; @@ -536,6 +567,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -568,6 +601,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalInferenceEndpointDetailedSchema); }; @@ -614,6 +649,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -646,6 +683,8 @@ export class ModelServingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { contents: httpResp.body ?? undefined, diff --git a/packages/modelserving/src/v1/utils.ts b/packages/modelserving/src/v1/utils.ts index a56d896d..6743e4c7 100755 --- a/packages/modelserving/src/v1/utils.ts +++ b/packages/modelserving/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { @@ -188,10 +222,21 @@ export function flattenQueryParams( export async function sendAndCheckError( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // These methods stream the request body, so it is never drained for logging. + if (opts.request.body !== undefined && opts.request.body !== null) { + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -201,10 +246,22 @@ export async function sendAndCheckError( throw e; } - opts.logger.debug('HTTP response', {statusCode: resp.statusCode}); - if (resp.statusCode < 200 || resp.statusCode >= 300) { const body = await readAll(resp.body); + const responseLog: Record = { + statusCode: resp.statusCode, + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders( + resp.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { throw apiErr; @@ -212,5 +269,15 @@ export async function sendAndCheckError( throw new Error(`unexpected HTTP status ${String(resp.statusCode)}`); } + // The success body is a stream returned to the caller, so it is not drained. + const responseLog: Record = { + statusCode: resp.statusCode, + body: '', + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); + return resp; } diff --git a/packages/modelservingquery/src/v1/client.ts b/packages/modelservingquery/src/v1/client.ts index b494fd52..90a48b67 100644 --- a/packages/modelservingquery/src/v1/client.ts +++ b/packages/modelservingquery/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -36,6 +37,9 @@ export class ModelServingQueryClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -48,6 +52,9 @@ export class ModelServingQueryClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -75,6 +82,8 @@ export class ModelServingQueryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQueryEndpointResponseSchema); }; diff --git a/packages/modelservingquery/src/v1/utils.ts b/packages/modelservingquery/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/modelservingquery/src/v1/utils.ts +++ b/packages/modelservingquery/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/networking/src/v1/client.ts b/packages/networking/src/v1/client.ts index 8b7d50e1..24f32fc8 100755 --- a/packages/networking/src/v1/client.ts +++ b/packages/networking/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -151,6 +152,9 @@ export class NetworkingClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -164,6 +168,9 @@ export class NetworkingClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -206,6 +213,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -234,6 +243,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -262,6 +273,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -290,6 +303,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -334,6 +349,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -381,6 +398,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -422,6 +441,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -453,6 +474,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -476,6 +499,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -513,6 +538,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListEndpointsResponseSchema); }; @@ -572,6 +599,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateIpAccessListResponseSchema); }; @@ -600,6 +629,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteIpAccessListResponseSchema); }; @@ -628,6 +659,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetIpAccessListResponseSchema); }; @@ -656,6 +689,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListIpAccessListsResponseSchema); }; @@ -698,6 +733,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -744,6 +781,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateIpAccessListResponseSchema); }; @@ -783,6 +822,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -810,6 +851,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -830,6 +873,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -864,6 +909,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -924,6 +971,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNccPrivateEndpointRuleSchema); }; @@ -955,6 +1004,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNccPrivateEndpointRuleSchema); }; @@ -980,6 +1031,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNccPrivateEndpointRuleSchema); }; @@ -1011,6 +1064,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1072,6 +1127,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNccPrivateEndpointRuleSchema); }; @@ -1104,6 +1161,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountNetworkPolicySchema); }; @@ -1128,6 +1187,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1148,6 +1209,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountNetworkPolicySchema); }; @@ -1179,6 +1242,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1228,6 +1293,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountNetworkPolicySchema); }; @@ -1254,6 +1321,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNetworkSchema); }; @@ -1288,6 +1357,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1329,6 +1400,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerFacingVpcEndpointSchema); }; @@ -1358,6 +1431,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNetworkSchema); }; @@ -1383,6 +1458,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1411,6 +1488,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerFacingVpcEndpointSchema); }; @@ -1436,6 +1515,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNetworkSchema); }; @@ -1461,6 +1542,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1494,6 +1577,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomerFacingVpcEndpointSchema); }; @@ -1519,6 +1604,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { networks: parseResponse( @@ -1549,6 +1636,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { privateAccessSettings: parseResponse( @@ -1581,6 +1670,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { vpcEndpoints: parseResponse( @@ -1620,6 +1711,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1651,6 +1744,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceNetworkOptionSchema); }; @@ -1683,6 +1778,8 @@ export class NetworkingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceNetworkOptionSchema); }; diff --git a/packages/networking/src/v1/utils.ts b/packages/networking/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/networking/src/v1/utils.ts +++ b/packages/networking/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/notificationdestinations/src/v1/client.ts b/packages/notificationdestinations/src/v1/client.ts index 084835fc..5d6a93bb 100644 --- a/packages/notificationdestinations/src/v1/client.ts +++ b/packages/notificationdestinations/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class NotificationDestinationsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class NotificationDestinationsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -91,6 +98,8 @@ export class NotificationDestinationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNotificationDestinationSchema); }; @@ -119,6 +128,8 @@ export class NotificationDestinationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEmptySchema); }; @@ -147,6 +158,8 @@ export class NotificationDestinationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNotificationDestinationSchema); }; @@ -184,6 +197,8 @@ export class NotificationDestinationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -236,6 +251,8 @@ export class NotificationDestinationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalNotificationDestinationSchema); }; diff --git a/packages/notificationdestinations/src/v1/utils.ts b/packages/notificationdestinations/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/notificationdestinations/src/v1/utils.ts +++ b/packages/notificationdestinations/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/oauth/src/v1/client.ts b/packages/oauth/src/v1/client.ts index 6e15a07d..d9107dfe 100755 --- a/packages/oauth/src/v1/client.ts +++ b/packages/oauth/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -72,6 +73,9 @@ export class OAuthClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -84,6 +88,9 @@ export class OAuthClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -115,6 +122,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -151,6 +160,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -182,6 +193,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -213,6 +226,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -241,6 +256,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCustomOAuthAppIntegrationSchema); }; @@ -266,6 +283,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -309,6 +328,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -363,6 +384,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -420,6 +443,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -472,6 +497,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -507,6 +534,8 @@ export class OAuthClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/oauth/src/v1/utils.ts b/packages/oauth/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/oauth/src/v1/utils.ts +++ b/packages/oauth/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/options/src/client/index.ts b/packages/options/src/client/index.ts index 971e866f..88e6ee5f 100644 --- a/packages/options/src/client/index.ts +++ b/packages/options/src/client/index.ts @@ -47,4 +47,16 @@ export interface ClientOptions { /** Logger used to record diagnostic messages. */ logger?: Logger; + + /** + * When true, request and response headers are included in debug-level HTTP + * logs. Authorization-family headers are always redacted. Defaults to false. + */ + debugHeaders?: boolean; + + /** + * Maximum number of bytes logged per string value and header value in + * debug-level HTTP logs. Defaults to 96. + */ + debugTruncateBytes?: number; } diff --git a/packages/pipelines/src/v2/client.ts b/packages/pipelines/src/v2/client.ts index a57ae054..2c146771 100755 --- a/packages/pipelines/src/v2/client.ts +++ b/packages/pipelines/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -83,6 +84,9 @@ export class PipelinesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -95,6 +99,9 @@ export class PipelinesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -126,6 +133,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalApplyEnvironmentResponseSchema); }; @@ -159,6 +168,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalClonePipelineResponseSchema); }; @@ -191,6 +202,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreatePipelineResponseSchema); }; @@ -231,6 +244,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeletePipelineResponseSchema); }; @@ -260,6 +275,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEditPipelineResponseSchema); }; @@ -303,6 +320,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListPipelineEventsResponseSchema); }; @@ -348,6 +367,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetPipelineResponseSchema); }; @@ -376,6 +397,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetUpdateResponseSchema); }; @@ -419,6 +442,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListPipelinesResponseSchema); }; @@ -476,6 +501,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListUpdatesResponseSchema); }; @@ -505,6 +532,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStartUpdateResponseSchema); }; @@ -534,6 +563,8 @@ export class PipelinesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStopPipelineResponseSchema); }; diff --git a/packages/pipelines/src/v2/utils.ts b/packages/pipelines/src/v2/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/pipelines/src/v2/utils.ts +++ b/packages/pipelines/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/policyfamilies/src/v2/client.ts b/packages/policyfamilies/src/v2/client.ts index f8e6de92..40fc66c2 100755 --- a/packages/policyfamilies/src/v2/client.ts +++ b/packages/policyfamilies/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -40,6 +41,9 @@ export class PolicyFamiliesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -52,6 +56,9 @@ export class PolicyFamiliesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -84,6 +91,8 @@ export class PolicyFamiliesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPolicyFamilySchema); }; @@ -121,6 +130,8 @@ export class PolicyFamiliesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListPolicyFamiliesResponseSchema); }; diff --git a/packages/policyfamilies/src/v2/utils.ts b/packages/policyfamilies/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/policyfamilies/src/v2/utils.ts +++ b/packages/policyfamilies/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/postgres/src/v1/client.ts b/packages/postgres/src/v1/client.ts index bd5fbe09..8a611b8a 100644 --- a/packages/postgres/src/v1/client.ts +++ b/packages/postgres/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -126,6 +127,9 @@ export class PostgresClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -138,6 +142,9 @@ export class PostgresClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -180,6 +187,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -229,6 +238,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -282,6 +293,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -334,6 +347,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -383,6 +398,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -432,6 +449,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -481,6 +500,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -523,6 +544,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -559,6 +582,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -595,6 +620,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -631,6 +658,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -673,6 +702,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -715,6 +746,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -751,6 +784,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -791,6 +826,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseCredentialSchema); }; @@ -819,6 +856,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalBranchSchema); }; @@ -847,6 +886,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogSchema); }; @@ -875,6 +916,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDatabaseSchema); }; @@ -903,6 +946,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -931,6 +976,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -959,6 +1006,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalProjectSchema); }; @@ -984,6 +1033,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRoleSchema); }; @@ -1012,6 +1063,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSyncedTableSchema); }; @@ -1052,6 +1105,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListBranchesResponseSchema); }; @@ -1106,6 +1161,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListDatabasesResponseSchema); }; @@ -1160,6 +1217,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListEndpointsResponseSchema); }; @@ -1217,6 +1276,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListProjectsResponseSchema); }; @@ -1271,6 +1332,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListRolesResponseSchema); }; @@ -1317,6 +1380,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1354,6 +1419,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1403,6 +1470,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1452,6 +1521,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1501,6 +1572,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1550,6 +1623,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; @@ -1599,6 +1674,8 @@ export class PostgresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOperationSchema); }; diff --git a/packages/postgres/src/v1/utils.ts b/packages/postgres/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/postgres/src/v1/utils.ts +++ b/packages/postgres/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/queries/src/v1/client.ts b/packages/queries/src/v1/client.ts index 747afaa4..a239d8bf 100644 --- a/packages/queries/src/v1/client.ts +++ b/packages/queries/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -53,6 +54,9 @@ export class QueriesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -65,6 +69,9 @@ export class QueriesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -92,6 +99,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQuerySchema); }; @@ -117,6 +126,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQuerySchema); }; @@ -154,6 +165,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListQueriesResponseSchema); }; @@ -208,6 +221,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -256,6 +271,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEmptySchema); }; @@ -285,6 +302,8 @@ export class QueriesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQuerySchema); }; diff --git a/packages/queries/src/v1/utils.ts b/packages/queries/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/queries/src/v1/utils.ts +++ b/packages/queries/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/queryhistory/src/v1/client.ts b/packages/queryhistory/src/v1/client.ts index 146e6758..42594ede 100755 --- a/packages/queryhistory/src/v1/client.ts +++ b/packages/queryhistory/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -36,6 +37,9 @@ export class QueryHistoryClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -48,6 +52,9 @@ export class QueryHistoryClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -99,6 +106,8 @@ export class QueryHistoryClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListQueriesResponseSchema); }; diff --git a/packages/queryhistory/src/v1/utils.ts b/packages/queryhistory/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/queryhistory/src/v1/utils.ts +++ b/packages/queryhistory/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/repos/src/v1/client.ts b/packages/repos/src/v1/client.ts index e3586fbe..2fb929fe 100755 --- a/packages/repos/src/v1/client.ts +++ b/packages/repos/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -53,6 +54,9 @@ export class ReposClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -65,6 +69,9 @@ export class ReposClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -96,6 +103,8 @@ export class ReposClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateRepoResponseSchema); }; @@ -124,6 +133,8 @@ export class ReposClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteProjectResponseSchema); }; @@ -152,6 +163,8 @@ export class ReposClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetRepoResponseSchema); }; @@ -192,6 +205,8 @@ export class ReposClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListReposResponseSchema); }; @@ -241,6 +256,8 @@ export class ReposClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateRepoResponseSchema); }; diff --git a/packages/repos/src/v1/utils.ts b/packages/repos/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/repos/src/v1/utils.ts +++ b/packages/repos/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/scim/src/v1/client.ts b/packages/scim/src/v1/client.ts index d711725b..3ba586c4 100644 --- a/packages/scim/src/v1/client.ts +++ b/packages/scim/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -125,6 +126,9 @@ export class ScimClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -138,6 +142,9 @@ export class ScimClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -162,6 +169,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountGroupSchema); }; @@ -186,6 +195,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -206,6 +217,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountGroupSchema); }; @@ -260,6 +273,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAccountGroupsResponseSchema); }; @@ -303,6 +318,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -323,6 +340,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -347,6 +366,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountServicePrincipalSchema); }; @@ -371,6 +392,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -391,6 +414,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountServicePrincipalSchema); }; @@ -440,6 +465,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -489,6 +516,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -516,6 +545,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -537,6 +568,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountUserSchema); }; @@ -561,6 +594,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -605,6 +640,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccountUserSchema); }; @@ -654,6 +691,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAccountUsersResponseSchema); }; @@ -697,6 +736,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -717,6 +758,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -746,6 +789,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUserSchema); }; @@ -775,6 +820,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGroupSchema); }; @@ -802,6 +849,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -822,6 +871,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGroupSchema); }; @@ -874,6 +925,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListGroupsResponseSchema); }; @@ -920,6 +973,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -943,6 +998,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -970,6 +1027,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalServicePrincipalSchema); }; @@ -997,6 +1056,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1020,6 +1081,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalServicePrincipalSchema); }; @@ -1072,6 +1135,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1121,6 +1186,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1151,6 +1218,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1175,6 +1244,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUserSchema); }; @@ -1202,6 +1273,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1225,6 +1298,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -1256,6 +1331,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPasswordPermissionsSchema); }; @@ -1305,6 +1382,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUserSchema); }; @@ -1357,6 +1436,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListUsersResponseSchema); }; @@ -1400,6 +1481,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -1424,6 +1507,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPasswordPermissionsSchema); }; @@ -1453,6 +1538,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPasswordPermissionsSchema); }; @@ -1481,6 +1568,8 @@ export class ScimClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); diff --git a/packages/scim/src/v1/utils.ts b/packages/scim/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/scim/src/v1/utils.ts +++ b/packages/scim/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/secrets/src/v1/client.ts b/packages/secrets/src/v1/client.ts index 9bff515e..1e7caff8 100755 --- a/packages/secrets/src/v1/client.ts +++ b/packages/secrets/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -74,6 +75,9 @@ export class SecretsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -86,6 +90,9 @@ export class SecretsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -154,6 +161,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateScopeResponseSchema); }; @@ -200,6 +209,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteAclResponseSchema); }; @@ -243,6 +254,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteScopeResponseSchema); }; @@ -288,6 +301,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteSecretResponseSchema); }; @@ -339,6 +354,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAclItemSchema); }; @@ -405,6 +422,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetSecretResponseSchema); }; @@ -460,6 +479,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListAclsResponseSchema); }; @@ -506,6 +527,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListScopesResponseSchema); }; @@ -566,6 +589,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSecretsResponseSchema); }; @@ -628,6 +653,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPutAclResponseSchema); }; @@ -688,6 +715,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPutSecretResponseSchema); }; diff --git a/packages/secrets/src/v1/utils.ts b/packages/secrets/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/secrets/src/v1/utils.ts +++ b/packages/secrets/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/settings/src/v2/client.ts b/packages/settings/src/v2/client.ts index 1e61a043..d300c1a7 100644 --- a/packages/settings/src/v2/client.ts +++ b/packages/settings/src/v2/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -60,6 +61,9 @@ export class SettingsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -73,6 +77,9 @@ export class SettingsClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -96,6 +103,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSettingSchema); }; @@ -125,6 +134,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUserPreferenceSchema); }; @@ -153,6 +164,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSettingSchema); }; @@ -191,6 +204,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -251,6 +266,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -315,6 +332,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -366,6 +385,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSettingSchema); }; @@ -398,6 +419,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUserPreferenceSchema); }; @@ -432,6 +455,8 @@ export class SettingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSettingSchema); }; diff --git a/packages/settings/src/v2/utils.ts b/packages/settings/src/v2/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/settings/src/v2/utils.ts +++ b/packages/settings/src/v2/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/sharing/src/v1/client.ts b/packages/sharing/src/v1/client.ts index 1b450cdd..0eaa6ca8 100755 --- a/packages/sharing/src/v1/client.ts +++ b/packages/sharing/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -108,6 +109,9 @@ export class SharingClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -120,6 +124,9 @@ export class SharingClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -167,6 +174,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -199,6 +208,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalProviderInfoSchema); }; @@ -231,6 +242,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRecipientInfoSchema); }; @@ -263,6 +276,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalShareInfoSchema); }; @@ -293,6 +308,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -316,6 +333,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteProviderResponseSchema); }; @@ -344,6 +363,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteRecipientResponseSchema); }; @@ -372,6 +393,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteShareResponseSchema); }; @@ -400,6 +423,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -434,6 +459,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFederationPolicySchema); }; @@ -465,6 +492,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalProviderInfoSchema); }; @@ -498,6 +527,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRecipientInfoSchema); }; @@ -532,6 +563,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalShareInfoSchema); }; @@ -572,6 +605,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -638,6 +673,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -683,6 +720,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListProviderSharesResponseSchema); }; @@ -749,6 +788,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListProvidersResponseSchema); }; @@ -803,6 +844,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -855,6 +898,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListRecipientsResponseSchema); }; @@ -912,6 +957,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -955,6 +1002,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSharesResponseSchema); }; @@ -1003,6 +1052,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRetrieveTokenResponseSchema); }; @@ -1035,6 +1086,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRecipientInfoSchema); }; @@ -1067,6 +1120,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalProviderInfoSchema); }; @@ -1099,6 +1154,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRecipientInfoSchema); }; @@ -1144,6 +1201,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalShareInfoSchema); }; @@ -1183,6 +1242,8 @@ export class SharingClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/sharing/src/v1/utils.ts b/packages/sharing/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/sharing/src/v1/utils.ts +++ b/packages/sharing/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/statementexecution/src/v1/client.ts b/packages/statementexecution/src/v1/client.ts index edeea1b0..c63b3844 100644 --- a/packages/statementexecution/src/v1/client.ts +++ b/packages/statementexecution/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -47,6 +48,9 @@ export class StatementExecutionClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -59,6 +63,9 @@ export class StatementExecutionClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -89,6 +96,8 @@ export class StatementExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCancelStatementResponseSchema); }; @@ -168,6 +177,8 @@ export class StatementExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStatementResponseSchema); }; @@ -205,6 +216,8 @@ export class StatementExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalResultDataSchema); }; @@ -244,6 +257,8 @@ export class StatementExecutionClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStatementResponseSchema); }; diff --git a/packages/statementexecution/src/v1/utils.ts b/packages/statementexecution/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/statementexecution/src/v1/utils.ts +++ b/packages/statementexecution/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/storageconfigurations/src/v1/client.ts b/packages/storageconfigurations/src/v1/client.ts index 734bae24..f4f9a7c8 100644 --- a/packages/storageconfigurations/src/v1/client.ts +++ b/packages/storageconfigurations/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -43,6 +44,9 @@ export class StorageConfigurationsClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -55,6 +59,9 @@ export class StorageConfigurationsClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -82,6 +89,8 @@ export class StorageConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageConfigurationSchema); }; @@ -107,6 +116,8 @@ export class StorageConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageConfigurationSchema); }; @@ -132,6 +143,8 @@ export class StorageConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageConfigurationSchema); }; @@ -157,6 +170,8 @@ export class StorageConfigurationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { storageConfigurations: parseResponse( diff --git a/packages/storageconfigurations/src/v1/utils.ts b/packages/storageconfigurations/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/storageconfigurations/src/v1/utils.ts +++ b/packages/storageconfigurations/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/supervisoragents/src/v1/client.ts b/packages/supervisoragents/src/v1/client.ts index e068b716..a073a0b6 100644 --- a/packages/supervisoragents/src/v1/client.ts +++ b/packages/supervisoragents/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -65,6 +66,9 @@ export class SupervisorAgentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -77,6 +81,9 @@ export class SupervisorAgentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -104,6 +111,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -136,6 +145,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSupervisorAgentSchema); }; @@ -177,6 +188,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalToolSchema); }; @@ -204,6 +217,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -226,6 +241,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -248,6 +265,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -271,6 +290,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -299,6 +320,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSupervisorAgentSchema); }; @@ -324,6 +347,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalToolSchema); }; @@ -361,6 +386,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListExamplesResponseSchema); }; @@ -415,6 +442,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -472,6 +501,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListToolsResponseSchema); }; @@ -530,6 +561,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExampleSchema); }; @@ -577,6 +610,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSupervisorAgentSchema); }; @@ -621,6 +656,8 @@ export class SupervisorAgentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalToolSchema); }; diff --git a/packages/supervisoragents/src/v1/utils.ts b/packages/supervisoragents/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/supervisoragents/src/v1/utils.ts +++ b/packages/supervisoragents/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/tagassignments/src/v1/client.ts b/packages/tagassignments/src/v1/client.ts index 0472db16..ca89f419 100644 --- a/packages/tagassignments/src/v1/client.ts +++ b/packages/tagassignments/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class TagAssignmentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class TagAssignmentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -84,6 +91,8 @@ export class TagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagAssignmentSchema); }; @@ -111,6 +120,8 @@ export class TagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -134,6 +145,8 @@ export class TagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagAssignmentSchema); }; @@ -171,6 +184,8 @@ export class TagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTagAssignmentsResponseSchema); }; @@ -229,6 +244,8 @@ export class TagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagAssignmentSchema); }; diff --git a/packages/tagassignments/src/v1/utils.ts b/packages/tagassignments/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/tagassignments/src/v1/utils.ts +++ b/packages/tagassignments/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/tagpolicies/src/v1/client.ts b/packages/tagpolicies/src/v1/client.ts index c91883f2..8a70459e 100644 --- a/packages/tagpolicies/src/v1/client.ts +++ b/packages/tagpolicies/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class TagPoliciesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class TagPoliciesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -84,6 +91,8 @@ export class TagPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagPolicySchema); }; @@ -111,6 +120,8 @@ export class TagPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -134,6 +145,8 @@ export class TagPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagPolicySchema); }; @@ -171,6 +184,8 @@ export class TagPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTagPoliciesResponseSchema); }; @@ -229,6 +244,8 @@ export class TagPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTagPolicySchema); }; diff --git a/packages/tagpolicies/src/v1/utils.ts b/packages/tagpolicies/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/tagpolicies/src/v1/utils.ts +++ b/packages/tagpolicies/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/tokenmanagement/src/v1/client.ts b/packages/tokenmanagement/src/v1/client.ts index a10594d2..f5e26f5a 100755 --- a/packages/tokenmanagement/src/v1/client.ts +++ b/packages/tokenmanagement/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -52,6 +53,9 @@ export class TokenManagementClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -64,6 +68,9 @@ export class TokenManagementClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -91,6 +98,8 @@ export class TokenManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -122,6 +131,8 @@ export class TokenManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRevokeTokenResponseSchema); }; @@ -150,6 +161,8 @@ export class TokenManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetTokenResponseSchema); }; @@ -187,6 +200,8 @@ export class TokenManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTokensResponseSchema); }; @@ -216,6 +231,8 @@ export class TokenManagementClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAdminTokenInfoSchema); }; diff --git a/packages/tokenmanagement/src/v1/utils.ts b/packages/tokenmanagement/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/tokenmanagement/src/v1/utils.ts +++ b/packages/tokenmanagement/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/tokens/src/v1/client.ts b/packages/tokens/src/v1/client.ts index 57f6415c..a8f948ff 100755 --- a/packages/tokens/src/v1/client.ts +++ b/packages/tokens/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -50,6 +51,9 @@ export class TokensClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -62,6 +66,9 @@ export class TokensClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -93,6 +100,8 @@ export class TokensClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateTokenResponseSchema); }; @@ -121,6 +130,8 @@ export class TokensClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTokensResponseSchema); }; @@ -154,6 +165,8 @@ export class TokensClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRevokeTokenResponseSchema); }; @@ -187,6 +200,8 @@ export class TokensClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateTokenResponseSchema); }; diff --git a/packages/tokens/src/v1/utils.ts b/packages/tokens/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/tokens/src/v1/utils.ts +++ b/packages/tokens/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/abacpolicies/src/v1/client.ts b/packages/uc/abacpolicies/src/v1/client.ts index e5663db1..7138d03e 100755 --- a/packages/uc/abacpolicies/src/v1/client.ts +++ b/packages/uc/abacpolicies/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -47,6 +48,9 @@ export class AbacPoliciesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -59,6 +63,9 @@ export class AbacPoliciesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -89,6 +96,8 @@ export class AbacPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPolicyInfoSchema); }; @@ -117,6 +126,8 @@ export class AbacPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeletePolicyResponseSchema); }; @@ -145,6 +156,8 @@ export class AbacPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPolicyInfoSchema); }; @@ -191,6 +204,8 @@ export class AbacPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListPoliciesResponseSchema); }; @@ -249,6 +264,8 @@ export class AbacPoliciesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalPolicyInfoSchema); }; diff --git a/packages/uc/abacpolicies/src/v1/utils.ts b/packages/uc/abacpolicies/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/abacpolicies/src/v1/utils.ts +++ b/packages/uc/abacpolicies/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/artifactallowlists/src/v1/client.ts b/packages/uc/artifactallowlists/src/v1/client.ts index 973fe0cc..0822e583 100644 --- a/packages/uc/artifactallowlists/src/v1/client.ts +++ b/packages/uc/artifactallowlists/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -40,6 +41,9 @@ export class ArtifactAllowlistsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -52,6 +56,9 @@ export class ArtifactAllowlistsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -82,6 +89,8 @@ export class ArtifactAllowlistsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalArtifactAllowlistInfoSchema); }; @@ -116,6 +125,8 @@ export class ArtifactAllowlistsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalArtifactAllowlistInfoSchema); }; diff --git a/packages/uc/artifactallowlists/src/v1/utils.ts b/packages/uc/artifactallowlists/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/artifactallowlists/src/v1/utils.ts +++ b/packages/uc/artifactallowlists/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/catalogs/src/v1/client.ts b/packages/uc/catalogs/src/v1/client.ts index fe20bf78..c568fe53 100755 --- a/packages/uc/catalogs/src/v1/client.ts +++ b/packages/uc/catalogs/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class CatalogsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class CatalogsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -87,6 +94,8 @@ export class CatalogsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogInfoSchema); }; @@ -121,6 +130,8 @@ export class CatalogsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteCatalogResponseSchema); }; @@ -158,6 +169,8 @@ export class CatalogsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogInfoSchema); }; @@ -211,6 +224,8 @@ export class CatalogsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListCatalogsResponseSchema); }; @@ -260,6 +275,8 @@ export class CatalogsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCatalogInfoSchema); }; diff --git a/packages/uc/catalogs/src/v1/utils.ts b/packages/uc/catalogs/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/catalogs/src/v1/utils.ts +++ b/packages/uc/catalogs/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/connections/src/v1/client.ts b/packages/uc/connections/src/v1/client.ts index bfa5feb8..c99e698f 100755 --- a/packages/uc/connections/src/v1/client.ts +++ b/packages/uc/connections/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class ConnectionsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class ConnectionsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -92,6 +99,8 @@ export class ConnectionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalConnectionInfoSchema); }; @@ -120,6 +129,8 @@ export class ConnectionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteConnectionResponseSchema); }; @@ -148,6 +159,8 @@ export class ConnectionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalConnectionInfoSchema); }; @@ -192,6 +205,8 @@ export class ConnectionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListConnectionsResponseSchema); }; @@ -238,6 +253,8 @@ export class ConnectionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalConnectionInfoSchema); }; diff --git a/packages/uc/connections/src/v1/utils.ts b/packages/uc/connections/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/connections/src/v1/utils.ts +++ b/packages/uc/connections/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/credentials/src/v1/client.ts b/packages/uc/credentials/src/v1/client.ts index 360a57ac..66c889f7 100755 --- a/packages/uc/credentials/src/v1/client.ts +++ b/packages/uc/credentials/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -113,6 +114,9 @@ export class CredentialsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -126,6 +130,9 @@ export class CredentialsClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -160,6 +167,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -194,6 +203,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -225,6 +236,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -253,6 +266,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -288,6 +303,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -326,6 +343,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -362,6 +381,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -396,6 +417,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteCredentialResponseSchema); }; @@ -430,6 +453,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -474,6 +499,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -512,6 +539,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTemporaryCredentialsSchema); }; @@ -549,6 +578,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -589,6 +620,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -623,6 +656,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -654,6 +689,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -703,6 +740,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -773,6 +812,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -827,6 +868,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -864,6 +907,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStorageCredentialInfoSchema); }; @@ -906,6 +951,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalValidateCredentialResponseSchema); }; @@ -948,6 +995,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -983,6 +1032,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCredentialsSchema); }; @@ -1008,6 +1059,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCredentialsSchema); }; @@ -1033,6 +1086,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCredentialsSchema); }; @@ -1058,6 +1113,8 @@ export class CredentialsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { credentials: parseResponse( diff --git a/packages/uc/credentials/src/v1/utils.ts b/packages/uc/credentials/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/credentials/src/v1/utils.ts +++ b/packages/uc/credentials/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/entitytagassignments/src/v1/client.ts b/packages/uc/entitytagassignments/src/v1/client.ts index c8aa18ac..075c504a 100644 --- a/packages/uc/entitytagassignments/src/v1/client.ts +++ b/packages/uc/entitytagassignments/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class EntityTagAssignmentsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class EntityTagAssignmentsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -96,6 +103,8 @@ export class EntityTagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEntityTagAssignmentSchema); }; @@ -132,6 +141,8 @@ export class EntityTagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -155,6 +166,8 @@ export class EntityTagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEntityTagAssignmentSchema); }; @@ -197,6 +210,8 @@ export class EntityTagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -270,6 +285,8 @@ export class EntityTagAssignmentsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEntityTagAssignmentSchema); }; diff --git a/packages/uc/entitytagassignments/src/v1/utils.ts b/packages/uc/entitytagassignments/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/entitytagassignments/src/v1/utils.ts +++ b/packages/uc/entitytagassignments/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/externallineage/src/v1/client.ts b/packages/uc/externallineage/src/v1/client.ts index c77f104b..62c81665 100644 --- a/packages/uc/externallineage/src/v1/client.ts +++ b/packages/uc/externallineage/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -49,6 +50,9 @@ export class ExternalLineageClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class ExternalLineageClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -91,6 +98,8 @@ export class ExternalLineageClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -133,6 +142,8 @@ export class ExternalLineageClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -175,6 +186,8 @@ export class ExternalLineageClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -242,6 +255,8 @@ export class ExternalLineageClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/uc/externallineage/src/v1/utils.ts b/packages/uc/externallineage/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/externallineage/src/v1/utils.ts +++ b/packages/uc/externallineage/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/externallocations/src/v1/client.ts b/packages/uc/externallocations/src/v1/client.ts index 057870bc..d1c1a28e 100755 --- a/packages/uc/externallocations/src/v1/client.ts +++ b/packages/uc/externallocations/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class ExternalLocationsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class ExternalLocationsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -93,6 +100,8 @@ export class ExternalLocationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalLocationInfoSchema); }; @@ -127,6 +136,8 @@ export class ExternalLocationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -167,6 +178,8 @@ export class ExternalLocationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalLocationInfoSchema); }; @@ -219,6 +232,8 @@ export class ExternalLocationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -274,6 +289,8 @@ export class ExternalLocationsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalLocationInfoSchema); }; diff --git a/packages/uc/externallocations/src/v1/utils.ts b/packages/uc/externallocations/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/externallocations/src/v1/utils.ts +++ b/packages/uc/externallocations/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/externalmetadata/src/v1/client.ts b/packages/uc/externalmetadata/src/v1/client.ts index aaa9fbcd..e31a4ee0 100644 --- a/packages/uc/externalmetadata/src/v1/client.ts +++ b/packages/uc/externalmetadata/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class ExternalMetadataClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class ExternalMetadataClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -90,6 +97,8 @@ export class ExternalMetadataClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalMetadataSchema); }; @@ -120,6 +129,8 @@ export class ExternalMetadataClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -146,6 +157,8 @@ export class ExternalMetadataClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalMetadataSchema); }; @@ -188,6 +201,8 @@ export class ExternalMetadataClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -256,6 +271,8 @@ export class ExternalMetadataClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalExternalMetadataSchema); }; diff --git a/packages/uc/externalmetadata/src/v1/utils.ts b/packages/uc/externalmetadata/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/externalmetadata/src/v1/utils.ts +++ b/packages/uc/externalmetadata/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/functions/src/v1/client.ts b/packages/uc/functions/src/v1/client.ts index 87abeaaf..44c2c601 100755 --- a/packages/uc/functions/src/v1/client.ts +++ b/packages/uc/functions/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class FunctionsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class FunctionsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -95,6 +102,8 @@ export class FunctionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFunctionInfoSchema); }; @@ -135,6 +144,8 @@ export class FunctionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteFunctionResponseSchema); }; @@ -176,6 +187,8 @@ export class FunctionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFunctionInfoSchema); }; @@ -232,6 +245,8 @@ export class FunctionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListFunctionsResponseSchema); }; @@ -285,6 +300,8 @@ export class FunctionsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalFunctionInfoSchema); }; diff --git a/packages/uc/functions/src/v1/utils.ts b/packages/uc/functions/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/functions/src/v1/utils.ts +++ b/packages/uc/functions/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/grants/src/v1/client.ts b/packages/uc/grants/src/v1/client.ts index b877c243..d6710137 100755 --- a/packages/uc/grants/src/v1/client.ts +++ b/packages/uc/grants/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class GrantsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class GrantsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -102,6 +109,8 @@ export class GrantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -152,6 +161,8 @@ export class GrantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetPermissionsResponseSchema); }; @@ -181,6 +192,8 @@ export class GrantsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdatePermissionsResponseSchema); }; diff --git a/packages/uc/grants/src/v1/utils.ts b/packages/uc/grants/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/grants/src/v1/utils.ts +++ b/packages/uc/grants/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/metastores/src/v1/client.ts b/packages/uc/metastores/src/v1/client.ts index 1f2a79a2..899410fa 100755 --- a/packages/uc/metastores/src/v1/client.ts +++ b/packages/uc/metastores/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -102,6 +103,9 @@ export class MetastoresClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -115,6 +119,9 @@ export class MetastoresClient { this.accountId = options.accountId; this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -142,6 +149,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -174,6 +183,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -208,6 +219,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -236,6 +249,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -264,6 +279,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -297,6 +314,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -325,6 +344,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -353,6 +374,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -385,6 +408,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -417,6 +442,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -454,6 +481,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMetastoreInfoSchema); }; @@ -490,6 +519,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -527,6 +558,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteMetastoreResponseSchema); }; @@ -561,6 +594,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -592,6 +627,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMetastoreAssignmentSchema); }; @@ -620,6 +657,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMetastoreInfoSchema); }; @@ -651,6 +690,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -699,6 +740,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListMetastoresResponseSchema); }; @@ -749,6 +792,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalMetastoreInfoSchema); }; @@ -785,6 +830,8 @@ export class MetastoresClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/uc/metastores/src/v1/utils.ts b/packages/uc/metastores/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/metastores/src/v1/utils.ts +++ b/packages/uc/metastores/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/onlinetables/src/v1/client.ts b/packages/uc/onlinetables/src/v1/client.ts index bb5b1c16..5b8ab277 100644 --- a/packages/uc/onlinetables/src/v1/client.ts +++ b/packages/uc/onlinetables/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -45,6 +46,9 @@ export class OnlineTablesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class OnlineTablesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -84,6 +91,8 @@ export class OnlineTablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOnlineTableSchema); }; @@ -126,6 +135,8 @@ export class OnlineTablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -149,6 +160,8 @@ export class OnlineTablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalOnlineTableSchema); }; diff --git a/packages/uc/onlinetables/src/v1/utils.ts b/packages/uc/onlinetables/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/uc/onlinetables/src/v1/utils.ts +++ b/packages/uc/onlinetables/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/registeredmodels/src/v1/client.ts b/packages/uc/registeredmodels/src/v1/client.ts index 66376b0a..dae5f2c8 100755 --- a/packages/uc/registeredmodels/src/v1/client.ts +++ b/packages/uc/registeredmodels/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -67,6 +68,9 @@ export class RegisteredModelsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -79,6 +83,9 @@ export class RegisteredModelsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -118,6 +125,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRegisteredModelInfoSchema); }; @@ -153,6 +162,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteModelVersionResponseSchema); }; @@ -187,6 +198,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -224,6 +237,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -271,6 +286,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalModelVersionInfoSchema); }; @@ -312,6 +329,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalModelVersionInfoSchema); }; @@ -356,6 +375,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRegisteredModelInfoSchema); }; @@ -412,6 +433,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListModelVersionsResponseSchema); }; @@ -490,6 +513,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -548,6 +573,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRegisteredModelAliasInfoSchema); }; @@ -585,6 +612,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalModelVersionInfoSchema); }; @@ -622,6 +651,8 @@ export class RegisteredModelsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalRegisteredModelInfoSchema); }; diff --git a/packages/uc/registeredmodels/src/v1/utils.ts b/packages/uc/registeredmodels/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/registeredmodels/src/v1/utils.ts +++ b/packages/uc/registeredmodels/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/resourcequotas/src/v1/client.ts b/packages/uc/resourcequotas/src/v1/client.ts index d70967ce..b1a6bc96 100755 --- a/packages/uc/resourcequotas/src/v1/client.ts +++ b/packages/uc/resourcequotas/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -41,6 +42,9 @@ export class ResourceQuotasClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -53,6 +57,9 @@ export class ResourceQuotasClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -83,6 +90,8 @@ export class ResourceQuotasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetQuotaResponseSchema); }; @@ -126,6 +135,8 @@ export class ResourceQuotasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListQuotasResponseSchema); }; diff --git a/packages/uc/resourcequotas/src/v1/utils.ts b/packages/uc/resourcequotas/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/resourcequotas/src/v1/utils.ts +++ b/packages/uc/resourcequotas/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/rfa/src/v1/client.ts b/packages/uc/rfa/src/v1/client.ts index 84536a38..4af6d615 100644 --- a/packages/uc/rfa/src/v1/client.ts +++ b/packages/uc/rfa/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -44,6 +45,9 @@ export class RfaClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -56,6 +60,9 @@ export class RfaClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -94,6 +101,8 @@ export class RfaClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -132,6 +141,8 @@ export class RfaClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccessRequestDestinationsSchema); }; @@ -185,6 +196,8 @@ export class RfaClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalAccessRequestDestinationsSchema); }; diff --git a/packages/uc/rfa/src/v1/utils.ts b/packages/uc/rfa/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/rfa/src/v1/utils.ts +++ b/packages/uc/rfa/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/schemas/src/v1/client.ts b/packages/uc/schemas/src/v1/client.ts index b29cdfef..dafaac42 100755 --- a/packages/uc/schemas/src/v1/client.ts +++ b/packages/uc/schemas/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class SchemasClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class SchemasClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -90,6 +97,8 @@ export class SchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSchemaInfoSchema); }; @@ -127,6 +136,8 @@ export class SchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteSchemaResponseSchema); }; @@ -164,6 +175,8 @@ export class SchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSchemaInfoSchema); }; @@ -217,6 +230,8 @@ export class SchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSchemasResponseSchema); }; @@ -267,6 +282,8 @@ export class SchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSchemaInfoSchema); }; diff --git a/packages/uc/schemas/src/v1/utils.ts b/packages/uc/schemas/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/schemas/src/v1/utils.ts +++ b/packages/uc/schemas/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/secrets/src/v1/client.ts b/packages/uc/secrets/src/v1/client.ts index de066c5f..9366466c 100644 --- a/packages/uc/secrets/src/v1/client.ts +++ b/packages/uc/secrets/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -45,6 +46,9 @@ export class SecretsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -57,6 +61,9 @@ export class SecretsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -92,6 +99,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSecretSchema); }; @@ -123,6 +132,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -160,6 +171,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSecretSchema); }; @@ -215,6 +228,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSecretsResponseSchema); }; @@ -281,6 +296,8 @@ export class SecretsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSecretSchema); }; diff --git a/packages/uc/secrets/src/v1/utils.ts b/packages/uc/secrets/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/secrets/src/v1/utils.ts +++ b/packages/uc/secrets/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/systemschemas/src/v1/client.ts b/packages/uc/systemschemas/src/v1/client.ts index 479290eb..5ef40574 100755 --- a/packages/uc/systemschemas/src/v1/client.ts +++ b/packages/uc/systemschemas/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -46,6 +47,9 @@ export class SystemSchemasClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -58,6 +62,9 @@ export class SystemSchemasClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -87,6 +94,8 @@ export class SystemSchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -122,6 +131,8 @@ export class SystemSchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEnableSystemSchemaResponseSchema); }; @@ -167,6 +178,8 @@ export class SystemSchemasClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListSystemSchemasResponseSchema); }; diff --git a/packages/uc/systemschemas/src/v1/utils.ts b/packages/uc/systemschemas/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/systemschemas/src/v1/utils.ts +++ b/packages/uc/systemschemas/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/tables/src/v1/client.ts b/packages/uc/tables/src/v1/client.ts index 4c2385f1..cedf38c0 100755 --- a/packages/uc/tables/src/v1/client.ts +++ b/packages/uc/tables/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -64,6 +65,9 @@ export class TablesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -76,6 +80,9 @@ export class TablesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -128,6 +135,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTableInfoSchema); }; @@ -167,6 +176,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTableConstraintSchema); }; @@ -199,6 +210,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteTableResponseSchema); }; @@ -246,6 +259,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -300,6 +315,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTableInfoSchema); }; @@ -364,6 +381,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTableSummariesResponseSchema); }; @@ -454,6 +473,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListTablesResponseSchema); }; @@ -509,6 +530,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalTableExistsResponseSchema); }; @@ -542,6 +565,8 @@ export class TablesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalUpdateTableResponseSchema); }; diff --git a/packages/uc/tables/src/v1/utils.ts b/packages/uc/tables/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/tables/src/v1/utils.ts +++ b/packages/uc/tables/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/volumes/src/v1/client.ts b/packages/uc/volumes/src/v1/client.ts index 40e2087d..41996e95 100755 --- a/packages/uc/volumes/src/v1/client.ts +++ b/packages/uc/volumes/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -48,6 +49,9 @@ export class VolumesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -60,6 +64,9 @@ export class VolumesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -106,6 +113,8 @@ export class VolumesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVolumeInfoSchema); }; @@ -140,6 +149,8 @@ export class VolumesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteVolumeResponseSchema); }; @@ -181,6 +192,8 @@ export class VolumesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVolumeInfoSchema); }; @@ -242,6 +255,8 @@ export class VolumesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListVolumesResponseSchema); }; @@ -296,6 +311,8 @@ export class VolumesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVolumeInfoSchema); }; diff --git a/packages/uc/volumes/src/v1/utils.ts b/packages/uc/volumes/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/volumes/src/v1/utils.ts +++ b/packages/uc/volumes/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/uc/workspacebindings/src/v1/client.ts b/packages/uc/workspacebindings/src/v1/client.ts index e5ae6618..b0ded3d9 100755 --- a/packages/uc/workspacebindings/src/v1/client.ts +++ b/packages/uc/workspacebindings/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -50,6 +51,9 @@ export class WorkspaceBindingsClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -62,6 +66,9 @@ export class WorkspaceBindingsClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -91,6 +98,8 @@ export class WorkspaceBindingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -139,6 +148,8 @@ export class WorkspaceBindingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -194,6 +205,8 @@ export class WorkspaceBindingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -232,6 +245,8 @@ export class WorkspaceBindingsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/uc/workspacebindings/src/v1/utils.ts b/packages/uc/workspacebindings/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/uc/workspacebindings/src/v1/utils.ts +++ b/packages/uc/workspacebindings/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/usagedashboards/src/v1/client.ts b/packages/usagedashboards/src/v1/client.ts index 32450b51..be95664f 100755 --- a/packages/usagedashboards/src/v1/client.ts +++ b/packages/usagedashboards/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {HttpClient} from '@databricks/sdk-core/http'; @@ -41,6 +42,9 @@ export class UsageDashboardsClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -53,6 +57,9 @@ export class UsageDashboardsClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -80,6 +87,8 @@ export class UsageDashboardsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -117,6 +126,8 @@ export class UsageDashboardsClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/usagedashboards/src/v1/utils.ts b/packages/usagedashboards/src/v1/utils.ts index 23e0e9dc..ceca94e6 100755 --- a/packages/usagedashboards/src/v1/utils.ts +++ b/packages/usagedashboards/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import JSONBig from 'json-bigint'; import type {z} from 'zod'; @@ -22,6 +26,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -71,10 +79,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -86,10 +111,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/vectorsearch/src/v1/client.ts b/packages/vectorsearch/src/v1/client.ts index 6647c4b6..027c2dd3 100644 --- a/packages/vectorsearch/src/v1/client.ts +++ b/packages/vectorsearch/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -97,6 +98,9 @@ export class VectorSearchClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -109,6 +113,9 @@ export class VectorSearchClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -136,6 +143,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -176,6 +185,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVectorIndexSchema); }; @@ -210,6 +221,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -241,6 +254,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteEndpointResponseSchema); }; @@ -269,6 +284,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteVectorIndexResponseSchema); }; @@ -297,6 +314,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -334,6 +353,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalVectorIndexSchema); }; @@ -368,6 +389,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListEndpointResponseSchema); }; @@ -422,6 +445,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListVectorIndexResponseSchema); }; @@ -468,6 +493,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEndpointSchema); }; @@ -500,6 +527,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -532,6 +561,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQueryVectorIndexResponseSchema); }; @@ -564,6 +595,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalQueryVectorIndexResponseSchema); }; @@ -596,6 +629,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -628,6 +663,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalScanVectorIndexResponseSchema); }; @@ -657,6 +694,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalSyncVectorIndexResponseSchema); }; @@ -689,6 +728,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -721,6 +762,8 @@ export class VectorSearchClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, diff --git a/packages/vectorsearch/src/v1/utils.ts b/packages/vectorsearch/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/vectorsearch/src/v1/utils.ts +++ b/packages/vectorsearch/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/warehouses/src/v1/client.ts b/packages/warehouses/src/v1/client.ts index e1e7daf2..990c2c30 100755 --- a/packages/warehouses/src/v1/client.ts +++ b/packages/warehouses/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -82,6 +83,9 @@ export class WarehousesClient { private readonly workspaceId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -94,6 +98,9 @@ export class WarehousesClient { this.host = options.host.replace(/\/$/, ''); this.workspaceId = options.workspaceId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -142,6 +149,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDefaultWarehouseOverrideSchema); }; @@ -171,6 +180,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalCreateWarehouseResponseSchema); }; @@ -213,6 +224,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); }; await executeCall(call, options); @@ -236,6 +249,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDeleteWarehouseResponseSchema); }; @@ -265,6 +280,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalEditWarehouseResponseSchema); }; @@ -308,6 +325,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDefaultWarehouseOverrideSchema); }; @@ -336,6 +355,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalGetWarehouseResponseSchema); }; @@ -364,6 +385,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -407,6 +430,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -467,6 +492,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalListWarehousesResponseSchema); }; @@ -516,6 +543,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse( respBody, @@ -548,6 +577,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStartResponseSchema); }; @@ -588,6 +619,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalStopResponseSchema); }; @@ -649,6 +682,8 @@ export class WarehousesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalDefaultWarehouseOverrideSchema); }; diff --git a/packages/warehouses/src/v1/utils.ts b/packages/warehouses/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/warehouses/src/v1/utils.ts +++ b/packages/warehouses/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) { diff --git a/packages/workspaces/src/v1/client.ts b/packages/workspaces/src/v1/client.ts index 6560775b..74d82aeb 100644 --- a/packages/workspaces/src/v1/client.ts +++ b/packages/workspaces/src/v1/client.ts @@ -4,6 +4,7 @@ import {VERSION as AUTH_VERSION} from '@databricks/sdk-auth'; import {createDefault} from '@databricks/sdk-core/clientinfo'; import type {Logger} from '@databricks/sdk-core/logger'; import {NoOpLogger} from '@databricks/sdk-core/logger'; +import {DEFAULT_DEBUG_TRUNCATE_BYTES} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {ClientOptions} from '@databricks/sdk-options/client'; import type {LroOptions} from '@databricks/sdk-options/lro'; @@ -49,6 +50,9 @@ export class WorkspacesClient { private readonly accountId: string | undefined; private readonly httpClient: HttpClient; private readonly logger: Logger; + // Resolved debug-logging toggles passed into each HTTP call. + private readonly debugHeaders: boolean; + private readonly debugTruncateBytes: number; // User-Agent header value. Composed once at construction from // createDefault() merged with this package's identity and the active // credential's name. @@ -61,6 +65,9 @@ export class WorkspacesClient { this.host = options.host.replace(/\/$/, ''); this.accountId = options.accountId; this.logger = options.logger ?? new NoOpLogger(); + this.debugHeaders = options.debugHeaders ?? false; + this.debugTruncateBytes = + options.debugTruncateBytes ?? DEFAULT_DEBUG_TRUNCATE_BYTES; const info = createDefault() .with(PACKAGE_SEGMENT) .with({key: 'sdk-js-auth', value: AUTH_VERSION}) @@ -97,6 +104,8 @@ export class WorkspacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceSchema); }; @@ -135,6 +144,8 @@ export class WorkspacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceSchema); }; @@ -163,6 +174,8 @@ export class WorkspacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceSchema); }; @@ -188,6 +201,8 @@ export class WorkspacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = { workspaces: parseResponse( @@ -234,6 +249,8 @@ export class WorkspacesClient { request: httpReq, httpClient: this.httpClient, logger: this.logger, + debugHeaders: this.debugHeaders, + debugTruncateBytes: this.debugTruncateBytes, }); resp = parseResponse(respBody, unmarshalWorkspaceSchema); }; diff --git a/packages/workspaces/src/v1/utils.ts b/packages/workspaces/src/v1/utils.ts index 5e8d4841..cf05b091 100755 --- a/packages/workspaces/src/v1/utils.ts +++ b/packages/workspaces/src/v1/utils.ts @@ -9,6 +9,10 @@ import type { HttpResponse, } from '@databricks/sdk-core/http'; import type {Logger} from '@databricks/sdk-core/logger'; +import { + redactedDumpBody, + redactHeaders, +} from '@databricks/sdk-core/logger/debug'; import type {CallOptions} from '@databricks/sdk-options/call'; import type {LroOptions} from '@databricks/sdk-options/lro'; import JSONBig from 'json-bigint'; @@ -23,6 +27,10 @@ export interface HttpCallOptions { readonly request: HttpRequest; readonly httpClient: HttpClient; readonly logger: Logger; + // When true, redacted request/response headers are logged at debug level. + readonly debugHeaders: boolean; + // Per-value byte budget for debug-level body and header logs. + readonly debugTruncateBytes: number; } /** @@ -95,10 +103,27 @@ async function readAll( export async function executeHttpCall( opts: HttpCallOptions ): Promise { - opts.logger.debug('HTTP request', { + const requestLog: Record = { method: opts.request.method, url: opts.request.url, - }); + }; + // Bodies are logged independent of debugHeaders, matching the Go SDK. + if (typeof opts.request.body === 'string') { + requestLog.requestBody = redactedDumpBody( + opts.request.body, + opts.debugTruncateBytes + ); + } else if (opts.request.body !== undefined && opts.request.body !== null) { + // A streaming body is not drained, matching the Go SDK's . + requestLog.requestBody = ''; + } + if (opts.debugHeaders) { + requestLog.headers = redactHeaders( + opts.request.headers, + opts.debugTruncateBytes + ); + } + opts.logger.debug('HTTP request', requestLog); let resp: HttpResponse; try { @@ -110,10 +135,19 @@ export async function executeHttpCall( const body = await readAll(resp.body); - opts.logger.debug('HTTP response', { + // Secret-bearing fields are redacted by key and every value is truncated, so + // the body is safe to log; matches the Go SDK. + const responseLog: Record = { statusCode: resp.statusCode, - body: new TextDecoder().decode(body), - }); + body: redactedDumpBody( + new TextDecoder().decode(body), + opts.debugTruncateBytes + ), + }; + if (opts.debugHeaders) { + responseLog.headers = redactHeaders(resp.headers, opts.debugTruncateBytes); + } + opts.logger.debug('HTTP response', responseLog); const apiErr = ApiError.fromHttpError(resp.statusCode, resp.headers, body); if (apiErr !== undefined) {