Skip to content

Commit 92c9aae

Browse files
committed
feat(debug): add detailed HTTP request logging for failed API calls
Enhance debugApiResponse function to log comprehensive request details for failed API requests (status >= 400 or errors): - HTTP method (GET, POST, etc.) - Full URL - Response status code - Request duration in milliseconds - Sanitized headers (Authorization redacted for security) Update API functions (queryApiSafeText, sendApiRequest) to track timing and pass request details to debugApiResponse. This improves debugging of API failures by providing complete context for failed requests. Security: Sensitive headers are automatically redacted before logging.
1 parent 352d2b1 commit 92c9aae

File tree

2 files changed

+124
-10
lines changed

2 files changed

+124
-10
lines changed

packages/cli/src/utils/debug.mts

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,88 @@ import {
2828
isDebugNs,
2929
} from '@socketsecurity/lib/debug'
3030

31+
export type ApiRequestDebugInfo = {
32+
method?: string | undefined
33+
url?: string | undefined
34+
headers?: Record<string, string> | undefined
35+
durationMs?: number | undefined
36+
}
37+
3138
/**
32-
* Debug an API response.
39+
* Sanitize headers to remove sensitive information.
40+
* Redacts Authorization and API key headers.
41+
*/
42+
function sanitizeHeaders(
43+
headers?: Record<string, string> | undefined,
44+
): Record<string, string> | undefined {
45+
if (!headers) {
46+
return undefined
47+
}
48+
49+
const sanitized: Record<string, string> = Object.create(null)
50+
for (const [key, value] of Object.entries(headers)) {
51+
const lowerKey = key.toLowerCase()
52+
if (lowerKey === 'authorization' || lowerKey.includes('api-key')) {
53+
sanitized[key] = '[REDACTED]'
54+
} else {
55+
sanitized[key] = value
56+
}
57+
}
58+
return sanitized
59+
}
60+
61+
/**
62+
* Debug an API response with detailed request information.
3363
* Logs essential info without exposing sensitive data.
64+
*
65+
* For failed requests (status >= 400 or error), logs:
66+
* - HTTP method (GET, POST, etc.)
67+
* - Full URL
68+
* - Response status code
69+
* - Sanitized headers (Authorization redacted)
70+
* - Request duration in milliseconds
3471
*/
3572
export function debugApiResponse(
3673
endpoint: string,
3774
status?: number | undefined,
3875
error?: unknown | undefined,
76+
requestInfo?: ApiRequestDebugInfo | undefined,
3977
): void {
4078
if (error) {
41-
debugDir({
79+
const errorDetails = {
80+
__proto__: null,
4281
endpoint,
4382
error: error instanceof Error ? error.message : UNKNOWN_ERROR,
44-
})
83+
...(requestInfo?.method ? { method: requestInfo.method } : {}),
84+
...(requestInfo?.url ? { url: requestInfo.url } : {}),
85+
...(requestInfo?.durationMs !== undefined
86+
? { durationMs: requestInfo.durationMs }
87+
: {}),
88+
...(requestInfo?.headers
89+
? { headers: sanitizeHeaders(requestInfo.headers) }
90+
: {}),
91+
}
92+
debugDir(errorDetails)
4593
} else if (status && status >= 400) {
46-
debug(`API ${endpoint}: HTTP ${status}`)
94+
// For failed requests, log detailed information.
95+
if (requestInfo) {
96+
const failureDetails = {
97+
__proto__: null,
98+
endpoint,
99+
status,
100+
...(requestInfo.method ? { method: requestInfo.method } : {}),
101+
...(requestInfo.url ? { url: requestInfo.url } : {}),
102+
...(requestInfo.durationMs !== undefined
103+
? { durationMs: requestInfo.durationMs }
104+
: {}),
105+
...(requestInfo.headers
106+
? { headers: sanitizeHeaders(requestInfo.headers) }
107+
: {}),
108+
}
109+
debugDir(failureDetails)
110+
} else {
111+
debug(`API ${endpoint}: HTTP ${status}`)
112+
}
47113
/* c8 ignore next 3 */
48114
} else if (isDebugNs('notice')) {
49115
debugNs('notice', `API ${endpoint}: ${status || 'pending'}`)

packages/cli/src/utils/socket/api.mts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,23 +360,41 @@ export async function queryApiSafeText(
360360
spinner?.start(`Requesting ${description} from API...`)
361361
}
362362

363+
const baseUrl = getDefaultApiBaseUrl()
364+
const fullUrl = `${baseUrl}${baseUrl?.endsWith('/') ? '' : '/'}${path}`
365+
const startTime = Date.now()
366+
363367
let result: any
364368
try {
365369
result = await queryApi(path, apiToken)
370+
const durationMs = Date.now() - startTime
366371
if (description) {
367372
spinner?.successAndStop(
368373
`Received Socket API response (after requesting ${description}).`,
369374
)
370375
}
376+
// Log success for debugging.
377+
debugApiResponse(description || 'Query API', result.status, undefined, {
378+
method: 'GET',
379+
url: fullUrl,
380+
durationMs,
381+
headers: { Authorization: '[REDACTED]' },
382+
})
371383
} catch (e) {
384+
const durationMs = Date.now() - startTime
372385
if (description) {
373386
spinner?.failAndStop(
374387
`An error was thrown while requesting ${description}.`,
375388
)
376389
}
377390

378391
debug('Query API request failed')
379-
debugDir(e)
392+
debugApiResponse(description || 'Query API', undefined, e, {
393+
method: 'GET',
394+
url: fullUrl,
395+
durationMs,
396+
headers: { Authorization: '[REDACTED]' },
397+
})
380398

381399
const errStr = e ? String(e).trim() : ''
382400
const message = 'API request failed'
@@ -392,6 +410,14 @@ export async function queryApiSafeText(
392410

393411
if (!result.ok) {
394412
const { status } = result
413+
const durationMs = Date.now() - startTime
414+
// Log detailed error information.
415+
debugApiResponse(description || 'Query API', status, undefined, {
416+
method: 'GET',
417+
url: fullUrl,
418+
durationMs,
419+
headers: { Authorization: '[REDACTED]' },
420+
})
395421
// Log required permissions for 403 errors when in a command context.
396422
if (commandPath && status === 403) {
397423
logPermissionsFor403(commandPath)
@@ -495,6 +521,9 @@ export async function sendApiRequest<T>(
495521
spinner?.start(`Requesting ${description} from API...`)
496522
}
497523

524+
const fullUrl = `${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`
525+
const startTime = Date.now()
526+
498527
let result: any
499528
try {
500529
const fetchOptions = {
@@ -506,24 +535,35 @@ export async function sendApiRequest<T>(
506535
...(body ? { body: JSON.stringify(body) } : {}),
507536
}
508537

509-
result = await fetch(
510-
`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`,
511-
fetchOptions,
512-
)
538+
result = await fetch(fullUrl, fetchOptions)
539+
const durationMs = Date.now() - startTime
513540
if (description) {
514541
spinner?.successAndStop(
515542
`Received Socket API response (after requesting ${description}).`,
516543
)
517544
}
545+
// Log success for debugging.
546+
debugApiResponse(description || 'Send API Request', result.status, undefined, {
547+
method,
548+
url: fullUrl,
549+
durationMs,
550+
headers: { Authorization: '[REDACTED]', 'Content-Type': 'application/json' },
551+
})
518552
} catch (e) {
553+
const durationMs = Date.now() - startTime
519554
if (description) {
520555
spinner?.failAndStop(
521556
`An error was thrown while requesting ${description}.`,
522557
)
523558
}
524559

525560
debug(`API ${method} request failed`)
526-
debugDir(e)
561+
debugApiResponse(description || 'Send API Request', undefined, e, {
562+
method,
563+
url: fullUrl,
564+
durationMs,
565+
headers: { Authorization: '[REDACTED]', 'Content-Type': 'application/json' },
566+
})
527567

528568
const errStr = e ? String(e).trim() : ''
529569
const message = 'API request failed'
@@ -539,6 +579,14 @@ export async function sendApiRequest<T>(
539579

540580
if (!result.ok) {
541581
const { status } = result
582+
const durationMs = Date.now() - startTime
583+
// Log detailed error information.
584+
debugApiResponse(description || 'Send API Request', status, undefined, {
585+
method,
586+
url: fullUrl,
587+
durationMs,
588+
headers: { Authorization: '[REDACTED]', 'Content-Type': 'application/json' },
589+
})
542590
// Log required permissions for 403 errors when in a command context.
543591
if (commandPath && status === 403) {
544592
logPermissionsFor403(commandPath)

0 commit comments

Comments
 (0)