Download src/logger.js from pigracing/sd: direct link, hf CLI and curl.
- Browser
- Download file 5.41 kB
-
https://huggingface.co/spaces/pigracing/sd/resolve/main/src/logger.js
- Command line
-
hf download hf://spaces/pigracing/sd/src/logger.js
-
curl -L -o logger.js https://huggingface.co/spaces/pigracing/sd/resolve/main/src/logger.js
5.41 kB
| // Request logger — writes all requests + full conversations to disk | |
| // Usage: import { requestLogger, getRecentLogs, getLogStats, readHistoricalLogs, listLogDates } from './logger.js'; | |
| // app.use(requestLogger('deepseek-2api')); | |
| import { appendFileSync, mkdirSync, readFileSync, readdirSync } from 'fs'; | |
| import { join } from 'path'; | |
| import { recordRequest } from './metrics.js'; | |
| const MEMORY_LIMIT = 1000; | |
| const recentLogs = []; | |
| let totalLogged = 0; | |
| let logDir = process.env.LOG_DIR || '/srv/threadripper-backups/newapi/logs'; | |
| let serviceName = 'default'; | |
| function getLogPath(date) { | |
| const dir = join(logDir, serviceName); | |
| mkdirSync(dir, { recursive: true }); | |
| return join(dir, `${date || new Date().toISOString().slice(0, 10)}.jsonl`); | |
| } | |
| function getChatLogPath(date) { | |
| const dir = join(logDir, serviceName, 'chats'); | |
| mkdirSync(dir, { recursive: true }); | |
| return join(dir, `${date || new Date().toISOString().slice(0, 10)}.jsonl`); | |
| } | |
| function writeLog(entry) { | |
| try { | |
| appendFileSync(getLogPath(), JSON.stringify(entry) + '\n'); | |
| } catch (e) { | |
| console.error('Log write failed:', e.message); | |
| } | |
| } | |
| function writeChatLog(entry) { | |
| try { | |
| appendFileSync(getChatLogPath(), JSON.stringify(entry) + '\n'); | |
| } catch (e) { | |
| console.error('Chat log write failed:', e.message); | |
| } | |
| } | |
| function parseSSEChunks(chunks) { | |
| const messages = []; | |
| for (const chunk of chunks) { | |
| const lines = chunk.split('\n'); | |
| for (const line of lines) { | |
| if (line.startsWith('data: ') && line !== 'data: [DONE]') { | |
| try { | |
| const data = JSON.parse(line.slice(6)); | |
| const delta = data.choices?.[0]?.delta; | |
| if (delta?.content) messages.push(delta.content); | |
| } catch {} | |
| } | |
| } | |
| } | |
| return messages.join(''); | |
| } | |
| export function requestLogger(name) { | |
| serviceName = name || serviceName; | |
| if (process.env.LOG_DIR) logDir = process.env.LOG_DIR; | |
| return (req, res, next) => { | |
| const start = Date.now(); | |
| const isChat = req.path === '/v1/chat/completions' || req.path === '/api/v0/chat/completion'; | |
| const messages = req.body?.messages || null; | |
| const model = req.body?.model || '-'; | |
| const stream = req.body?.stream || false; | |
| // Capture response body | |
| const chunks = []; | |
| const originalWrite = res.write.bind(res); | |
| const originalEnd = res.end.bind(res); | |
| res.write = function (chunk, ...args) { | |
| chunks.push(typeof chunk === 'string' ? chunk : chunk.toString()); | |
| return originalWrite(chunk, ...args); | |
| }; | |
| res.end = function (chunk, ...args) { | |
| if (chunk) chunks.push(typeof chunk === 'string' ? chunk : chunk.toString()); | |
| return originalEnd(chunk, ...args); | |
| }; | |
| res.on('finish', () => { | |
| const duration = Date.now() - start; | |
| const entry = { | |
| time: new Date().toISOString(), | |
| method: req.method, | |
| path: req.path, | |
| model, | |
| status: res.statusCode, | |
| duration, | |
| }; | |
| console.log(`[${entry.time}] ${entry.method} ${entry.path} model=${entry.model} ${entry.status} ${entry.duration}ms`); | |
| // Record to metrics collector | |
| recordRequest(model, duration, res.statusCode); | |
| // Write request log (metadata only) | |
| writeLog(entry); | |
| totalLogged++; | |
| // Keep recent in memory | |
| if (recentLogs.length >= MEMORY_LIMIT) recentLogs.shift(); | |
| recentLogs.push(entry); | |
| // Write full chat log for chat endpoints | |
| if (isChat && messages && res.statusCode === 200) { | |
| let assistantContent = ''; | |
| if (stream) { | |
| assistantContent = parseSSEChunks(chunks); | |
| } else { | |
| try { | |
| const body = chunks.join(''); | |
| const json = JSON.parse(body); | |
| assistantContent = json.choices?.[0]?.message?.content || ''; | |
| } catch {} | |
| } | |
| writeChatLog({ | |
| time: entry.time, | |
| model, | |
| stream, | |
| duration, | |
| messages, | |
| response: assistantContent, | |
| }); | |
| } | |
| }); | |
| next(); | |
| }; | |
| } | |
| export function getRecentLogs(count = 50) { | |
| return recentLogs.slice(-count); | |
| } | |
| export function getLogStats() { | |
| const now = Date.now(); | |
| const last5min = recentLogs.filter(e => now - new Date(e.time).getTime() < 300000); | |
| const errors = last5min.filter(e => e.status >= 400); | |
| const avgDuration = last5min.length | |
| ? Math.round(last5min.reduce((s, e) => s + e.duration, 0) / last5min.length) | |
| : 0; | |
| return { | |
| totalLogged, | |
| memoryBuffer: recentLogs.length, | |
| last5min: last5min.length, | |
| errors5min: errors.length, | |
| avgDuration5min: avgDuration, | |
| }; | |
| } | |
| export function readHistoricalLogs(date, count = 100) { | |
| try { | |
| const data = readFileSync(getLogPath(date), 'utf-8'); | |
| const lines = data.trim().split('\n').filter(Boolean); | |
| return lines.map(l => JSON.parse(l)).slice(-count); | |
| } catch { | |
| return []; | |
| } | |
| } | |
| export function readChatLogs(date, count = 50) { | |
| try { | |
| const data = readFileSync(getChatLogPath(date), 'utf-8'); | |
| const lines = data.trim().split('\n').filter(Boolean); | |
| return lines.map(l => JSON.parse(l)).slice(-count); | |
| } catch { | |
| return []; | |
| } | |
| } | |
| export function listLogDates() { | |
| try { | |
| const dir = join(logDir, serviceName); | |
| return readdirSync(dir) | |
| .filter(f => f.endsWith('.jsonl')) | |
| .map(f => f.replace('.jsonl', '')) | |
| .sort(); | |
| } catch { | |
| return []; | |
| } | |
| } | |