get_error_context() 是一个自定义的PHP函数概念,用于在错误发生时获取详细的上下文信息。虽然PHP没有内置这个函数,但我们可以创建自己的实现,用于收集错误发生时的环境、变量、调用栈等详细信息。
set_error_handler()设置)会接收到$errcontext参数,但在PHP 7.2.0中已弃用。我们可以创建自己的上下文收集函数。
$errcontext参数已被弃用。如果需要获取错误上下文,应该使用debug_backtrace()和get_defined_vars()等函数。
了解错误发生时变量的值、调用栈、环境信息,能够更快地定位和修复问题。
详细的上下文信息有助于重现错误,特别是在生产环境中难以复现的问题。
结构化的上下文信息便于日志分析和错误模式识别。
在用户报告错误时,上下文信息能帮助技术支持人员更快理解问题。
我们可以设计一个get_error_context()函数,它收集错误发生时的各种上下文信息。
array get_error_context(array $options = [])
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| $options | array | [] | 配置选项,控制收集哪些上下文信息。 |
array - 包含错误上下文信息的关联数组。
实现一个基本的get_error_context()函数:
<?php
/**
* 获取错误上下文信息
*
* @param array $options 配置选项
* @return array
*/
function get_error_context(array $options = []) {
$defaultOptions = [
'include_backtrace' => true,
'include_vars' => true,
'include_env' => true,
'include_request' => true,
'include_session' => true,
'include_cookies' => true,
'backtrace_limit' => 5,
'var_filter' => null, // 回调函数,用于过滤敏感变量
];
$options = array_merge($defaultOptions, $options);
$context = [
'timestamp' => date('Y-m-d H:i:s'),
'memory_usage' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true),
];
// 收集回溯信息
if ($options['include_backtrace']) {
$context['backtrace'] = get_backtrace_info($options['backtrace_limit']);
}
// 收集变量信息
if ($options['include_vars']) {
$context['variables'] = get_variables_info($options['var_filter']);
}
// 收集环境信息
if ($options['include_env']) {
$context['environment'] = get_environment_info();
}
// 收集请求信息
if ($options['include_request'] && php_sapi_name() !== 'cli') {
$context['request'] = get_request_info();
}
// 收集会话信息
if ($options['include_session'] && session_status() === PHP_SESSION_ACTIVE) {
$context['session'] = get_session_info();
}
// 收集Cookie信息
if ($options['include_cookies'] && !empty($_COOKIE)) {
$context['cookies'] = get_cookies_info();
}
return $context;
}
/**
* 获取回溯信息
*/
function get_backtrace_info($limit = 5) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit + 2);
// 移除get_backtrace_info和get_error_context的调用
array_shift($backtrace); // 移除get_backtrace_info
array_shift($backtrace); // 移除get_error_context
$formatted = [];
foreach ($backtrace as $i => $trace) {
$formatted[] = [
'file' => $trace['file'] ?? 'unknown',
'line' => $trace['line'] ?? 0,
'function' => $trace['function'] ?? 'unknown',
'class' => $trace['class'] ?? null,
'type' => $trace['type'] ?? null,
'args' => isset($trace['args']) ? format_args($trace['args']) : [],
];
}
return $formatted;
}
/**
* 获取变量信息
*/
function get_variables_info($filter = null) {
$vars = get_defined_vars();
// 应用过滤器
if (is_callable($filter)) {
$vars = $filter($vars);
}
return $vars;
}
/**
* 获取环境信息
*/
function get_environment_info() {
return [
'php_version' => PHP_VERSION,
'php_sapi' => php_sapi_name(),
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? null,
'server_name' => $_SERVER['SERVER_NAME'] ?? null,
'document_root' => $_SERVER['DOCUMENT_ROOT'] ?? null,
'script_filename' => $_SERVER['SCRIPT_FILENAME'] ?? null,
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null,
'request_time' => $_SERVER['REQUEST_TIME'] ?? date('Y-m-d H:i:s'),
'timezone' => date_default_timezone_get(),
];
}
/**
* 获取请求信息
*/
function get_request_info() {
return [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'uri' => $_SERVER['REQUEST_URI'] ?? null,
'query_string' => $_SERVER['QUERY_STRING'] ?? null,
'headers' => getallheaders(),
'get' => $_GET,
'post' => $_POST,
'files' => $_FILES,
];
}
/**
* 获取会话信息
*/
function get_session_info() {
return isset($_SESSION) ? $_SESSION : [];
}
/**
* 获取Cookie信息
*/
function get_cookies_info() {
return $_COOKIE;
}
/**
* 格式化参数
*/
function format_args($args, $max_length = 100) {
$formatted = [];
foreach ($args as $arg) {
if (is_object($arg)) {
$formatted[] = get_class($arg) . ' object';
} elseif (is_array($arg)) {
$formatted[] = 'Array(' . count($arg) . ')';
} elseif (is_resource($arg)) {
$formatted[] = 'Resource(' . get_resource_type($arg) . ')';
} elseif (is_string($arg)) {
if (strlen($arg) > $max_length) {
$arg = substr($arg, 0, $max_length) . '...';
}
$formatted[] = '"' . htmlspecialchars($arg) . '"';
} else {
$formatted[] = var_export($arg, true);
}
}
return $formatted;
}
/**
* 过滤敏感变量
*/
function filter_sensitive_vars($vars) {
$sensitive_keys = ['password', 'passwd', 'pwd', 'secret', 'token', 'key', 'credit_card', 'cc_number'];
foreach ($vars as $key => $value) {
foreach ($sensitive_keys as $sensitive) {
if (stripos($key, $sensitive) !== false) {
$vars[$key] = '[FILTERED]';
break;
}
}
// 递归处理数组
if (is_array($value)) {
$vars[$key] = filter_sensitive_vars($value);
}
}
return $vars;
}
// 使用示例
echo "<h4>测试get_error_context()基本实现:</h4>";
// 设置一些测试数据
$_GET['page'] = 1;
$_POST['username'] = 'testuser';
$_POST['password'] = 'secret123'; // 敏感数据
$testVar = '测试变量';
// 获取上下文信息
$context = get_error_context([
'include_backtrace' => true,
'include_vars' => true,
'include_env' => true,
'include_request' => true,
'backtrace_limit' => 3,
'var_filter' => 'filter_sensitive_vars',
]);
echo "<pre>" . htmlspecialchars(print_r($context, true)) . "</pre>";
// 测试在函数中获取上下文
function test_function() {
$localVar = '局部变量';
$context = get_error_context(['backtrace_limit' => 2]);
return $context;
}
echo "<h4>在函数中获取上下文:</h4>";
$funcContext = test_function();
echo "<pre>" . htmlspecialchars(print_r($funcContext['backtrace'], true)) . "</pre>";
演示如何在自定义错误处理器中使用get_error_context():
<?php
// 包含上一个示例中的get_error_context()函数
// 这里假设get_error_context()和相关函数已定义
/**
* 自定义错误处理器
*/
function custom_error_handler($errno, $errstr, $errfile, $errline) {
// 获取错误上下文
$context = get_error_context([
'include_backtrace' => true,
'include_vars' => true,
'include_env' => true,
'include_request' => true,
'backtrace_limit' => 10,
'var_filter' => 'filter_sensitive_vars',
]);
// 构建错误信息
$error = [
'type' => get_error_type_name($errno),
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'timestamp' => date('Y-m-d H:i:s'),
'context' => $context,
];
// 记录错误日志
log_error($error);
// 根据环境显示错误
if (getenv('APP_ENV') === 'development') {
display_debug_error($error);
}
// 对于致命错误,不要返回true
if (in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
return false;
}
return true; // 阻止PHP默认处理器
}
/**
* 获取错误类型名称
*/
function get_error_type_name($type) {
$types = [
1 => 'E_ERROR',
2 => 'E_WARNING',
4 => 'E_PARSE',
8 => 'E_NOTICE',
16 => 'E_CORE_ERROR',
32 => 'E_CORE_WARNING',
64 => 'E_COMPILE_ERROR',
128 => 'E_COMPILE_WARNING',
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',
];
return isset($types[$type]) ? $types[$type] : "UNKNOWN($type)";
}
/**
* 记录错误日志
*/
function log_error($error) {
$logFile = 'logs/errors.log';
// 确保目录存在
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$logEntry = json_encode($error, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
/**
* 显示调试错误
*/
function display_debug_error($error) {
echo "<div style='background:#f8f9fa; padding:20px; margin:20px 0; border:1px solid #ddd; font-family:monospace;'>";
echo "<h3 style='color:#dc3545; margin-top:0;'>错误详情</h3>";
echo "<table style='width:100%; border-collapse:collapse;'>";
echo "<tr><td style='width:120px; padding:5px; font-weight:bold;'>类型:</td><td style='padding:5px;'>" . $error['type'] . "</td></tr>";
echo "<tr><td style='padding:5px; font-weight:bold;'>消息:</td><td style='padding:5px;'>" . htmlspecialchars($error['message']) . "</td></tr>";
echo "<tr><td style='padding:5px; font-weight:bold;'>文件:</td><td style='padding:5px;'>" . $error['file'] . "</td></tr>";
echo "<tr><td style='padding:5px; font-weight:bold;'>行号:</td><td style='padding:5px;'>" . $error['line'] . "</td></tr>";
echo "</table>";
echo "</div>";
}
// 设置环境
putenv('APP_ENV=development');
// 设置自定义错误处理器
set_error_handler('custom_error_handler', E_ALL);
// 使用示例
echo "<h4>测试自定义错误处理器:</h4>";
// 设置一些测试数据
$_GET['id'] = 123;
$_POST['email'] = 'user@example.com';
$config = ['db_host' => 'localhost', 'db_name' => 'test'];
// 触发不同类型的错误
echo "<strong>触发警告:</strong><br>";
$result = 10 / 0; // 除以零警告
echo "<br><strong>触发通知:</strong><br>";
echo $undefinedVariable; // 使用未定义变量
echo "<br><strong>触发用户错误:</strong><br>";
trigger_error("这是一个用户自定义错误", E_USER_ERROR);
echo "<div class='alert alert-info'>";
echo "查看 logs/errors.log 文件获取详细错误日志(包含上下文信息)";
echo "</div>";
演示如何在异常处理器中使用get_error_context():
<?php
// 包含get_error_context()函数
// 这里假设get_error_context()和相关函数已定义
/**
* 异常上下文收集器
*/
class ExceptionContextCollector {
/**
* 收集异常上下文
*/
public static function collect($exception, $options = []) {
$defaultOptions = [
'include_exception' => true,
'include_backtrace' => true,
'include_vars' => true,
'include_code_snippet' => true,
'code_snippet_lines' => 5,
];
$options = array_merge($defaultOptions, $options);
$context = get_error_context($options);
// 添加异常特定信息
if ($options['include_exception']) {
$context['exception'] = [
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'previous' => $exception->getPrevious() ? get_class($exception->getPrevious()) : null,
];
}
// 添加代码片段
if ($options['include_code_snippet'] && file_exists($exception->getFile())) {
$context['code_snippet'] = self::getCodeSnippet(
$exception->getFile(),
$exception->getLine(),
$options['code_snippet_lines']
);
}
return $context;
}
/**
* 获取代码片段
*/
private static function getCodeSnippet($file, $line, $lines = 5) {
if (!file_exists($file)) {
return null;
}
$source = file($file);
$start = max(0, $line - $lines - 1);
$end = min(count($source), $line + $lines);
$snippet = [];
for ($i = $start; $i < $end; $i++) {
$snippet[$i + 1] = [
'line' => $i + 1,
'code' => rtrim($source[$i]),
'current' => ($i + 1) == $line,
];
}
return $snippet;
}
/**
* 设置异常日志记录器
*/
public static function setExceptionLogger($options = []) {
set_exception_handler(function($exception) use ($options) {
$context = self::collect($exception, $options);
self::logException($exception, $context);
// 重新抛出异常
throw $exception;
});
}
/**
* 记录异常
*/
private static function logException($exception, $context) {
$logFile = 'logs/exceptions.log';
// 确保目录存在
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$logData = [
'timestamp' => date('Y-m-d H:i:s'),
'exception' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'context' => $context,
];
$logEntry = json_encode($logData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
}
// 使用示例
echo "<h4>测试异常上下文收集:</h4>";
// 设置异常日志记录器
ExceptionContextCollector::setExceptionLogger([
'include_code_snippet' => true,
'code_snippet_lines' => 3,
'include_backtrace' => true,
'backtrace_limit' => 5,
]);
// 定义自定义异常
class ValidationException extends Exception {
private $errors = [];
public function __construct($message, $errors = [], $code = 0, Exception $previous = null) {
$this->errors = $errors;
parent::__construct($message, $code, $previous);
}
public function getErrors() {
return $this->errors;
}
}
// 测试函数
function validateUser($data) {
if (empty($data['username'])) {
throw new ValidationException("用户名不能为空", ['username' => 'required']);
}
if (strlen($data['username']) < 3) {
throw new ValidationException("用户名至少3个字符", ['username' => 'min_length']);
}
return true;
}
// 测试异常
try {
$userData = ['username' => 'ab']; // 无效的用户名
validateUser($userData);
} catch (ValidationException $e) {
echo "<div class='alert alert-warning'>";
echo "验证异常: " . $e->getMessage();
echo "</div>";
// 收集上下文但不记录(因为异常已被捕获)
$context = ExceptionContextCollector::collect($e, [
'include_code_snippet' => true,
'code_snippet_lines' => 5,
]);
echo "<h5>异常上下文:</h5>";
echo "<pre style='max-height:300px; overflow:auto;'>" .
htmlspecialchars(print_r($context, true)) . "</pre>";
}
// 这个异常会被异常处理器记录
function processOrder($orderId) {
if ($orderId <= 0) {
throw new InvalidArgumentException("无效的订单ID: " . $orderId);
}
// 模拟数据库操作
if (!is_numeric($orderId)) {
throw new RuntimeException("数据库查询失败: 订单不存在");
}
return ['id' => $orderId, 'status' => 'processing'];
}
// 触发未捕获的异常
processOrder(-1);
echo "<div class='alert alert-info'>";
echo "查看 logs/exceptions.log 文件获取异常日志(包含完整上下文)";
echo "</div>";
创建一个上下文感知的调试工具,可以在任意位置获取当前上下文:
<?php
/**
* 上下文感知的调试工具
*/
class ContextAwareDebugger {
private static $snapshots = [];
private static $enabled = true;
/**
* 启用/禁用调试器
*/
public static function enable($enabled = true) {
self::$enabled = $enabled;
}
/**
* 获取当前上下文快照
*/
public static function snapshot($label = null, $options = []) {
if (!self::$enabled) {
return null;
}
$context = get_error_context($options);
$snapshot = [
'label' => $label ?: 'Snapshot ' . (count(self::$snapshots) + 1),
'timestamp' => microtime(true),
'context' => $context,
];
self::$snapshots[] = $snapshot;
return $snapshot;
}
/**
* 获取所有快照
*/
public static function getSnapshots() {
return self::$snapshots;
}
/**
* 清除所有快照
*/
public static function clearSnapshots() {
self::$snapshots = [];
}
/**
* 在代码中设置检查点
*/
public static function checkpoint($label = null, $condition = null) {
if (!self::$enabled) {
return;
}
$snapshot = self::snapshot($label, [
'include_backtrace' => true,
'backtrace_limit' => 3,
]);
// 如果提供了条件,检查并记录
if ($condition !== null && !$condition) {
self::logCheckpointFailure($snapshot, $condition);
}
return $snapshot;
}
/**
* 记录检查点失败
*/
private static function logCheckpointFailure($snapshot, $condition) {
$logFile = 'logs/checkpoints.log';
$logData = [
'timestamp' => date('Y-m-d H:i:s'),
'checkpoint' => $snapshot['label'],
'condition' => var_export($condition, true),
'context' => $snapshot['context'],
];
$logEntry = json_encode($logData, JSON_PRETTY_PRINT) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
/**
* 跟踪变量变化
*/
public static function trace($variable, $name = null) {
if (!self::$enabled) {
return;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1] ?? $backtrace[0];
$trace = [
'timestamp' => microtime(true),
'variable' => $name ?: 'unnamed',
'value' => self::formatVariable($variable),
'type' => gettype($variable),
'caller' => [
'file' => $caller['file'] ?? 'unknown',
'line' => $caller['line'] ?? 0,
'function' => $caller['function'] ?? 'unknown',
],
];
self::$snapshots[] = [
'label' => 'Variable Trace: ' . ($name ?: 'unnamed'),
'timestamp' => $trace['timestamp'],
'context' => ['trace' => $trace],
];
return $trace;
}
/**
* 格式化变量
*/
private static function formatVariable($var, $maxDepth = 2, $depth = 0) {
if ($depth >= $maxDepth) {
return '...';
}
if (is_object($var)) {
return get_class($var) . ' object';
} elseif (is_array($var)) {
$result = [];
foreach ($var as $key => $value) {
$result[$key] = self::formatVariable($value, $maxDepth, $depth + 1);
}
return $result;
} elseif (is_resource($var)) {
return 'Resource(' . get_resource_type($var) . ')';
} elseif (is_string($var)) {
if (strlen($var) > 100) {
$var = substr($var, 0, 100) . '...';
}
return $var;
} else {
return var_export($var, true);
}
}
/**
* 生成调试报告
*/
public static function generateReport() {
if (empty(self::$snapshots)) {
return ['message' => 'No snapshots available'];
}
$report = [
'generated_at' => date('Y-m-d H:i:s'),
'total_snapshots' => count(self::$snapshots),
'snapshots' => self::$snapshots,
'memory_usage' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true),
'execution_time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
];
return $report;
}
}
// 使用示例
echo "<h4>测试ContextAwareDebugger:</h4>";
// 启用调试器
ContextAwareDebugger::enable(true);
// 模拟应用程序流程
echo "<strong>模拟应用程序执行流程:</strong><br>";
// 检查点1:应用程序启动
ContextAwareDebugger::checkpoint('App Startup');
// 设置一些变量
$config = ['debug' => true, 'environment' => 'development'];
$user = ['id' => 123, 'name' => 'John Doe', 'email' => 'john@example.com'];
// 跟踪变量
ContextAwareDebugger::trace($config, 'config');
ContextAwareDebugger::trace($user, 'user');
// 检查点2:处理请求
ContextAwareDebugger::checkpoint('Process Request', !empty($_GET));
// 模拟处理
function processData($data) {
// 检查点:在处理函数内部
ContextAwareDebugger::checkpoint('Inside processData');
if (empty($data)) {
return false;
}
return array_map('strtoupper', $data);
}
$data = ['item1', 'item2', 'item3'];
$processed = processData($data);
ContextAwareDebugger::trace($processed, 'processed_data');
// 检查点3:处理完成
ContextAwareDebugger::checkpoint('Processing Complete', $processed !== false);
// 生成报告
$report = ContextAwareDebugger::generateReport();
echo "<div class='alert alert-success'>";
echo "生成了 " . $report['total_snapshots'] . " 个上下文快照";
echo "</div>";
echo "<h5>快照列表:</h5>";
foreach ($report['snapshots'] as $i => $snapshot) {
echo "<div style='background:#f8f9fa; padding:10px; margin:5px; border-left:4px solid #007bff;'>";
echo "<strong>" . ($i + 1) . ". " . $snapshot['label'] . "</strong><br>";
echo "时间: " . date('H:i:s', (int)$snapshot['timestamp']) . "<br>";
if (isset($snapshot['context']['trace'])) {
echo "变量: " . $snapshot['context']['trace']['variable'] . " = " .
htmlspecialchars(print_r($snapshot['context']['trace']['value'], true));
}
echo "</div>";
}
// 获取详细报告
echo "<h5>详细调试报告:</h5>";
echo "<pre style='max-height:400px; overflow:auto;'>" .
htmlspecialchars(print_r($report, true)) . "</pre>";
演示在生产环境中安全地收集错误上下文:
<?php
/**
* 生产环境错误上下文收集器
*/
class ProductionErrorCollector {
private static $config = [
'enabled' => true,
'log_file' => 'logs/production_errors.log',
'max_log_size' => 10485760, // 10MB
'max_log_files' => 5,
'include_stack_trace' => true,
'include_request_data' => true,
'include_session_data' => false, // 生产环境通常不记录会话数据
'include_cookie_data' => false, // 生产环境通常不记录Cookie数据
'filter_sensitive_data' => true,
'sensitive_patterns' => [
'/password/i',
'/passwd/i',
'/pwd/i',
'/secret/i',
'/token/i',
'/key/i',
'/credit.?card/i',
'/cc.?number/i',
'/cvv/i',
'/ssn/i',
'/social.?security/i',
],
'sanitize_paths' => true,
'obfuscate_ips' => true,
];
/**
* 初始化
*/
public static function init($config = []) {
self::$config = array_merge(self::$config, $config);
if (!self::$config['enabled']) {
return;
}
// 设置错误处理器
set_error_handler([self::class, 'handleError']);
// 设置异常处理器
set_exception_handler([self::class, 'handleException']);
// 注册关闭函数
register_shutdown_function([self::class, 'handleShutdown']);
}
/**
* 处理错误
*/
public static function handleError($errno, $errstr, $errfile, $errline) {
$context = self::collectContext();
$error = [
'type' => 'error',
'level' => self::getErrorLevelName($errno),
'message' => $errstr,
'file' => self::sanitizePath($errfile),
'line' => $errline,
'timestamp' => date('c'),
'context' => $context,
];
self::log($error);
// 对于非致命错误,返回false让PHP继续处理
if (!in_array($errno, [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
return false;
}
// 对于致命错误,我们已经记录了,让PHP处理
return false;
}
/**
* 处理异常
*/
public static function handleException($exception) {
$context = self::collectContext();
$error = [
'type' => 'exception',
'exception' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => self::sanitizePath($exception->getFile()),
'line' => $exception->getLine(),
'timestamp' => date('c'),
'context' => $context,
];
if (self::$config['include_stack_trace']) {
$error['stack_trace'] = self::sanitizeStackTrace($exception->getTrace());
}
self::log($error);
// 重新抛出异常
throw $exception;
}
/**
* 处理关闭
*/
public static function handleShutdown() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$context = self::collectContext();
$log = [
'type' => 'fatal_error',
'level' => self::getErrorLevelName($error['type']),
'message' => $error['message'],
'file' => self::sanitizePath($error['file']),
'line' => $error['line'],
'timestamp' => date('c'),
'context' => $context,
];
self::log($log);
}
}
/**
* 收集上下文
*/
private static function collectContext() {
$context = [
'timestamp' => date('c'),
'memory' => [
'usage' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
],
'environment' => [
'php_version' => PHP_VERSION,
'sapi' => php_sapi_name(),
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? null,
],
];
// 请求数据
if (self::$config['include_request_data'] && php_sapi_name() !== 'cli') {
$context['request'] = self::collectRequestData();
}
// 会话数据
if (self::$config['include_session_data'] && session_status() === PHP_SESSION_ACTIVE) {
$context['session'] = self::filterSensitiveData($_SESSION ?? []);
}
// Cookie数据
if (self::$config['include_cookie_data'] && !empty($_COOKIE)) {
$context['cookies'] = self::filterSensitiveData($_COOKIE);
}
return $context;
}
/**
* 收集请求数据
*/
private static function collectRequestData() {
$request = [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
'uri' => $_SERVER['REQUEST_URI'] ?? null,
];
// 清理URI中的敏感信息
if (self::$config['sanitize_paths'] && isset($request['uri'])) {
$request['uri'] = self::sanitizePath($request['uri']);
}
// GET参数
if (!empty($_GET)) {
$request['get'] = self::filterSensitiveData($_GET);
}
// POST参数
if (!empty($_POST)) {
$request['post'] = self::filterSensitiveData($_POST);
}
// 头部信息
$headers = [];
foreach ($_SERVER as $key => $value) {
if (strpos($key, 'HTTP_') === 0) {
$header = str_replace('_', '-', strtolower(substr($key, 5)));
$headers[$header] = $value;
}
}
if (!empty($headers)) {
$request['headers'] = $headers;
}
// IP地址
if (self::$config['obfuscate_ips']) {
$request['ip'] = self::obfuscateIp($_SERVER['REMOTE_ADDR'] ?? null);
} else {
$request['ip'] = $_SERVER['REMOTE_ADDR'] ?? null;
}
return $request;
}
/**
* 过滤敏感数据
*/
private static function filterSensitiveData($data) {
if (!self::$config['filter_sensitive_data']) {
return $data;
}
if (!is_array($data)) {
return $data;
}
$filtered = [];
foreach ($data as $key => $value) {
$shouldFilter = false;
// 检查键名是否匹配敏感模式
foreach (self::$config['sensitive_patterns'] as $pattern) {
if (preg_match($pattern, $key)) {
$shouldFilter = true;
break;
}
}
if ($shouldFilter) {
$filtered[$key] = '[FILTERED]';
} elseif (is_array($value)) {
$filtered[$key] = self::filterSensitiveData($value);
} else {
$filtered[$key] = $value;
}
}
return $filtered;
}
/**
* 清理路径
*/
private static function sanitizePath($path) {
if (!self::$config['sanitize_paths'] || empty($path)) {
return $path;
}
// 移除文档根路径
$docRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
if ($docRoot && strpos($path, $docRoot) === 0) {
$path = '[DOCROOT]' . substr($path, strlen($docRoot));
}
// 移除用户名
$path = preg_replace('/\/home\/[^\/]+\//', '/home/[USER]/', $path);
return $path;
}
/**
* 清理堆栈跟踪
*/
private static function sanitizeStackTrace($trace) {
$sanitized = [];
foreach ($trace as $frame) {
$sanitizedFrame = $frame;
if (isset($frame['file'])) {
$sanitizedFrame['file'] = self::sanitizePath($frame['file']);
}
// 清理参数中的敏感数据
if (isset($frame['args'])) {
$sanitizedFrame['args'] = self::filterSensitiveData($frame['args']);
}
$sanitized[] = $sanitizedFrame;
}
return $sanitized;
}
/**
* 混淆IP地址
*/
private static function obfuscateIp($ip) {
if (empty($ip)) {
return null;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
// IPv4: 保留前两个字节
$parts = explode('.', $ip);
if (count($parts) === 4) {
return $parts[0] . '.' . $parts[1] . '.x.x';
}
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// IPv6: 简化
return substr($ip, 0, 8) . '...';
}
return $ip;
}
/**
* 获取错误级别名称
*/
private static function getErrorLevelName($level) {
$levels = [
1 => 'E_ERROR',
2 => 'E_WARNING',
4 => 'E_PARSE',
8 => 'E_NOTICE',
16 => 'E_CORE_ERROR',
32 => 'E_CORE_WARNING',
64 => 'E_COMPILE_ERROR',
128 => 'E_COMPILE_WARNING',
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',
];
return $levels[$level] ?? "UNKNOWN($level)";
}
/**
* 记录错误
*/
private static function log($data) {
$logFile = self::$config['log_file'];
// 确保目录存在
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// 检查日志轮转
self::rotateLogIfNeeded();
$logEntry = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
file_put_contents($logFile, $logEntry, FILE_APPEND);
}
/**
* 日志轮转
*/
private static function rotateLogIfNeeded() {
$logFile = self::$config['log_file'];
if (!file_exists($logFile)) {
return;
}
$maxSize = self::$config['max_log_size'];
$maxFiles = self::$config['max_log_files'];
if (filesize($logFile) < $maxSize) {
return;
}
// 轮转日志文件
for ($i = $maxFiles - 1; $i > 0; $i--) {
$oldFile = $logFile . '.' . $i;
$newFile = $logFile . '.' . ($i + 1);
if (file_exists($oldFile)) {
if ($i === $maxFiles - 1 && file_exists($newFile)) {
unlink($newFile);
}
rename($oldFile, $newFile);
}
}
rename($logFile, $logFile . '.1');
}
}
// 使用示例
echo "<h4>测试生产环境错误上下文收集:</h4>";
// 初始化生产环境错误收集器
ProductionErrorCollector::init([
'log_file' => 'logs/prod_errors.log',
'include_session_data' => false,
'include_cookie_data' => false,
'filter_sensitive_data' => true,
'obfuscate_ips' => true,
]);
echo "<div class='alert alert-success'>";
echo "生产环境错误收集器已初始化";
echo "</div>";
// 模拟生产环境错误
echo "<strong>触发测试错误:</strong><br>";
// 设置一些测试数据(包含敏感信息)
$_GET['page'] = 1;
$_POST['username'] = 'admin';
$_POST['password'] = 'SuperSecret123!'; // 敏感数据
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
$_SERVER['DOCUMENT_ROOT'] = '/var/www/html';
// 触发错误
trigger_error("测试生产环境错误处理", E_USER_WARNING);
echo "<div class='alert alert-info'>";
echo "查看 logs/prod_errors.log 文件获取错误日志(已过滤敏感信息)";
echo "</div>";
// 测试异常
try {
throw new RuntimeException("测试生产环境异常");
} catch (Exception $e) {
echo "<div class='alert alert-warning'>";
echo "异常已被捕获: " . $e->getMessage();
echo "</div>";
}
// 模拟文件系统错误
@file_get_contents('/nonexistent/file.txt');
echo "<div class='alert alert-info'>";
echo "所有错误都已记录到日志文件,敏感信息已被过滤";
echo "</div>";
创建一个完整的错误上下文工具包,包含各种实用功能:
<?php
/**
* 完整的错误上下文工具包
*/
class ErrorContextToolkit {
/**
* 获取错误上下文报告
*/
public static function getReport($options = []) {
$defaultOptions = [
'level' => 'normal', // 'minimal', 'normal', 'detailed', 'debug'
'include' => [
'basic' => true,
'environment' => true,
'request' => true,
'session' => false,
'cookies' => false,
'server' => false,
'backtrace' => true,
'variables' => false,
'constants' => false,
'loaded_extensions' => false,
'ini_settings' => false,
'database' => false,
'cache' => false,
'queue' => false,
],
'filters' => [],
'formatters' => [],
];
$options = array_merge_recursive($defaultOptions, $options);
$report = [];
// 基本上下文
if ($options['include']['basic']) {
$report['basic'] = self::getBasicContext();
}
// 环境信息
if ($options['include']['environment']) {
$report['environment'] = self::getEnvironmentContext();
}
// 请求信息
if ($options['include']['request'] && php_sapi_name() !== 'cli') {
$report['request'] = self::getRequestContext();
}
// 会话信息
if ($options['include']['session'] && session_status() === PHP_SESSION_ACTIVE) {
$report['session'] = self::getSessionContext();
}
// Cookie信息
if ($options['include']['cookies'] && !empty($_COOKIE)) {
$report['cookies'] = self::getCookieContext();
}
// 服务器信息
if ($options['include']['server']) {
$report['server'] = self::getServerContext();
}
// 回溯信息
if ($options['include']['backtrace']) {
$report['backtrace'] = self::getBacktraceContext($options['level']);
}
// 变量信息
if ($options['include']['variables']) {
$report['variables'] = self::getVariableContext($options['level']);
}
// 常量信息
if ($options['include']['constants']) {
$report['constants'] = self::getConstantContext();
}
// 加载的扩展
if ($options['include']['loaded_extensions']) {
$report['loaded_extensions'] = get_loaded_extensions();
}
// INI设置
if ($options['include']['ini_settings']) {
$report['ini_settings'] = self::getIniContext();
}
// 数据库上下文
if ($options['include']['database']) {
$report['database'] = self::getDatabaseContext();
}
// 缓存上下文
if ($options['include']['cache']) {
$report['cache'] = self::getCacheContext();
}
// 队列上下文
if ($options['include']['queue']) {
$report['queue'] = self::getQueueContext();
}
// 应用过滤器
foreach ($options['filters'] as $filter) {
if (is_callable($filter)) {
$report = $filter($report);
}
}
// 应用格式化器
foreach ($options['formatters'] as $formatter) {
if (is_callable($formatter)) {
$report = $formatter($report);
}
}
return $report;
}
/**
* 获取基本上下文
*/
private static function getBasicContext() {
return [
'timestamp' => date('c'),
'microtime' => microtime(true),
'memory_usage' => memory_get_usage(true),
'memory_peak' => memory_get_peak_usage(true),
'included_files' => get_included_files(),
];
}
/**
* 获取环境上下文
*/
private static function getEnvironmentContext() {
return [
'php_version' => PHP_VERSION,
'php_sapi' => php_sapi_name(),
'os' => PHP_OS,
'architecture' => (PHP_INT_SIZE * 8) . '-bit',
'timezone' => date_default_timezone_get(),
'locale' => setlocale(LC_ALL, 0),
'max_execution_time' => ini_get('max_execution_time'),
'max_input_time' => ini_get('max_input_time'),
'memory_limit' => ini_get('memory_limit'),
'error_reporting' => error_reporting(),
'display_errors' => ini_get('display_errors'),
'log_errors' => ini_get('log_errors'),
'error_log' => ini_get('error_log'),
];
}
/**
* 获取请求上下文
*/
private static function getRequestContext() {
$request = [
'method' => $_SERVER['REQUEST_METHOD'] ?? null,
'scheme' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http',
'host' => $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? null,
'port' => $_SERVER['SERVER_PORT'] ?? null,
'uri' => $_SERVER['REQUEST_URI'] ?? null,
'query_string' => $_SERVER['QUERY_STRING'] ?? null,
'script_name' => $_SERVER['SCRIPT_NAME'] ?? null,
'path_info' => $_SERVER['PATH_INFO'] ?? null,
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null,
'remote_port' => $_SERVER['REMOTE_PORT'] ?? null,
'server_addr' => $_SERVER['SERVER_ADDR'] ?? null,
'server_port' => $_SERVER['SERVER_PORT'] ?? null,
'request_time' => $_SERVER['REQUEST_TIME'] ?? null,
'request_time_float' => $_SERVER['REQUEST_TIME_FLOAT'] ?? null,
];
// 头部信息
$headers = [];
foreach ($_SERVER as $key => $value) {
if (strpos($key, 'HTTP_') === 0) {
$header = str_replace('_', '-', strtolower(substr($key, 5)));
$headers[$header] = $value;
}
}
if (!empty($headers)) {
$request['headers'] = $headers;
}
// GET参数
if (!empty($_GET)) {
$request['get'] = $_GET;
}
// POST参数
if (!empty($_POST)) {
$request['post'] = $_POST;
}
// 文件上传
if (!empty($_FILES)) {
$request['files'] = array_keys($_FILES);
}
return $request;
}
/**
* 获取会话上下文
*/
private static function getSessionContext() {
return [
'id' => session_id(),
'name' => session_name(),
'status' => session_status(),
'save_path' => session_save_path(),
'data' => $_SESSION ?? [],
];
}
/**
* 获取Cookie上下文
*/
private static function getCookieContext() {
return $_COOKIE;
}
/**
* 获取服务器上下文
*/
private static function getServerContext() {
$server = [];
// 收集服务器变量,排除敏感信息
$exclude = ['HTTP_AUTHORIZATION', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PATH', 'PATHEXT'];
foreach ($_SERVER as $key => $value) {
if (!in_array($key, $exclude) && !preg_match('/PASS/i', $key)) {
$server[$key] = $value;
}
}
return $server;
}
/**
* 获取回溯上下文
*/
private static function getBacktraceContext($level = 'normal') {
$limit = 0;
switch ($level) {
case 'minimal':
$limit = 3;
break;
case 'normal':
$limit = 10;
break;
case 'detailed':
$limit = 20;
break;
case 'debug':
$limit = 0;
break;
default:
$limit = 10;
}
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
// 移除工具包自身的调用
$filtered = [];
foreach ($backtrace as $trace) {
if (isset($trace['class']) && $trace['class'] === __CLASS__) {
continue;
}
$filtered[] = $trace;
}
return $filtered;
}
/**
* 获取变量上下文
*/
private static function getVariableContext($level = 'normal') {
$vars = get_defined_vars();
// 根据级别过滤
switch ($level) {
case 'minimal':
// 只保留全局变量
$vars = array_intersect_key($vars, $GLOBALS);
break;
case 'normal':
// 移除超全局变量
unset($vars['GLOBALS']);
break;
case 'detailed':
case 'debug':
// 包含所有变量
break;
}
return $vars;
}
/**
* 获取常量上下文
*/
private static function getConstantContext() {
return get_defined_constants(true);
}
/**
* 获取INI上下文
*/
private static function getIniContext() {
$ini = ini_get_all();
// 分组INI设置
$grouped = [];
foreach ($ini as $key => $value) {
$parts = explode('.', $key);
$group = count($parts) > 1 ? $parts[0] : 'other';
if (!isset($grouped[$group])) {
$grouped[$group] = [];
}
$grouped[$group][$key] = $value;
}
return $grouped;
}
/**
* 获取数据库上下文
*/
private static function getDatabaseContext() {
$context = [];
// 尝试检测常见的数据库连接
if (class_exists('PDO')) {
$context['pdo_drivers'] = PDO::getAvailableDrivers();
}
if (function_exists('mysqli_connect')) {
$context['mysqli'] = [
'client_version' => mysqli_get_client_version(),
'client_info' => mysqli_get_client_info(),
];
}
return $context;
}
/**
* 获取缓存上下文
*/
private static function getCacheContext() {
$context = [];
// 检查常见缓存扩展
$extensions = ['apc', 'apcu', 'memcached', 'redis', 'xcache'];
foreach ($extensions as $ext) {
if (extension_loaded($ext)) {
$context[$ext] = true;
}
}
return $context;
}
/**
* 获取队列上下文
*/
private static function getQueueContext() {
$context = [];
// 检查常见队列扩展
if (class_exists('Redis')) {
$context['redis'] = true;
}
if (class_exists('AMQPConnection')) {
$context['amqp'] = true;
}
if (class_exists('GearmanClient')) {
$context['gearman'] = true;
}
return $context;
}
/**
* 格式化报告为字符串
*/
public static function formatReport($report, $format = 'text') {
switch ($format) {
case 'json':
return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
case 'html':
return self::formatAsHtml($report);
case 'yaml':
return self::formatAsYaml($report);
case 'text':
default:
return print_r($report, true);
}
}
/**
* 格式化为HTML
*/
private static function formatAsHtml($report) {
$html = "<div class='error-context-report'>";
foreach ($report as $section => $data) {
$html .= "<h3>" . htmlspecialchars($section) . "</h3>";
$html .= "<pre>" . htmlspecialchars(print_r($data, true)) . "</pre>";
}
$html .= "</div>";
return $html;
}
/**
* 格式化为YAML
*/
private static function formatAsYaml($report) {
// 简单的YAML格式化
$yaml = '';
foreach ($report as $section => $data) {
$yaml .= $section . ":\n";
if (is_array($data)) {
foreach ($data as $key => $value) {
$yaml .= " " . $key . ": " . self::yamlValue($value, 2) . "\n";
}
} else {
$yaml .= " " . self::yamlValue($data, 2) . "\n";
}
}
return $yaml;
}
/**
* YAML值格式化
*/
private static function yamlValue($value, $indent = 0) {
if (is_array($value)) {
$result = "\n";
foreach ($value as $k => $v) {
$result .= str_repeat(' ', $indent) . $k . ": " . self::yamlValue($v, $indent + 2) . "\n";
}
return $result;
} elseif (is_bool($value)) {
return $value ? 'true' : 'false';
} elseif (is_null($value)) {
return 'null';
} elseif (is_numeric($value)) {
return $value;
} else {
return '"' . addcslashes($value, '"\\') . '"';
}
}
}
// 使用示例
echo "<h4>测试ErrorContextToolkit:</h4>";
// 获取不同级别的报告
echo "<h5>1. 最小化报告:</h5>";
$minimalReport = ErrorContextToolkit::getReport([
'level' => 'minimal',
'include' => [
'basic' => true,
'environment' => true,
'request' => true,
'backtrace' => true,
'variables' => false,
],
]);
echo "<pre style='max-height:200px; overflow:auto;'>" .
htmlspecialchars(ErrorContextToolkit::formatReport($minimalReport, 'text')) . "</pre>";
echo "<h5>2. 详细报告:</h5>";
$detailedReport = ErrorContextToolkit::getReport([
'level' => 'detailed',
'include' => [
'basic' => true,
'environment' => true,
'request' => true,
'backtrace' => true,
'variables' => true,
'constants' => true,
'loaded_extensions' => true,
],
]);
echo "报告大小: " . count($detailedReport) . " 个部分<br>";
echo "<div class='alert alert-info'>";
echo "详细报告包含大量信息,这里不全部显示";
echo "</div>";
echo "<h5>3. JSON格式报告:</h5>";
$jsonReport = ErrorContextToolkit::formatReport($minimalReport, 'json');
echo "<pre style='max-height:200px; overflow:auto;'>" .
htmlspecialchars($jsonReport) . "</pre>";
echo "<h5>4. 在错误处理器中使用:</h5>";
// 创建自定义错误处理器
set_error_handler(function($errno, $errstr, $errfile, $errline) {
$context = ErrorContextToolkit::getReport([
'level' => 'normal',
'include' => [
'basic' => true,
'environment' => true,
'request' => true,
'backtrace' => true,
],
]);
$error = [
'type' => 'error',
'level' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'context' => $context,
];
// 记录到文件
$log = json_encode($error, JSON_PRETTY_PRINT) . "\n";
file_put_contents('logs/context_errors.log', $log, FILE_APPEND);
echo "<div class='alert alert-warning'>";
echo "错误已记录,包含完整上下文信息";
echo "</div>";
return true;
});
// 触发错误测试
trigger_error("测试错误上下文收集", E_USER_NOTICE);
echo "<div class='alert alert-success'>";
echo "ErrorContextToolkit 提供了完整的错误上下文收集功能";
echo "</div>";
虽然PHP没有内置get_error_context()函数,但我们可以创建自定义函数来收集错误发生时的上下文信息。以下是关键要点:
debug_backtrace()、get_defined_vars()等函数实现上下文收集最佳实践: