diff --git a/docs/deployment.md b/docs/deployment.md index 4e75934..22be3cc 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -95,6 +95,51 @@ docker compose --profile deploy up -d The `Dockerfile` in the project root builds the Ponder image: two-stage Node 22 Alpine, installs dependencies with `--frozen-lockfile`, exposes port 3000, runs `pnpm start`. The health check hits `/ready` with a 24-hour start period (initial sync takes hours). +### Kubernetes Probes + +The indexer exposes two health endpoints with distinct semantics: + +| Endpoint | Semantic | Returns 200 when | +|----------|----------|-----------------| +| `/health` | **Liveness** — is the process alive? | Always, once the server starts | +| `/ready` | **Readiness** — is the index fully synced? | Only when fully synced | + +Map these to different K8s probe types. The specific timing values (`periodSeconds`, `failureThreshold`, `initialDelaySeconds`) depend on your cluster's SLOs; what matters is which path and port to use: + +```yaml +livenessProbe: + httpGet: + path: /health + port: 3000 +readinessProbe: + httpGet: + path: /ready + port: 3000 +``` + +**Do not** use `/ready` as the liveness probe. A pod that is still indexing (which takes hours on a cold start) returns 200 on `/health` but not on `/ready`. Using `/ready` for liveness would kill the pod before it ever finishes syncing. + +A pod in `NotReady` state is not killed — it is simply removed from load-balancer rotation. On a cold start (no existing database), the pod will be `NotReady` for the duration of the historical backfill (hours). That is expected: the old pod (if any) keeps serving traffic during this window, and once the new pod catches up, K8s starts routing to it. + +The Docker Compose health check uses `/ready` with a 24-hour start period as a pragmatic fallback for single-container deployments, not as a K8s-style probe. + +### Structured Logging + +`pnpm start` runs with `--log-format json`, which makes both Ponder's internal log lines and the handler log lines emit newline-delimited JSON. Each handler log line includes structured fields (e.g. `chainId`, `block`) enabling log aggregators (Datadog, CloudWatch, Loki) to filter and alert by chain. + +`pnpm dev` uses Ponder's default pretty format for readability during local development. + +**Convention:** all code under `src/application/` uses `log()` from `src/application/helpers/logger.ts` instead of `console.log/warn/error` directly. The `src/api/` layer (Hono routes) is exempt — Hono handles its own logging. Example: + +```ts +import { log } from "../helpers/logger"; + +log("info", "c2:confirmed", { chainId, orderUid, block: String(event.block.number) }); +log("warn", "c2:timeout", { chainId, block: String(event.block.number) }); +``` + +`warn` and `error` level messages go to `stderr`; `info` goes to `stdout`. The `level` field in the JSON payload is what log aggregators use to route and alert. + ### PostgreSQL Memory Flags Memory settings are hardcoded in the `command:` block of `docker-compose.yml`, tuned for 1G RAM: diff --git a/package.json b/package.json index 128a690..da50219 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "ponder dev", - "start": "ponder start -p 3000 --schema ${DATABASE_SCHEMA:-public}", + "start": "ponder start -p 3000 --schema ${DATABASE_SCHEMA:-public} --log-format json", "db": "ponder db", "codegen": "ponder codegen", "lint": "eslint . --ext .ts", diff --git a/src/application/handlers/blockHandler.ts b/src/application/handlers/blockHandler.ts index d50ad12..c08d374 100644 --- a/src/application/handlers/blockHandler.ts +++ b/src/application/handlers/blockHandler.ts @@ -42,6 +42,7 @@ import { parsePollError, } from "../helpers/pollResultErrors"; import { computeOrderUid, type GPv2OrderData } from "../helpers/orderUid"; +import { log } from "../helpers/logger"; import { type OrderType } from "../../utils/order-types"; type DiscreteStatus = (typeof discreteOrderStatusEnum.enumValues)[number]; @@ -122,9 +123,7 @@ ponder.on("ContractPoller:block", async ({ event, context }) => { if (dueOrders.length === 0) return; - console.log( - `[COW:C1] ENTER block=${currentBlock} chain=${chainId} due=${dueOrders.length}`, - ); + log("info", "C1:ENTER", { block: String(currentBlock), chainId, due: dueOrders.length }); const c1MulticallPromise = context.client.multicall({ contracts: dueOrders.map((order) => ({ @@ -150,9 +149,7 @@ ponder.on("ContractPoller:block", async ({ event, context }) => { ); } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:C1] multicall timeout block=${currentBlock} chain=${chainId} due=${dueOrders.length}`, - ); + log("warn", "C1:multicall_timeout", { block: String(currentBlock), chainId, due: dueOrders.length }); return; } throw err; @@ -266,9 +263,7 @@ ponder.on("ContractPoller:block", async ({ event, context }) => { eq(conditionalOrderGenerator.eventId, order.generatorId), ), ); - console.log( - `[COW:C1] NEVER generatorId=${order.generatorId} reason=${pollResult.reason} block=${currentBlock} chain=${chainId}`, - ); + log("info", "C1:NEVER", { block: String(currentBlock), chainId, generatorId: order.generatorId, reason: pollResult.reason }); neverCount++; break; @@ -287,9 +282,7 @@ ponder.on("ContractPoller:block", async ({ event, context }) => { eq(conditionalOrderGenerator.eventId, order.generatorId), ), ); - console.log( - `[COW:C1] CANCELLED generatorId=${order.generatorId} block=${currentBlock} chain=${chainId}`, - ); + log("info", "C1:CANCELLED", { block: String(currentBlock), chainId, generatorId: order.generatorId }); break; } } @@ -298,9 +291,7 @@ ponder.on("ContractPoller:block", async ({ event, context }) => { await Promise.all(successPromises); const capped = dueOrders.length === maxGeneratorsPerBlock; - console.log( - `[COW:C1] DONE block=${currentBlock} chain=${chainId} due=${dueOrders.length} success=${successCount} never=${neverCount} backedOff=${backedOffCount}${capped ? " CAPPED" : ""}`, - ); + log("info", "C1:DONE", { block: String(currentBlock), chainId, due: dueOrders.length, success: successCount, never: neverCount, backedOff: backedOffCount, capped }); }); // ─── C2: Candidate Confirmer ───────────────────────────────────────────────── @@ -413,9 +404,7 @@ ponder.on("CandidateConfirmer:block", async ({ event, context }) => { ); const preflightKnown = preflightStatuses.size; - console.log( - `[COW:C2] c2:parent-cancelled block=${event.block.number} chainId=${chainId} parentCancelled=${orphanCandidates.length} preflightKnown=${preflightKnown}`, - ); + log("info", "C2:parent_cancelled", { block: String(event.block.number), chainId, parentCancelled: orphanCandidates.length, preflightKnown }); } } @@ -574,9 +563,7 @@ ponder.on("CandidateConfirmer:block", async ({ event, context }) => { } if (confirmed > 0 || stale.length > 0) { - console.log( - `[COW:C2] block=${event.block.number} chain=${chainId} candidates=${unconfirmed.length} confirmed=${confirmed} expired=${stale.length}`, - ); + log("info", "C2:DONE", { block: String(event.block.number), chainId, candidates: unconfirmed.length, confirmed, expired: stale.length }); } }); @@ -628,9 +615,7 @@ ponder.on("StatusUpdater:block", async ({ event, context }) => { } if (updated > 0) { - console.log( - `[COW:C3] block=${event.block.number} chain=${chainId} open=${openOrders.length} updated=${updated}`, - ); + log("info", "C3:DONE", { block: String(event.block.number), chainId, open: openOrders.length, updated }); } } @@ -694,9 +679,7 @@ ponder.on("HistoricalBootstrap:block", async ({ event, context }) => { .from(bootstrapRetryQueue) .where(eq(bootstrapRetryQueue.chainId, chainId)); - console.log( - `[COW:C4] block=${currentBlock} chain=${chainId} pending_retry=${queued.length}`, - ); + log("info", "C4:START", { block: String(currentBlock), chainId, pendingRetry: queued.length }); let totalDiscovered = 0; const retriedOwners = new Set(); @@ -716,9 +699,7 @@ ponder.on("HistoricalBootstrap:block", async ({ event, context }) => { .where(and(eq(bootstrapRetryQueue.chainId, chainId), eq(bootstrapRetryQueue.owner, owner as Hex))); } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:C4] owner retry timeout owner=${owner} chain=${chainId} retry_count=${retryCount + 1} after=${BOOTSTRAP_OWNER_FETCH_TIMEOUT_MS}ms`, - ); + log("warn", "C4:owner_retry_timeout", { block: String(currentBlock), chainId, owner, retryCount: retryCount + 1, timeoutMs: BOOTSTRAP_OWNER_FETCH_TIMEOUT_MS }); await context.db.sql .update(bootstrapRetryQueue) .set({ retryCount: retryCount + 1, lastRetryAt: currentBlock }) @@ -761,14 +742,12 @@ ponder.on("HistoricalBootstrap:block", async ({ event, context }) => { const freshOwners = new Set(generators.map((g) => g.owner).filter((o) => !retriedOwners.has(o))); if (freshOwners.size === 0 && retriedOwners.size === 0) { - console.log(`[COW:C4] block=${currentBlock} chain=${chainId} no generators need bootstrap`); + log("info", "C4:no_bootstrap_needed", { block: String(currentBlock), chainId }); return; } if (freshOwners.size > 0) { - console.log( - `[COW:C4] block=${currentBlock} chain=${chainId} generators=${generators.length} fresh_owners=${freshOwners.size}`, - ); + log("info", "C4:bootstrap_start", { block: String(currentBlock), chainId, generators: generators.length, freshOwners: freshOwners.size }); } for (const owner of freshOwners) { @@ -782,9 +761,7 @@ ponder.on("HistoricalBootstrap:block", async ({ event, context }) => { totalDiscovered += count; } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:C4] owner timeout owner=${owner} chain=${chainId} after=${BOOTSTRAP_OWNER_FETCH_TIMEOUT_MS}ms`, - ); + log("warn", "C4:owner_timeout", { block: String(currentBlock), chainId, owner, timeoutMs: BOOTSTRAP_OWNER_FETCH_TIMEOUT_MS }); await context.db.sql .insert(bootstrapRetryQueue) .values({ chainId, owner, firstTimeoutAt: currentBlock, retryCount: 1, lastRetryAt: currentBlock }) @@ -795,9 +772,7 @@ ponder.on("HistoricalBootstrap:block", async ({ event, context }) => { } } - console.log( - `[COW:C4] DONE block=${currentBlock} chain=${chainId} discovered=${totalDiscovered}`, - ); + log("info", "C4:DONE", { block: String(currentBlock), chainId, discovered: totalDiscovered }); }); // ─── C5: Deterministic Cancellation Sweeper ────────────────────────────────── @@ -851,9 +826,7 @@ ponder.on("DeterministicCancellationSweeper:block", async ({ event, context }) = if (dueGenerators.length === 0) return; - console.log( - `[COW:C5] ENTER block=${currentBlock} chain=${chainId} due=${dueGenerators.length}`, - ); + log("info", "C5:ENTER", { block: String(currentBlock), chainId, due: dueGenerators.length }); const c5MulticallPromise = context.client.multicall({ contracts: dueGenerators.map((g) => ({ @@ -874,9 +847,7 @@ ponder.on("DeterministicCancellationSweeper:block", async ({ event, context }) = ); } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:C5] multicall timeout block=${currentBlock} chain=${chainId} due=${dueGenerators.length}`, - ); + log("warn", "C5:multicall_timeout", { block: String(currentBlock), chainId, due: dueGenerators.length }); return; } throw err; @@ -913,9 +884,7 @@ ponder.on("DeterministicCancellationSweeper:block", async ({ event, context }) = eq(conditionalOrderGenerator.eventId, gen.generatorId), ), ); - console.log( - `[COW:C5] CANCELLED generatorId=${gen.generatorId} orderType=${gen.orderType} block=${currentBlock} chain=${chainId}`, - ); + log("info", "C5:CANCELLED", { block: String(currentBlock), chainId, generatorId: gen.generatorId, orderType: gen.orderType }); cancelledCount++; } else { await context.db.sql @@ -935,9 +904,7 @@ ponder.on("DeterministicCancellationSweeper:block", async ({ event, context }) = } } - console.log( - `[COW:C5] DONE block=${currentBlock} chain=${chainId} due=${dueGenerators.length} cancelled=${cancelledCount} stillActive=${stillActiveCount} errors=${errorCount}`, - ); + log("info", "C5:DONE", { block: String(currentBlock), chainId, due: dueGenerators.length, cancelled: cancelledCount, stillActive: stillActiveCount, errors: errorCount }); }); // ─── Shared helpers ────────────────────────────────────────────────────────── diff --git a/src/application/handlers/composableCow.ts b/src/application/handlers/composableCow.ts index 61b72a0..3168285 100644 --- a/src/application/handlers/composableCow.ts +++ b/src/application/handlers/composableCow.ts @@ -47,6 +47,7 @@ import { getOrderTypeFromHandler } from "../../utils/order-types"; import { decodeStaticInput } from "../../decoders/index"; import { precomputeAndDiscover } from "../helpers/uidPrecompute"; import { CirclesBackingOrderAbi } from "../../../abis/CirclesBackingOrderAbi"; +import { log } from "../helpers/logger"; // ─── CirclesBackingOrder immutables cache ─────────────────────────────────── // @@ -130,14 +131,9 @@ async function insertGenerator( const orderType = getOrderTypeFromHandler(handler, chainId); if (orderType === "Unknown") { - console.warn( - `[ComposableCow] Unknown handler ${handler} on chain ${chainId}, ` + - `saving as Unknown — event=${event.id}`, - ); + log("warn", "composableCow:unknownHandler", { handler, chainId, event: event.id }); } else { - console.log( - `[ComposableCow] ConditionalOrderCreated event=${event.id} chain=${chainId} orderType=${orderType} block=${event.block.number}`, - ); + log("info", "composableCow:created", { event: event.id, chainId, orderType, block: String(event.block.number) }); } // Decode staticInput; for CirclesBackingOrder, also merge in handler immutables. @@ -171,13 +167,9 @@ async function insertGenerator( }; } - console.log( - `[ComposableCow] Decoded event=${event.id} orderType=${orderType} decodedParams=${decodedParams ? "ok" : "null"}`, - ); + log("info", "composableCow:decoded", { event: event.id, orderType, decodedParams: decodedParams ? "ok" : "null" }); } catch (err) { - console.warn( - `[ComposableCow] Decode failed event=${event.id} orderType=${orderType} err=${err}`, - ); + log("warn", "composableCow:decodeFailed", { event: event.id, orderType, err: String(err) }); decodedParams = null; decodeError = "invalid_static_input"; } diff --git a/src/application/handlers/settlement.ts b/src/application/handlers/settlement.ts index 60ffda5..fe4825b 100644 --- a/src/application/handlers/settlement.ts +++ b/src/application/handlers/settlement.ts @@ -2,6 +2,7 @@ import { ponder } from "ponder:registry"; import { AddressType, conditionalOrderGenerator, ownerMapping, transaction } from "ponder:schema"; import { and, eq } from "ponder"; import { keccak256, toBytes } from "viem"; +import { log } from "../helpers/logger"; import { AaveV3AdapterHelperAbi } from "../../../abis/AaveV3AdapterHelperAbi"; import { AAVE_V3_ADAPTER_FACTORY_ADDRESSES, @@ -31,15 +32,15 @@ function logStatsIfIntervalPassed() { if (Date.now() - statsLastLogAt < LOG_INTERVAL_MS) return; const contractAddresses = stats.tradeLogsFound - stats.skippedAlreadyMapped - stats.skippedEOA; - console.log( - `[SETTLEMENT:STATS] settlements=${stats.total}` + - ` tradeLogs=${stats.tradeLogsFound}` + - ` alreadyMapped=${stats.skippedAlreadyMapped}` + - ` eoa=${stats.skippedEOA}` + - ` notAdapter=${stats.skippedNotAdapter}` + - ` mapped=${stats.mapped}` + - ` | avgFactory=${contractAddresses > 0 ? (stats.msFactory / contractAddresses).toFixed(1) : 0}ms`, - ); + log("info", "settlement:stats", { + settlements: stats.total, + tradeLogs: stats.tradeLogsFound, + alreadyMapped: stats.skippedAlreadyMapped, + eoa: stats.skippedEOA, + notAdapter: stats.skippedNotAdapter, + mapped: stats.mapped, + avgFactoryMs: contractAddresses > 0 ? Number((stats.msFactory / contractAddresses).toFixed(1)) : 0, + }); statsLastLogAt = Date.now(); } @@ -79,15 +80,15 @@ ponder.on("GPv2Settlement:Settlement", async ({ event, context }) => { hash: event.transaction.hash, }); - for (const log of receipt.logs) { + for (const txLog of receipt.logs) { // Only Trade logs emitted by GPv2Settlement in this same transaction - if (log.address.toLowerCase() !== settlementAddress) continue; - if (log.topics[0] !== TRADE_TOPIC) continue; + if (txLog.address.toLowerCase() !== settlementAddress) continue; + if (txLog.topics[0] !== TRADE_TOPIC) continue; stats.tradeLogsFound++; // Decode owner from topics[1] — ABI-encoded 32-byte padded address - const owner = `0x${log.topics[1]!.slice(26)}` as `0x${string}`; + const owner = `0x${txLog.topics[1]!.slice(26)}` as `0x${string}`; const ownerAddress = owner.toLowerCase() as `0x${string}`; // Skip if already mapped (adapter seen in a prior settlement) @@ -194,13 +195,12 @@ ponder.on("GPv2Settlement:Settlement", async ({ event, context }) => { stats.mapped++; logStatsIfIntervalPassed(); - console.log( - `[COW:SETTLEMENT:TRADE] AAVE_ADAPTER_MAPPED` + - ` adapter=${ownerAddress}` + - ` eoa=${eoaOwner.toLowerCase()}` + - ` block=${event.block.number}` + - ` chain=${chainId}`, - ); + log("info", "settlement:aave_adapter_mapped", { + block: String(event.block.number), + chainId, + adapter: ownerAddress, + eoa: eoaOwner.toLowerCase(), + }); } logStatsIfIntervalPassed(); diff --git a/src/application/handlers/setup.ts b/src/application/handlers/setup.ts index 95a9ea6..e69f25c 100644 --- a/src/application/handlers/setup.ts +++ b/src/application/handlers/setup.ts @@ -1,5 +1,6 @@ import { ponder } from "ponder:registry"; import { sql } from "ponder"; +import { log } from "../helpers/logger"; /** * Creates the cow_cache schema and persistent cache tables on startup. @@ -37,7 +38,5 @@ ponder.on("ComposableCow:setup", async ({ context }) => { ) as { count: number }[]; const count = result[0]?.count ?? 0; - console.log( - `[COW:SETUP] cow_cache.order_uid_cache ready — ${count} entr${count === 1 ? "y" : "ies"} from previous run`, - ); + log("info", "setup:cacheReady", { count, entries: `${count} entr${count === 1 ? "y" : "ies"} from previous run` }); }); diff --git a/src/application/helpers/logger.ts b/src/application/helpers/logger.ts new file mode 100644 index 0000000..272c2a5 --- /dev/null +++ b/src/application/helpers/logger.ts @@ -0,0 +1,16 @@ +// Structured JSON logger for handler code — always emits one JSON line per call regardless of Ponder's --log-format setting. + +type LogLevel = "info" | "warn" | "error"; + +export function log( + level: LogLevel, + msg: string, + fields: Record = {}, +): void { + const line = JSON.stringify({ time: Date.now(), level, msg, ...fields }); + if (level === "warn" || level === "error") { + console.error(line); + } else { + console.log(line); + } +} diff --git a/src/application/helpers/orderbookClient.ts b/src/application/helpers/orderbookClient.ts index c16fa57..705870f 100644 --- a/src/application/helpers/orderbookClient.ts +++ b/src/application/helpers/orderbookClient.ts @@ -26,6 +26,7 @@ import { COMPOSABLE_COW_HANDLER_ADDRESSES, ORDERBOOK_API_URLS } from "../../data import { ORDERBOOK_HTTP_TIMEOUT_MS, SIGNING_SCHEME_EIP1271 } from "../../constants"; import { decodeEip1271Signature } from "../decoders/erc1271Signature"; import { fetchWithTimeout, TimeoutError, withTimeout } from "./withTimeout"; +import { log } from "./logger"; // ─── Types ─────────────────────────────────────────────────────────────────── @@ -87,16 +88,16 @@ export async function fetchComposableOrders( ): Promise { const apiBaseUrl = ORDERBOOK_API_URLS[chainId]; if (!apiBaseUrl) { - console.warn(`[COW:OB] No API URL for chainId=${chainId}`); + log("warn", "ob:noApiUrl", { chainId }); return []; } - console.log(`[COW:OB] FETCH owner=${owner} chain=${chainId}`); + log("info", "ob:fetch", { owner, chainId }); const allApiOrders = await fetchAccountOrders(apiBaseUrl, owner); const composable = await filterAndProcess(context, chainId, allApiOrders); if (composable.length === 0) { - console.log(`[COW:OB] owner=${owner} chain=${chainId} apiTotal=${allApiOrders.length} composable=0`); + log("info", "ob:fetchResult", { owner, chainId, apiTotal: allApiOrders.length, composable: 0 }); return []; } @@ -141,9 +142,7 @@ export async function fetchComposableOrders( } } - console.log( - `[COW:OB] owner=${owner} chain=${chainId} apiTotal=${allApiOrders.length} composable=${composable.length} cached=${composable.length - toRefresh.length} refreshed=${toRefresh.length}`, - ); + log("info", "ob:fetchResult", { owner, chainId, apiTotal: allApiOrders.length, composable: composable.length, cached: composable.length - toRefresh.length, refreshed: toRefresh.length }); return results; } @@ -254,9 +253,7 @@ export async function fetchOrderStatusByUids( ); } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:OB] statusByUids timeout chain=${chainId} toFetch=${toFetch.length} after=${ORDERBOOK_HTTP_TIMEOUT_MS * 2}ms`, - ); + log("warn", "ob:statusByUidsTimeout", { chainId, toFetch: toFetch.length, after: ORDERBOOK_HTTP_TIMEOUT_MS * 2 }); return result; // cache-only map — caller treats missing UIDs as "not on API yet" } throw err; @@ -317,7 +314,7 @@ async function fetchAccountOrders( "ob:account", ); if (!response.ok) { - console.warn(`[COW:OB] API ${response.status} owner=${owner}`); + log("warn", "ob:accountError", { status: response.status, owner }); break; } const page = (await response.json()) as OrderbookOrder[]; @@ -326,12 +323,10 @@ async function fetchAccountOrders( offset += page.length; } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:OB] Account fetch timeout owner=${owner} offset=${offset} after=${ORDERBOOK_HTTP_TIMEOUT_MS}ms`, - ); + log("warn", "ob:accountTimeout", { owner, offset, after: ORDERBOOK_HTTP_TIMEOUT_MS }); break; } - console.warn(`[COW:OB] Fetch failed owner=${owner} err=${err}`); + log("warn", "ob:accountFetchFailed", { owner, err: String(err) }); break; } } @@ -363,19 +358,17 @@ async function fetchOrdersByUids( "ob:byUids", ); if (!response.ok) { - console.warn(`[COW:OB] Batch fetch ${response.status} uids=${chunk.length} offset=${i}`); + log("warn", "ob:batchFetchError", { status: response.status, uids: chunk.length, offset: i }); continue; } const raw = (await response.json()) as { order: OrderbookOrder }[]; results.push(...raw.map((item) => item.order)); } catch (err) { if (err instanceof TimeoutError) { - console.warn( - `[COW:OB] Batch fetch timeout uids=${chunk.length} offset=${i} after=${ORDERBOOK_HTTP_TIMEOUT_MS}ms`, - ); + log("warn", "ob:batchFetchTimeout", { uids: chunk.length, offset: i, after: ORDERBOOK_HTTP_TIMEOUT_MS }); continue; } - console.warn(`[COW:OB] Batch fetch failed err=${err} offset=${i}`); + log("warn", "ob:batchFetchFailed", { err: String(err), offset: i }); } } diff --git a/src/application/helpers/uidPrecompute.ts b/src/application/helpers/uidPrecompute.ts index bd25086..16a3644 100644 --- a/src/application/helpers/uidPrecompute.ts +++ b/src/application/helpers/uidPrecompute.ts @@ -21,6 +21,7 @@ import { candidateDiscreteOrder, conditionalOrderGenerator, discreteOrder } from import { computeOrderUid, type GPv2OrderData } from "./orderUid"; import { fetchOrderStatusByUids } from "./orderbookClient"; import { type OrderType, DETERMINISTIC_ORDER_TYPE } from "../../utils/order-types"; +import { log } from "./logger"; // GPv2Order.sol constant hashes const KIND_SELL = "0xf3b277728b3fee749481eb3e0b3b48980dbbab78658fc419025cb16eee346775" as Hex; @@ -61,7 +62,7 @@ export function precomputeOrderUids( ): PrecomputedOrder[] | null { if (!decodedParams) { if (DETERMINISTIC_ORDER_TYPE[orderType]) { - console.warn(`[COW:PRECOMPUTE] SKIP type=${orderType} owner=${owner} chain=${chainId} reason=decodedParams_null`); + log("warn", "precompute:skip", { orderType, owner, chainId, reason: "decodedParams_null" }); } return null; } @@ -167,9 +168,7 @@ export async function precomputeAndDiscover( eq(conditionalOrderGenerator.eventId, generatorEventId), ), ); - console.log( - `[ComposableCow] All ${precomputed.length} pre-computed orders terminal on API — generator=${generatorEventId} marked Completed`, - ); + log("info", "precompute:allTerminal", { count: precomputed.length, generatorEventId }); return true; } @@ -221,7 +220,7 @@ function precomputeTwapUids( const appData = params["appData"] as Hex | undefined; if (!sellToken || !buyToken || !partSellAmount || !minPartLimit || !n || !t || !appData) { - console.warn(`[COW:PRECOMPUTE] SKIP type=TWAP owner=${owner} chain=${chainId} reason=missing_params missing=${[!sellToken && "sellToken", !buyToken && "buyToken", !partSellAmount && "partSellAmount", !minPartLimit && "minPartLimit", !n && "n", !t && "t", !appData && "appData"].filter(Boolean).join(",")}`); + log("warn", "precompute:skip", { orderType: "TWAP", owner, chainId, reason: "missing_params", missing: [!sellToken && "sellToken", !buyToken && "buyToken", !partSellAmount && "partSellAmount", !minPartLimit && "minPartLimit", !n && "n", !t && "t", !appData && "appData"].filter(Boolean).join(",") }); return null; } @@ -232,11 +231,11 @@ function precomputeTwapUids( const t0 = BigInt(t0Raw ?? "0") === 0n ? blockTimestamp : BigInt(t0Raw!); if (nParts <= 0 || tSeconds <= 0n) { - console.warn(`[COW:PRECOMPUTE] SKIP type=TWAP owner=${owner} chain=${chainId} reason=invalid_math nParts=${nParts} tSeconds=${tSeconds}`); + log("warn", "precompute:skip", { orderType: "TWAP", owner, chainId, reason: "invalid_math", nParts, tSeconds: String(tSeconds) }); return null; } if (nParts > 100000) { - console.warn(`[COW:PRECOMPUTE] SKIP type=TWAP owner=${owner} chain=${chainId} reason=too_many_parts nParts=${nParts}`); + log("warn", "precompute:skip", { orderType: "TWAP", owner, chainId, reason: "too_many_parts", nParts }); return null; } @@ -318,7 +317,7 @@ function precomputeStopLossUid( const validTo = params["validTo"]; if (!sellToken || !buyToken || !sellAmount || !buyAmount || !appData || !validTo) { - console.warn(`[COW:PRECOMPUTE] SKIP type=StopLoss owner=${owner} chain=${chainId} reason=missing_params missing=${[!sellToken && "sellToken", !buyToken && "buyToken", !sellAmount && "sellAmount", !buyAmount && "buyAmount", !appData && "appData", !validTo && "validTo"].filter(Boolean).join(",")}`); + log("warn", "precompute:skip", { orderType: "StopLoss", owner, chainId, reason: "missing_params", missing: [!sellToken && "sellToken", !buyToken && "buyToken", !sellAmount && "sellAmount", !buyAmount && "buyAmount", !appData && "appData", !validTo && "validTo"].filter(Boolean).join(",") }); return null; }