From 7a3446a7b78fe4b6553994d53b6ce329d419999a Mon Sep 17 00:00:00 2001 From: Parth Bansal Date: Thu, 4 Jun 2026 13:34:49 +0000 Subject: [PATCH] Log redacted+truncated HTTP bodies at debug level Restores HTTP body logging in the generated executeHttpCall / sendAndCheckError helpers, which had been dropped entirely to avoid leaking plaintext secrets (e.g. getSecret()). Bodies are now logged at debug the way the Go SDK does it: secret-bearing JSON fields (token, password, access_token, string_value, ...) are replaced with **REDACTED** and every value is truncated to debugTruncateBytes, so secrets no longer reach the logs while the debugging signal returns. Adds debugHeaders and debugTruncateBytes to ClientOptions (defaulting to false and 96, matching Go's DebugHeaders / DebugTruncateBytes). Headers are logged only when debugHeaders is true, and Authorization-family headers are always redacted to REDACTED. The redaction and truncation logic lives in a new, unit-tested @databricks/sdk-core/logger/debug module (onlyNBytes, redactedDumpBody, redactHeaders) exported under the ./logger/debug subpath. Regenerates utils.ts and client.ts across all packages to thread the new options and emit the redacted body/header logs. Adds core unit tests for the redaction helpers and an examples integration test asserting the body is logged (redacted) at debug and headers only appear when debugHeaders is set. Co-authored-by: Isaac --- packages/accessmanagement/src/v1/client.ts | 37 +++++ packages/accessmanagement/src/v1/utils.ts | 44 ++++- packages/alerts/src/v1/client.ts | 17 ++ packages/alerts/src/v1/utils.ts | 44 ++++- packages/alerts/src/v2/client.ts | 17 ++ packages/alerts/src/v2/utils.ts | 44 ++++- packages/apps/src/v1/client.ts | 57 +++++++ packages/apps/src/v1/utils.ts | 44 ++++- packages/authentication/src/v1/client.ts | 39 +++++ packages/authentication/src/v1/utils.ts | 44 ++++- packages/budgetpolicy/src/v1/client.ts | 17 ++ packages/budgetpolicy/src/v1/utils.ts | 44 ++++- packages/budgets/src/v1/client.ts | 17 ++ packages/budgets/src/v1/utils.ts | 44 ++++- packages/bundledeployments/src/v1/client.ts | 35 ++++ packages/bundledeployments/src/v1/utils.ts | 44 ++++- packages/cleanrooms/src/v1/client.ts | 47 ++++++ packages/cleanrooms/src/v1/utils.ts | 44 ++++- packages/clusterlibraries/src/v2/client.ts | 15 ++ packages/clusterlibraries/src/v2/utils.ts | 44 ++++- packages/clusterpolicies/src/v2/client.ts | 17 ++ packages/clusterpolicies/src/v2/utils.ts | 44 ++++- packages/clusters/src/v2/client.ts | 47 ++++++ packages/clusters/src/v2/utils.ts | 44 ++++- packages/commandexecution/src/v2/client.ts | 19 +++ packages/commandexecution/src/v2/utils.ts | 44 ++++- packages/core/package.json | 5 + packages/core/src/logger/debug.ts | 154 ++++++++++++++++++ packages/core/src/logger/index.ts | 6 + packages/core/tests/logger/debug.test.ts | 106 ++++++++++++ packages/customllms/src/v1/client.ts | 19 +++ packages/customllms/src/v1/utils.ts | 44 ++++- packages/database/src/v1/client.ts | 55 +++++++ packages/database/src/v1/utils.ts | 44 ++++- packages/dataclassification/src/v1/client.ts | 15 ++ packages/dataclassification/src/v1/utils.ts | 44 ++++- packages/dataquality/src/v1/client.ts | 29 ++++ packages/dataquality/src/v1/utils.ts | 44 ++++- packages/disasterrecovery/src/v1/client.ts | 27 +++ packages/disasterrecovery/src/v1/utils.ts | 44 ++++- packages/environments/src/v1/client.ts | 25 +++ packages/environments/src/v1/utils.ts | 44 ++++- packages/examples/tests/debug-logging.test.ts | 106 ++++++++++++ packages/experiments/src/v1/client.ts | 77 +++++++++ packages/experiments/src/v1/utils.ts | 44 ++++- packages/features/src/v1/client.ts | 49 ++++++ packages/features/src/v1/utils.ts | 44 ++++- packages/featurestore/src/v1/client.ts | 21 +++ packages/featurestore/src/v1/utils.ts | 44 ++++- packages/files/src/v2/client.ts | 43 +++++ packages/files/src/v2/utils.ts | 85 +++++++++- packages/forecasting/src/v1/client.ts | 11 ++ packages/forecasting/src/v1/utils.ts | 44 ++++- packages/genie/src/v1/client.ts | 63 +++++++ packages/genie/src/v1/utils.ts | 44 ++++- packages/gitcredentials/src/v1/client.ts | 17 ++ packages/gitcredentials/src/v1/utils.ts | 44 ++++- packages/globalinitscripts/src/v2/client.ts | 17 ++ packages/globalinitscripts/src/v2/utils.ts | 44 ++++- packages/instancepools/src/v2/client.ts | 17 ++ packages/instancepools/src/v2/utils.ts | 44 ++++- packages/instanceprofiles/src/v2/client.ts | 15 ++ packages/instanceprofiles/src/v2/utils.ts | 44 ++++- packages/jobs/src/v2/client.ts | 45 +++++ packages/jobs/src/v2/utils.ts | 44 ++++- packages/keyconfigurations/src/v1/client.ts | 15 ++ packages/keyconfigurations/src/v1/utils.ts | 44 ++++- packages/knowledgeassistants/src/v1/client.ts | 39 +++++ packages/knowledgeassistants/src/v1/utils.ts | 44 ++++- packages/lakeview/src/v1/client.ts | 47 ++++++ packages/lakeview/src/v1/utils.ts | 44 ++++- packages/logdelivery/src/v1/client.ts | 15 ++ packages/logdelivery/src/v1/utils.ts | 44 ++++- packages/marketplaces/src/v1/client.ts | 107 ++++++++++++ packages/marketplaces/src/v1/utils.ts | 44 ++++- packages/modelregistry/src/v1/client.ts | 71 ++++++++ packages/modelregistry/src/v1/utils.ts | 44 ++++- packages/modelserving/src/v1/client.ts | 39 +++++ packages/modelserving/src/v1/utils.ts | 85 +++++++++- packages/modelservingquery/src/v1/client.ts | 9 + packages/modelservingquery/src/v1/utils.ts | 44 ++++- packages/networking/src/v1/client.ts | 97 +++++++++++ packages/networking/src/v1/utils.ts | 44 ++++- .../notificationdestinations/src/v1/client.ts | 17 ++ .../notificationdestinations/src/v1/utils.ts | 44 ++++- packages/oauth/src/v1/client.ts | 29 ++++ packages/oauth/src/v1/utils.ts | 44 ++++- packages/options/src/client/index.ts | 12 ++ packages/pipelines/src/v2/client.ts | 31 ++++ packages/pipelines/src/v2/utils.ts | 44 ++++- packages/policyfamilies/src/v2/client.ts | 11 ++ packages/policyfamilies/src/v2/utils.ts | 44 ++++- packages/postgres/src/v1/client.ts | 77 +++++++++ packages/postgres/src/v1/utils.ts | 44 ++++- packages/queries/src/v1/client.ts | 19 +++ packages/queries/src/v1/utils.ts | 44 ++++- packages/queryhistory/src/v1/client.ts | 9 + packages/queryhistory/src/v1/utils.ts | 44 ++++- packages/repos/src/v1/client.ts | 17 ++ packages/repos/src/v1/utils.ts | 44 ++++- packages/scim/src/v1/client.ts | 89 ++++++++++ packages/scim/src/v1/utils.ts | 44 ++++- packages/secrets/src/v1/client.ts | 29 ++++ packages/secrets/src/v1/utils.ts | 44 ++++- packages/settings/src/v2/client.ts | 25 +++ packages/settings/src/v2/utils.ts | 44 ++++- packages/sharing/src/v1/client.ts | 61 +++++++ packages/sharing/src/v1/utils.ts | 44 ++++- packages/statementexecution/src/v1/client.ts | 15 ++ packages/statementexecution/src/v1/utils.ts | 44 ++++- .../storageconfigurations/src/v1/client.ts | 15 ++ .../storageconfigurations/src/v1/utils.ts | 44 ++++- packages/supervisoragents/src/v1/client.ts | 37 +++++ packages/supervisoragents/src/v1/utils.ts | 44 ++++- packages/tagassignments/src/v1/client.ts | 17 ++ packages/tagassignments/src/v1/utils.ts | 44 ++++- packages/tagpolicies/src/v1/client.ts | 17 ++ packages/tagpolicies/src/v1/utils.ts | 44 ++++- packages/tokenmanagement/src/v1/client.ts | 17 ++ packages/tokenmanagement/src/v1/utils.ts | 44 ++++- packages/tokens/src/v1/client.ts | 15 ++ packages/tokens/src/v1/utils.ts | 44 ++++- packages/uc/abacpolicies/src/v1/client.ts | 17 ++ packages/uc/abacpolicies/src/v1/utils.ts | 44 ++++- .../uc/artifactallowlists/src/v1/client.ts | 11 ++ .../uc/artifactallowlists/src/v1/utils.ts | 44 ++++- packages/uc/catalogs/src/v1/client.ts | 17 ++ packages/uc/catalogs/src/v1/utils.ts | 44 ++++- packages/uc/connections/src/v1/client.ts | 17 ++ packages/uc/connections/src/v1/utils.ts | 44 ++++- packages/uc/credentials/src/v1/client.ts | 57 +++++++ packages/uc/credentials/src/v1/utils.ts | 44 ++++- .../uc/entitytagassignments/src/v1/client.ts | 17 ++ .../uc/entitytagassignments/src/v1/utils.ts | 44 ++++- packages/uc/externallineage/src/v1/client.ts | 15 ++ packages/uc/externallineage/src/v1/utils.ts | 44 ++++- .../uc/externallocations/src/v1/client.ts | 17 ++ packages/uc/externallocations/src/v1/utils.ts | 44 ++++- packages/uc/externalmetadata/src/v1/client.ts | 17 ++ packages/uc/externalmetadata/src/v1/utils.ts | 44 ++++- packages/uc/functions/src/v1/client.ts | 17 ++ packages/uc/functions/src/v1/utils.ts | 44 ++++- packages/uc/grants/src/v1/client.ts | 13 ++ packages/uc/grants/src/v1/utils.ts | 44 ++++- packages/uc/metastores/src/v1/client.ts | 47 ++++++ packages/uc/metastores/src/v1/utils.ts | 44 ++++- packages/uc/onlinetables/src/v1/client.ts | 13 ++ packages/uc/onlinetables/src/v1/utils.ts | 44 ++++- packages/uc/registeredmodels/src/v1/client.ts | 31 ++++ packages/uc/registeredmodels/src/v1/utils.ts | 44 ++++- packages/uc/resourcequotas/src/v1/client.ts | 11 ++ packages/uc/resourcequotas/src/v1/utils.ts | 44 ++++- packages/uc/rfa/src/v1/client.ts | 13 ++ packages/uc/rfa/src/v1/utils.ts | 44 ++++- packages/uc/schemas/src/v1/client.ts | 17 ++ packages/uc/schemas/src/v1/utils.ts | 44 ++++- packages/uc/secrets/src/v1/client.ts | 17 ++ packages/uc/secrets/src/v1/utils.ts | 44 ++++- packages/uc/systemschemas/src/v1/client.ts | 13 ++ packages/uc/systemschemas/src/v1/utils.ts | 44 ++++- packages/uc/tables/src/v1/client.ts | 25 +++ packages/uc/tables/src/v1/utils.ts | 44 ++++- packages/uc/volumes/src/v1/client.ts | 17 ++ packages/uc/volumes/src/v1/utils.ts | 44 ++++- .../uc/workspacebindings/src/v1/client.ts | 15 ++ packages/uc/workspacebindings/src/v1/utils.ts | 44 ++++- packages/usagedashboards/src/v1/client.ts | 11 ++ packages/usagedashboards/src/v1/utils.ts | 44 ++++- packages/vectorsearch/src/v1/client.ts | 43 +++++ packages/vectorsearch/src/v1/utils.ts | 44 ++++- packages/warehouses/src/v1/client.ts | 35 ++++ packages/warehouses/src/v1/utils.ts | 44 ++++- packages/workspaces/src/v1/client.ts | 17 ++ packages/workspaces/src/v1/utils.ts | 44 ++++- 174 files changed, 6191 insertions(+), 428 deletions(-) create mode 100644 packages/core/src/logger/debug.ts create mode 100644 packages/core/tests/logger/debug.test.ts create mode 100644 packages/examples/tests/debug-logging.test.ts 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) {