Skip to content

Commit 0e8e165

Browse files
authored
Log API requests/responses (#895)
* Log API requests/responses * bump sdk version
1 parent eeba429 commit 0e8e165

File tree

8 files changed

+197
-55
lines changed

8 files changed

+197
-55
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
"@socketregistry/packageurl-js": "1.0.9",
122122
"@socketsecurity/config": "3.0.1",
123123
"@socketsecurity/registry": "1.1.17",
124-
"@socketsecurity/sdk": "1.4.93",
124+
"@socketsecurity/sdk": "1.4.94",
125125
"@types/blessed": "0.1.25",
126126
"@types/cmd-shim": "5.0.2",
127127
"@types/js-yaml": "4.0.9",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/scan/create-scan-from-github.mts

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { confirm, select } from '@socketsecurity/registry/lib/prompts'
1616
import { fetchSupportedScanFileNames } from './fetch-supported-scan-file-names.mts'
1717
import { handleCreateNewScan } from './handle-create-new-scan.mts'
1818
import constants from '../../constants.mts'
19+
import { debugApiRequest, debugApiResponse } from '../../utils/debug.mts'
1920
import { formatErrorWithDetail } from '../../utils/errors.mts'
2021
import { isReportSupportedFile } from '../../utils/glob.mts'
2122
import { fetchListAllRepos } from '../repository/fetch-list-all-repos.mts'
@@ -390,12 +391,20 @@ async function downloadManifestFile({
390391
const fileUrl = `${repoApiUrl}/contents/${file}?ref=${defaultBranch}`
391392
debugDir('inspect', { fileUrl })
392393

393-
const downloadUrlResponse = await fetch(fileUrl, {
394-
method: 'GET',
395-
headers: {
396-
Authorization: `Bearer ${githubToken}`,
397-
},
398-
})
394+
debugApiRequest('GET', fileUrl)
395+
let downloadUrlResponse: Response
396+
try {
397+
downloadUrlResponse = await fetch(fileUrl, {
398+
method: 'GET',
399+
headers: {
400+
Authorization: `Bearer ${githubToken}`,
401+
},
402+
})
403+
debugApiResponse('GET', fileUrl, downloadUrlResponse.status)
404+
} catch (e) {
405+
debugApiResponse('GET', fileUrl, undefined, e)
406+
throw e
407+
}
399408
debugFn('notice', 'complete: request')
400409

401410
const downloadUrlText = await downloadUrlResponse.text()
@@ -448,7 +457,9 @@ async function streamDownloadWithFetch(
448457
let response // Declare response here to access it in catch if needed
449458

450459
try {
460+
debugApiRequest('GET', downloadUrl)
451461
response = await fetch(downloadUrl)
462+
debugApiResponse('GET', downloadUrl, response.status)
452463

453464
if (!response.ok) {
454465
const errorMsg = `Download failed due to bad server response: ${response.status} ${response.statusText} for ${downloadUrl}`
@@ -483,6 +494,9 @@ async function streamDownloadWithFetch(
483494
// It resolves when the piping is fully complete and fileStream is closed.
484495
return { ok: true, data: localPath }
485496
} catch (e) {
497+
if (!response) {
498+
debugApiResponse('GET', downloadUrl, undefined, e)
499+
}
486500
logger.fail(
487501
'An error was thrown while trying to download a manifest file... url:',
488502
downloadUrl,
@@ -542,11 +556,19 @@ async function getLastCommitDetails({
542556
const commitApiUrl = `${repoApiUrl}/commits?sha=${defaultBranch}&per_page=1`
543557
debugFn('inspect', 'url: commit', commitApiUrl)
544558

545-
const commitResponse = await fetch(commitApiUrl, {
546-
headers: {
547-
Authorization: `Bearer ${githubToken}`,
548-
},
549-
})
559+
debugApiRequest('GET', commitApiUrl)
560+
let commitResponse: Response
561+
try {
562+
commitResponse = await fetch(commitApiUrl, {
563+
headers: {
564+
Authorization: `Bearer ${githubToken}`,
565+
},
566+
})
567+
debugApiResponse('GET', commitApiUrl, commitResponse.status)
568+
} catch (e) {
569+
debugApiResponse('GET', commitApiUrl, undefined, e)
570+
throw e
571+
}
550572

551573
const commitText = await commitResponse.text()
552574
debugFn('inspect', 'response: commit', commitText)
@@ -646,12 +668,20 @@ async function getRepoDetails({
646668
const repoApiUrl = `${githubApiUrl}/repos/${orgGithub}/${repoSlug}`
647669
debugDir('inspect', { repoApiUrl })
648670

649-
const repoDetailsResponse = await fetch(repoApiUrl, {
650-
method: 'GET',
651-
headers: {
652-
Authorization: `Bearer ${githubToken}`,
653-
},
654-
})
671+
let repoDetailsResponse: Response
672+
try {
673+
debugApiRequest('GET', repoApiUrl)
674+
repoDetailsResponse = await fetch(repoApiUrl, {
675+
method: 'GET',
676+
headers: {
677+
Authorization: `Bearer ${githubToken}`,
678+
},
679+
})
680+
debugApiResponse('GET', repoApiUrl, repoDetailsResponse.status)
681+
} catch (e) {
682+
debugApiResponse('GET', repoApiUrl, undefined, e)
683+
throw e
684+
}
655685
logger.success(`Request completed.`)
656686

657687
const repoDetailsText = await repoDetailsResponse.text()
@@ -702,12 +732,20 @@ async function getRepoBranchTree({
702732
const treeApiUrl = `${repoApiUrl}/git/trees/${defaultBranch}?recursive=1`
703733
debugFn('inspect', 'url: tree', treeApiUrl)
704734

705-
const treeResponse = await fetch(treeApiUrl, {
706-
method: 'GET',
707-
headers: {
708-
Authorization: `Bearer ${githubToken}`,
709-
},
710-
})
735+
let treeResponse: Response
736+
try {
737+
debugApiRequest('GET', treeApiUrl)
738+
treeResponse = await fetch(treeApiUrl, {
739+
method: 'GET',
740+
headers: {
741+
Authorization: `Bearer ${githubToken}`,
742+
},
743+
})
744+
debugApiResponse('GET', treeApiUrl, treeResponse.status)
745+
} catch (e) {
746+
debugApiResponse('GET', treeApiUrl, undefined, e)
747+
throw e
748+
}
711749

712750
const treeText = await treeResponse.text()
713751
debugFn('inspect', 'response: tree', treeText)

src/commands/scan/fetch-create-org-full-scan.mts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
4+
import { logger } from '@socketsecurity/registry/lib/logger'
5+
6+
import constants from '../../constants.mts'
17
import { handleApiCall } from '../../utils/api.mts'
28
import { setupSdk } from '../../utils/sdk.mts'
39

@@ -51,6 +57,19 @@ export async function fetchCreateOrgFullScan(
5157
}
5258
const sockSdk = sockSdkCResult.data
5359

60+
if (constants.ENV.SOCKET_CLI_DEBUG) {
61+
const fileInfo = await Promise.all(
62+
packagePaths.map(async p => {
63+
const absPath = path.resolve(process.cwd(), p)
64+
const stat = await fs.promises.stat(absPath)
65+
return { path: absPath, size: stat.size }
66+
}),
67+
)
68+
logger.info(
69+
`[DEBUG] ${new Date().toISOString()} Uploading full scan manifests: ${JSON.stringify(fileInfo)}`,
70+
)
71+
}
72+
5473
return await handleApiCall(
5574
sockSdk.createOrgFullScan(orgSlug, packagePaths, cwd, {
5675
...(branchName ? { branch: branchName } : {}),

src/constants.mts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export type ENV = Remap<
154154
npm_config_user_agent: string
155155
PATH: string
156156
SOCKET_CLI_ACCEPT_RISKS: boolean
157+
SOCKET_CLI_DEBUG: boolean
157158
SOCKET_CLI_API_BASE_URL: string
158159
SOCKET_CLI_API_PROXY: string
159160
SOCKET_CLI_API_TIMEOUT: number
@@ -574,6 +575,8 @@ const LAZY_ENV = () => {
574575
PATH: envAsString(env['PATH']),
575576
// Accept risks of a Socket wrapped npm/npx run.
576577
SOCKET_CLI_ACCEPT_RISKS: envAsBoolean(env[SOCKET_CLI_ACCEPT_RISKS]),
578+
// Enable debug logging in Socket CLI.
579+
SOCKET_CLI_DEBUG: envAsBoolean(env['SOCKET_CLI_DEBUG']),
577580
// Change the base URL for Socket API calls.
578581
// https://github.com/SocketDev/socket-cli?tab=readme-ov-file#environment-variables-for-development
579582
SOCKET_CLI_API_BASE_URL:

src/utils/api.mts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
2626
import { isNonEmptyString } from '@socketsecurity/registry/lib/strings'
2727

2828
import { getConfigValueOrUndef } from './config.mts'
29-
import { debugApiResponse } from './debug.mts'
29+
import { debugApiRequest, debugApiResponse } from './debug.mts'
3030
import constants, {
3131
CONFIG_KEY_API_BASE_URL,
3232
EMPTY_VALUE,
@@ -166,9 +166,6 @@ export async function handleApiCall<T extends SocketSdkOperations>(
166166
}
167167
if (description) {
168168
logger.fail(`An error was thrown while requesting ${description}`)
169-
debugApiResponse(description, undefined, e)
170-
} else {
171-
debugApiResponse('Socket API', undefined, e)
172169
}
173170
debugDir('inspect', { socketSdkErrorResult })
174171
return socketSdkErrorResult
@@ -177,7 +174,7 @@ export async function handleApiCall<T extends SocketSdkOperations>(
177174
// Note: TS can't narrow down the type of result due to generics.
178175
if (sdkResult.success === false) {
179176
const endpoint = description || 'Socket API'
180-
debugApiResponse(endpoint, sdkResult.status as number)
177+
debugApiResponse('API', endpoint, sdkResult.status as number)
181178
debugDir('inspect', { sdkResult })
182179

183180
const errCResult = sdkResult as SocketSdkErrorResult<T>
@@ -263,18 +260,20 @@ export async function handleApiCallNoSpinner<T extends SocketSdkOperations>(
263260
}
264261
}
265262

266-
export async function queryApi(path: string, apiToken: string) {
263+
async function queryApi(path: string, apiToken: string) {
267264
const baseUrl = getDefaultApiBaseUrl()
268265
if (!baseUrl) {
269266
throw new Error('Socket API base URL is not configured.')
270267
}
271268

272-
return await fetch(`${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`, {
269+
const url = `${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${path}`
270+
const result = await fetch(url, {
273271
method: 'GET',
274272
headers: {
275273
Authorization: `Basic ${btoa(`${apiToken}:`)}`,
276274
},
277275
})
276+
return result
278277
}
279278

280279
/**
@@ -299,21 +298,34 @@ export async function queryApiSafeText(
299298

300299
if (description) {
301300
spinner.start(`Requesting ${description} from API...`)
301+
debugApiRequest('GET', path, constants.ENV.SOCKET_CLI_API_TIMEOUT)
302302
}
303303

304304
let result
305+
const startTime = Date.now()
305306
try {
306307
result = await queryApi(path, apiToken)
308+
const duration = Date.now() - startTime
309+
debugApiResponse(
310+
'GET',
311+
path,
312+
result.status,
313+
undefined,
314+
duration,
315+
Object.fromEntries(result.headers.entries()),
316+
)
307317
if (description) {
308318
spinner.successAndStop(
309319
`Received Socket API response (after requesting ${description}).`,
310320
)
311321
}
312322
} catch (e) {
323+
const duration = Date.now() - startTime
313324
if (description) {
314325
spinner.failAndStop(
315326
`An error was thrown while requesting ${description}.`,
316327
)
328+
debugApiResponse('GET', path, undefined, e, duration)
317329
}
318330

319331
debugFn('error', 'Query API request failed')

src/utils/debug.mts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,62 @@
1818
*/
1919

2020
import { debugDir, debugFn, isDebug } from '@socketsecurity/registry/lib/debug'
21+
import { logger } from '@socketsecurity/registry/lib/logger'
22+
23+
import constants from '../constants.mts'
2124

2225
/**
23-
* Debug an API response.
26+
* Debug an API request start.
27+
* Logs essential info without exposing sensitive data.
28+
*/
29+
export function debugApiRequest(
30+
method: string,
31+
endpoint: string,
32+
timeout?: number | undefined,
33+
): void {
34+
if (constants.ENV.SOCKET_CLI_DEBUG) {
35+
const timeoutStr = timeout !== undefined ? ` (timeout: ${timeout}ms)` : ''
36+
logger.info(
37+
`[DEBUG] ${new Date().toISOString()} request started: ${method} ${endpoint}${timeoutStr}`,
38+
)
39+
}
40+
}
41+
42+
/**
43+
* Debug an API response end.
2444
* Logs essential info without exposing sensitive data.
2545
*/
2646
export function debugApiResponse(
47+
method: string,
2748
endpoint: string,
2849
status?: number | undefined,
2950
error?: unknown | undefined,
51+
duration?: number | undefined,
52+
headers?: Record<string, string> | undefined,
3053
): void {
54+
if (!constants.ENV.SOCKET_CLI_DEBUG) {
55+
return
56+
}
57+
3158
if (error) {
32-
debugDir('error', {
33-
endpoint,
34-
error: error instanceof Error ? error.message : 'Unknown error',
35-
})
36-
} else if (status && status >= 400) {
37-
debugFn('warn', `API ${endpoint}: HTTP ${status}`)
38-
} else if (isDebug('notice')) {
39-
debugFn('notice', `API ${endpoint}: ${status || 'pending'}`)
59+
logger.fail(
60+
`[DEBUG] ${new Date().toISOString()} request error: ${method} ${endpoint} - ${error instanceof Error ? error.message : 'Unknown error'}${duration !== undefined ? ` (${duration}ms)` : ''}`,
61+
)
62+
if (headers) {
63+
logger.info(
64+
`[DEBUG] response headers: ${JSON.stringify(headers, null, 2)}`,
65+
)
66+
}
67+
} else {
68+
const durationStr = duration !== undefined ? ` (${duration}ms)` : ''
69+
logger.info(
70+
`[DEBUG] ${new Date().toISOString()} request ended: ${method} ${endpoint}: HTTP ${status}${durationStr}`,
71+
)
72+
if (headers && status && status >= 400) {
73+
logger.info(
74+
`[DEBUG] response headers: ${JSON.stringify(headers, null, 2)}`,
75+
)
76+
}
4077
}
4178
}
4279

0 commit comments

Comments
 (0)