feat: system logs - email log + system log with admin UI
- db: email_logs (sent/failed) + system_logs (info/warn/error) tables, auto-migrated for existing installs - backend/logger.js: logEmail/logSystem with try/catch (never breaks flow) - mailer: log send result (SMTP unconfigured/sent/failed + error msg) - webhook: log blocked-internal, non-2xx response, send failure - server: error middleware records 500 errors to system_logs - routes/logs.js: GET /logs/emails + /logs/system (admin/owner, filters) - UI: 系统日志 page (owner/admin) with system/email tabs + filters - API docs updated
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
const { query, getRow } = require('./db');
|
||||
const { logSystem } = require('./logger');
|
||||
|
||||
const EVENT_LABELS = { ticket_created:'工单创建', ticket_claimed:'工单认领', ticket_transferred:'工单转交', ticket_updated:'工单更新' };
|
||||
|
||||
@@ -24,14 +25,20 @@ async function sendWebhook(event, data) {
|
||||
for (const cfg of configs) {
|
||||
const events = cfg.events || 'all';
|
||||
if (events !== 'all' && !events.split(',').map(s=>s.trim()).includes(event)) continue;
|
||||
if (cfg.webhook_url && await isPrivateUrl(cfg.webhook_url)) { console.error('[Webhook] blocked internal URL:', cfg.name); continue; }
|
||||
if (cfg.webhook_url && await isPrivateUrl(cfg.webhook_url)) { console.error('[Webhook] blocked internal URL:', cfg.name); await logSystem('warn', 'webhook', `拦截内网地址: ${cfg.name}`, { url: cfg.webhook_url }); continue; }
|
||||
const embed = buildEmbed(event, data);
|
||||
try {
|
||||
const ctrl = new AbortController();
|
||||
const t = setTimeout(() => ctrl.abort(), 10000);
|
||||
await fetch(cfg.webhook_url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(embed), signal: ctrl.signal });
|
||||
const ctl = new AbortController();
|
||||
const t = setTimeout(() => ctl.abort(), 10000);
|
||||
const resp = await fetch(cfg.webhook_url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(embed), signal: ctl.signal });
|
||||
clearTimeout(t);
|
||||
} catch (e) { console.error('[Webhook]', cfg.name, e.message); }
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
await logSystem('warn', 'webhook', `Webhook ${cfg.name} 返回 ${resp.status}`, { event, url: cfg.webhook_url });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Webhook]', cfg.name, e.message);
|
||||
await logSystem('error', 'webhook', `Webhook ${cfg.name} 发送失败: ${e.message}`, { event, url: cfg.webhook_url });
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user