日志使用方法

基本记录

使用门面调用

php
use Viswoole\Log\Facade\Log;

// 记录信息日志
Log::info('用户注册成功', ['user_id' => 100, 'email' => 'test@example.com']);

// 记录错误日志
Log::error('支付失败', ['order_id' => 'ORD001', 'reason' => '余额不足']);

// 记录调试日志
Log::debug('API请求', ['url' => '/api/users', 'method' => 'GET']);

// 记录警告日志
Log::warning('内存使用率过高', ['usage' => '85%']);

支持的快捷方法

php
// 框架提供的所有日志级别快捷方法
Log::alert($message, $context);       // 必须立即采取行动
Log::error($message, $context);       // 运行时错误
Log::warning($message, $context);     // 警告异常情况
Log::info($message, $context);        // 一般信息
Log::debug($message, $context);       // 调试详细信息
Log::sql($message, $context);         // SQL 执行记录
Log::task($message, $context);        // 异步任务记录

// emergency、critical、notice 等非内置级别请使用 mixed 方法:
Log::mixed('emergency', $message, $context);
Log::mixed('critical', $message, $context);
Log::mixed('notice', $message, $context);

自定义级别

php
// 记录自定义级别的日志
Log::mixed('custom', '自定义级别消息', ['key' => 'value']);

上下文数据

传递上下文

php
// 传递关联数据
Log::info('订单创建', [
    'order_id' => 'ORD-20240101-001',
    'amount' => 299.00,
    'user_id' => 1001,
    'ip' => request()->getRealIp()
]);

// 日志输出示例:
// {"timestamp":"2024-01-01T12:00:00+08:00","level":"info","message":"订单创建","context":{"order_id":"ORD-20240101-001","amount":299,"user_id":1001,"ip":"192.168.1.100"},"source":"Controller.php:45"}

对象消息支持

php
// 消息参数支持 Stringable 接口的对象
class LogMessage implements Stringable
{
    public function __toString(): string
    {
        return '自定义格式化消息';
    }
}

Log::info(new LogMessage(), ['data' => '...']);

写入模式

record() - 协程缓存模式(推荐)

默认模式下,日志会缓存在当前协程中,协程结束时自动批量写入

php
// 这些日志不会立即写入文件
Log::info('步骤1完成');
Log::debug('中间变量', ['x' => 10]);
Log::info('步骤2完成');

// 协程结束时,以上3条日志一次性批量写入

这是框架在 Swoole 协程环境下的推荐模式,能有效减少 I/O 操作次数。

write() - 直接写入模式

绕过协程缓存,立即将日志写入存储:

php
// 立即写入,不经过协程缓存
Log::write('error', '严重错误需要立即持久化', ['critical' => true]);

// 注意:write() 与 error() 的区别
// - write() 绕过协程缓存,立即落盘,适合需要即时持久化的关键日志
// - error() 在协程环境下会缓存到协程结束,再批量写入
Log::error('严重错误...', [...]);  // 协程环境下会缓存

手动管理缓存

php
// 获取当前协程已缓存的日志(尚未写入)
$records = Log::getRecord();

// 清除当前协程缓存的日志(不写入)
Log::clearRecord();

// 手动触发批量保存(通常无需手动调用)
Log::save($records);

指定通道写入

切换通道

php
// 向指定通道写入日志
Log::channel('file')->info('只写文件通道');

// 判断通道是否存在
if (Log::hasChannel('file')) {
    Log::channel('file')->debug('调试信息');
}

动态添加通道

php
// 运行时添加新通道(需在服务启动前)
Log::addChannel('custom', CustomDriver::class);

// 通过数组配置添加
Log::addChannel('custom', [
    'driver' => \Viswoole\Log\Drives\File::class,
    'options' => [
        'log_dir' => BASE_PATH . '/runtime/custom_logs',
        'json' => false,
    ]
]);

实际应用示例

请求日志中间件

php
class RequestLogMiddleware implements MiddlewareInterface
{
    public function process(Closure $handler): mixed
    {
        $start = microtime(true);

        $response = $handler();

        $duration = round((microtime(true) - $start) * 1000, 2);

        Log::info('HTTP请求', [
            'method' => request()->getMethod(),
            'uri' => request()->getRequestUri(),
            'status' => $response->getStatusCode(),
            'duration' => "{$duration}ms",
            'ip' => request()->getRealIp(),
        ]);

        return $response;
    }
}

异常日志记录

php
try {
    // 业务代码...
} catch (\Throwable $e) {
    Log::error('业务异常', [
        'exception' => get_class($e),
        'message' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'trace' => $e->getTraceAsString(),
    ]);

    throw $e;
}

SQL 日志记录

php
class DatabaseService
{
    public function query(string $sql, array $params = []): array
    {
        $start = microtime(true);

        try {
            $result = $this->pdo->query($sql);
            $duration = round((microtime(true) - $start) * 1000, 2);

            Log::sql('查询执行成功', [
                'sql' => $sql,
                'params' => $params,
                'duration' => "{$duration}ms",
                'rows' => count($result),
            ]);

            return $result;
        } catch (\PDOException $e) {
            Log::error('数据库查询失败', [
                'sql' => $sql,
                'error' => $e->getMessage(),
            ]);

            throw $e;
        }
    }
}