feat(logs): 활동 로그 다이얼로그 상세 정보 표시 + 에러 정보 보강
- logs API: longtext로 저장된 details를 JSON 객체로 파싱해 반환 - 응답 스키마에 additionalProperties: true 추가 (fast-json-stringify가 스키마 미정의 키를 제거하던 문제 해결) - scheduler 에러 로그: err.cause / err.code / err.causeCode 함께 기록 (fetch failed 등 모호한 메시지의 진짜 원인 식별 가능) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9e3bff9762
commit
6a24b997dd
2 changed files with 29 additions and 4 deletions
|
|
@ -9,6 +9,20 @@ const REDIS_PREFIX = 'bot:status:';
|
||||||
const TIMEZONE = 'Asia/Seoul';
|
const TIMEZONE = 'Asia/Seoul';
|
||||||
const MAX_CONSECUTIVE_ERRORS = 10;
|
const MAX_CONSECUTIVE_ERRORS = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에러 객체에서 활동 로그용 details 구성
|
||||||
|
* - err.cause(Node fetch failed의 진짜 원인 등), err.code를 함께 포함
|
||||||
|
*/
|
||||||
|
function buildErrorDetails(err) {
|
||||||
|
const d = { error: err.message };
|
||||||
|
if (err.code) d.code = err.code;
|
||||||
|
if (err.cause) {
|
||||||
|
d.cause = err.cause.message || String(err.cause);
|
||||||
|
if (err.cause.code) d.causeCode = err.cause.code;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
async function schedulerPlugin(fastify, opts) {
|
async function schedulerPlugin(fastify, opts) {
|
||||||
const tasks = new Map();
|
const tasks = new Map();
|
||||||
const burstTimers = new Map(); // weekly 모드 내부 setInterval 핸들
|
const burstTimers = new Map(); // weekly 모드 내부 setInterval 핸들
|
||||||
|
|
@ -269,7 +283,7 @@ async function schedulerPlugin(fastify, opts) {
|
||||||
action: 'error',
|
action: 'error',
|
||||||
category: 'sync',
|
category: 'sync',
|
||||||
summary: `${botId} 동기화 오류: ${err.message}`,
|
summary: `${botId} 동기화 오류: ${err.message}`,
|
||||||
details: { error: err.message },
|
details: buildErrorDetails(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
||||||
|
|
@ -279,7 +293,7 @@ async function schedulerPlugin(fastify, opts) {
|
||||||
action: 'stop',
|
action: 'stop',
|
||||||
category: 'bot',
|
category: 'bot',
|
||||||
summary: `${botId} 연속 ${MAX_CONSECUTIVE_ERRORS}회 실패로 자동 정지`,
|
summary: `${botId} 연속 ${MAX_CONSECUTIVE_ERRORS}회 실패로 자동 정지`,
|
||||||
details: { error: err.message, consecutiveErrors },
|
details: { ...buildErrorDetails(err), consecutiveErrors },
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await stopBot(botId);
|
await stopBot(botId);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export default async function logsRoutes(fastify) {
|
||||||
target_type: { type: 'string', nullable: true },
|
target_type: { type: 'string', nullable: true },
|
||||||
target_id: { type: 'integer', nullable: true },
|
target_id: { type: 'integer', nullable: true },
|
||||||
summary: { type: 'string' },
|
summary: { type: 'string' },
|
||||||
details: { type: 'object', nullable: true },
|
details: { type: 'object', nullable: true, additionalProperties: true },
|
||||||
created_at: { type: 'string' },
|
created_at: { type: 'string' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -147,8 +147,19 @@ export default async function logsRoutes(fastify) {
|
||||||
[...params, limit, offset]
|
[...params, limit, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// details는 longtext(JSON 문자열)로 저장되어 있으므로 객체로 파싱
|
||||||
|
const parsedLogs = logs.map(log => {
|
||||||
|
if (!log.details) return log;
|
||||||
|
if (typeof log.details === 'object') return log;
|
||||||
|
try {
|
||||||
|
return { ...log, details: JSON.parse(log.details) };
|
||||||
|
} catch {
|
||||||
|
return { ...log, details: { raw: log.details } };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logs,
|
logs: parsedLogs,
|
||||||
total,
|
total,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue