From f62323c59f837d47decc013f946f8c718192b460 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 20:41:23 +0000 Subject: [PATCH] refactor: structured logging across server Create server/src/lib/logger.ts with info/warn/error methods that add [botfights:tag] timestamps. Replace bare console.log/warn/error calls in index.ts, seed.ts, routes/fights.ts, engine/queue.ts, engine/mock.ts. Co-Authored-By: Claude Opus 4.6 --- server/src/engine/mock.ts | 9 +++++---- server/src/engine/queue.ts | 9 +++++---- server/src/index.ts | 13 +++++++------ server/src/lib/logger.ts | 19 +++++++++++++++++++ server/src/routes/fights.ts | 5 +++-- server/src/seed.ts | 5 +++-- 6 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 server/src/lib/logger.ts diff --git a/server/src/engine/mock.ts b/server/src/engine/mock.ts index fb675e8..2d34760 100644 --- a/server/src/engine/mock.ts +++ b/server/src/engine/mock.ts @@ -1,4 +1,5 @@ import { nanoid } from 'nanoid' +import { logger } from '../lib/logger.js' import { createHash, randomBytes } from 'crypto' import { db, schema, sqlite } from '../db/index.js' import { randomArena } from './arenas.js' @@ -335,7 +336,7 @@ export async function seedClassicBots(): Promise { }) } - console.log(`[botfights] seeded ${CLASSIC_BOTS.length} classic bots`) + logger.info('mock', `seeded ${CLASSIC_BOTS.length} classic bots`) } export function isClassicBot(webhookUrl: string): boolean { @@ -400,7 +401,7 @@ export async function seedMockBots(): Promise { inserted++ } - console.log(`[botfights] mock bots: ${inserted} inserted, ${updated} updated`) + logger.info('mock', `mock bots: ${inserted} inserted, ${updated} updated`) } export async function runMockFight(botAId: string, botBId: string): Promise { @@ -558,7 +559,7 @@ export function generateMockBotResponse( export async function seedMockFights(count: number = 12): Promise { const allBots = await db.select({ id: schema.bots.id, eloRating: schema.bots.eloRating }).from(schema.bots) if (allBots.length < 2) { - console.log('[botfights] need at least 2 bots to seed fights') + logger.info('mock', 'need at least 2 bots to seed fights') return } @@ -584,5 +585,5 @@ export async function seedMockFights(count: number = 12): Promise { } } - console.log(`[botfights] seeded ${count} mock fights`) + logger.info('mock', `seeded ${count} mock fights`) } diff --git a/server/src/engine/queue.ts b/server/src/engine/queue.ts index 85b5d8b..2b72021 100644 --- a/server/src/engine/queue.ts +++ b/server/src/engine/queue.ts @@ -1,4 +1,5 @@ import { db, schema } from '../db/index.js' +import { logger } from '../lib/logger.js' import { eq } from 'drizzle-orm' import { runFightAsync, isInFight } from './orchestrator.js' import { seedMockBots } from './mock.js' @@ -69,7 +70,7 @@ export async function joinQueue(botId: string): Promise { throw new Error('Bot is deactivated due to webhook errors. Re-test your webhook to reactivate.') } - console.log(`[queue] joinQueue botId=${botId} name=${bot.name} webhook=${bot.webhookUrl}`) + logger.info('queue', `joinQueue botId=${botId} name=${bot.name} webhook=${bot.webhookUrl}`) // Don't allow same bot twice in queue const existing = waitingQueue.findIndex(e => e.botId === botId) @@ -145,7 +146,7 @@ async function startFight(botAId: string, botBId: string): Promise { } async function matchAgainstMock(botId: string, webhookUrl: string): Promise { - console.log(`[queue] matchAgainstMock botId=${botId} webhook=${webhookUrl}`) + logger.info('queue', `matchAgainstMock botId=${botId} webhook=${webhookUrl}`) const allBots = await db.select({ id: schema.bots.id, webhookUrl: schema.bots.webhookUrl, @@ -155,7 +156,7 @@ async function matchAgainstMock(botId: string, webhookUrl: string): Promise b.id !== botId && b.webhookUrl.startsWith('http://mock.local')) if (mockBots.length === 0) { - console.log('[queue] No mock bots found, seeding...') + logger.info('queue', 'No mock bots found, seeding...') await seedMockBots() return matchAgainstMock(botId, webhookUrl) } @@ -166,6 +167,6 @@ async function matchAgainstMock(botId: string, webhookUrl: string): Promise Math.abs(a.eloRating - botElo) - Math.abs(b.eloRating - botElo)) const opponent = mockBots[0] - console.log(`[queue] starting fight: ${botId} vs mock ${opponent.id}`) + logger.info('queue', `starting fight: ${botId} vs mock ${opponent.id}`) return runFightAsync(botId, opponent.id) } diff --git a/server/src/index.ts b/server/src/index.ts index 935b40b..3135c6c 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,4 +1,5 @@ import { serve } from '@hono/node-server' +import { logger } from './lib/logger.js' import { app } from './app.js' import { runMigrations } from './db/startup.js' import { seedMockBots, seedClassicBots } from './engine/mock.js' @@ -11,10 +12,10 @@ if (process.env.NODE_ENV === 'production') { const walletVars = ['BOTFIGHTS_NWC_URL', 'BOTFIGHTS_WALLET_ENCRYPTION_KEY'] const missing = walletVars.filter(k => !process.env[k]) if (missing.length > 0) { - console.warn(`[botfights] WARNING: Missing env vars: ${missing.join(', ')} — wallet/payment features disabled`) + logger.warn('env', `Missing env vars: ${missing.join(', ')} — wallet/payment features disabled`) } if (!process.env.CORS_ORIGIN || process.env.CORS_ORIGIN === '*') { - console.warn('[botfights] WARNING: CORS_ORIGIN not set — allowing all origins') + logger.warn('env', 'CORS_ORIGIN not set — allowing all origins') } } @@ -26,7 +27,7 @@ await seedClassicBots() const port = Number(process.env.PORT) || 9100 serve({ fetch: app.fetch, port }, () => { - console.log(`[botfights] server listening on http://localhost:${port}`) + logger.info('server', `listening on http://localhost:${port}`) // Start background fight loop so the site always has fresh activity startBackgroundFights() @@ -34,14 +35,14 @@ serve({ fetch: app.fetch, port }, () => { // Graceful shutdown — drain in-flight fights before exiting const shutdown = async (signal: string) => { - console.log(`[botfights] ${signal} received, shutting down...`) + logger.info('server', `${signal} received, shutting down...`) const active = getActiveFighterCount() if (active > 0) { - console.log(`[botfights] ${active} fighters still active, waiting 10s...`) + logger.info('server', `${active} fighters still active, waiting 10s...`) } await new Promise(r => setTimeout(r, active > 0 ? 10_000 : 500)) clearAllPending() - console.log('[botfights] shutdown complete') + logger.info('server', 'shutdown complete') process.exit(0) } process.on('SIGTERM', () => shutdown('SIGTERM')) diff --git a/server/src/lib/logger.ts b/server/src/lib/logger.ts new file mode 100644 index 0000000..fc560d4 --- /dev/null +++ b/server/src/lib/logger.ts @@ -0,0 +1,19 @@ +function ts(): string { + return new Date().toISOString() +} + +export const logger = { + info(tag: string, message: string) { + console.log(`[botfights:${tag}] ${ts()} ${message}`) + }, + warn(tag: string, message: string) { + console.warn(`[botfights:${tag}] ${ts()} ${message}`) + }, + error(tag: string, message: string, err?: unknown) { + if (err) { + console.error(`[botfights:${tag}] ${ts()} ${message}`, err) + } else { + console.error(`[botfights:${tag}] ${ts()} ${message}`) + } + }, +} diff --git a/server/src/routes/fights.ts b/server/src/routes/fights.ts index 72a341e..fe55bf9 100644 --- a/server/src/routes/fights.ts +++ b/server/src/routes/fights.ts @@ -1,4 +1,5 @@ import { Hono } from 'hono' +import { logger } from '../lib/logger.js' import { streamSSE } from 'hono/streaming' import { db, schema } from '../db/index.js' import { eq, desc } from 'drizzle-orm' @@ -175,8 +176,8 @@ fightsRouter.post('/mock/batch/:count', async (c) => { const capped = Math.min(count, 500) startFightLoop({ maxFights: capped, intervalMs: 500, matchmakingStyle: 'mixed' }) - .then(() => console.log(`[botfights] batch of ${capped} fights completed`)) - .catch(err => console.error('[botfights] batch error:', err)) + .then(() => logger.info('fights', `batch of ${capped} fights completed`)) + .catch(err => logger.error('fights', 'batch error', err)) return c.json({ message: `Started batch of ${capped} fights in background.` }) }) diff --git a/server/src/seed.ts b/server/src/seed.ts index 21c5409..24ad9a2 100644 --- a/server/src/seed.ts +++ b/server/src/seed.ts @@ -1,4 +1,5 @@ import '../src/db/index.js' +import { logger } from './lib/logger.js' import { seedMockBots, seedMockFights } from './engine/mock.js' async function main() { @@ -8,8 +9,8 @@ async function main() { await seedMockBots() await seedMockFights(15) - console.log('[botfights] seed complete!') + logger.info('seed', 'seed complete!') process.exit(0) } -main().catch(console.error) +main().catch((err) => logger.error('seed', 'seed failed', err))