diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 5e0614591f..07b9abfb24 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -200,7 +200,10 @@ export function withOutputFile(args: Argv) { } export function withWarpRouteId(args: Argv) { - return args.describe('warpRouteId', 'warp route id').string('warpRouteId'); + return args + .describe('warpRouteId', 'warp route id') + .string('warpRouteId') + .choices('warpRouteId', Object.values(WarpRouteIds)); } export function withWarpRouteIdRequired(args: Argv) { diff --git a/typescript/infra/scripts/warp-routes/monitor/status.ts b/typescript/infra/scripts/warp-routes/monitor/status.ts new file mode 100644 index 0000000000..a6908a1fe0 --- /dev/null +++ b/typescript/infra/scripts/warp-routes/monitor/status.ts @@ -0,0 +1,106 @@ +import chalk from 'chalk'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { HelmManager } from '../../../src/utils/helm.js'; +import { WarpRouteMonitorHelmManager } from '../../../src/warp/helm.js'; +import { + assertCorrectKubeContext, + getArgs, + withWarpRouteIdRequired, +} from '../../agent-utils.js'; +import { getEnvironmentConfig } from '../../core-utils.js'; + +const orange = chalk.hex('#FFA500'); +const GRAFANA_LINK = + 'https://abacusworks.grafana.net/d/ddz6ma94rnzswc/warp-routes?orgId=1&var-warp_route_id='; +const LOG_AMOUNT = 5; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + const { environment, warpRouteId } = await withWarpRouteIdRequired( + getArgs(), + ).parse(); + + const config = getEnvironmentConfig(environment); + await assertCorrectKubeContext(config); + + try { + const podWarpRouteId = `${WarpRouteMonitorHelmManager.getHelmReleaseName( + warpRouteId, + )}-0`; + + rootLogger.info(chalk.grey.italic(`Fetching pod status...`)); + const pod = HelmManager.runK8sCommand( + 'get pod', + podWarpRouteId, + environment, + ); + rootLogger.info(chalk.green(pod)); + + rootLogger.info(chalk.gray.italic(`Fetching latest logs...`)); + const latestLogs = HelmManager.runK8sCommand( + 'logs', + podWarpRouteId, + environment, + [`--tail=${LOG_AMOUNT}`], + ); + formatAndPrintLogs(latestLogs); + + rootLogger.info( + orange.bold(`Grafana Dashboard Link: ${GRAFANA_LINK}${warpRouteId}`), + ); + } catch (error) { + rootLogger.error(error); + process.exit(1); + } +} + +function formatAndPrintLogs(rawLogs: string) { + try { + const logs = rawLogs + .trim() + .split('\n') + .map((line) => JSON.parse(line)); + logs.forEach((log) => { + const { time, module, msg, labels, balance, valueUSD } = log; + const timestamp = new Date(time).toISOString(); + const chain = labels?.chain_name || 'Unknown Chain'; + const token = labels?.token_name || 'Unknown Token'; + const warpRoute = labels?.warp_route_id || 'Unknown Warp Route'; + const tokenStandard = labels?.token_standard || 'Unknown Standard'; + const tokenAddress = labels?.token_address || 'Unknown Token Address'; + const walletAddress = labels?.wallet_address || 'Unknown Wallet'; + + let logMessage = + chalk.gray(`[${timestamp}] `) + chalk.white(`[${module}] `); + logMessage += chalk.blue(`${warpRoute} `); + logMessage += chalk.green(`${chain} `); + logMessage += chalk.blue.italic(`Token: ${token} (${tokenAddress}) `); + logMessage += chalk.green.italic(`${tokenStandard} `); + logMessage += chalk.blue.italic(`Wallet: ${walletAddress} `); + + if (balance) { + logMessage += chalk.yellow.italic(`Balance: ${balance} `); + } + if (valueUSD) { + logMessage += chalk.green.italic(`Value (USD): ${valueUSD} `); + } + logMessage += chalk.white(`→ ${msg}\n`); + + rootLogger.info(logMessage); + }); + } catch (error) { + rootLogger.error(`Failed to parse logs: ${error}`); + } +} + +main().catch((err) => { + rootLogger.error('Error in main:', err); + process.exit(1); +}); diff --git a/typescript/infra/src/utils/helm.ts b/typescript/infra/src/utils/helm.ts index 4bd23f1047..4d18a1fc43 100644 --- a/typescript/infra/src/utils/helm.ts +++ b/typescript/infra/src/utils/helm.ts @@ -1,3 +1,5 @@ +import { execSync } from 'child_process'; + import { DockerConfig } from '../config/agent/agent.js'; import { HelmChartConfig, @@ -211,4 +213,19 @@ export abstract class HelmManager { // Split on new lines and remove empty strings return output.split('\n').filter(Boolean); } + + static runK8sCommand( + command: string, + podId: string, + namespace: string, + args: string[] = [], + ) { + const argsString = args.join(' '); + return execSync( + `kubectl ${command} ${podId} -n ${namespace} ${argsString}`, + { + encoding: 'utf-8', + }, + ); + } }