PHP编写日志记录系统与错误追踪实现技巧
引言
在Web开发中,日志记录和错误追踪是确保应用稳定性和可维护性的关键环节。PHP作为广泛使用的服务器端脚本语言,提供了多种实现日志记录和错误追踪的方式。本文将详细介绍如何使用PHP构建一个完整的日志记录系统,并分享一些实用的错误追踪技巧。
一、PHP日志基础
1. PHP内置日志函数
PHP提供了几个基本的日志函数:
error_log()- 发送错误消息到某个地方syslog()- 发送消息到系统日志openlog()- 打开到系统日志的连接closelog()- 关闭到系统日志的连接
2. 基本使用示例
// 记录错误信息到PHP系统日志
error_log('这是一个测试错误信息');
// 发送邮件日志
error_log('致命错误发生', 1, 'admin@ipipp.com');
// 发送到指定文件
error_log('自定义日志消息', 3, '/var/log/myapp.log');
// 使用syslog
openlog('myapp', LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING, '这是一个警告消息');
closelog();二、构建自定义日志类
1. 基础日志类设计
创建一个灵活的日志类,支持不同日志级别和存储方式:
class Logger {
const LEVEL_DEBUG = 'DEBUG';
const LEVEL_INFO = 'INFO';
const LEVEL_WARNING = 'WARNING';
const LEVEL_ERROR = 'ERROR';
const LEVEL_CRITICAL = 'CRITICAL';
private $logFile;
private $logLevel;
private $maxFileSize;
public function __construct($logFile = 'app.log', $logLevel = self::LEVEL_INFO, $maxFileSize = 10485760) {
$this->logFile = $logFile;
$this->logLevel = $logLevel;
$this->maxFileSize = $maxFileSize;
}
public function log($message, $level = self::LEVEL_INFO, $context = []) {
if (!$this->shouldLog($level)) {
return false;
}
$timestamp = date('Y-m-d H:i:s');
$logEntry = [
'timestamp' => $timestamp,
'level' => $level,
'message' => $message,
'context' => $context,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown'
];
$formattedMessage = $this->formatLogEntry($logEntry);
// 检查文件大小,必要时轮转
$this->checkAndRotate();
return file_put_contents($this->logFile, $formattedMessage . PHP_EOL, FILE_APPEND | LOCK_EX);
}
private function shouldLog($level) {
$levels = [
self::LEVEL_DEBUG => 0,
self::LEVEL_INFO => 1,
self::LEVEL_WARNING => 2,
self::LEVEL_ERROR => 3,
self::LEVEL_CRITICAL => 4
];
return $levels[$level] >= $levels[$this->logLevel];
}
private function formatLogEntry($logEntry) {
$contextStr = !empty($logEntry['context']) ? json_encode($logEntry['context'], JSON_UNESCAPED_UNICODE) : '';
return sprintf(
"[%s] [%s] [%s] [%s] %s %s",
$logEntry['timestamp'],
$logEntry['level'],
$logEntry['ip'],
$logEntry['uri'],
$logEntry['message'],
$contextStr
);
}
private function checkAndRotate() {
if (file_exists($this->logFile) && filesize($this->logFile) >= $this->maxFileSize) {
$backupFile = $this->logFile . '.' . date('Y-m-d-H-i-s');
rename($this->logFile, $backupFile);
}
}
// 便捷方法
public function debug($message, $context = []) {
return $this->log($message, self::LEVEL_DEBUG, $context);
}
public function info($message, $context = []) {
return $this->log($message, self::LEVEL_INFO, $context);
}
public function warning($message, $context = []) {
return $this->log($message, self::LEVEL_WARNING, $context);
}
public function error($message, $context = []) {
return $this->log($message, self::LEVEL_ERROR, $context);
}
public function critical($message, $context = []) {
return $this->log($message, self::LEVEL_CRITICAL, $context);
}
}2. 使用自定义日志类
// 初始化日志器
$logger = new Logger('/var/log/myapp/app.log', Logger::LEVEL_DEBUG);
// 记录不同级别的日志
$logger->debug('调试信息', ['user_id' => 123, 'action' => 'login']);
$logger->info('用户登录成功', ['user_id' => 123, 'username' => 'john_doe']);
$logger->warning('API响应时间过长', ['endpoint' => '/api/users', 'time' => 2500]);
$logger->error('数据库连接失败', ['host' => 'localhost', 'database' => 'myapp']);
$logger->critical('系统内存不足', ['memory_usage' => '95%']);
// 在实际业务中使用
try {
// 业务逻辑
$result = someRiskyOperation();
$logger->info('操作成功完成', ['operation' => 'someRiskyOperation']);
} catch (Exception $e) {
$logger->error('操作失败', [
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
throw $e;
}三、错误追踪实现
1. 自定义错误处理
class ErrorHandler {
private $logger;
private $displayErrors;
public function __construct(Logger $logger, $displayErrors = false) {
$this->logger = $logger;
$this->displayErrors = $displayErrors;
$this->registerHandlers();
}
private function registerHandlers() {
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
}
public function handleError($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
return false;
}
$errorTypes = [
E_ERROR => 'FATAL ERROR',
E_WARNING => 'WARNING',
E_PARSE => 'PARSE ERROR',
E_NOTICE => 'NOTICE',
E_CORE_ERROR => 'CORE ERROR',
E_CORE_WARNING => 'CORE WARNING',
E_COMPILE_ERROR => 'COMPILE ERROR',
E_COMPILE_WARNING => 'COMPILE WARNING',
E_USER_ERROR => 'USER ERROR',
E_USER_WARNING => 'USER WARNING',
E_USER_NOTICE => 'USER NOTICE',
E_STRICT => 'STRICT',
E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR',
E_DEPRECATED => 'DEPRECATED',
E_USER_DEPRECATED => 'USER DEPRECATED'
];
$errorType = $errorTypes[$errno] ?? 'UNKNOWN ERROR';
$this->logger->error("{$errorType}: {$errstr}", [
'file' => $errfile,
'line' => $errline,
'type' => $errno
]);
if ($this->displayErrors) {
echo "";
}
return true;
}
public function handleException(Throwable $exception) {
$this->logger->critical("Uncaught Exception: " . $exception->getMessage(), [
'exception' => get_class($exception),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
]);
if ($this->displayErrors) {
echo "";
echo "";
}
}
public function handleShutdown() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}2. 集成错误处理器
// 创建日志器和错误处理器
$logger = new Logger('/var/log/myapp/app.log', Logger::LEVEL_DEBUG);
$errorHandler = new ErrorHandler($logger, true); // 设置为true以在页面显示错误
// 现在所有的错误和异常都会被自动记录四、高级特性
1. 日志轮转与归档
class AdvancedLogger extends Logger {
private $maxFiles;
private $compressOldLogs;
public function __construct($logFile = 'app.log', $logLevel = self::LEVEL_INFO, $maxFileSize = 10485760, $maxFiles = 10, $compressOldLogs = true) {
parent::__construct($logFile, $logLevel, $maxFileSize);
$this->maxFiles = $maxFiles;
$this->compressOldLogs = $compressOldLogs;
}
protected function rotateLogs() {
if (!file_exists($this->logFile)) {
return;
}
$files = glob($this->logFile . '.*');
$count = count($files);
// 删除最旧的文件
if ($count >= $this->maxFiles) {
usort($files, function($a, $b) {
return filemtime($a) - filemtime($b);
});
$oldestFile = array_shift($files);
unlink($oldestFile);
}
// 重命名当前日志文件
$timestamp = date('Y-m-d-His');
$newFile = $this->logFile . '.' . $timestamp;
rename($this->logFile, $newFile);
// 压缩旧日志
if ($this->compressOldLogs) {
$gzFile = $newFile . '.gz';
$data = file_get_contents($newFile);
$gzData = gzencode($data, 9);
file_put_contents($gzFile, $gzData);
unlink($newFile);
}
}
}2. 数据库日志存储
class DatabaseLogger extends Logger {
private $dbConnection;
private $tableName;
public function __construct($dbConnection, $tableName = 'logs', $logLevel = self::LEVEL_INFO) {
$this->dbConnection = $dbConnection;
$this->tableName = $tableName;
$this->logLevel = $logLevel;
}
public function log($message, $level = self::LEVEL_INFO, $context = []) {
if (!$this->shouldLog($level)) {
return false;
}
$logEntry = [
'timestamp' => date('Y-m-d H:i:s'),
'level' => $level,
'message' => $message,
'context' => json_encode($context, JSON_UNESCAPED_UNICODE),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'uri' => $_SERVER['REQUEST_URI'] ?? 'unknown'
];
$columns = implode(', ', array_keys($logEntry));
$placeholders = ':' . implode(', :', array_keys($logEntry));
$sql = "INSERT INTO {$this->tableName} ({$columns}) VALUES ({$placeholders})";
$stmt = $this->dbConnection->prepare($sql);
return $stmt->execute($logEntry);
}
}3. 性能监控日志
class PerformanceLogger {
private $logger;
private $startTime;
private $startMemory;
public function __construct(Logger $logger) {
$this->logger = $logger;
$this->startTime = microtime(true);
$this->startMemory = memory_get_usage();
}
public function measure($operation, callable $callback) {
$startTime = microtime(true);
$startMemory = memory_get_usage();
try {
$result = $callback();
$endTime = microtime(true);
$endMemory = memory_get_usage();
$executionTime = round(($endTime - $startTime) * 1000, 2);
$memoryUsage = $endMemory - $startMemory;
$this->logger->info("Performance: {$operation}", [
'execution_time_ms' => $executionTime,
'memory_usage_bytes' => $memoryUsage,
'status' => 'success'
]);
return $result;
} catch (Exception $e) {
$endTime = microtime(true);
$endMemory = memory_get_usage();
$executionTime = round(($endTime - $startTime) * 1000, 2);
$memoryUsage = $endMemory - $startMemory;
$this->logger->error("Performance: {$operation} failed", [
'execution_time_ms' => $executionTime,
'memory_usage_bytes' => $memoryUsage,
'status' => 'failed',
'exception' => $e->getMessage()
]);
throw $e;
}
}
public function getExecutionStats() {
$endTime = microtime(true);
$endMemory = memory_get_usage();
return [
'total_execution_time' => round(($endTime - $this->startTime) * 1000, 2),
'peak_memory_usage' => memory_get_peak_usage(),
'current_memory_usage' => $endMemory - $this->startMemory
];
}
}五、最佳实践与注意事项
1. 日志级别使用建议
- DEBUG: 详细的调试信息,仅在开发环境使用
- INFO: 重要的业务流程信息
- WARNING: 不影响主要功能但需要关注的问题
- ERROR: 影响功能但不会导致系统崩溃的错误
- CRITICAL: 导致系统无法正常运行的严重错误
2. 安全考虑
- 避免在日志中记录敏感信息(密码、信用卡号等)
- 对用户输入进行适当的过滤和转义
- 设置适当的文件权限,防止未授权访问
- 定期清理和归档旧日志文件
3. 性能优化
- 在生产环境中禁用过于详细的DEBUG日志
- 使用异步日志记录减少I/O阻塞
- 合理设置日志文件大小和轮转策略
- 考虑使用内存缓存批量写入日志
4. 实际应用示例
// 完整的使用示例
class Application {
private $logger;
private $performanceLogger;
public function __construct() {
$this->logger = new Logger('/var/log/myapp/app.log', Logger::LEVEL_INFO);
$this->performanceLogger = new PerformanceLogger($this->logger);
$errorHandler = new ErrorHandler($this->logger);
}
public function processUserRequest($userId) {
return $this->performanceLogger->measure("process_user_{$userId}", function() use ($userId) {
$this->logger->info("开始处理用户请求", ['user_id' => $userId]);
// 模拟业务逻辑
$userData = $this->fetchUserData($userId);
$this->validateUserData($userData);
$result = $this->updateUserProfile($userData);
$this->logger->info("用户请求处理完成", ['user_id' => $userId, 'result' => $result]);
return $result;
});
}
private function fetchUserData($userId) {
// 模拟数据获取
return ['id' => $userId, 'name' => 'John Doe', 'email' => 'john@ippipp.com'];
}
private function validateUserData($userData) {
if (empty($userData['email'])) {
throw new InvalidArgumentException('用户邮箱不能为空');
}
}
private function updateUserProfile($userData) {
// 模拟更新操作
return true;
}
}
// 使用应用
$app = new Application();
try {
$result = $app->processUserRequest(123);
echo "处理成功: " . ($result ? '是' : '否') . "\n";
} catch (Exception $e) {
echo "处理失败: " . $e->getMessage() . "\n";
}结语
通过本文介绍的方法,您可以构建一个功能完善、性能优良的PHP日志记录和错误追踪系统。记住,良好的日志记录习惯不仅能帮助您快速定位和解决问题,还能为系统维护和性能优化提供宝贵的数据支持。根据您的具体需求,可以进一步扩展和优化这些基础实现,打造适合您项目的日志解决方案。