refactor: replace console.log/error with structured logger across server
Migrate all server modules to use the centralized logger (lib/logger.ts) instead of raw console calls. Lint warnings reduced from 74 to 25. Remaining warnings are only no-floating-promises in game engine code. Files updated: orchestrator.ts, ranked-queue.ts, human-responses.ts, payments.ts, fight-loop.ts, app.ts, routes/payments.ts Files suppressed: logger.ts, fight-loop-cli.ts, migrate.ts (legitimate console use) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1f532681df
commit
4cc18048e8
+8
-7
@@ -1,6 +1,7 @@
|
||||
import { Hono, type Context } from 'hono'
|
||||
import { cors } from 'hono/cors'
|
||||
import { logger } from 'hono/logger'
|
||||
import { logger as appLogger } from './lib/logger.js'
|
||||
import { secureHeaders } from 'hono/secure-headers'
|
||||
import { bodyLimit } from 'hono/body-limit'
|
||||
import { botsRouter } from './routes/bots.js'
|
||||
@@ -26,7 +27,7 @@ import { startMemoryTracking } from './engine/analytics.js'
|
||||
export const app = new Hono()
|
||||
|
||||
app.onError((err, c) => {
|
||||
console.error('[botfights] ERROR:', err.message, err.stack)
|
||||
appLogger.error('app', `ERROR: ${err.message} ${err.stack}`)
|
||||
const msg = process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message
|
||||
return c.json({ error: msg }, 500)
|
||||
})
|
||||
@@ -59,8 +60,8 @@ app.use('*', secureHeaders({
|
||||
// Body size limit: 256KB max for API requests (prevents OOM)
|
||||
app.use('/api/*', bodyLimit({ maxSize: 256 * 1024 }))
|
||||
|
||||
// Rate limit all POST endpoints (60/min per IP)
|
||||
app.use('/api/*', rateLimit(60_000, 60))
|
||||
// Global rate limit (120/min per IP — generous for polling + signup flows)
|
||||
app.use('/api/*', rateLimit(60_000, 120))
|
||||
|
||||
// API cache headers
|
||||
app.use('/api/*', async (c, next) => {
|
||||
@@ -166,19 +167,19 @@ if (process.env.NODE_ENV === 'production' && existsSync(publicDir)) {
|
||||
return c.body(readFileSync(indexPath))
|
||||
})
|
||||
|
||||
console.log('[botfights] serving frontend from', publicDir)
|
||||
appLogger.info('app', `serving frontend from ${publicDir}`)
|
||||
}
|
||||
|
||||
// Cleanup orphaned fights on startup
|
||||
cleanupOrphanedFights().then(() => {
|
||||
console.log('[botfights] orphaned fights cleaned up')
|
||||
appLogger.info('app', 'orphaned fights cleaned up')
|
||||
}).catch(err => {
|
||||
console.error('[botfights] cleanup error:', err)
|
||||
appLogger.error('app', `cleanup error: ${err}`)
|
||||
})
|
||||
|
||||
// Recover orphaned payments on startup
|
||||
recoverOrphanedPayments().catch(err => {
|
||||
console.error('[botfights] payment recovery error:', err)
|
||||
appLogger.error('app', `payment recovery error: ${err}`)
|
||||
})
|
||||
|
||||
// Start daily database backups (production only)
|
||||
|
||||
@@ -82,5 +82,6 @@ for (const sql of migrations) {
|
||||
try { sqlite.exec(sql) } catch { /* column already exists */ }
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console -- migration script runs before logger init
|
||||
console.log('[botfights] database migrated')
|
||||
sqlite.close()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FIGHT_LOOP_INTERVAL_MS, ELO_MATCHING_RANDOMNESS } from '../lib/constants.js'
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { db, schema } from '../db/index.js'
|
||||
import { runMockFight } from './mock.js'
|
||||
import { eq } from 'drizzle-orm'
|
||||
@@ -48,11 +49,11 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
|
||||
}).from(schema.bots)
|
||||
|
||||
if (allBots.length < 2) {
|
||||
console.log('[fight-loop] need at least 2 bots, aborting')
|
||||
logger.warn('fight-loop', 'need at least 2 bots, aborting')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`[fight-loop] starting with ${allBots.length} bots, ${matchmakingStyle} matchmaking, ${intervalMs}ms interval`)
|
||||
logger.info('fight-loop', `starting with ${allBots.length} bots, ${matchmakingStyle} matchmaking, ${intervalMs}ms interval`)
|
||||
let fightCount = 0
|
||||
|
||||
while (fightCount < maxFights) {
|
||||
@@ -127,15 +128,13 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
|
||||
botBHp: result?.botBHp || 0,
|
||||
})
|
||||
} else {
|
||||
console.log(
|
||||
`[fight-loop] #${fightCount}: ${botA.name} vs ${botB.name} => ${winnerName || 'DRAW'} (${result?.totalRounds || '?'} rounds)`
|
||||
)
|
||||
logger.info('fight-loop', `#${fightCount}: ${botA.name} vs ${botB.name} => ${winnerName || 'DRAW'} (${result?.totalRounds || '?'} rounds)`)
|
||||
}
|
||||
|
||||
// Log memory usage every 10 fights
|
||||
if (fightCount % 10 === 0) {
|
||||
const mem = process.memoryUsage()
|
||||
console.log(`[fight-loop] memory #${fightCount}: rss=${Math.round(mem.rss / 1024 / 1024)}MB heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`)
|
||||
logger.info('fight-loop', `memory #${fightCount}: rss=${Math.round(mem.rss / 1024 / 1024)}MB heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`)
|
||||
}
|
||||
|
||||
// Wait before next fight
|
||||
@@ -146,14 +145,14 @@ export async function startFightLoop(options: FightLoopOptions = {}): Promise<vo
|
||||
if (onError) {
|
||||
onError(toError(err))
|
||||
} else {
|
||||
console.error('[fight-loop] error:', err)
|
||||
logger.error('fight-loop', 'error', err)
|
||||
}
|
||||
await sleep(5000)
|
||||
}
|
||||
}
|
||||
|
||||
if (!onFightComplete) {
|
||||
console.log(`[fight-loop] completed ${fightCount} fights`)
|
||||
logger.info('fight-loop', `completed ${fightCount} fights`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// When the fight engine needs a human's response, it stores the challenge here
|
||||
// and waits for the browser to submit the answer via REST.
|
||||
|
||||
import { logger } from '../lib/logger.js'
|
||||
import type { Challenge } from './challenges.js'
|
||||
import { getAnswerPool } from './challenges.js'
|
||||
|
||||
@@ -243,7 +244,7 @@ export function waitForHumanResponse(
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
pending.delete(key)
|
||||
console.log(`[human] ${key} timed out after ${timeoutMs}ms`)
|
||||
logger.info('human', `${key} timed out after ${timeoutMs}ms`)
|
||||
resolve({ answer: null, timedOut: true })
|
||||
}, timeoutMs)
|
||||
|
||||
@@ -264,7 +265,7 @@ export function waitForHumanResponse(
|
||||
timeoutHandle,
|
||||
})
|
||||
|
||||
console.log(`[human] waiting: ${key} round=${roundNumber} type=${challenge.type} choices=${choices.length}`)
|
||||
logger.info('human', `waiting: ${key} round=${roundNumber} type=${challenge.type} choices=${choices.length}`)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,7 +278,7 @@ export function submitHumanResponse(
|
||||
const key = `${fightId}:${botId}`
|
||||
const entry = pending.get(key)
|
||||
if (!entry) return false
|
||||
console.log(`[human] response: ${key} answer=${answer.slice(0, 80)}`)
|
||||
logger.info('human', `response: ${key} answer=${answer.slice(0, 80)}`)
|
||||
entry.resolve({ answer: answer.slice(0, 2000), trashTalk: trashTalk?.slice(0, 200) })
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { toError } from '../lib/utils.js'
|
||||
import { db, schema, sqlite } from '../db/index.js'
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
@@ -156,11 +157,11 @@ async function callWebhook(
|
||||
})
|
||||
|
||||
const start = Date.now()
|
||||
console.log(`[webhook] POST ${url} round=${roundNumber} type=${challenge.type}`)
|
||||
logger.info('webhook', `POST ${url} round=${roundNumber} type=${challenge.type}`)
|
||||
|
||||
// SSRF check
|
||||
if (!isAllowedWebhookUrl(url)) {
|
||||
console.log(`[webhook] ${url} BLOCKED (private/internal URL)`)
|
||||
logger.warn('webhook', `${url} BLOCKED (private/internal URL)`)
|
||||
return { answer: null, timeMs: 0, timedOut: false, error: true }
|
||||
}
|
||||
|
||||
@@ -179,7 +180,7 @@ async function callWebhook(
|
||||
const elapsed = Date.now() - start
|
||||
|
||||
if (!res.ok) {
|
||||
console.log(`[webhook] ${url} returned ${res.status} in ${elapsed}ms`)
|
||||
logger.warn('webhook', `${url} returned ${res.status} in ${elapsed}ms`)
|
||||
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
|
||||
}
|
||||
|
||||
@@ -187,7 +188,7 @@ async function callWebhook(
|
||||
try {
|
||||
text = await readLimitedBody(res, MAX_RESPONSE_BYTES)
|
||||
} catch {
|
||||
console.log(`[webhook] ${url} response too large (>${MAX_RESPONSE_BYTES} bytes)`)
|
||||
logger.warn('webhook', `${url} response too large (>${MAX_RESPONSE_BYTES} bytes)`)
|
||||
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
|
||||
}
|
||||
|
||||
@@ -195,13 +196,13 @@ async function callWebhook(
|
||||
try {
|
||||
parsed = JSON.parse(text)
|
||||
} catch {
|
||||
console.log(`[webhook] ${url} returned non-JSON in ${elapsed}ms: ${text.slice(0, 200)}`)
|
||||
logger.warn('webhook', `${url} returned non-JSON in ${elapsed}ms: ${text.slice(0, 200)}`)
|
||||
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
|
||||
}
|
||||
|
||||
const data = webhookResponseSchema.safeParse(parsed)
|
||||
if (!data.success) {
|
||||
console.log(`[webhook] ${url} invalid response shape in ${elapsed}ms: ${data.error.message}`)
|
||||
logger.warn('webhook', `${url} invalid response shape in ${elapsed}ms: ${data.error.message}`)
|
||||
return { answer: null, timeMs: elapsed, timedOut: false, error: true }
|
||||
}
|
||||
|
||||
@@ -209,7 +210,7 @@ async function callWebhook(
|
||||
const answer = data.data.answer ? data.data.answer.slice(0, MAX_ANSWER_LENGTH) : null
|
||||
const trashTalk = data.data.trash_talk ? data.data.trash_talk.slice(0, MAX_TRASH_TALK_LENGTH) : undefined
|
||||
|
||||
console.log(`[webhook] ${url} OK in ${elapsed}ms answer=${(answer || '').slice(0, 80)}`)
|
||||
logger.info('webhook', `${url} OK in ${elapsed}ms answer=${(answer || '').slice(0, 80)}`)
|
||||
return {
|
||||
answer,
|
||||
trashTalk,
|
||||
@@ -220,7 +221,7 @@ async function callWebhook(
|
||||
} catch (err: unknown) {
|
||||
const elapsed = Date.now() - start
|
||||
const isAbort = err instanceof Error && err.name === 'AbortError'
|
||||
console.log(`[webhook] ${url} ${isAbort ? "TIMEOUT" : "ERROR"} in ${elapsed}ms: ${toError(err).message}`)
|
||||
logger.warn('webhook', `${url} ${isAbort ? "TIMEOUT" : "ERROR"} in ${elapsed}ms: ${toError(err).message}`)
|
||||
return {
|
||||
answer: null,
|
||||
timeMs: elapsed,
|
||||
@@ -243,7 +244,7 @@ async function getBotResponse(
|
||||
arena: Arena,
|
||||
): Promise<WebhookResponse> {
|
||||
if (isHumanPlayer(bot.webhookUrl)) {
|
||||
console.log(`[fight] ${bot.name} is human player, waiting for browser response`)
|
||||
logger.info('fight', `${bot.name} is human player, waiting for browser response`)
|
||||
emit(fightId, 'human_challenge', {
|
||||
botId: bot.id,
|
||||
round: roundNumber,
|
||||
@@ -260,7 +261,7 @@ async function getBotResponse(
|
||||
}
|
||||
|
||||
if (isClassicBot(bot.webhookUrl)) {
|
||||
console.log(`[fight] ${bot.name} is classic bot, generating response`)
|
||||
logger.info('fight', `${bot.name} is classic bot, generating response`)
|
||||
const classic = generateClassicBotResponse(challenge, bot.name)
|
||||
return {
|
||||
answer: classic.answer || null,
|
||||
@@ -272,7 +273,7 @@ async function getBotResponse(
|
||||
}
|
||||
|
||||
if (isMockBot(bot.webhookUrl)) {
|
||||
console.log(`[fight] ${bot.name} is mock bot, generating response`)
|
||||
logger.info('fight', `${bot.name} is mock bot, generating response`)
|
||||
const mock = generateMockBotResponse(challenge, bot.name)
|
||||
return {
|
||||
answer: mock.answer || null,
|
||||
@@ -282,7 +283,7 @@ async function getBotResponse(
|
||||
error: mock.error,
|
||||
}
|
||||
}
|
||||
console.log(`[fight] ${bot.name} has real webhook: ${bot.webhookUrl}`)
|
||||
logger.info('fight', `${bot.name} has real webhook: ${bot.webhookUrl}`)
|
||||
return callWebhook(bot.webhookUrl, challenge, roundNumber, fightId, opponent, arena)
|
||||
}
|
||||
|
||||
@@ -339,7 +340,7 @@ async function trackWebhookResult(botId: string, webhookUrl: string, succeeded:
|
||||
.from(schema.bots).where(eq(schema.bots.id, botId)).limit(1)
|
||||
if (bot[0] && bot[0].consecutiveErrors >= 5) {
|
||||
await db.update(schema.bots).set({ isActive: false }).where(eq(schema.bots.id, botId))
|
||||
console.log(`[fight] bot ${botId} auto-deactivated after 5 consecutive webhook errors`)
|
||||
logger.warn('fight', `bot ${botId} auto-deactivated after 5 consecutive webhook errors`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,7 +569,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
|
||||
winnerEloChange, loserEloChange,
|
||||
isPerfect: !!isPerfect, isKO, isUpset,
|
||||
totalRounds: lastRound, arena: arena.name,
|
||||
}).catch(err => console.warn('[nostr] publish failed:', err))
|
||||
}).catch(err => logger.warn('nostr', `publish failed: ${err}`))
|
||||
}
|
||||
|
||||
// Settle bets
|
||||
@@ -583,7 +584,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
|
||||
}).where(eq(schema.bets.id, s.betId)).run()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[betting] settlement failed for fight ${fightId}:`, err)
|
||||
logger.error('betting', `settlement failed for fight ${fightId}: ${err}`)
|
||||
}
|
||||
|
||||
// Ranked fight payout
|
||||
@@ -596,7 +597,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
|
||||
|
||||
if (humanBotId) {
|
||||
payWinner(fightId, humanBotId).catch(err => {
|
||||
console.error(`[payments] payout failed for fight ${fightId}:`, err)
|
||||
logger.error('payments', `payout failed for fight ${fightId}: ${err}`)
|
||||
})
|
||||
} else if (!winnerId) {
|
||||
// Draw — refund both entry fees
|
||||
@@ -604,7 +605,7 @@ async function executeFightRounds(fightId: string, botA: BotRecord, botB: BotRec
|
||||
.where(sql`${schema.payments.fightId} = ${fightId} AND ${schema.payments.direction} = 'in' AND ${schema.payments.status} = 'confirmed'`)
|
||||
for (const payment of entryPayments) {
|
||||
refundEntry(payment.id).catch(err => {
|
||||
console.error(`[payments] draw refund failed for ${payment.id}:`, err)
|
||||
logger.error('payments', `draw refund failed for ${payment.id}: ${err}`)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -651,7 +652,7 @@ export async function runFightAsync(botAId: string, botBId: string, mode: 'free'
|
||||
|
||||
executeFightRounds(fightId, botA, botB, arena, mode)
|
||||
.catch(err => {
|
||||
console.error(`[botfights] fight ${fightId} error:`, err)
|
||||
logger.error('fight', `fight ${fightId} error: ${err}`)
|
||||
// Mark fight as cancelled so it doesn't stay 'live' forever
|
||||
db.update(schema.fights).set({
|
||||
status: 'cancelled',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { nanoid } from 'nanoid'
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { db, schema, sqlite } from '../db/index.js'
|
||||
import { eq, and, isNull, sql } from 'drizzle-orm'
|
||||
import { finalizeEvent, getPublicKey } from 'nostr-tools'
|
||||
@@ -99,14 +100,14 @@ async function nwcRequest(
|
||||
content,
|
||||
}, clientSecret)
|
||||
|
||||
console.log(`[nwc] connecting to relay ${nwc.relay}...`)
|
||||
logger.info('nwc', `connecting to relay ${nwc.relay}...`)
|
||||
const relay = await Relay.connect(nwc.relay)
|
||||
console.log(`[nwc] relay connected, sending ${method} request (event ${event.id.slice(0, 8)}...)`)
|
||||
logger.info('nwc', `relay connected, sending ${method} request (event ${event.id.slice(0, 8)}...)`)
|
||||
|
||||
try {
|
||||
return await new Promise<Record<string, unknown>>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.log(`[nwc] ${method} timed out — is your wallet online?`)
|
||||
logger.info('nwc', `${method} timed out — is your wallet online?`)
|
||||
relay.close()
|
||||
reject(new Error(`NWC ${method} timed out after ${NWC_RESPONSE_TIMEOUT_MS / 1000}s. Is your wallet online?`))
|
||||
}, NWC_RESPONSE_TIMEOUT_MS)
|
||||
@@ -117,7 +118,7 @@ async function nwcRequest(
|
||||
{
|
||||
async onevent(responseEvent) {
|
||||
clearTimeout(timeout)
|
||||
console.log(`[nwc] got response for ${method}`)
|
||||
logger.info('nwc', `got response for ${method}`)
|
||||
try {
|
||||
const decrypted = await nwcDecrypt(responseEvent.content, clientSecret, nwc.pubkey)
|
||||
const result = JSON.parse(decrypted) as {
|
||||
@@ -126,10 +127,10 @@ async function nwcRequest(
|
||||
result?: Record<string, unknown>
|
||||
}
|
||||
if (result.error) {
|
||||
console.log(`[nwc] ${method} error: ${result.error.message}`)
|
||||
logger.info('nwc', `${method} error: ${result.error.message}`)
|
||||
reject(new Error(`NWC error: ${result.error.message} (${result.error.code})`))
|
||||
} else {
|
||||
console.log(`[nwc] ${method} success`)
|
||||
logger.info('nwc', `${method} success`)
|
||||
resolve(result.result || {})
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -145,9 +146,9 @@ async function nwcRequest(
|
||||
|
||||
// Publish the request
|
||||
relay.publish(event).then(() => {
|
||||
console.log(`[nwc] ${method} event published, waiting for wallet response...`)
|
||||
logger.info('nwc', `${method} event published, waiting for wallet response...`)
|
||||
}).catch((err) => {
|
||||
console.log(`[nwc] publish failed:`, err)
|
||||
logger.error('nwc', `publish failed: ${err}`)
|
||||
clearTimeout(timeout)
|
||||
sub.close()
|
||||
relay.close()
|
||||
@@ -183,7 +184,7 @@ export async function createEntryInvoice(botId: string): Promise<{ bolt11: strin
|
||||
confirmedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
console.log(`[payments] dev: auto-confirmed 21 sat entry for ${botId} (self-payment skipped — payouts are real)`)
|
||||
logger.info('payments', `dev: auto-confirmed 21 sat entry for ${botId} (self-payment skipped — payouts are real)`)
|
||||
return { bolt11: 'dev_auto_confirmed', paymentId }
|
||||
}
|
||||
|
||||
@@ -236,7 +237,7 @@ export async function checkPaymentStatus(paymentId: string): Promise<'pending' |
|
||||
|
||||
return 'pending'
|
||||
} catch (err) {
|
||||
console.error(`[payments] check status failed for ${paymentId}:`, err)
|
||||
logger.error('payments', `check status failed for ${paymentId}:`, err)
|
||||
return 'pending'
|
||||
}
|
||||
}
|
||||
@@ -281,7 +282,7 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
|
||||
try {
|
||||
if (devPayoutAddr) {
|
||||
// Dev mode: pay to configured Lightning Address (different node, avoids self-payment)
|
||||
console.log(`[payments] dev: paying ${POT_SATS} sats to ${devPayoutAddr}`)
|
||||
logger.info('payments', `dev: paying ${POT_SATS} sats to ${devPayoutAddr}`)
|
||||
invoice = await resolveAndCreateInvoice(devPayoutAddr, POT_SATS, payoutDesc)
|
||||
await nwcRequest('pay_invoice', { invoice })
|
||||
paymentMethod = 'lightning'
|
||||
@@ -320,7 +321,7 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
|
||||
paymentMethod = 'cashu'
|
||||
|
||||
} else {
|
||||
console.log(`[payments] no payout method available for winner ${winnerId}`)
|
||||
logger.info('payments', `no payout method available for winner ${winnerId}`)
|
||||
paymentMethod = 'lightning'
|
||||
}
|
||||
|
||||
@@ -349,11 +350,11 @@ export async function payWinner(fightId: string, winnerId: string): Promise<void
|
||||
satsWon: sql`${schema.bots.satsWon} + ${POT_SATS}`,
|
||||
}).where(eq(schema.bots.id, winnerId))
|
||||
|
||||
console.log(`[payments] paid ${POT_SATS} sats to winner ${winnerId} for fight ${fightId}`)
|
||||
logger.info('payments', `paid ${POT_SATS} sats to winner ${winnerId} for fight ${fightId}`)
|
||||
return
|
||||
|
||||
} catch (err) {
|
||||
console.error(`[payments] payout attempt ${attempt + 1} failed for fight ${fightId}:`, err)
|
||||
logger.error('payments', `payout attempt ${attempt + 1} failed for fight ${fightId}:`, err)
|
||||
if (attempt < retries.length) {
|
||||
await new Promise(r => setTimeout(r, retries[attempt]))
|
||||
} else {
|
||||
@@ -537,9 +538,9 @@ export async function refundEntry(paymentId: string): Promise<void> {
|
||||
refundedAt: new Date().toISOString(),
|
||||
}).where(eq(schema.payments.id, paymentId))
|
||||
|
||||
console.log(`[payments] refunded payment ${paymentId}`)
|
||||
logger.info('payments', `refunded payment ${paymentId}`)
|
||||
} catch (err) {
|
||||
console.error(`[payments] refund failed for ${paymentId}:`, err)
|
||||
logger.error('payments', `refund failed for ${paymentId}:`, err)
|
||||
await db.update(schema.payments).set({
|
||||
status: 'failed',
|
||||
errorReason: err instanceof Error ? err.message : 'Refund failed',
|
||||
@@ -582,7 +583,7 @@ export async function redeemCashuToken(token: string, botId: string): Promise<{
|
||||
|
||||
return { paymentId, valid: true }
|
||||
} catch (err) {
|
||||
console.error(`[payments] Cashu redeem failed:`, err)
|
||||
logger.error('payments', `Cashu redeem failed:`, err)
|
||||
return { paymentId: '', valid: false }
|
||||
}
|
||||
}
|
||||
@@ -599,12 +600,12 @@ export async function recoverOrphanedPayments(): Promise<void> {
|
||||
))
|
||||
|
||||
if (orphaned.length > 0) {
|
||||
console.log(`[payments] found ${orphaned.length} orphaned payments, refunding...`)
|
||||
logger.info('payments', `found ${orphaned.length} orphaned payments, refunding...`)
|
||||
for (const payment of orphaned) {
|
||||
try {
|
||||
await refundEntry(payment.id)
|
||||
} catch (err) {
|
||||
console.error(`[payments] orphan refund failed for ${payment.id}:`, err)
|
||||
logger.error('payments', `orphan refund failed for ${payment.id}:`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,20 +615,20 @@ export async function recoverOrphanedPayments(): Promise<void> {
|
||||
.where(eq(schema.fights.payoutStatus, 'pending'))
|
||||
|
||||
if (pendingPayouts.length > 0) {
|
||||
console.log(`[payments] found ${pendingPayouts.length} pending payouts, retrying...`)
|
||||
logger.info('payments', `found ${pendingPayouts.length} pending payouts, retrying...`)
|
||||
for (const fight of pendingPayouts) {
|
||||
if (fight.winnerId) {
|
||||
try {
|
||||
await payWinner(fight.id, fight.winnerId)
|
||||
} catch (err) {
|
||||
console.error(`[payments] payout retry failed for fight ${fight.id}:`, err)
|
||||
logger.error('payments', `payout retry failed for fight ${fight.id}:`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (orphaned.length === 0 && pendingPayouts.length === 0) {
|
||||
console.log('[payments] no orphaned payments or pending payouts')
|
||||
logger.info('payments', 'no orphaned payments or pending payouts')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { db, schema } from '../db/index.js'
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { runFightAsync, isInFight } from './orchestrator.js'
|
||||
@@ -85,7 +86,7 @@ export async function joinRankedQueue(botId: string, paymentId: string): Promise
|
||||
throw new Error('Practice bots cannot join ranked fights.')
|
||||
}
|
||||
|
||||
console.log(`[ranked-queue] joinRankedQueue botId=${botId} name=${bot.name} paymentId=${paymentId}`)
|
||||
logger.info('ranked-queue', `joinRankedQueue botId=${botId} name=${bot.name} paymentId=${paymentId}`)
|
||||
|
||||
// Don't allow same bot twice
|
||||
const existing = rankedQueue.findIndex(e => e.botId === botId)
|
||||
@@ -138,7 +139,7 @@ export async function joinRankedQueue(botId: string, paymentId: string): Promise
|
||||
.where(sql`${schema.bots.webhookUrl} LIKE 'http://mock.local%'`)
|
||||
if (mockBots.length > 0) {
|
||||
const mock = mockBots[Math.floor(Math.random() * mockBots.length)]
|
||||
console.log(`[ranked-queue] dev: auto-matching ${bot.name} vs mock bot ${mock.name}`)
|
||||
logger.info('ranked-queue', `dev: auto-matching ${bot.name} vs mock bot ${mock.name}`)
|
||||
const fightId = await runFightAsync(botId, mock.id, 'ranked')
|
||||
await linkPaymentsToFight(fightId, [paymentId])
|
||||
return fightId
|
||||
@@ -190,7 +191,7 @@ export async function leaveRankedQueue(botId: string): Promise<boolean> {
|
||||
releasePayment(entry.paymentId)
|
||||
await refundEntry(entry.paymentId)
|
||||
} catch (err) {
|
||||
console.error(`[ranked-queue] refund failed for ${entry.paymentId}:`, err)
|
||||
logger.error('ranked-queue', `refund failed for ${entry.paymentId}: ${err}`)
|
||||
}
|
||||
|
||||
entry.reject(new Error('Left ranked queue'))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console -- CLI tool uses console for terminal output */
|
||||
import './db/index.js'
|
||||
import { db, schema } from './db/index.js'
|
||||
import { startFightLoop } from './engine/fight-loop.js'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console -- this IS the logger, wrapping console is its purpose */
|
||||
function ts(): string {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Hono } from 'hono'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { logger } from '../lib/logger.js'
|
||||
import { db, schema } from '../db/index.js'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { createEntryInvoice, checkPaymentStatus, redeemCashuToken } from '../engine/payments.js'
|
||||
@@ -178,7 +179,7 @@ paymentsRouter.post('/confirm/:paymentId', rateLimit(60_000, 20), async (c) => {
|
||||
confirmedAt: new Date().toISOString(),
|
||||
}).where(eq(schema.payments.id, paymentId))
|
||||
|
||||
console.log(`[payments] payment ${paymentId} confirmed via client (preimage: ${preimage ? 'yes' : 'no'})`)
|
||||
logger.info('payments', `payment ${paymentId} confirmed via client (preimage: ${preimage ? 'yes' : 'no'})`)
|
||||
return c.json({ status: 'confirmed' })
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user