Yii2 高性能 REST API 日志系统最佳实践指南

yii2 Yii2 · best · 于 1天前 发布 · 26 次阅读

Yii2 高性能 REST API 日志系统最佳实践指南

1. 概述

在高性能 REST API 架构中,日志系统是监控、调试和运维的核心组件。Yii2 提供了强大且灵活的日志组件,但很多项目往往因配置不当导致日志混乱或性能下降。本文档定义了使用 PHP-FPM + Yii2 框架开发高性能 REST API 时的日志处理标准和最佳实践。

在微服务或前后端分离架构中,全链路追踪(Traceability)至关重要。核心思路是生成一个唯一的 Request ID,并让它贯穿整个请求生命周期(Request -> Logs -> Response -> Client)。

1.1 核心目标

  • 高性能:最小化日志记录对请求响应时间的影响
  • 结构化:REST API 使用 JSON 格式便于后续日志分析
  • 可观测性:提供完整的请求追踪和错误监控能力
  • 可运维:支持日志轮转、归档和实时监控

1.2 核心原则

  • 永远不要使用 file_put_contentsfopen 手动写日志。
  • 统一入口:所有日志必须通过 Yii::info(), Yii::warning(), Yii::error() 记录。
  • 按需分类:利用 category 参数对日志进行精准分类,便于后续过滤和分发。

1.3 实现方法

  1. 唯一标识:每个请求自动生成一个 UUID。
  2. 全链路携带:Request ID 必须出现在:
    • 应用日志中(每一行日志都带上)
    • API 响应头中(X-Request-Id
    • 异常响应体中
  3. 零侵入:开发人员在写 Yii::info() 时不需要手动传 ID。

2. 日志级别定义

2.1 级别规范

/**
 * 日志级别常量定义
 */
class LogLevel
{
    // 系统级日志
    const EMERGENCY = 'emergency'; // 系统不可用,需要立即处理
    const ALERT     = 'alert';     // 需要立即处理的错误
    const CRITICAL  = 'critical';  // 严重错误
    
    // 应用级日志
    const ERROR     = 'error';     // 运行时错误
    const WARNING   = 'warning';   // 警告信息
    const NOTICE    = 'notice';    // 正常但重要的信息
    
    // 调试级日志
    const INFO      = 'info';      // 一般信息
    const DEBUG     = 'debug';     // 调试信息(仅开发环境)
}

2.2 级别使用指南

级别触发场景示例
EMERGENCY数据库连接完全失败、服务完全不可用Redis 集群全部宕机
ALERT第三方服务超时、配置错误支付网关无响应
CRITICAL关键业务异常、数据损坏订单创建失败
ERROR普通业务错误、可恢复异常参数验证失败
WARNING性能警告、潜在问题响应时间超过阈值
NOTICE重要业务事件用户登录成功
INFO一般业务事件API 请求统计
DEBUG详细调试信息SQL 执行语句

3. 日志格式设计

3.1 RestAPI JSON 结构化日志格式

{
  "timestamp": "2024-01-15T10:30:00.000Z",
  "level": "INFO",
  "message": "API request completed",
  "service": {
    "name": "user-api",
    "version": "2.1.0",
    "environment": "production"
  },
  "request": {
    "id": "req_abc123",
    "method": "GET",
    "path": "/api/v1/users/123",
    "ip": "192.168.1.100",
    "user_agent": "Mozilla/5.0",
    "user_id": 456
  },
  "response": {
    "status_code": 200,
    "time_ms": 45.23
  },
  "context": {
    "controller": "UserController",
    "action": "view",
    "additional_data": {}
  },
  "trace": {
    "parent_span_id": "span_xyz789",
    "span_id": "span_abc123"
  }
}

3.2 Yii2 日志目标配置

规范化配置 (config/web.php)

不要把所有日志都塞进 app.log。应根据业务模块(如支付、用户中心、API对接)配置独立的 Log Target。

Yii2 默认的日志行为是按文件大小切分(轮转)。要实现“按天切分”并“长期保留”,需要对 logFile 进行动态配置。

'components' => [
    'log' => [
        'traceLevel' => YII_DEBUG ? 3 : 0,
        'flushInterval' => 1000, // 默认 1000,内存中积攒 1000 条内存才刷盘(重要性能参数)
        'targets' => [
            // 1. 全局错误日志 (兜底)
            [
                'class' => 'yii\log\FileTarget',
                'levels' => ['error', 'warning'],
                // 核心:文件名包含日期
                'logFile' => '@runtime/logs/app-error' . date('Y-m-d') . '.log', 
                'maxFileSize' => 10240, // 10MB 切割
                'maxLogFiles' => 1000, // 保留最近 20 个文件
                'bgParams' => true, // 只有在通过 yiisoft/yii2-queue 等异步上下文中才需要考虑禁用
            ],
            // 2. 核心业务日志 - 支付模块
            [
                'class' => 'yii\log\FileTarget',
                'categories' => ['payment'], // 关键:只捕获 payment 分类的日志
                'levels' => ['info', 'error', 'warning'],
                'logFile' => '@runtime/logs/payment' . date('Y-m-d') . '.log',
                'logVars' => [], // 生产环境建议清空,避免记录 GET/POST/COOKIE 等敏感冗余信息
                'maxFileSize' => 10240, // 10MB 切割
                'maxLogFiles' => 100, // 保留最近 20 个文件
                // 核心:自动添加 ID 前缀
                'prefix' => function ($message) {
                    $reqId = Yii::$app->params['request_id'] ?? '-';
                    $ip = Yii::$app->request->getUserIP() ?? '-';
                    $userId = Yii::$app->has('user', true) && Yii::$app->user->id ? Yii::$app->user->id : '-';
                    // 输出格式: [RequestID][IP][UserID]
                    return "[$reqId][$ip][$userId]";
                },
            ],
            // 3. 第三方 API 请求日志
            [
                'class' => 'yii\log\FileTarget',
                'categories' => ['external_api'],
                'logFile' => '@runtime/logs/api_trace' . date('Y-m-d') . '.log',
                'logVars' => [], 
            ],
        ],
    ],
],

效果: 开发人员调用 Yii::info('Payment started'),日志文件中实际显示:

2023-10-27 10:00:01 [info] [application] [5f9a1b2c3d4e][192.168.1.1][10086] Payment started

长期保留的“副作用”与清理

由于上述配置会导致日志文件无限累积,如果磁盘空间有限,建议添加一个 Crontab 任务来清理 N 天前的日志。

Crontab 示例 (保留最近 180 天), 每天凌晨 3 点清理 180 天前的日志:

0 3 * * * find /path/to/project/runtime/logs -name "app-*.log*" -mtime +180 -delete

⚠️ 注意:对于 常驻内存 的应用(如 yii2-queue 的监听进程),直接在配置写 date('Y-m-d') 是无效的。因为配置只在进程启动时加载一次,日志文件名会永远停留在“启动那天”。


4. 代码中使用规范

4.1 正确使用 Category

这是日志筛选的灵魂。格式建议为:模块名.子模块

// ❌ 错误示范:没有 category,甚至不知道这日志是哪来的
Yii::info('Payment success: ' . $orderId); 

// ✅ 正确示范:清晰的分类
Yii::info("Order {$orderId} paid successfully via WeChat.", 'payment');

// ✅ 外部 API 调用记录
Yii::info("Requesting Aleph2 API: {$url}", 'external_api');

4.2 结构化数据记录

不要拼接大段字符串,如果条件允许,尽量记录数组或 JSON,便于日志分析系统(ELK)解析。

// ✅ 推荐:记录数组
Yii::warning([
    'msg' => 'Invalid Login Attempt',
    'username' => $username,
    'ip' => $ip,
    'reason' => 'Password mismatch'
], 'security.auth');

4.3 异常捕获记录

永远不要只记录 Exception 的 Message,要记录完整的 Exception 对象,Yii 会自动格式化它(包含堆栈信息)。

try {
    // 业务逻辑
} catch (\Exception $e) {
    // ✅ Yii 会自动把堆栈信息格式化好记录下来
    Yii::error($e, 'order.checkout'); 
}

5. 性能优化

5.1 调整 flushInterval 和 exportInterval

这是 Yii2 日志高性能的秘密。

  • flushInterval (Logger层): 内存中积累多少条消息后,触发 flush 到 Target。默认 1000。
  • exportInterval (Target层): Target 积累多少条消息后,真正执行 export (如写盘)。默认 1000。

建议:

  • 对于高并发接口,保持默认 1000 即可。
  • 如果是长运行脚本 (CLI/Daemon),务必根据业务量调整。如果脚本运行很久但日志很少,可能发生脚本跑完了才看到日志,或者崩溃时丢失最后 1000 条日志。此时可适当调小(如 100)。

5.2 关闭 logVars

默认情况下,FileTarget 会记录 $_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, $_SERVER

  • 开发环境: 开启,便于调试。
  • 生产环境: 必须设置 logVars => []。这些信息极其庞大,既浪费 I/O,又可能泄露用户隐私(如 Cookie、Password)。

5. 日志轮转 (Log Rotation)

为了防止单个日志文件无限膨胀占满磁盘:

'maxFileSize' => 10240, // KB,即 10MB。生产环境建议 10MB - 100MB
'maxLogFiles' => 50,    //保留历史日志文件数

app.log 达到 10MB 时,会自动更名为 app.log.1,旧的 app.log.1 变为 app.log.2,以此类推。

本文由 best 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。

共收到 0 条回复
没有找到数据。
添加回复 (需要登录)
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册