Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user