Files
edu/bbs/debug_log.php

157 lines
6.5 KiB
PHP

<?php
/**
* bbs/debug_log.php
* ─────────────────────────────────────────────────────────────────
* API 로그 뷰어 (개발/운영 디버깅용)
*
* 접근: /bbs/debug_log.php?key=baron_debug_2026
* key 파라미터가 틀리면 403 반환
* ─────────────────────────────────────────────────────────────────
*/
define('DEBUG_KEY', 'baron_debug_2026'); // ← 필요 시 변경
define('LOG_FILE', __DIR__ . '/logs/api.log');
define('LINES_MAX', 300); // 최대 표시 줄 수
// ── 인증 ──────────────────────────────────────────────────────────
if (($_GET['key'] ?? '') !== DEBUG_KEY) {
http_response_code(403);
exit('403 Forbidden');
}
// ── 로그 파일 읽기 ────────────────────────────────────────────────
$action = $_GET['action'] ?? 'view';
// 로그 삭제
if ($action === 'clear') {
@file_put_contents(LOG_FILE, '');
header('Location: debug_log.php?key=' . DEBUG_KEY . '&cleared=1');
exit;
}
// 로그 다운로드
if ($action === 'download') {
if (file_exists(LOG_FILE)) {
header('Content-Type: text/plain; charset=utf-8');
header('Content-Disposition: attachment; filename="api_' . date('Ymd_His') . '.log"');
readfile(LOG_FILE);
}
exit;
}
// 로그 내용 조회
$logLines = [];
$fileSize = 0;
$fileExist = file_exists(LOG_FILE);
if ($fileExist) {
$fileSize = filesize(LOG_FILE);
$raw = file(LOG_FILE, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
// 최신 줄이 위로 오도록 역순 + 최대 LINES_MAX 줄
$logLines = array_slice(array_reverse($raw), 0, LINES_MAX);
}
// ── 필터 ─────────────────────────────────────────────────────────
$filter = trim($_GET['q'] ?? '');
if ($filter !== '') {
$logLines = array_values(array_filter(
$logLines,
fn($l) => stripos($l, $filter) !== false
));
}
// ── HTML 출력 ─────────────────────────────────────────────────────
$cleared = isset($_GET['cleared']);
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>API Log Viewer</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Consolas', monospace; background: #0d1117; color: #c9d1d9; padding: 16px; font-size: 13px; }
h1 { color: #58a6ff; margin-bottom: 12px; font-size: 18px; }
.toolbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
.toolbar input { flex: 1; min-width: 200px; padding: 6px 10px; background: #161b22; border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px; }
.toolbar a, .toolbar button { padding: 6px 14px; border-radius: 4px; text-decoration: none; font-size: 12px; cursor: pointer; border: none; }
.btn-refresh { background: #1f6feb; color: #fff; }
.btn-clear { background: #da3633; color: #fff; }
.btn-dl { background: #238636; color: #fff; }
.meta { color: #8b949e; font-size: 11px; margin-bottom: 8px; }
.notice { background: #1c2128; border: 1px solid #30363d; padding: 24px; border-radius: 6px; color: #8b949e; text-align: center; }
.log-table { width: 100%; border-collapse: collapse; }
.log-table tr:nth-child(even) { background: #161b22; }
.log-table td { padding: 4px 8px; vertical-align: top; border-bottom: 1px solid #21262d; word-break: break-all; }
.td-no { color: #8b949e; width: 40px; text-align: right; user-select: none; }
.td-line { white-space: pre-wrap; }
/* 태그별 색상 */
.tag-save_learning { color: #79c0ff; }
.tag-user_keywords { color: #56d364; }
.tag-error { color: #f85149; }
.tag-warn { color: #d29922; }
.cleared { background: #1b2a1b; border: 1px solid #238636; color: #56d364; padding: 8px 12px; border-radius: 4px; margin-bottom: 12px; }
</style>
</head>
<body>
<h1>🔍 API Log Viewer</h1>
<?php if ($cleared): ?>
<div class="cleared">✅ 로그가 초기화됐습니다.</div>
<?php endif; ?>
<div class="toolbar">
<form method="get" style="display:contents">
<input type="hidden" name="key" value="<?= htmlspecialchars(DEBUG_KEY) ?>">
<input type="text" name="q" placeholder="필터 (예: save_learning, unregistered, U001 ...)"
value="<?= htmlspecialchars($filter) ?>">
<button type="submit" class="btn-refresh">🔍 검색</button>
</form>
<a href="?key=<?= DEBUG_KEY ?>" class="btn-refresh">↺ 새로고침</a>
<a href="?key=<?= DEBUG_KEY ?>&action=download" class="btn-dl">⬇ 다운로드</a>
<a href="?key=<?= DEBUG_KEY ?>&action=clear"
onclick="return confirm('로그를 모두 삭제하시겠습니까?')" class="btn-clear">🗑 초기화</a>
</div>
<?php if (!$fileExist || $fileSize === 0): ?>
<div class="notice">
<?= $fileExist ? '로그 파일이 비어 있습니다.' : '아직 로그 파일이 없습니다.<br>API를 한 번 호출하면 자동 생성됩니다.' ?>
</div>
<?php else: ?>
<div class="meta">
파일: <?= htmlspecialchars(LOG_FILE) ?> |
크기: <?= number_format($fileSize) ?> bytes |
표시: <?= count($logLines) ?>줄
<?= $filter !== '' ? ' (필터 적용: <strong>' . htmlspecialchars($filter) . '</strong>)' : '' ?>
</div>
<table class="log-table">
<?php foreach ($logLines as $i => $line):
// 태그 추출 for 색상 ([2026-03-10 12:34:56] [태그] ...)
preg_match('/\[([^\]]+)\]\s*\[([^\]]+)\]/', $line, $m);
$tagClass = '';
if (!empty($m[2])) {
$t = strtolower($m[2]);
$tagClass = 'tag-' . preg_replace('/[^a-z0-9_]/', '_', $t);
}
?>
<tr>
<td class="td-no"><?= count($logLines) - $i ?></td>
<td class="td-line <?= htmlspecialchars($tagClass) ?>"><?= htmlspecialchars($line) ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<script>
// 30초마다 자동 갱신 (필터 없을 때만)
<?php if ($filter === ''): ?>
setTimeout(() => location.reload(), 30000);
<?php endif; ?>
</script>
</body>
</html>