error_log() 是PHP内置的错误日志记录函数,用于将错误消息发送到系统日志、指定文件、电子邮件或自定义目的地。它是PHP错误处理和调试的重要组成部分。
error_get_last()不同,error_log()是主动发送错误消息,而不是获取已发生的错误。
bool error_log(string $message[, int $message_type = 0[, string $destination[, string $additional_headers]]])
返回布尔值表示日志记录是否成功。
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| $message | string | 必填 | 要记录的错误消息 |
| $message_type | int | 0 |
0:发送到PHP系统日志(默认) 1:发送到指定的电子邮件地址 2:已弃用(不再使用) 3:追加到指定文件 4:发送到SAPI日志处理器 |
| $destination | string | 空字符串 |
|
| $additional_headers | string | 空字符串 | 仅在$message_type=1时使用,指定额外的邮件头(如From、Cc、Bcc等),多个头用CRLF(\r\n)分隔 |
bool - 如果成功记录日志返回true,失败返回false。
false。但即使返回true,也不保证邮件一定被接收,只表示成功传递给了邮件系统。
默认情况下,error_log()将消息记录到PHP系统日志:
<?php
// 记录错误到系统日志(默认)
$result = error_log("这是一个测试错误消息");
if ($result) {
echo "<div class='alert alert-success'>";
echo "错误已成功记录到系统日志";
echo "</div>";
} else {
echo "<div class='alert alert-danger'>";
echo "记录错误失败";
echo "</div>";
}
// 记录包含更多信息的错误
$userId = 123;
$action = "更新个人资料";
$errorMsg = "数据库连接失败";
$logMessage = sprintf(
"[%s] 用户ID: %d, 操作: %s, 错误: %s",
date('Y-m-d H:i:s'),
$userId,
$action,
$errorMsg
);
error_log($logMessage);
echo "<div class='alert alert-info'>";
echo "已记录格式化消息: " . htmlspecialchars($logMessage);
echo "</div>";
系统日志位置:
/var/log/php_errors.log或/var/log/apache2/error.logerror_log设置ini_get('error_log')获取当前配置的日志文件路径使用message_type=3将错误记录到自定义文件:
<?php
// 指定自定义日志文件
$logFile = 'application_errors.log';
// 记录错误到指定文件
$result = error_log("数据库连接失败", 3, $logFile);
if ($result) {
echo "<div class='alert alert-success'>";
echo "错误已记录到文件: " . htmlspecialchars($logFile);
echo "</div>";
}
// 记录更多信息
$errorData = [
'timestamp' => date('Y-m-d H:i:s'),
'user_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown',
'error_message' => '文件上传失败: 文件大小超过限制',
'file_size' => '2.5MB',
'max_size' => '1MB'
];
$logEntry = sprintf(
"[%s] IP: %s, URL: %s, 错误: %s (文件: %s, 限制: %s)\n",
$errorData['timestamp'],
$errorData['user_ip'],
$errorData['request_uri'],
$errorData['error_message'],
$errorData['file_size'],
$errorData['max_size']
);
error_log($logEntry, 3, $logFile);
// 查看日志文件内容
if (file_exists($logFile)) {
echo "<div class='alert alert-info'>";
echo "<strong>日志文件内容:</strong><br>";
echo "<pre style='background:#f5f5f5; padding:10px; border:1px solid #ddd;'>";
echo htmlspecialchars(file_get_contents($logFile));
echo "</pre>";
echo "</div>";
}
使用message_type=1将错误通过电子邮件发送:
<?php
// 配置邮件参数
$to = "admin@example.com";
$subject = "网站错误报告";
$message = "网站发生了一个严重的错误,请立即处理!\n\n";
$message .= "错误时间: " . date('Y-m-d H:i:s') . "\n";
$message .= "错误信息: 数据库连接失败\n";
$message .= "服务器IP: " . ($_SERVER['SERVER_ADDR'] ?? '未知') . "\n";
$message .= "请求URI: " . ($_SERVER['REQUEST_URI'] ?? '未知') . "\n\n";
$message .= "此邮件由系统自动发送,请勿直接回复。";
// 邮件头
$headers = "From: system@example.com\r\n";
$headers .= "Reply-To: no-reply@example.com\r\n";
$headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/plain; charset=utf-8\r\n";
// 发送错误邮件
$result = error_log($message, 1, $to, $headers);
if ($result) {
echo "<div class='alert alert-success'>";
echo "错误报告已发送到: " . htmlspecialchars($to);
echo "</div>";
} else {
echo "<div class='alert alert-danger'>";
echo "发送错误邮件失败";
echo "</div>";
}
// 更复杂的邮件示例
function sendErrorEmail($errorLevel, $errorMessage, $errorContext = []) {
$to = "dev-team@example.com";
$subject = "[{$errorLevel}] 应用程序错误 - " . date('Y-m-d H:i:s');
$message = "应用程序错误报告\n";
$message .= "===================\n\n";
$message .= "错误级别: {$errorLevel}\n";
$message .= "错误时间: " . date('Y-m-d H:i:s') . "\n";
$message .= "错误消息: {$errorMessage}\n\n";
if (!empty($errorContext)) {
$message .= "上下文信息:\n";
foreach ($errorContext as $key => $value) {
$message .= " {$key}: " . (is_array($value) ? json_encode($value) : $value) . "\n";
}
}
$message .= "\n";
$message .= "服务器信息:\n";
$message .= " PHP版本: " . phpversion() . "\n";
$message .= " 服务器IP: " . ($_SERVER['SERVER_ADDR'] ?? '未知') . "\n";
$message .= " 用户IP: " . ($_SERVER['REMOTE_ADDR'] ?? '未知') . "\n";
$message .= " 请求URL: " . ($_SERVER['REQUEST_URI'] ?? '未知') . "\n";
$headers = "From: error-report@example.com\r\n";
$headers .= "Content-Type: text/plain; charset=utf-8\r\n";
return error_log($message, 1, $to, $headers);
}
// 测试发送复杂错误邮件
$context = [
'user_id' => 123,
'action' => 'process_payment',
'order_id' => 'ORD-2023-001',
'amount' => 99.99
];
sendErrorEmail('CRITICAL', '支付处理失败: 支付网关无响应', $context);
echo "<div class='alert alert-info'>";
echo "已发送详细错误报告邮件";
echo "</div>";
结合set_error_handler()和error_log():
<?php
// 自定义错误处理函数
function customErrorHandler($errno, $errstr, $errfile, $errline) {
// 错误类型映射
$errorTypes = [
1 => 'E_ERROR',
2 => 'E_WARNING',
8 => 'E_NOTICE',
256 => 'E_USER_ERROR',
512 => 'E_USER_WARNING',
1024 => 'E_USER_NOTICE',
2048 => 'E_STRICT',
4096 => 'E_RECOVERABLE_ERROR',
8192 => 'E_DEPRECATED',
16384 => 'E_USER_DEPRECATED',
];
$errorType = $errorTypes[$errno] ?? "UNKNOWN({$errno})";
// 构建日志消息
$logMessage = sprintf(
"[%s] [%s] %s in %s on line %d\n",
date('Y-m-d H:i:s'),
$errorType,
$errstr,
$errfile,
$errline
);
// 记录到自定义日志文件
$logFile = 'custom_errors.log';
error_log($logMessage, 3, $logFile);
// 如果是严重错误,也发送邮件通知
if (in_array($errno, [E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR])) {
$emailMessage = "严重错误发生!\n\n" . $logMessage;
error_log($emailMessage, 1, 'admin@example.com', "From: system@example.com");
}
// 返回false让PHP继续执行内置错误处理器
return false;
}
// 设置自定义错误处理器
set_error_handler("customErrorHandler", E_ALL);
// 触发不同类型的错误
echo $undefinedVariable; // Notice
$result = 10 / 0; // Warning
// trigger_error("用户自定义错误", E_USER_ERROR); // 取消注释测试用户错误
echo "<div class='alert alert-success'>";
echo "自定义错误处理器已激活。检查 custom_errors.log 文件查看错误日志。";
echo "</div>";
// 恢复默认错误处理器
restore_error_handler();
创建一个完整的错误日志记录类:
<?php
/**
* 错误日志记录器类
*/
class ErrorLogger {
private $logFile;
private $emailRecipients = [];
private $logLevel = 0; // 0=所有, 1=警告及以上, 2=错误及以上
public function __construct($config = []) {
$this->logFile = $config['log_file'] ?? 'app_errors.log';
if (isset($config['email_recipients'])) {
$this->emailRecipients = is_array($config['email_recipients'])
? $config['email_recipients']
: [$config['email_recipients']];
}
$this->logLevel = $config['log_level'] ?? 0;
}
/**
* 记录错误
*/
public function log($message, $level = 'INFO', $context = []) {
$levels = ['DEBUG' => 0, 'INFO' => 0, 'WARNING' => 1, 'ERROR' => 2, 'CRITICAL' => 2];
$currentLevel = $levels[strtoupper($level)] ?? 0;
// 检查日志级别
if ($currentLevel < $this->logLevel) {
return true;
}
// 构建日志条目
$timestamp = date('Y-m-d H:i:s');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$uri = $_SERVER['REQUEST_URI'] ?? 'unknown';
$logEntry = sprintf(
"[%s] [%s] [IP:%s] [URL:%s] %s",
$timestamp,
str_pad($level, 8),
$ip,
$uri,
$message
);
if (!empty($context)) {
$logEntry .= " | Context: " . json_encode($context, JSON_UNESCAPED_UNICODE);
}
$logEntry .= PHP_EOL;
// 记录到文件
$fileResult = error_log($logEntry, 3, $this->logFile);
// 如果是错误或严重错误,发送邮件
if (in_array($level, ['ERROR', 'CRITICAL']) && !empty($this->emailRecipients)) {
$this->sendErrorEmail($level, $message, $context, $logEntry);
}
return $fileResult;
}
/**
* 发送错误邮件
*/
private function sendErrorEmail($level, $message, $context, $logEntry) {
$subject = "[{$level}] 应用程序错误 - " . date('Y-m-d H:i:s');
$emailMessage = "错误报告\n";
$emailMessage .= "==========\n\n";
$emailMessage .= "级别: {$level}\n";
$emailMessage .= "时间: " . date('Y-m-d H:i:s') . "\n";
$emailMessage .= "消息: {$message}\n\n";
if (!empty($context)) {
$emailMessage .= "上下文信息:\n";
$emailMessage .= print_r($context, true) . "\n";
}
$emailMessage .= "\n完整日志条目:\n";
$emailMessage .= $logEntry;
$headers = "From: error-reporter@example.com\r\n";
$headers .= "Content-Type: text/plain; charset=utf-8\r\n";
// 发送给所有收件人
foreach ($this->emailRecipients as $recipient) {
error_log($emailMessage, 1, $recipient, $headers);
}
}
/**
* 设置日志级别
*/
public function setLogLevel($level) {
$validLevels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'];
if (in_array(strtoupper($level), $validLevels)) {
$this->logLevel = array_search(strtoupper($level), $validLevels);
}
return $this;
}
}
// 使用示例
$config = [
'log_file' => 'application.log',
'email_recipients' => ['admin@example.com', 'dev@example.com'],
'log_level' => 'WARNING' // 只记录WARNING及以上级别的日志
];
$logger = new ErrorLogger($config);
// 记录不同级别的日志
$logger->log("应用程序启动", "INFO", ['version' => '1.0.0']);
$logger->log("用户登录成功", "INFO", ['user_id' => 123, 'username' => 'john_doe']);
$logger->log("数据库查询较慢", "WARNING", ['query_time' => '2.5s', 'query' => 'SELECT * FROM users']);
$logger->log("支付处理失败", "ERROR", ['order_id' => 456, 'gateway' => 'stripe', 'reason' => 'insufficient_funds']);
$logger->log("数据库连接失败", "CRITICAL", ['host' => 'localhost', 'port' => 3306]);
echo "<div class='alert alert-success'>";
echo "已记录多种级别的日志到 application.log 文件";
echo "</div>";
// 查看日志文件
if (file_exists('application.log')) {
echo "<div class='alert alert-info'>";
echo "<strong>最新日志内容:</strong><br>";
echo "<pre style='max-height: 200px; overflow-y: auto;'>";
$lines = file('application.log', FILE_IGNORE_NEW_LINES);
$recentLines = array_slice($lines, -10); // 显示最后10行
foreach ($recentLines as $line) {
echo htmlspecialchars($line) . "\n";
}
echo "</pre>";
echo "</div>";
}
在try-catch块中使用error_log()记录异常:
<?php
// 设置异常处理器
set_exception_handler(function($exception) {
$logMessage = sprintf(
"[%s] 未捕获异常: %s in %s on line %d\n堆栈跟踪:\n%s\n",
date('Y-m-d H:i:s'),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
);
// 记录到文件
error_log($logMessage, 3, 'exceptions.log');
// 发送邮件通知
$emailSubject = "未捕获异常: " . $exception->getMessage();
$emailHeaders = "From: exception-reporter@example.com\r\n";
error_log($logMessage, 1, 'admin@example.com', $emailHeaders);
// 显示用户友好的错误页面
if (php_sapi_name() !== 'cli') {
echo "<div style='text-align:center; padding:50px;'>";
echo "<h1 style='color:#dc3545;'>抱歉,出了点问题</h1>";
echo "<p>我们的技术团队已收到通知,正在处理此问题。</p>";
echo "<p><a href='/'>返回首页</a></p>";
echo "</div>";
}
});
// 模拟数据库操作异常
class DatabaseException extends Exception {
protected $query;
public function __construct($message, $query = null, $code = 0, Exception $previous = null) {
$this->query = $query;
parent::__construct($message, $code, $previous);
}
public function getQuery() {
return $this->query;
}
}
// 模拟业务逻辑
function processOrder($orderId) {
try {
if (!is_numeric($orderId)) {
throw new InvalidArgumentException("订单ID必须是数字");
}
if ($orderId < 1) {
throw new DatabaseException("找不到订单", "SELECT * FROM orders WHERE id = {$orderId}");
}
return "订单 {$orderId} 处理成功";
} catch (InvalidArgumentException $e) {
// 记录参数错误
error_log("参数错误: " . $e->getMessage(), 3, 'validation_errors.log');
throw $e;
} catch (DatabaseException $e) {
// 记录数据库错误
$logMessage = sprintf(
"数据库异常: %s | 查询: %s | 文件: %s:%d\n",
$e->getMessage(),
$e->getQuery(),
$e->getFile(),
$e->getLine()
);
error_log($logMessage, 3, 'database_errors.log');
throw $e;
}
}
// 测试异常处理
try {
$result = processOrder(-1);
echo $result;
} catch (Exception $e) {
// 这里不会执行,因为异常已经被set_exception_handler处理
echo "捕获到异常: " . $e->getMessage();
}
echo "<div class='alert alert-info'>";
echo "异常处理系统已激活。检查 exceptions.log、validation_errors.log 和 database_errors.log 文件。";
echo "</div>";
| 配置项 | 默认值 | 描述 |
|---|---|---|
error_log |
空 | 指定错误日志文件路径。如果未设置,错误会发送到SAPI错误记录器 |
log_errors |
Off | 是否将错误记录到错误日志。当设置为On时,错误会被记录到error_log指定的位置 |
log_errors_max_len |
1024 | 设置错误日志的最大长度。0表示无限制 |
ignore_repeated_errors |
Off | 是否忽略重复的错误消息。当设置为On时,相同错误的重复实例不会被记录 |
ignore_repeated_source |
Off | 是否忽略来自相同源(文件:行号)的重复错误 |
html_errors |
On | 是否在错误消息中使用HTML标签格式化 |
<?php
echo "<div style='background:#f5f5f5; padding:10px; border:1px solid #ddd;'>";
echo "<strong>当前错误日志配置:</strong><br>";
echo "error_log: " . ini_get('error_log') . "<br>";
echo "log_errors: " . ini_get('log_errors') . "<br>";
echo "log_errors_max_len: " . ini_get('log_errors_max_len') . "<br>";
echo "error_reporting: " . ini_get('error_reporting') . "<br>";
echo "display_errors: " . ini_get('display_errors') . "<br>";
echo "</div>";
始终使用结构化格式记录日志,包含时间戳、错误级别、上下文信息等。
[2023-10-15 14:30:25] [ERROR] 数据库连接失败 | Context: {"host":"localhost","port":3306,"user":"root"}
不要在日志中记录密码、API密钥、信用卡号等敏感信息。
// 错误 ❌
error_log("用户登录失败: 用户名=admin, 密码=123456");
// 正确 ✅
error_log("用户登录失败: 用户名=admin");
定期轮转日志文件,避免单个文件过大。可以使用logrotate或其他工具。
# 每天轮转,保留30天
/var/log/app/*.log {
daily
rotate 30
compress
missingok
notifempty
}
根据错误严重程度使用不同的错误级别,便于过滤和监控。
对于复杂的应用程序,可以考虑以下替代方案:
| 方案 | 描述 | 适用场景 |
|---|---|---|
| Monolog库 | PHP最流行的日志库,支持文件、数据库、邮件、Slack、Syslog等多种处理器 | 企业级应用,需要高级日志功能 |
| Syslog | 使用系统日志服务,如Linux的syslog或systemd-journald | 服务器级别的日志记录 |
| 专用日志服务 | 如ELK Stack (Elasticsearch, Logstash, Kibana)、Graylog、Sentry等 | 大规模分布式系统 |
| 数据库记录 | 将错误记录到数据库表中,便于查询和分析 | 需要复杂查询和分析的场景 |