From 3d19d3be80055ff401463ee6db7b66ad2ebe886e Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Thu, 19 Mar 2026 19:28:43 -0700 Subject: [PATCH] pino logs --- app/api/chat/route.ts | 8 +++- app/api/shares/route.ts | 4 +- app/share/[id]/page.tsx | 6 +-- lib/log.ts | 13 +++--- next.config.ts | 2 +- package.json | 3 +- pnpm-lock.yaml | 87 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 107 insertions(+), 16 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 9c969c3..23139f9 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -2,7 +2,7 @@ import { NextRequest } from 'next/server'; import { getProvider, toOpenAIMessages, toAnthropicMessages, toGeminiContents, parseOpenAIChunk, parseAnthropicChunk, parseGeminiChunk, parseOpenAIResponsesChunk, type Message } from '@/lib/chat'; import { auth } from '@/auth'; import { query } from '@/lib/db'; -import { log } from '@/lib/log'; +import logger from '@/lib/log'; export const runtime = 'nodejs'; @@ -341,6 +341,10 @@ export async function POST(req: NextRequest) { ctx.error = String(err).slice(0, 120); throw err; } finally { - log('chat', { ...ctx, ms: Date.now() - t }); + const fields = { ...ctx, ms: Date.now() - t }; + const status = ctx.status as number | undefined; + if (!status || status >= 500) logger.error(fields, 'chat'); + else if (status >= 400) logger.warn(fields, 'chat'); + else logger.info(fields, 'chat'); } } diff --git a/app/api/shares/route.ts b/app/api/shares/route.ts index 0834785..7f7d857 100644 --- a/app/api/shares/route.ts +++ b/app/api/shares/route.ts @@ -1,7 +1,7 @@ import { NextRequest } from 'next/server'; import { auth } from '@/auth'; import { query } from '@/lib/db'; -import { log } from '@/lib/log'; +import logger from '@/lib/log'; export async function POST(req: NextRequest) { const session = await auth(); @@ -24,6 +24,6 @@ export async function POST(req: NextRequest) { [id, session.user.email, model, systemPrompt || null, JSON.stringify(messages)], ); - log('share.create', { user: session.user.email, id, model, msgs: messages.length }); + logger.info({ user: session.user.email, id, model, msgs: messages.length }, 'share.create'); return Response.json({ id }); } diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx index d979e06..4b8897f 100644 --- a/app/share/[id]/page.tsx +++ b/app/share/[id]/page.tsx @@ -5,7 +5,7 @@ import { auth, signIn } from '@/auth'; import { query } from '@/lib/db'; import { renderMarkdown } from '@/lib/markdown'; import ForkButton from './fork-button'; -import { log } from '@/lib/log'; +import logger from '@/lib/log'; type Image = { mimeType: string; data: string }; type Message = { role: string; content: string; images?: Image[] }; @@ -39,12 +39,12 @@ export default async function SharePage({ params }: { params: Promise<{ id: stri ]); if (dbResult.rows.length === 0) { - log('share.view', { id, found: false }); + logger.warn({ id }, 'share.view not_found'); notFound(); } const share = dbResult.rows[0]; - log('share.view', { id, model: share.model, msgs: (share.messages as unknown[]).length, authed: !!sessionResult }); + logger.info({ id, model: share.model, msgs: (share.messages as unknown[]).length, authed: !!sessionResult }, 'share.view'); const messages: Message[] = share.messages; const date = new Date(share.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', diff --git a/lib/log.ts b/lib/log.ts index 37ea3d2..cb4118e 100644 --- a/lib/log.ts +++ b/lib/log.ts @@ -1,8 +1,7 @@ -type Fields = Record; +import pino from 'pino'; -export function log(event: string, fields: Fields): void { - const parts = Object.entries(fields) - .filter(([, v]) => v !== undefined) - .map(([k, v]) => `${k}=${v}`); - console.log(`[${event}] ${parts.join(' ')}`); -} +const logger = pino({ + level: process.env.LOG_LEVEL ?? 'info', +}); + +export default logger; diff --git a/next.config.ts b/next.config.ts index d5c65f5..81f8ab5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,7 +3,7 @@ import type { NextConfig } from 'next'; const config: NextConfig = { eslint: { ignoreDuringBuilds: true }, typescript: { ignoreBuildErrors: true }, - serverExternalPackages: ['pg'], + serverExternalPackages: ['pg', 'pino'], experimental: { optimizePackageImports: ['highlight.js'], }, diff --git a/package.json b/package.json index d1c77de..17d0396 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "next": "^15.2.3", "next-auth": "5.0.0-beta.30", "pg": "^8.20.0", + "pino": "^10.3.1", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -24,6 +25,6 @@ "@types/pg": "^8.18.0", "@types/react": "^19", "@types/react-dom": "^19", -"typescript": "^5" + "typescript": "^5" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68901fb..adcf856 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: pg: specifier: ^8.20.0 version: 8.20.0 + pino: + specifier: ^10.3.1 + version: 10.3.1 react: specifier: ^19.0.0 version: 19.2.4 @@ -259,6 +262,9 @@ packages: '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -276,6 +282,10 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + caniuse-lite@1.0.30001779: resolution: {integrity: sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==} @@ -351,6 +361,10 @@ packages: oauth4webapi@3.8.5: resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -388,6 +402,16 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -416,6 +440,12 @@ packages: preact@10.24.3: resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -425,6 +455,14 @@ packages: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -437,6 +475,9 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -458,6 +499,10 @@ packages: babel-plugin-macros: optional: true + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -613,6 +658,8 @@ snapshots: '@panva/hkdf@1.2.1': {} + '@pinojs/redact@0.4.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -635,6 +682,8 @@ snapshots: dependencies: csstype: 3.2.3 + atomic-sleep@1.0.0: {} + caniuse-lite@1.0.30001779: {} client-only@0.0.1: {} @@ -687,6 +736,8 @@ snapshots: oauth4webapi@3.8.5: {} + on-exit-leak-free@2.1.2: {} + pg-cloudflare@1.3.0: optional: true @@ -724,6 +775,26 @@ snapshots: picocolors@1.1.1: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -746,6 +817,10 @@ snapshots: preact@10.24.3: {} + process-warning@5.0.0: {} + + quick-format-unescaped@4.0.4: {} + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -753,6 +828,10 @@ snapshots: react@19.2.4: {} + real-require@0.2.0: {} + + safe-stable-stringify@2.5.0: {} + scheduler@0.27.0: {} semver@7.7.4: @@ -790,6 +869,10 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} split2@4.2.0: {} @@ -799,6 +882,10 @@ snapshots: client-only: 0.0.1 react: 19.2.4 + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + tslib@2.8.1: {} typescript@5.9.3: {} -- 2.51.2