首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

PHP MySQL 錯誤日誌記錄最佳實踐:結構化、安全、可觀測

     編輯:MySQL 資料庫相關知識

原始代碼問題分析


function error() {
    $fp = @fopen("error.dat", "a+");
    $time = date("Y-m-d H:i:s");
    $msg = "{VisitedTime:$time}\t{VisiterIP:$_SERVER[REMOTE_ADDR]}\t{Errormsg:".mysql_error()."}\t{File:->$_SERVER[PHP_SELF]}\t{User:$_SESSION[user]}\r\n";
    @fputs($fp, $msg);
    @fclose($fp);
}

痛點

  • 使用已棄用 mysql_* 擴展 (PHP 7.0+ 移除)。
  • 手工拼接文本格式,難以解析、查詢、告警。
  • @ 抑制錯誤,掩蓋寫入失敗 (磁盤滿、權限)。
  • 直接暴露 $_SERVER/$_SESSION 敏感信息,無脫敏。
  • 單文件無輪轉,無限增長導致磁盤爆滿。

現代化重構:Monolog + 結構化 JSON + 日誌輪轉

1. Composer 依賴


composer require monolog/monolog

2. 錯誤日誌服務類


setFormatter(new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true));
        
        $this->logger = new Logger('mysql_errors');
        $this->logger->pushHandler($handler);
        // 自動注入請求上下文 (URL、IP、Method、Session ID 等)
        $this->logger->pushProcessor(new WebProcessor());
        $this->logger->pushProcessor(new MemoryUsageProcessor());
        $this->logger->pushProcessor(new PsrLogMessageProcessor());
    }

    public function logError(Throwable $e, \PDO $pdo = null, array $context = []): void
    {
        $errorInfo = $pdo ? $pdo->errorInfo() : ['driver' => 'unknown'];
        
        $record = array_merge([
            'event' => 'mysql_error',
            'error_code' => $errorInfo[0] ?? 'HY000',
            'error_sqlstate' => $errorInfo[1] ?? '',
            'error_message' => $e->getMessage(),
            'error_file' => $e->getFile(),
            'error_line' => $e->getLine(),
            'error_trace' => $e->getTraceAsString(),
            'sql_query' => $context['query'] ?? null,
            'sql_params' => $this->sanitizeParams($context['params'] ?? []),
            'db_host' => $context['db_host'] ?? null,
            'db_name' => $context['db_name'] ?? null,
        ], $context);

        $this->logger->error($e->getMessage(), $record);
    }

    private function sanitizeParams(array $params): array
    {
        // 脫敏:密碼、Token、身份證、手機號等
        $sensitiveKeys = ['password', 'passwd', 'pwd', 'token', 'secret', 'idcard', 'mobile', 'phone', 'ssn'];
        foreach ($params as $k => $v) {
            if (in_array(strtolower($k), $sensitiveKeys, true)) {
                $params[$k] = '***REDACTED***';
            }
        }
        return $params;
    }
}

3. 在 PDO 異常捕獲中調用


try {
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
    $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
    $stmt->execute([$email]);
} catch (PDOException $e) {
    $logger = new \App\Logging\MysqlErrorLogger();
    $logger->logError($e, $pdo, [
        'query' => $stmt->queryString ?? 'unknown',
        'params' => [$email],
        'db_host' => $config['db_host'],
        'db_name' => $config['db_name'],
        'user_id' => $_SESSION['user_id'] ?? null,
    ]);
    // 用戶友好提示,不暴露細節
    throw new \RuntimeException('數據庫暫時不可用,請稍後重試');
}

日誌輸出樣例 (JSON Lines)


{"datetime":"2024-01-15T14:30:45.123+08:00","channel":"mysql_errors","level":40,"level_name":"ERROR","message":"SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'test@test.com' for key 'users.email_unique'","context":{"event":"mysql_error","error_code":"23000","error_sqlstate":"23000","error_message":"SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'test@test.com' for key 'users.email_unique'","error_file":"/var/www/html/app/Repository/UserRepository.php","error_line":42,"error_trace":"#0 ...","sql_query":"SELECT * FROM users WHERE email = ?","sql_params":["test@test.com"],"db_host":"db-master","db_name":"app_prod","user_id":12345,"url":"https://app.example.com/register","ip":"203.0.113.42","http_method":"POST","referrer":"https://app.example.com/","memory_usage":8.5}

可觀測性增強:ELK / Loki / ClickHouse 接入

  • Filebeat / Promtail 采集 JSON 日誌 → Elasticsearch / Loki 索引。
  • Kibana / Grafana 儀表板:按 error_code、db_host、sql_query 關鍵字聚合、趨勢、Top N。
  • 告警規則:error_code IN ('HY000','08004','40001') AND rate > 5/min → PagerDuty/企業微信/釘釘。

安全合規清單

  1. 日誌文件權限 0640,屬主 www-data:adm,禁止 World 可讀。
  2. 敏感字段脫敏 (密碼、Token、PII) —— 代碼中已內置 sanitizeParams。
  3. 日誌保留策略:熱存儲 30 天,冷存儲 1 年 (合規要求)。
  4. 異地備份日誌,防範勒索軟件加密本地日誌。

避坑指南

  • ❌ 繼續使用 mysql_* / mysqli_* 過程式 API → 全面遷移 PDO (支持多驅動、參數化、異常模式)。
  • ❌ 生產環境 display_errors=On → display_errors=Off, log_errors=On。
  • ❌ 錯誤信息直接返回前端 → 統一錯誤碼映射,前端僅顯示友好提示。
  • ✅ 所有數據庫操作封裝 Repository/DAO 層,統一異常捕獲、日誌記錄、事務管理。

錯誤日誌不是「寫到文件裡就完事」,而是**可查詢、可告警、可審計、合規安全**的可觀測性基礎設施。**引入 Monolog 等成熟日誌庫,輸出結構化 JSON,接入統一日誌平台,建立告警閉環**,才能在故障發生時「秒級發現、分鐘級定位、小時級修復」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved