sd / src /logger.js
pigracing's picture
Upload 22 files
bd27f3c verified
Raw History Blame Contribute Delete
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 [];
}
}