174 lines
7.3 KiB
PHP
174 lines
7.3 KiB
PHP
<?php
|
|
// 后台:系统日志查看器
|
|
require __DIR__ . '/../includes/init.php';
|
|
requireStaff();
|
|
require '_common.php';
|
|
$isAdmin = isAdminStaff();
|
|
|
|
$logDir = realpath(__DIR__ . '/../storage/logs') ?: (__DIR__ . '/../storage/logs');
|
|
$msg = '';
|
|
$err = '';
|
|
|
|
// 确保日志目录存在
|
|
if (!is_dir($logDir)) {
|
|
@mkdir($logDir, 0755, true);
|
|
}
|
|
|
|
// 获取请求的日期,默认今天
|
|
$date = trim($_GET['date'] ?? '');
|
|
if ($date === '') {
|
|
$date = date('Y-m-d');
|
|
}
|
|
// 安全校验:只允许 YYYY-MM-DD 格式
|
|
if (!preg_match('#^\d{4}-\d{2}-\d{2}$#', $date)) {
|
|
$date = date('Y-m-d');
|
|
}
|
|
|
|
$logFile = $logDir . DIRECTORY_SEPARATOR . $date . '.log';
|
|
$lines = [];
|
|
$totalLines = 0;
|
|
$pageSize = 100;
|
|
$page = max(1, (int) ($_GET['page'] ?? 1));
|
|
|
|
// 读取日志文件
|
|
if (file_exists($logFile) && is_readable($logFile)) {
|
|
$content = file_get_contents($logFile);
|
|
$allLines = explode("\n", rtrim($content));
|
|
$totalLines = count($allLines);
|
|
|
|
// 分页(最新的在前面,所以倒序)
|
|
$allLines = array_reverse($allLines);
|
|
$offset = ($page - 1) * $pageSize;
|
|
$lines = array_slice($allLines, $offset, $pageSize);
|
|
} elseif (file_exists($logFile)) {
|
|
$err = '日志文件不可读,请检查目录权限。';
|
|
}
|
|
|
|
// 获取所有可用的日志日期
|
|
$logFiles = glob($logDir . DIRECTORY_SEPARATOR . '*.log');
|
|
$dates = [];
|
|
foreach ($logFiles as $f) {
|
|
$n = basename($f);
|
|
// 排除轮转文件 (.log.1)
|
|
if (preg_match('#^(\d{4}-\d{2}-\d{2})\.log$#', $n, $m)) {
|
|
$dates[] = $m[1];
|
|
}
|
|
}
|
|
rsort($dates);
|
|
$totalPages = max(1, ceil($totalLines / $pageSize));
|
|
|
|
adminHeader('系统日志', 'logs');
|
|
?>
|
|
<h1 class="page-title"><i class="fas fa-scroll"></i> 系统日志</h1>
|
|
|
|
<?php if ($msg): ?><p class="banner-ok"><i class="fas fa-check-circle"></i> <?= h($msg) ?></p><?php endif; ?>
|
|
<?php if ($err): ?><p class="form-err"><?= h($err) ?></p><?php endif; ?>
|
|
|
|
<!-- 日期选择 -->
|
|
<div class="panel" style="margin-bottom:18px;">
|
|
<form method="get" action="" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<label style="font-weight:600;margin:0">选择日期:
|
|
<input type="date" name="date" value="<?= h($date) ?>" style="padding:6px 10px;border:1px solid var(--outline);border-radius:8px;font-size:.9rem">
|
|
</label>
|
|
<button type="submit" class="btn btn-ghost btn-sm"><i class="fas fa-search"></i> 查看</button>
|
|
<?php if (!empty($dates)): ?>
|
|
<span class="muted" style="font-size:.82rem">可用日期:
|
|
<?php foreach (array_slice($dates, 0, 14) as $d): ?>
|
|
<a href="?date=<?= h($d) ?>" class="<?= $d === $date ? 'btn btn-sm btn-primary' : 'btn btn-ghost btn-sm' ?>" style="padding:2px 8px;font-size:.78rem"><?= h($d) ?></a>
|
|
<?php endforeach; ?>
|
|
<?php if (count($dates) > 14): ?><span class="muted">... 等 <?= count($dates) ?> 天</span><?php endif; ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h3>
|
|
<i class="fas fa-file-lines"></i> <?= h($date) ?>.log
|
|
<?php if ($totalLines > 0): ?>
|
|
<span class="muted" style="font-weight:400;font-size:.85rem">(共 <?= number_format($totalLines) ?> 条)</span>
|
|
<?php endif; ?>
|
|
</h3>
|
|
|
|
<?php if (empty($lines) && empty($err)): ?>
|
|
<p class="empty">当天暂无日志记录。</p>
|
|
<p class="muted" style="font-size:.85rem">
|
|
日志级别:DEBUG / INFO / WARN / ERROR。当前模式:<strong><?= defined('DEBUG') && DEBUG ? 'DEBUG(记录全部)' : 'INFO(及以上)' ?></strong><br>
|
|
可在 config.php 中修改 <code>define('DEBUG', true)</code> 以开启 DEBUG 级别记录。
|
|
</p>
|
|
<?php else: ?>
|
|
<!-- 分页 -->
|
|
<?php if ($totalPages > 1): ?>
|
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;font-size:.88rem">
|
|
<?php if ($page > 1): ?>
|
|
<a href="?date=<?= h($date) ?>&page=<?= $page - 1 ?>" class="btn btn-ghost btn-sm">« 上一页</a>
|
|
<?php endif; ?>
|
|
<span class="muted">第 <strong><?= $page ?></strong> / <?= $totalPages ?> 页</span>
|
|
<?php if ($page < $totalPages): ?>
|
|
<a href="?date=<?= h($date) ?>&page=<?= $page + 1 ?>" class="btn btn-ghost btn-sm">下一页 »</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="log-viewer">
|
|
<table class="data-table">
|
|
<thead><tr><th style="width:160px">时间</th><th style="width:70px">级别</th><th style="width:140px">来源</th><th>内容</th></tr></thead>
|
|
<tbody>
|
|
<?php foreach ($lines as $line):
|
|
$line = trim($line);
|
|
if ($line === '') continue;
|
|
// 解析格式: [2026-08-13 10:30:00] [WARN] [file.php:42] message [ctx]
|
|
preg_match('#^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s*\[(\w+)\](?:\s*\[([^\]]+)\])?\s*(.+)$#sU', $line, $m);
|
|
$time = $m[1] ?? '';
|
|
$level = $m[2] ?? '';
|
|
$source = $m[3] ?? '';
|
|
$body = $m[4] ?? $line;
|
|
|
|
$levelClass = match($level) {
|
|
'DEBUG' => 'log-debug',
|
|
'INFO' => 'log-info',
|
|
'WARN' => 'log-warn',
|
|
'ERROR' => 'log-error',
|
|
default => ''
|
|
};
|
|
?>
|
|
<tr class="<?= $levelClass ?>">
|
|
<td><code style="font-size:.8rem;white-space:nowrap"><?= h($time) ?: '—' ?></code></td>
|
|
<td><span class="badge-status status-<?= strtolower($level) ?>"><?= h($level) ?></span></td>
|
|
<td><code style="font-size:.78rem;color:var(--muted)"><?= h($source) ?: '' ?></code></td>
|
|
<td style="word-break:break-all;font-family:monospace;font-size:.84rem"><?= h($body) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 底部分页 -->
|
|
<?php if ($totalPages > 1): ?>
|
|
<div style="display:flex;align-items:center;gap:8px;margin-top:12px;font-size:.88rem">
|
|
<?php if ($page > 1): ?>
|
|
<a href="?date=<?= h($date) ?>&page=<?= $page - 1 ?>" class="btn btn-ghost btn-sm">« 上一页</a>
|
|
<?php endif; ?>
|
|
<span class="muted">第 <strong><?= $page ?></strong> / <?= $totalPages ?> 页</span>
|
|
<?php if ($page < $totalPages): ?>
|
|
<a href="?date=<?= h($date) ?>&page=<?= $page + 1 ?>" class="btn btn-ghost btn-sm">下一页 »</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<style>
|
|
.log-viewer { overflow-x: auto; }
|
|
.log-viewer table { font-size:.85rem; }
|
|
.log-debug { opacity: 0.55; }
|
|
.log-info { /* default */ }
|
|
.log-warn { background: color-mix(in srgb, var(--warn) 8%, transparent); }
|
|
.log-error { background: color-mix(in srgb, var(--danger) 8%, transparent); }
|
|
.status-debug { background:#909399;color:#fff }
|
|
.status-info { background:var(--primary);color:#fff }
|
|
.status-warn { background:var(--warn);color:#000 }
|
|
.status-error { background:var(--danger);color:#fff }
|
|
</style>
|
|
<?php adminFooter(); ?>
|