PHPerror_log()函数

简介

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 空字符串
  • $message_type=1时:收件人电子邮件地址
  • $message_type=3时:目标文件路径
  • 其他类型:通常忽略
$additional_headers string 空字符串 仅在$message_type=1时使用,指定额外的邮件头(如From、Cc、Bcc等),多个头用CRLF(\r\n)分隔

返回值

bool - 如果成功记录日志返回true,失败返回false

注意:当$message_type=1(发送邮件)时,如果邮件发送失败,函数返回false。但即使返回true,也不保证邮件一定被接收,只表示成功传递给了邮件系统。

示例

示例1:基本用法 - 记录到系统日志

默认情况下,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>";

系统日志位置:

  • Linux/Unix: 通常为/var/log/php_errors.log/var/log/apache2/error.log
  • Windows: 查看PHP配置文件(php.ini)中的error_log设置
  • 可以通过ini_get('error_log')获取当前配置的日志文件路径

示例2:记录到指定文件

使用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>";
}

示例3:发送错误邮件

使用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>";

示例4:在自定义错误处理器中使用

结合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();

示例5:完整的错误日志类

创建一个完整的错误日志记录类:

<?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>";
}

示例6:在异常处理中使用

在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>";

PHP配置相关

php.ini配置选项
配置项 默认值 描述
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
}
错误级别分类

根据错误严重程度使用不同的错误级别,便于过滤和监控。

  • DEBUG - 调试信息
  • INFO - 普通信息
  • WARNING - 警告
  • ERROR - 错误
  • CRITICAL - 严重错误

注意事项和限制

  • 邮件发送:当使用message_type=1时,需要正确配置PHP的邮件发送功能(如sendmail或SMTP)
  • 文件权限:当写入文件时,确保PHP进程有对目标目录的写入权限
  • 性能影响:频繁写入日志或发送邮件会影响性能,在生产环境中应合理配置日志级别
  • 邮件大小限制:电子邮件有大小限制,避免在邮件中附加大量数据
  • 并发写入:在高并发场景下,同时写入同一个日志文件可能导致数据丢失或损坏,考虑使用专门的日志库
  • 安全考虑:确保日志文件不可通过Web直接访问,通常应将日志文件放在Web根目录之外
  • 磁盘空间:监控日志文件大小,避免日志文件占满磁盘空间

替代方案

对于复杂的应用程序,可以考虑以下替代方案:

方案 描述 适用场景
Monolog库 PHP最流行的日志库,支持文件、数据库、邮件、Slack、Syslog等多种处理器 企业级应用,需要高级日志功能
Syslog 使用系统日志服务,如Linux的syslog或systemd-journald 服务器级别的日志记录
专用日志服务 如ELK Stack (Elasticsearch, Logstash, Kibana)、Graylog、Sentry等 大规模分布式系统
数据库记录 将错误记录到数据库表中,便于查询和分析 需要复杂查询和分析的场景