Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 2x 2x 2x 2x 1x 2x 2x 1x 1x 2x 1x 1x | /**
* Admin log query routes — query and trace logs stored in Firestore.
*
* GET /admin/logs → Query logs with filters (admin only)
* GET /admin/logs/trace/:traceId → Get all logs for a session trace (admin only)
*/
const router = require('express').Router();
const { db } = require('../utils/firebase');
const { requireAdmin } = require('../middleware/auth');
const log = require('../utils/log');
const DEFAULT_LIMIT = 50;
const MAX_LIMIT = 200;
const MAX_TRACE_LIMIT = 500;
// GET /admin/logs — Query logs with filters
router.get('/admin/logs', async (req, res) => {
try {
if (requireAdmin(req, res)) return;
const {
level,
source,
userId,
sessionTraceId,
requestTraceId,
route,
keyword,
startTime,
endTime,
cursor,
} = req.query;
let limit = Number.parseInt(req.query.limit, 10) || DEFAULT_LIMIT;
Iif (limit < 1) limit = DEFAULT_LIMIT;
Iif (limit > MAX_LIMIT) limit = MAX_LIMIT;
let query = db.collection('logs').orderBy('timestamp', 'desc');
// Apply Firestore .where() filters
if (level) query = query.where('level', '==', level);
if (source) query = query.where('source', '==', source);
if (userId) query = query.where('userId', '==', userId);
Iif (sessionTraceId) query = query.where('sessionTraceId', '==', sessionTraceId);
Iif (requestTraceId) query = query.where('requestTraceId', '==', requestTraceId);
// Time range filters
Iif (startTime) query = query.where('timestamp', '>=', Number(startTime));
Iif (endTime) query = query.where('timestamp', '<=', Number(endTime));
// Pagination
Iif (cursor) query = query.startAfter(Number(cursor));
query = query.limit(limit);
const snapshot = await query.get();
let logs = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
// Client-side filters (can't be compound-queried in Firestore)
Iif (route) {
logs = logs.filter((entry) => entry.context?.route === route || entry.route === route);
}
Iif (keyword) {
const lowerKeyword = keyword.toLowerCase();
logs = logs.filter(
(entry) =>
entry.message?.toLowerCase().includes(lowerKeyword) ||
(entry.context && JSON.stringify(entry.context).toLowerCase().includes(lowerKeyword)),
);
}
const nextCursor =
snapshot.docs.length === limit ? snapshot.docs.at(-1).data().timestamp : null;
res.json({ logs, nextCursor });
} catch (err) {
log.error('admin-logs', 'Error querying logs', { error: err.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// GET /admin/logs/trace/:traceId — Get all logs for a session trace
router.get('/admin/logs/trace/:traceId', async (req, res) => {
try {
if (requireAdmin(req, res)) return;
const { traceId } = req.params;
const snapshot = await db
.collection('logs')
.where('sessionTraceId', '==', traceId)
.orderBy('timestamp', 'asc')
.limit(MAX_TRACE_LIMIT)
.get();
const logs = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
res.json({ logs });
} catch (err) {
log.error('admin-logs', 'Error querying trace logs', {
traceId: req.params.traceId,
error: err.message,
});
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;
|