PHP 后台访问日志

wen PHP项目 2

本文目录导读:

PHP 后台访问日志

  1. 基础日志类设计
  2. 异常日志记录
  3. 日志查看器页面
  4. 使用示例
  5. 安全最佳实践

我来为您介绍PHP后台访问日志的完整实现方案:

基础日志类设计

<?php
/**
 * 访问日志记录类
 */
class AccessLogger {
    private $logPath;
    private $logFile;
    private $maxFileSize = 10485760; // 10MB
    public function __construct($logPath = '/var/logs/php/') {
        $this->logPath = $logPath;
        if (!file_exists($logPath)) {
            mkdir($logPath, 0755, true);
        }
    }
    /**
     * 记录访问日志
     */
    public function log($data = []) {
        $logData = array_merge([
            'ip' => $this->getClientIp(),
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
            'url' => $_SERVER['REQUEST_URI'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'referer' => $_SERVER['HTTP_REFERER'] ?? '',
            'timestamp' => date('Y-m-d H:i:s'),
            'session_id' => session_id() ?: '',
            'user_id' => isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 0,
            'request_data' => json_encode($data)
        ], $data);
        $logLine = json_encode($logData, JSON_UNESCAPED_UNICODE) . "\n";
        $this->writeLog($logLine);
    }
    /**
     * 获取客户端IP
     */
    private function getClientIp() {
        $ipAddresses = [
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        ];
        foreach ($ipAddresses as $key) {
            if (isset($_SERVER[$key])) {
                if ($key == 'HTTP_X_FORWARDED_FOR') {
                    $ipList = explode(',', $_SERVER[$key]);
                    return trim($ipList[0]);
                }
                return $_SERVER[$key];
            }
        }
        return 'unknown';
    }
    /**
     * 写入日志(自动轮转)
     */
    private function writeLog($content) {
        $this->logFile = $this->logPath . date('Y-m-d') . '_access.log';
        if (file_exists($this->logFile) && filesize($this->logFile) > $this->maxFileSize) {
            $this->rotateLog();
        }
        file_put_contents($this->logFile, $content, FILE_APPEND | LOCK_EX);
    }
    /**
     * 日志轮转
     */
    private function rotateLog() {
        $archiveFile = str_replace('.log', '_' . time() . '.log', $this->logFile);
        rename($this->logFile, $archiveFile);
        file_put_contents($this->logFile, "【" . date('Y-m-d H:i:s') . "】日志轮转\n");
    }
    /**
     * 查询日志
     */
    public function queryLogs($filters = [], $page = 1, $perPage = 10) {
        $logs = [];
        $files = glob($this->logPath . '*_access.log');
        if (empty($files)) return ['total' => 0, 'list' => []];
        // 按时间倒序排列文件
        rsort($files);
        foreach ($files as $file) {
            $handle = fopen($file, 'r');
            if (!$handle) continue;
            while (($line = fgets($handle)) !== false) {
                $logData = json_decode($line, true);
                if (!$logData) continue;
                if ($this->matchFilters($logData, $filters)) {
                    $logs[] = $logData;
                }
            }
            fclose($handle);
            if (count($logs) >= $page * $perPage) {
                break;
            }
        }
        // 倒序排列
        krsort($logs);
        $total = count($logs);
        $offset = ($page - 1) * $perPage;
        $list = array_slice($logs, $offset, $perPage);
        return [
            'total' => $total,
            'page' => $page,
            'perPage' => $perPage,
            'list' => $list
        ];
    }
    /**
     * 过滤器匹配
     */
    private function matchFilters($logData, $filters) {
        foreach ($filters as $key => $value) {
            if (isset($logData[$key]) && $logData[$key] == $value) {
                return true;
            }
        }
        return empty($filters);
    }
}

异常日志记录

<?php
/**
 * 异常日志记录类
 */
class ExceptionLogger {
    private static $instance = null;
    private $logPath;
    private function __construct($logPath = '/var/logs/php/error/') {
        $this->logPath = $logPath;
        if (!is_dir($logPath)) {
            mkdir($logPath, 0755, true);
        }
    }
    public static function getInstance($logPath = null) {
        if (self::$instance === null) {
            self::$instance = new self($logPath);
        }
        return self::$instance;
    }
    /**
     * 记录异常
     */
    public function logException(\Exception $e, $context = []) {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'message' => $e->getMessage(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString(),
            'context' => json_encode($context),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'url' => $_SERVER['REQUEST_URI'] ?? ''
        ];
        $logFile = $this->logPath . date('Y-m-d') . '_error.log';
        $logLine = json_encode($logData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
        file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX);
        // 可选:发送邮件通知
        if (ENVIRONMENT === 'production') {
            $this->sendEmailAlert($logData);
        }
    }
    /**
     * 记录PHP错误
     */
    public function logError($errorNo, $errorStr, $errorFile, $errorLine) {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'error_no' => $errorNo,
            'error_str' => $errorStr,
            'file' => $errorFile,
            'line' => $errorLine,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
        ];
        $logFile = $this->logPath . date('Y-m-d') . '_error.log';
        $logLine = json_encode($logData, JSON_UNESCAPED_UNICODE) . "\n";
        error_log($logLine, 3, $logFile);
    }
    private function sendEmailAlert($data) {
        // 实现邮件通知逻辑
        $to = ADMIN_EMAIL;
        $subject = 'PHP异常通知 - ' . date('Y-m-d H:i:s');
        $message = "异常信息:{$data['message']}\n文件:{$data['file']}:{$data['line']}";
        mail($to, $subject, $message);
    }
}

日志查看器页面

<!DOCTYPE html>
<html>
<head>后台访问日志查询</title>
    <style>
        body { font-family: Arial, sans-serif; }
        .filter-section { background: #f5f5f5; padding: 15px; margin-bottom: 20px; }
        .log-table { width: 100%; border-collapse: collapse; }
        .log-table th, .log-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        .pagination { margin-top: 20px; }
        .detail-modal { display: none; position: fixed; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.4); }
        .modal-content { background-color: #fefefe; margin: 10% auto; padding: 20px; border: 1px solid #888; width: 70%; }
    </style>
</head>
<body>
    <div class="filter-section">
        <h2>访问日志查询</h2>
        <form method="GET" action="">
            日期:<input type="date" name="date" value="<?php echo $_GET['date'] ?? date('Y-m-d'); ?>">
            IP:<input type="text" name="ip" value="<?php echo $_GET['ip'] ?? ''; ?>">
            用户ID:<input type="text" name="user_id" value="<?php echo $_GET['user_id'] ?? ''; ?>">
            <input type="submit" value="查询">
        </form>
    </div>
    <table class="log-table">
        <thead>
            <tr>
                <th>时间</th>
                <th>IP</th>
                <th>方法</th>
                <th>URL</th>
                <th>用户ID</th>
                <th>Session</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            <?php if (!empty($logs)): ?>
                <?php foreach ($logs as $log): ?>
                <tr>
                    <td><?php echo $log['timestamp']; ?></td>
                    <td><?php echo htmlspecialchars($log['ip']); ?></td>
                    <td><?php echo $log['method']; ?></td>
                    <td><?php echo htmlspecialchars($log['url']); ?></td>
                    <td><?php echo $log['user_id']; ?></td>
                    <td><?php echo substr($log['session_id'], 0, 8) . '...'; ?></td>
                    <td><button onclick="showDetail(<?php echo htmlspecialchars(json_encode($log)); ?>)">查看详情</button></td>
                </tr>
                <?php endforeach; ?>
            <?php else: ?>
                <tr><td colspan="7">暂无日志记录</td></tr>
            <?php endif; ?>
        </tbody>
    </table>
    <!-- 分页 -->
    <div class="pagination">
        <?php if ($totalPages > 1): ?>
            <?php for ($i = 1; $i <= $totalPages; $i++): ?>
                <a href="?page=<?php echo $i; ?>&date=<?php echo $_GET['date'] ?? ''; ?>"><?php echo $i; ?></a>
            <?php endfor; ?>
        <?php endif; ?>
    </div>
    <!-- 详情弹窗 -->
    <div id="detailModal" class="detail-modal">
        <div class="modal-content">
            <span onclick="closeDetail()" style="float:right;cursor:pointer;">关闭</span>
            <h3>日志详情</h3>
            <div id="detailContent"></div>
        </div>
    </div>
    <script>
        function showDetail(data) {
            document.getElementById('detailModal').style.display = 'block';
            var content = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
            document.getElementById('detailContent').innerHTML = content;
        }
        function closeDetail() {
            document.getElementById('detailModal').style.display = 'none';
        }
    </script>
</body>
</html>

使用示例

<?php
// 初始化日志系统
$accessLogger = new AccessLogger('/path/to/logs/');
// 记录访问
$accessLogger->log([
    'action' => 'user_login',
    'username' => 'admin',
    'is_success' => true
]);
// 记录访问日志在入口文件
// config.php 或 index.php
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    $logger = ExceptionLogger::getInstance();
    $logger->logError($errno, $errstr, $errfile, $errline);
    return true;
});
register_shutdown_function(function() {
    $error = error_get_last();
    if ($error) {
        $logger = ExceptionLogger::getInstance();
        $logger->logError($error['type'], $error['message'], $error['file'], $error['line']);
    }
});
?>

安全最佳实践

<?php
/**
 * 安全日志记录类 - 防止日志注入
 */
class SecureLogger {
    private function sanitize($data) {
        if (is_array($data)) {
            return array_map([$this, 'sanitize'], $data);
        }
        // 移除控制字符
        $data = preg_replace('/[\x00-\x1F\x7F]/', '', $data);
        // 防止注入
        $data = str_replace(
            ["\r\n", "\r", "\n", "\0"],
            ['', '', '', ''],
            $data
        );
        return $data;
    }
    public function log($data) {
        $cleanData = $this->sanitize($data);
        // 日志写入逻辑...
    }
}
// 日志文件权限设置
$logger = new AccessLogger();
if (function_exists('shell_exec')) {
    shell_exec('chmod 600 ' . $logger->getLogFile());
}
?>

这个日志系统包含:

  • 完整的访问日志记录
  • 异常和错误日志
  • 日志轮转机制
  • 日志查看和查询界面
  • 安全防护措施
  • 数据过滤和清洗功能

抱歉,评论功能暂时关闭!