All files / src/routes admin-logs.js

100% Statements 61/61
100% Branches 37/37
100% Functions 6/6
100% Lines 46/46

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 19x 19x                       18x   18x 18x 18x   18x     18x 18x 18x 18x 18x     18x 18x     18x   18x   18x   32x     17x 4x   17x 3x 3x   5x           17x   17x   1x 1x         1x 3x 3x 2x   2x             2x   1x   1x       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 (await 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;
    if (limit < 1) limit = DEFAULT_LIMIT;
    if (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);
    if (sessionTraceId) query = query.where('sessionTraceId', '==', sessionTraceId);
    if (requestTraceId) query = query.where('requestTraceId', '==', requestTraceId);
 
    // Time range filters
    if (startTime) query = query.where('timestamp', '>=', Number(startTime));
    if (endTime) query = query.where('timestamp', '<=', Number(endTime));
 
    // Pagination
    if (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)
    if (route) {
      logs = logs.filter((entry) => entry.context?.route === route || entry.route === route);
    }
    if (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 (await 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;