import chalk from 'chalk'; import { RootAPIHelper, getNewApiHelper } from '../helpers/root-api'; import { productModuleFromNetwork } from '../domain/product-module'; import { NetworkProductModuleCodeRun, ProductModuleCodeRun, ProductModuleCodeRunStatus, productModuleCodeRunFromNetwork, } from '../domain/product-module-code-run'; import { NetworkProductModuleCodeRunLogItem, ProductModuleCodeRunLogItem, ProductModuleCodeRunLogItemLevel, productModuleCodeRunLogItemFromNetwork, } from '../domain/product-module-code-run-log-item'; import { PlatformError } from '../errors/platform-error'; import { readAuthAndConfig } from '../helpers/read-auth-and-config'; import { isProductModuleConfig } from '../domain/root-config'; import { isCollectionModuleConfig } from '../domain/collection-modules/collection-module-config'; import { checkValidProductModuleDirectory } from '../helpers/check-valid-product-module-directory'; import { cmLogs } from './cm-logs'; import { runWithSpinner } from '../helpers/spinner'; import { symbols } from '../helpers/symbols'; interface ProductModuleCodeRunWithLogItems extends ProductModuleCodeRun { logItems: ProductModuleCodeRunLogItem[]; } export const addLogsToCodeRuns = async (params: { codeRuns: ProductModuleCodeRun[]; productModuleId: string; rootApi: RootAPIHelper; }) => { const { codeRuns, productModuleId, rootApi } = params; const runsWithLogs: ProductModuleCodeRunWithLogItems[] = await Promise.all( codeRuns.map(async (run) => { const networkLogs: NetworkProductModuleCodeRunLogItem[] = await rootApi.send({ path: `/insurance/product-modules/${productModuleId}/runs/${run.productModuleCodeRunId}/log-items`, }); const logs = networkLogs.map(productModuleCodeRunLogItemFromNetwork); return { ...run, logItems: logs }; }), ); return runsWithLogs; }; export const formatCodeRunStatus = (status: ProductModuleCodeRunStatus) => { switch (status) { case ProductModuleCodeRunStatus.Complete: { return chalk.green(`[${status}]`); } case ProductModuleCodeRunStatus.Failed: { return chalk.red(`[${status}]`); } default: { return `[${status}]`; } } }; const formatLogLevel = (level: ProductModuleCodeRunLogItemLevel) => { switch (level) { case ProductModuleCodeRunLogItemLevel.Error: { return chalk.red(`[${level}]`); } case ProductModuleCodeRunLogItemLevel.Warning: { return chalk.yellow(`[${level}]`); } default: { return `[${level}]`; } } }; const printLogs = (runsToDisplay: ProductModuleCodeRunWithLogItems[]) => { // Sort runs from earliest to latest runsToDisplay.sort((a, b) => (a.createdAt.isBefore(b.createdAt) ? -1 : 1)); for (const run of runsToDisplay) { const { createdAt: runCreatedAt, functionName, status } = run; const runHeading = chalk.bold(`${runCreatedAt.toISOString()} ${formatCodeRunStatus(status)} ${functionName}`); console.log(runHeading); if (run.logItems.length === 0) { console.log(`${symbols.info}No logs`); } else { run.logItems.sort((a, b) => (a.createdAt.isBefore(b.createdAt) ? -1 : 1)); for (const log of run.logItems) { const { createdAt, level, content } = log; console.log(`${createdAt.toISOString()} ${formatLogLevel(level)} ${content}`); } } console.log(); } }; export const logs = async (options: { policyId?: string; functionName?: string; status?: ProductModuleCodeRunStatus; count?: string; time?: string; level?: string; watch?: boolean; }) => { const { apiKey, config } = readAuthAndConfig(); // Auto-detect: if in a collection module directory, use CM logs if (isCollectionModuleConfig(config)) { await cmLogs({ ...options, apiKey, config }); return; } // Otherwise, use product module logs const { policyId, functionName, status, count: countString } = options; const count = Number.parseInt(countString || '20', 10); if (!(count >= 1 && count <= 100)) { throw new Error('Invalid value specified for count, must be a number between 1 and 100 (inclusive).'); } if (!isProductModuleConfig(config)) { throw new Error('.root-config.json is not a valid product module config file.'); } checkValidProductModuleDirectory({ productModuleConfig: config }); if (apiKey && apiKey.split('_')[0] !== 'sandbox') { // This feature can only be used in sandbox throw new Error('A sandbox API key is required to use this feature. Please update .root-auth accordingly.'); } const { host, productModuleKey } = config; const rootApi = getNewApiHelper({ host, apiKey, throwResponseErrors: true }); // We need the product module ID to be able to get the logs const productModule = productModuleFromNetwork( await runWithSpinner('Fetching product module...', () => rootApi.send({ path: `/insurance/product-modules/${productModuleKey}` }), ), ); const { productModuleId } = productModule; const queryParams = { ...(policyId && { policy_id: policyId }), ...(functionName && { function_name: functionName }), ...(status && { statuses: status }), page: '1', page_size: count.toString(), }; let networkCodeRuns: NetworkProductModuleCodeRun[]; try { networkCodeRuns = await runWithSpinner('Fetching product module code runs...', () => rootApi.send({ path: `/insurance/product-modules/${productModuleId}/runs`, searchParams: queryParams, }), ); } catch (error: any) { if (error instanceof PlatformError && error.networkError.error.type === 'authorization_error') { error.message += '\nPlease ensure the API key you are using has the permissions required to read product module run logs.'; } throw error; } if (networkCodeRuns.length === 0) { console.log(`${symbols.info}No product module code runs found.`); return; } const codeRuns = networkCodeRuns.map(productModuleCodeRunFromNetwork); const runsWithLogs = await runWithSpinner('Fetching run logs...', () => addLogsToCodeRuns({ codeRuns, productModuleId, rootApi }), ); printLogs(runsWithLogs); };