本文目录导读:

我来介绍几种 PHP 操作日志的实现方案,从简单到复杂:
基础文件日志方案
简单文件日志
<?php
class SimpleLogger {
private $logPath;
public function __construct($logPath = 'logs/') {
$this->logPath = $logPath;
if (!is_dir($logPath)) {
mkdir($logPath, 0777, true);
}
}
public function write($message, $type = 'INFO') {
$date = date('Y-m-d H:i:s');
$logLine = sprintf(
"[%s] %s: %s %s\n",
$date,
$type,
$message,
$this->getClientInfo()
);
$fileName = $this->logPath . date('Y-m-d') . '.log';
file_put_contents($fileName, $logLine, FILE_APPEND | LOCK_EX);
}
private function getClientInfo() {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$uri = $_SERVER['REQUEST_URI'] ?? 'unknown';
return "[IP: $ip] [URI: $uri]";
}
}
// 使用示例
$logger = new SimpleLogger();
$logger->write('用户登录成功', 'INFO');
$logger->write('登录失败,密码错误', 'WARNING');
?>
数据库日志方案
日志表结构
CREATE TABLE `operation_logs` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `username` varchar(50) DEFAULT NULL, `module` varchar(50) DEFAULT NULL, `action` varchar(50) DEFAULT NULL, `description` text, `request_data` text, `response_data` text, `ip` varchar(50) DEFAULT NULL, `user_agent` varchar(255) DEFAULT NULL, `execution_time` int(11) DEFAULT NULL, `created_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_module` (`module`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据库日志类
<?php
class DbLogger {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function log($module, $action, $description, $requestData = null, $responseData = null) {
$logData = [
'user_id' => $_SESSION['user_id'] ?? null,
'username' => $_SESSION['username'] ?? 'guest',
'module' => $module,
'action' => $action,
'description' => $description,
'request_data' => $requestData ? json_encode($requestData) : null,
'response_data' => $responseData ? json_encode($responseData) : null,
'ip' => $this->getClientIp(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'execution_time' => $this->getExecutionTime(),
'created_at' => date('Y-m-d H:i:s')
];
$sql = "INSERT INTO operation_logs
(user_id, username, module, action, description, request_data,
response_data, ip, user_agent, execution_time, created_at)
VALUES
(:user_id, :username, :module, :action, :description, :request_data,
:response_data, :ip, :user_agent, :execution_time, :created_at)";
$stmt = $this->db->prepare($sql);
return $stmt->execute($logData);
}
private function getClientIp() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'] ?? '';
}
}
private function getExecutionTime() {
if (defined('START_TIME')) {
return round((microtime(true) - START_TIME) * 1000);
}
return 0;
}
public function getLogs($filters = [], $page = 1, $perPage = 20) {
// 查询日志的实现
$offset = ($page - 1) * $perPage;
$where = [];
$params = [];
if (!empty($filters['user_id'])) {
$where[] = "user_id = :user_id";
$params[':user_id'] = $filters['user_id'];
}
if (!empty($filters['module'])) {
$where[] = "module = :module";
$params[':module'] = $filters['module'];
}
if (!empty($filters['start_date'])) {
$where[] = "created_at >= :start_date";
$params[':start_date'] = $filters['start_date'];
}
if (!empty($filters['end_date'])) {
$where[] = "created_at <= :end_date";
$params[':end_date'] = $filters['end_date'];
}
$whereSql = $where ? "WHERE " . implode(" AND ", $where) : '';
$countSql = "SELECT COUNT(*) as total FROM operation_logs $whereSql";
$countStmt = $this->db->prepare($countSql);
$countStmt->execute($params);
$total = $countStmt->fetchColumn();
$sql = "SELECT * FROM operation_logs
$whereSql
ORDER BY created_at DESC
LIMIT $perPage OFFSET $offset";
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
return [
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'data' => $logs
];
}
}
?>
框架集成方案
Laravel 中间件方案
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
class OperationLogMiddleware
{
public function handle($request, Closure $next)
{
$startTime = microtime(true);
// 记录请求开始
$response = $next($request);
// 记录请求结束
$executionTime = microtime(true) - $startTime;
// 只记录操作型请求
if ($request->method() != 'GET') {
Log::channel('operation')->info('用户操作', [
'user_id' => Auth::id(),
'method' => $request->method(),
'path' => $request->path(),
'data' => $request->all(),
'response_status' => $response->getStatusCode(),
'execution_time' => $executionTime * 1000 . 'ms',
'ip' => $request->ip()
]);
}
return $response;
}
}
?>
ThinkPHP 的行为钩子方案
<?php
namespace app\behavior;
use think\facade\Log;
use think\facade\Request;
class OperationLog
{
public function run()
{
$data = [
'url' => Request::url(),
'method' => Request::method(),
'data' => Request::param(),
'ip' => Request::ip(),
'time' => date('Y-m-d H:i:s')
];
Log::write('操作日志: ' . json_encode($data), 'info');
}
}
?>
完整日志系统示例
<?php
class OperationLogSystem {
private $db;
private $cache;
private $config;
public function __construct($db) {
$this->db = $db;
$this->config = [
'log_methods' => ['POST', 'PUT', 'DELETE'],
'exclude_urls' => ['/login', '/logout'],
'sensitive_data' => ['password', 'token', 'card_number'],
// 日志保留天数
'retention_days' => 30
];
}
public function record($module, $action, $description = '') {
try {
// 过滤敏感字段
$requestData = $this->filterSensitiveData($_POST);
$responseData = $this->getResponseData();
$logData = [
'user_id' => $this->getCurrentUserId(),
'module' => $module,
'action' => $action,
'description' => $description,
'request_data' => json_encode($requestData),
'response_data' => $responseData,
'ip' => $this->getRealIp(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'request_url' => $_SERVER['REQUEST_URI'] ?? '',
'execution_time' => $this->getExecTime(),
'created_at' => date('Y-m-d H:i:s')
];
// 写入日志
$this->writeToDatabase($logData);
// 同时写入文件备份
$this->writeToFile($logData);
} catch (Exception $e) {
// 日志记录失败不影响主流程
Log::error('日志记录失败: ' . $e->getMessage());
}
}
private function filterSensitiveData($data) {
foreach ($this->config['sensitive_data'] as $field) {
if (isset($data[$field])) {
$data[$field] = '***';
}
}
return $data;
}
private function writeToDatabase($logData) {
$sql = "INSERT INTO operation_logs
(user_id, module, action, description, request_data, response_data,
ip, user_agent, request_url, execution_time, created_at)
VALUES (:user_id, :module, :action, :description, :request_data, :response_data,
:ip, :user_agent, :request_url, :execution_time, :created_at)";
$stmt = $this->db->prepare($sql);
$stmt->execute($logData);
}
private function writeToFile($logData) {
$logDir = 'logs/';
if (!is_dir($logDir)) {
mkdir($logDir, 0777, true);
}
$filename = $logDir . date('Y-m-d') . '.json';
$logLine = json_encode($logData, JSON_UNESCAPED_UNICODE) . PHP_EOL;
file_put_contents($filename, $logLine, FILE_APPEND | LOCK_EX);
}
private function getRealIp() {
// IP获取逻辑
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim($ips[0]);
}
return $ip;
}
public function cleanupExpiredLogs() {
$expiredDate = date('Y-m-d', strtotime("-{$this->config['retention_days']} days"));
$sql = "DELETE FROM operation_logs WHERE created_at < ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$expiredDate]);
// 清理文件
$this->cleanupFiles($expiredDate);
}
private function cleanupFiles($expiredDate) {
$files = glob('logs/*.json');
foreach ($files as $file) {
$fileDate = basename($file, '.json');
if ($fileDate < $expiredDate) {
unlink($file);
}
}
}
}
// 使用示例
$logger = new OperationLogSystem($db);
$logger->record('user', 'add', '新增用户');
?>
最佳实践建议
日志管理后台视图
<!-- 日志查询页面 -->
<div class="container">
<h2>操作日志</h2>
<!-- 搜索过滤 -->
<form class="mb-3">
<div class="row">
<div class="col-md-3">
<input type="text" name="username" class="form-control" placeholder="用户名">
</div>
<div class="col-md-2">
<select name="module" class="form-control">
<option value="">全部模块</option>
<option value="user">用户管理</option>
<option value="role">角色管理</option>
<option value="system">系统设置</option>
</select>
</div>
<div class="col-md-3">
<div class="input-group">
<input type="date" name="start_date" class="form-control">
<span>-</span>
<input type="date" name="end_date" class="form-control">
</div>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">搜索</button>
</div>
</div>
</form>
<!-- 日志列表 -->
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>用户</th>
<th>模块</th>
<th>操作</th>
<th>描述</th>
<th>IP</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($logs as $log): ?>
<tr>
<td><?= $log['id'] ?></td>
<td><?= htmlspecialchars($log['username']) ?></td>
<td><?= htmlspecialchars($log['module']) ?></td>
<td><?= htmlspecialchars($log['action']) ?></td>
<td><?= htmlspecialchars($log['description']) ?></td>
<td><?= $log['ip'] ?></td>
<td><?= $log['created_at'] ?></td>
<td>
<button class="btn btn-sm btn-info"
onclick="viewDetail(<?= $log['id'] ?>)">详情</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 分页 -->
<?= $pagination ?>
</div>
性能优化建议
// 1. 异步日志处理
public function asyncLog($data) {
$redis = new Redis();
$redis->lpush('log_queue', json_encode($data));
// 后台消费者处理
// 可以定期从队列取出日志写入数据库
}
// 2. 日志缓存
public function cachedLog($data) {
$cache = new Redis();
$cache->sAdd('operation_logs_' . date('Y-m-d'), json_encode($data));
// 定时批量写入
if ($cache->sCard('operation_logs_' . date('Y-m-d')) >= 100) {
$this->batchWriteToDB($cache->sMembers('operation_logs_' . date('Y-m-d')));
$cache->del('operation_logs_' . date('Y-m-d'));
}
}
// 3. 日志级别控制
class LogLevel {
const LOG_DEBUG = 'DEBUG';
const LOG_INFO = 'INFO';
const LOG_WARNING = 'WARNING';
const LOG_ERROR = 'ERROR';
private $minLevel;
public function shouldLog($level) {
$levels = [self::LOG_DEBUG, self::LOG_INFO, self::LOG_WARNING, self::LOG_ERROR];
$currentIndex = array_search($level, $levels);
$minIndex = array_search($this->minLevel, $levels);
return $currentIndex >= $minIndex;
}
}
?>
推荐的日志方案选择:
- 简单项目:使用文件日志,简单易用
- 中大型项目:使用数据库日志,便于查询分析
- 框架项目:使用框架自带的日志功能或中间件
- 高并发项目:使用异步日志(Redis队列 + 批量写入)
实现要点:
- 记录关键信息:用户、模块、操作、时间、IP等
- 敏感信息过滤
- 日志分级和分类
- 定期清理和历史数据处理
- 日志查询和统计功能
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播