PHP项目异常信息如何脱敏记录日志

wen PHP项目 30

本文目录导读:

PHP项目异常信息如何脱敏记录日志

  1. 基础脱敏方案
  2. 通用脱敏类
  3. 集成到日志系统
  4. 框架集成示例
  5. 自定义脱敏规则
  6. 最佳实践建议

在PHP项目中记录异常信息时,对敏感数据进行脱敏处理是保护用户隐私和安全的重要措施,以下是几种常见的脱敏方案和实现方法:

基础脱敏方案

手机号脱敏

function maskPhone($phone) {
    if (empty($phone)) return '';
    return substr($phone, 0, 3) . '****' . substr($phone, -4);
}
// 188****8888

邮箱脱敏

function maskEmail($email) {
    if (empty($email)) return '';
    $parts = explode('@', $email);
    $name = $parts[0];
    $domain = $parts[1] ?? '';
    $maskedName = substr($name, 0, 2) . str_repeat('*', strlen($name) - 2);
    return $maskedName . '@' . $domain;
}
// ab***@example.com

身份证号脱敏

function maskIdCard($idCard) {
    if (empty($idCard)) return '';
    return substr($idCard, 0, 6) . '********' . substr($idCard, -4);
}
// 110101********1234

通用脱敏类

class DataMasker {
    private static $patterns = [
        'phone' => [
            'pattern' => '/(1[3-9]\d)\d{4}(\d{4})/',
            'replacement' => '$1****$2'
        ],
        'email' => [
            'pattern' => '/(\w{2})\w*@(\w+\.\w+)/',
            'replacement' => '$1***@$2'
        ],
        'id_card' => [
            'pattern' => '/^(\d{6})\d{8}(\d{4})$/',
            'replacement' => '$1********$2'
        ],
        'bank_card' => [
            'pattern' => '/^(\d{4})\d{8,12}(\d{4})$/',
            'replacement' => '$1****$2'
        ],
        'password' => [
            'pattern' => '/password["\']?\s*[:=]\s*["\'].*?["\']/i',
            'replacement' => 'password"***"'
        ],
        'token' => [
            'pattern' => '/token["\']?\s*[:=]\s*["\'].*?["\']/i',
            'replacement' => 'token"***"'
        ]
    ];
    public static function mask($data, $type = null) {
        if ($type && isset(self::$patterns[$type])) {
            return preg_replace(
                self::$patterns[$type]['pattern'],
                self::$patterns[$type]['replacement'],
                $data
            );
        }
        // 自动检测并脱敏
        $result = $data;
        foreach (self::$patterns as $type => $rule) {
            $result = preg_replace($rule['pattern'], $rule['replacement'], $result);
        }
        return $result;
    }
    public static function maskArray($data, $fields = []) {
        if (empty($fields)) {
            $fields = ['phone', 'email', 'id_card', 'bank_card', 'password', 'token'];
        }
        foreach ($data as $key => &$value) {
            if (in_array($key, $fields)) {
                if (method_exists(self, 'mask' . ucfirst($key))) {
                    $value = self::{'mask' . ucfirst($key)}($value);
                }
            } elseif (is_array($value)) {
                $value = self::maskArray($value, $fields);
            }
        }
        return $data;
    }
}

集成到日志系统

Monolog 处理器

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\ProcessorInterface;
class SensitiveDataProcessor implements ProcessorInterface {
    private $fields = ['password', 'token', 'credit_card', 'ssn'];
    private $masker;
    public function __construct() {
        $this->masker = new DataMasker();
    }
    public function __invoke(array $record): array {
        // 处理上下文中的敏感数据
        if (isset($record['context'])) {
            $record['context'] = DataMasker::maskArray($record['context'], $this->fields);
        }
        // 处理异常信息
        if (isset($record['context']['exception'])) {
            $record['context']['exception'] = $this->maskException($record['context']['exception']);
        }
        // 处理消息本身
        $record['message'] = DataMasker::mask($record['message']);
        return $record;
    }
    private function maskException($exception) {
        if ($exception instanceof \Throwable) {
            return [
                'class' => get_class($exception),
                'message' => DataMasker::mask($exception->getMessage()),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $this->maskTrace($exception->getTraceAsString())
            ];
        }
        return $exception;
    }
    private function maskTrace($trace) {
        // 脱敏栈追踪中的敏感信息
        return DataMasker::mask($trace);
    }
}
// 使用示例
$logger = new Logger('app');
$logger->pushProcessor(new SensitiveDataProcessor());
$logger->pushHandler(new StreamHandler('/var/log/app.log', Logger::DEBUG));

自定义日志包装器

class SafeLogger {
    private $logger;
    private $sensitiveKeys = ['password', 'token', 'secret', 'authorization'];
    public function __construct($logger) {
        $this->logger = $logger;
    }
    public function error($message, array $context = []) {
        $safeMessage = $this->maskSensitiveData($message);
        $safeContext = $this->maskContext($context);
        $this->logger->error($safeMessage, $safeContext);
    }
    public function exception(\Throwable $e, array $context = []) {
        $safeContext = $this->maskContext($context);
        $safeMessage = DataMasker::mask($e->getMessage());
        $this->logger->error($safeMessage, array_merge([
            'exception' => [
                'class' => get_class($e),
                'message' => $safeMessage,
                'code' => $e->getCode(),
                'file' => $this->maskFilePath($e->getFile()),
                'line' => $e->getLine()
            ]
        ], $safeContext));
    }
    private function maskSensitiveData($data) {
        if (is_string($data)) {
            return DataMasker::mask($data);
        }
        return $data;
    }
    private function maskContext(array $context) {
        foreach ($context as $key => $value) {
            if (in_array(strtolower($key), $this->sensitiveKeys)) {
                $context[$key] = '***MASKED***';
            } elseif (is_array($value)) {
                $context[$key] = $this->maskContext($value);
            }
        }
        return $context;
    }
    private function maskFilePath($file) {
        // 脱敏用户目录路径
        $homePath = getenv('HOME') ?: '/home';
        return str_replace($homePath, '/home/***', $file);
    }
}

框架集成示例

Laravel 集成

// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Logging\SensitiveDataProcessor;
class Handler extends ExceptionHandler
{
    protected function context()
    {
        try {
            $context = parent::context();
            // 脱敏敏感数据
            $processor = new SensitiveDataProcessor();
            return $processor->__invoke(['context' => $context])['context'];
        } catch (\Throwable $e) {
            return [];
        }
    }
}

ThinkPHP 集成

// app/provider.php
return [
    'think\Log' => [
        'type' => 'File',
        'path' => './runtime/log/',
        'processor' => \app\common\SensitiveDataProcessor::class,
    ],
];

自定义脱敏规则

class SensitiveDataFilter {
    private $rules = [];
    public function addRule($pattern, $replacement) {
        $this->rules[] = [
            'pattern' => $pattern,
            'replacement' => $replacement
        ];
    }
    public function filter($data) {
        if (is_array($data)) {
            return array_map([$this, 'filter'], $data);
        }
        $result = $data;
        foreach ($this->rules as $rule) {
            $result = preg_replace($rule['pattern'], $rule['replacement'], $result);
        }
        return $result;
    }
}
// 使用示例
$filter = new SensitiveDataFilter();
$filter->addRule('/\b\d{16,19}\b/', '****-****-****-****'); // 信用卡号
$filter->addRule('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '***@***.***'); // 邮箱

最佳实践建议

配置管理

class SensitiveConfig {
    private static $instance;
    private $config;
    private function __construct() {
        $this->config = require __DIR__ . '/sensitive_config.php';
    }
    public static function getInstance() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    public function getMaskRules() {
        return $this->config['mask_rules'] ?? [];
    }
    public function getSensitiveFields() {
        return $this->config['sensitive_fields'] ?? [];
    }
}
// sensitive_config.php
return [
    'mask_rules' => [
        'phone' => '/(1[3-9]\d)\d{4}(\d{4})/',
        'email' => '/(\w{2})\w*@(\w+\.\w+)/',
    ],
    'sensitive_fields' => [
        'password', 'token', 'secret', 'credit_card', 'ssn'
    ]
];

日志记录注意事项

  1. 分级记录:不同级别的日志记录不同详细程度的信息
  2. 定期审计:定期检查日志中是否包含未脱敏的敏感信息
  3. 开发环境:开发环境可以记录更多信息,但生产环境必须严格脱敏
  4. 错误码系统:使用错误码代替具体的错误信息

通过合理实现上述方案,可以有效保护用户隐私和系统安全,同时保证异常信息的可追溯性。

抱歉,评论功能暂时关闭!