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:
caadiq 2026-05-26 17:02:53 +09:00
parent 9e3bff9762
commit 6a24b997dd
2 changed files with 29 additions and 4 deletions

View file

@ -9,6 +9,20 @@ const REDIS_PREFIX = 'bot:status:';
const TIMEZONE = 'Asia/Seoul';
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) {
const tasks = new Map();
const burstTimers = new Map(); // weekly 모드 내부 setInterval 핸들
@ -269,7 +283,7 @@ async function schedulerPlugin(fastify, opts) {
action: 'error',
category: 'sync',
summary: `${botId} 동기화 오류: ${err.message}`,
details: { error: err.message },
details: buildErrorDetails(err),
});
}
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@ -279,7 +293,7 @@ async function schedulerPlugin(fastify, opts) {
action: 'stop',
category: 'bot',
summary: `${botId} 연속 ${MAX_CONSECUTIVE_ERRORS}회 실패로 자동 정지`,
details: { error: err.message, consecutiveErrors },
details: { ...buildErrorDetails(err), consecutiveErrors },
});
try {
await stopBot(botId);

View file

@ -77,7 +77,7 @@ export default async function logsRoutes(fastify) {
target_type: { type: 'string', nullable: true },
target_id: { type: 'integer', nullable: true },
summary: { type: 'string' },
details: { type: 'object', nullable: true },
details: { type: 'object', nullable: true, additionalProperties: true },
created_at: { type: 'string' },
},
},
@ -147,8 +147,19 @@ export default async function logsRoutes(fastify) {
[...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 {
logs,
logs: parsedLogs,
total,
page,
limit,