openb8/webman-trace

A request trace plugin for Webman framework

dev-main 2025-06-30 22:45 UTC

This package is not auto-updated.

Last update: 2025-07-01 02:05:11 UTC


README

Webman Plugin PHP Version License

一个功能强大的 Webman 框架请求链路追踪插件,提供完整的调试和监控解决方案。

✨ 核心特性

🎯 自动追踪

  • 唯一 Trace ID - 每个请求自动生成唯一标识
  • 全链路记录 - 请求、响应、SQL、异常一网打尽
  • 零配置启动 - 安装即用,无需手动配置
  • 中间件自动注册 - 全局拦截,无遗漏

🎨 美化输出

  • 🌈 控制台彩色显示 - 类似 thinkorm-log 的美观样式
  • 📊 Web查询界面 - 高级过滤、实时查看、详情展示
  • 🔧 详细/简洁模式 - 可切换的显示级别
  • 📝 JSON格式日志 - 便于 ELK/Loki 等系统接入

🛡️ 安全可控

  • 🔐 Web界面认证 - 可选密码保护
  • ⚙️ 灵活配置 - 开发/生产环境不同策略
  • 🚫 忽略路径 - 过滤不需要追踪的请求
  • 🎛️ 404追踪控制 - 可选的404页面追踪

🚀 开发友好

  • 📋 便捷API - jsont_success(), jsont_error() 等方法
  • 🔍 调试增强 - trace_info(), trace_error() 等日志函数
  • 🎨 美观展示 - 边框、图标、颜色一应俱全
  • 高性能 - 最小化性能影响

📦 快速安装

一键安装

composer require openb8/webman-trace

安装完成即可使用! 插件会自动:

  • ✅ 生成配置文件到 config/plugin/openb8/webman-trace/
  • ✅ 注册全局中间件
  • ✅ 配置Web查询路由
  • ✅ 启动所有追踪功能

启动服务

php start.php restart

立即享受完整的追踪功能!

🎯 立即可用功能

控制台美化输出

启动服务后,控制台会显示类似这样的美观输出:

🚀 新请求 [trace-abc123]
┃ 时间: 2024-01-30 10:00:00
┃ 方法: POST
┃ 路径: /api/user/login
┃ 来源IP: 127.0.0.1
┃ 请求参数:
┃ {
┃   "username": "admin",
┃   "password": "******"
┃ }

┌─────────────────────────────────────────────────────────────┐
│ 时间: 2024-01-30 10:00:00
│ Trace ID: trace-abc123
│ 状态码: ✅ 200
│ 请求方式: POST
│ 接口: /api/user/login
│ 耗时: 25.67ms
│ 响应数据:
│ {
│   "code": 0,
│   "msg": "登录成功",
│   "data": {...}
│ }
└─────────────────────────────────────────────────────────────┘

Web查询界面

访问 http://127.0.0.1:8787/trace 即可使用:

  • 🔍 高级过滤: 按 Trace ID、类型、级别、时间范围搜索
  • 📊 实时查看: 最新的 trace 日志
  • 📋 详情展示: 点击查看完整 trace 链路
  • 🗑️ 日志管理: 一键清理旧日志
  • 🔐 认证保护: 可选的密码保护

⚙️ 配置说明

配置文件:config/plugin/openb8/webman-trace/app.php

开发环境推荐配置

return [
    'enable' => true,
    
    // 控制台美化输出
    'console_output' => [
        'enable' => true,
        'verbose' => true,        // 显示详细信息
        'beautiful' => true,      // 美化显示
        'levels' => ['request', 'response', 'exception', 'slow_query'],
    ],
    
    // Web查询界面
    'web_interface' => [
        'enable' => true,
        'path_prefix' => '/trace',
        'auth_required' => false,  // 开发环境无需认证
    ],
    
    // 请求体和响应体记录
    'log_body' => [
        'request' => true,        // 记录请求参数
        'response' => true,       // 记录响应数据
        'max_length' => 1024,     // 最大长度限制
    ],
    
    // 404追踪
    'handle_404' => [
        'enable' => true,         // 启用404追踪
        'custom_response' => false, // 仅记录,不覆盖原有404页面
    ],
];

生产环境推荐配置

return [
    'enable' => true,
    
    // 关闭控制台输出(减少性能影响)
    'console_output' => [
        'enable' => false,
    ],
    
    // Web界面需要认证
    'web_interface' => [
        'enable' => true,
        'auth_required' => true,
        'access_password' => 'your-secure-password',
    ],
    
    // 减少日志体积
    'log_body' => [
        'request' => false,
        'response' => false,
    ],
    
    // 仅记录关键信息
    'log_levels' => [
        'request' => 'info',
        'response' => 'info',
        'sql' => 'warning',      // 只记录慢查询
        'exception' => 'error',
    ],
];

完整配置选项

return [
    // 基础开关
    'enable' => true,
    'log_sql' => true,
    'log_exception' => true,
    'log_request' => true,
    'debug_only' => false,
    
    // 控制台输出
    'console_output' => [
        'enable' => true,
        'levels' => ['request', 'response', 'exception', 'slow_query'],
        'verbose' => true,        // 详细模式开关
        'beautiful' => true,      // 美化输出开关
    ],
    
    // Web查询界面
    'web_interface' => [
        'enable' => true,
        'path_prefix' => '/trace',
        'auth_required' => false,
        'access_password' => 'trace123',
        'per_page' => 20,
    ],
    
    // 404处理
    'handle_404' => [
        'enable' => true,                    // 启用404追踪
        'custom_response' => false,          // 是否自定义404响应
        'response_body' => '404 Not Found',  // 自定义404页面内容
    ],
    
    // 日志配置
    'log_file' => 'runtime/logs/trace.log',
    'trace_id_key' => 'X-Trace-Id',
    'trace_id_generator' => \OpenB8\WebmanTrace\Generator\UuidGenerator::class,
    
    // 过滤配置
    'ignore_paths' => [
        '/favicon.ico',
        '/robots.txt',
        '/health',
    ],
    
    // 性能配置
    'slow_query_threshold' => 1000,  // 慢查询阈值(毫秒)
    'log_body' => [
        'request' => true,
        'response' => true,
        'max_length' => 1024,
    ],
    
    // 日志级别
    'log_levels' => [
        'request' => 'info',
        'response' => 'info',
        'sql' => 'debug',
        'exception' => 'error',
        'slow_query' => 'warning',
    ],
];

🛠️ 使用方法

获取 Trace ID

// 在任何地方获取当前请求的 Trace ID
$traceId = trace_id();
echo "当前 Trace ID: " . $traceId;

记录业务日志

// 使用便捷的日志函数(自动包含 trace_id)
trace_info('用户登录成功', ['user_id' => 123]);
trace_error('数据库连接失败', ['error' => $exception->getMessage()]);
trace_debug('调试信息', ['data' => $debugData]);
trace_warning('磁盘空间不足', ['usage' => '90%']);

JSON 响应方法

插件提供了四个便捷的 JSON 响应方法,自动包含 Trace ID:

1. jsont() - 基础响应

// 自动为任何数据添加 trace_id
return jsont(['name' => '张三', 'age' => 25]);
// 输出: {"name": "张三", "age": 25, "trace_id": "abc123"}

return jsont($user, 200, ['Custom-Header' => 'value']);

2. jsont_success() - 成功响应

return jsont_success($user, '获取用户信息成功');
// 输出: {
//   "code": 0,
//   "message": "获取用户信息成功", 
//   "data": {...},
//   "trace_id": "abc123"
// }

3. jsont_error() - 错误响应

return jsont_error('用户不存在', 1001, null, 404);
// 输出: {
//   "code": 1001,
//   "message": "用户不存在",
//   "trace_id": "abc123"
// }
// HTTP状态码: 404

// 带错误详情
return jsont_error('验证失败', 1002, ['field' => 'email']);

4. jsont_paginate() - 分页响应

return jsont_paginate($users, 100, 2, 10, '获取用户列表成功');
// 输出: {
//   "code": 0,
//   "message": "获取用户列表成功",
//   "data": {
//     "items": [...],
//     "pagination": {
//       "total": 100,
//       "page": 2,
//       "page_size": 10,
//       "total_pages": 10,
//       "has_next": true,
//       "has_prev": true
//     }
//   },
//   "trace_id": "abc123"
// }

控制器示例

<?php

class UserController
{
    public function login($request)
    {
        // 记录开始处理
        trace_info('开始处理用户登录', [
            'ip' => $request->getRealIp(),
            'user_agent' => $request->header('user-agent')
        ]);
        
        try {
            // 业务逻辑
            $user = $this->authenticate($request);
            
            // 记录成功
            trace_info('用户登录成功', ['user_id' => $user->id]);
            
            // 返回成功响应(自动包含 trace_id)
            return jsont_success([
                'user' => $user,
                'token' => $this->generateToken($user)
            ], '登录成功');
            
        } catch (\Exception $e) {
            // 记录异常(异常会被自动追踪)
            trace_error('用户登录失败', [
                'error' => $e->getMessage(),
                'code' => $e->getCode()
            ]);
            
            // 返回错误响应
            return jsont_error('登录失败: ' . $e->getMessage(), 1001, null, 401);
        }
    }
    
    public function getUserList($request)
    {
        $page = max(1, (int)$request->get('page', 1));
        $pageSize = min(100, max(1, (int)$request->get('page_size', 15)));
        
        try {
            $users = $this->userService->getUsers($page, $pageSize);
            $total = $this->userService->getUserCount();
            
            // 分页响应
            return jsont_paginate($users, $total, $page, $pageSize);
            
        } catch (\Exception $e) {
            return jsont_error('获取用户列表失败', 2001);
        }
    }
}

📊 日志格式

插件生成标准的 JSON 格式日志,便于各种日志系统解析:

请求日志

{
  "trace_id": "trace-abc123456789",
  "level": "INFO",
  "type": "request",
  "method": "POST",
  "path": "/api/user/login",
  "query": "remember=1",
  "user_agent": "Mozilla/5.0...",
  "ip": "127.0.0.1",
  "body": "{\"username\":\"admin\",\"password\":\"******\"}",
  "timestamp": "2024-01-30T10:00:00.123+08:00"
}

响应日志

{
  "trace_id": "trace-abc123456789",
  "level": "INFO", 
  "type": "response",
  "method": "POST",
  "path": "/api/user/login",
  "status": 200,
  "duration": "25.67ms",
  "body": "{\"code\":0,\"message\":\"登录成功\",\"data\":{...}}",
  "timestamp": "2024-01-30T10:00:00.148+08:00"
}

SQL日志

{
  "trace_id": "trace-abc123456789",
  "level": "DEBUG",
  "type": "sql",
  "sql": "SELECT * FROM users WHERE email = ? AND status = ?",
  "bindings": ["admin@example.com", 1],
  "time": "12.34ms",
  "timestamp": "2024-01-30T10:00:00.135+08:00"
}

异常日志

{
  "trace_id": "trace-abc123456789",
  "level": "ERROR",
  "type": "exception", 
  "class": "InvalidArgumentException",
  "message": "用户名不能为空",
  "file": "/var/www/app/controller/UserController.php",
  "line": 45,
  "trace": "Exception trace...",
  "method": "POST",
  "path": "/api/user/login",
  "timestamp": "2024-01-30T10:00:00.140+08:00"
}

🎨 控制台输出效果

详细模式 (verbose = true)

显示完整的请求参数和响应数据:

🚀 新请求 [trace-abc123]
┃ 时间: 2024-01-30 10:00:00
┃ 方法: POST
┃ 路径: /api/user/login
┃ 来源IP: 127.0.0.1
┃ 查询参数: remember=1
┃ 请求参数:
┃ {
┃   "username": "admin", 
┃   "password": "123456"
┃ }

┌─────────────────────────────────────────────────────────────┐
│ 时间: 2024-01-30 10:00:00
│ Trace ID: trace-abc123
│ 状态码: ✅ 200
│ 请求方式: POST 
│ 接口: /api/user/login
│ 耗时: 25.67ms
│ 响应数据:
│ {
│   "code": 0,
│   "message": "登录成功",
│   "data": {
│     "user": {...},
│     "token": "..."
│   }
│ }
└─────────────────────────────────────────────────────────────┘

💥 异常发生 [trace-abc123]
┃ 时间: 2024-01-30 10:00:05
┃ 类型: InvalidArgumentException
┃ 消息: 用户名不能为空
┃ 位置: UserController.php:45

🔍 SQL查询 [trace-abc123]  
┃ 时间: 2024-01-30 10:00:01
┃ 耗时: 12.34ms
┃ SQL: SELECT * FROM users WHERE email = ? AND status = ?
┃ 参数: ["admin@example.com", 1]

简洁模式 (verbose = false)

只显示关键信息:

🚀 新请求 [trace-abc123]
┃ 时间: 2024-01-30 10:00:00
┃ 方法: POST
┃ 路径: /api/user/login
┃ 来源IP: 127.0.0.1

┌─────────────────────────────────────────────────────────────┐
│ 时间: 2024-01-30 10:00:00
│ Trace ID: trace-abc123
│ 状态码: ✅ 200
│ 请求方式: POST
│ 接口: /api/user/login  
│ 耗时: 25.67ms
└─────────────────────────────────────────────────────────────┘

🔍 SQL查询 [trace-abc123]
┃ 时间: 2024-01-30 10:00:01
┃ 耗时: 12.34ms
┃ SQL: SELECT * FROM users WHERE email = ? AND...

颜色说明

  • 🟢 绿色: 请求日志(🚀 图标)
  • 🔵 蓝色: 响应日志(边框样式)
  • 🟣 紫色: SQL 日志(🔍 图标)
  • 🔴 红色: 异常日志(💥 图标)
  • 🟡 黄色: 慢查询(🐌 图标)

🌐 Web查询界面

启用Web界面

'web_interface' => [
    'enable' => true,
    'path_prefix' => '/trace',
    'auth_required' => true,        // 启用认证保护
    'access_password' => 'trace123',
    'per_page' => 20,
],

访问地址

http://127.0.0.1:8787/trace

界面功能

🔐 认证保护

  • 支持密码保护,防止未授权访问
  • 开发环境可关闭认证(auth_required: false
  • 生产环境建议启用认证

🔍 高级过滤

  • Trace ID: 精确或模糊搜索特定请求
  • 类型: request、response、sql、exception、slow_query
  • 级别: INFO、ERROR、WARNING、DEBUG
  • 时间范围: 自定义开始和结束时间
  • 组合过滤: 多条件组合查询

📊 数据展示

  • 列表视图: 分页显示,支持排序
  • 详情视图: 点击展开完整JSON数据
  • 实时刷新: 查看最新日志
  • 响应速度: 快速加载,流畅体验

🗑️ 日志管理

  • 一键清理: 删除指定天数前的日志
  • 存储统计: 显示日志文件大小和条数
  • 批量操作: 支持批量删除

🚀 高级功能

自定义 Trace ID 生成器

use OpenB8\WebmanTrace\Generator\GeneratorInterface;

class CustomTraceGenerator implements GeneratorInterface
{
    public function generate(): string
    {
        // 自定义生成逻辑
        return 'custom_' . uniqid() . '_' . time();
    }
}

// 在配置中指定
'trace_id_generator' => \App\Trace\CustomTraceGenerator::class,

外部 Trace ID 集成

插件支持从请求头中读取外部系统的 Trace ID:

# 传入外部 Trace ID
curl -H "X-Trace-Id: external-system-trace-123" \
     http://127.0.0.1:8787/api/users

忽略特定路径

'ignore_paths' => [
    '/favicon.ico',
    '/robots.txt', 
    '/health',
    '/metrics',
    '*.css',
    '*.js',
    '/static/*',
],

自定义日志处理

use OpenB8\WebmanTrace\TraceService;

$config = config('plugin.openb8.webman-trace.app');
$traceService = new TraceService($config);

// 手动记录 SQL
$traceService->logSql(
    'SELECT * FROM users WHERE id = ?', 
    [123], 
    25.6  // 执行时间(毫秒)
);

// 手动记录异常
try {
    // 业务代码
} catch (\Exception $e) {
    $traceService->logException($e, $request);
}

性能优化建议

开发环境

'console_output' => [
    'enable' => true,
    'verbose' => true,      // 显示详细信息,便于调试
    'beautiful' => true,    // 美化输出
],
'log_body' => [
    'request' => true,      // 记录请求参数
    'response' => true,     // 记录响应数据
],

生产环境

'console_output' => [
    'enable' => false,      // 关闭控制台输出,减少性能影响
],
'log_body' => [
    'request' => false,     // 关闭请求体记录,减少日志体积
    'response' => false,    // 关闭响应体记录
],
'slow_query_threshold' => 100,  // 降低慢查询阈值

📋 示例代码

API 控制器完整示例

<?php

namespace app\controller;

use support\Request;
use support\Response;

class ApiController
{
    /**
     * 用户注册
     */
    public function register(Request $request): Response
    {
        trace_info('开始用户注册', [
            'ip' => $request->getRealIp(),
            'user_agent' => $request->header('user-agent')
        ]);

        try {
            // 验证参数
            $username = $request->post('username');
            $email = $request->post('email');
            $password = $request->post('password');

            if (empty($username) || empty($email) || empty($password)) {
                return jsont_error('参数不完整', 1001, [
                    'required_fields' => ['username', 'email', 'password']
                ], 400);
            }

            // 检查用户是否存在
            if ($this->userExists($email)) {
                trace_warning('用户注册失败:邮箱已存在', ['email' => $email]);
                return jsont_error('邮箱已被注册', 1002, null, 409);
            }

            // 创建用户
            $user = $this->createUser($username, $email, $password);
            
            trace_info('用户注册成功', ['user_id' => $user->id]);

            return jsont_success([
                'user' => [
                    'id' => $user->id,
                    'username' => $user->username,
                    'email' => $user->email,
                    'created_at' => $user->created_at
                ]
            ], '注册成功');

        } catch (\Exception $e) {
            trace_error('用户注册异常', [
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine()
            ]);

            return jsont_error('注册失败,请稍后重试', 5000, null, 500);
        }
    }

    /**
     * 获取用户列表(分页)
     */
    public function userList(Request $request): Response
    {
        $page = max(1, (int)$request->get('page', 1));
        $pageSize = min(100, max(1, (int)$request->get('page_size', 15)));
        $keyword = $request->get('keyword', '');

        trace_info('获取用户列表', [
            'page' => $page,
            'page_size' => $pageSize,
            'keyword' => $keyword
        ]);

        try {
            $users = $this->searchUsers($keyword, $page, $pageSize);
            $total = $this->getUserCount($keyword);

            return jsont_paginate($users, $total, $page, $pageSize, '获取成功');

        } catch (\Exception $e) {
            return jsont_error('获取用户列表失败', 3001);
        }
    }

    /**
     * 批量操作示例
     */
    public function batchUpdate(Request $request): Response
    {
        $userIds = $request->post('user_ids', []);
        $status = $request->post('status');

        if (empty($userIds) || !is_array($userIds)) {
            return jsont_error('用户ID列表不能为空', 4001);
        }

        trace_info('批量更新用户状态', [
            'user_ids' => $userIds,
            'status' => $status,
            'count' => count($userIds)
        ]);

        try {
            $updated = 0;
            $failed = [];

            foreach ($userIds as $userId) {
                try {
                    $this->updateUserStatus($userId, $status);
                    $updated++;
                } catch (\Exception $e) {
                    $failed[] = $userId;
                    trace_warning('更新用户状态失败', [
                        'user_id' => $userId,
                        'error' => $e->getMessage()
                    ]);
                }
            }

            $result = [
                'updated' => $updated,
                'failed' => $failed,
                'total' => count($userIds)
            ];

            if ($updated > 0) {
                trace_info('批量更新完成', $result);
                return jsont_success($result, "成功更新 {$updated} 个用户");
            } else {
                return jsont_error('批量更新失败', 4002, $result);
            }

        } catch (\Exception $e) {
            return jsont_error('批量更新异常', 5001);
        }
    }

    // 私有方法示例...
    private function userExists(string $email): bool
    {
        // 模拟数据库查询
        // return User::where('email', $email)->exists();
        return false;
    }

    private function createUser(string $username, string $email, string $password): object
    {
        // 模拟用户创建
        return (object)[
            'id' => rand(1000, 9999),
            'username' => $username,
            'email' => $email,
            'created_at' => date('Y-m-d H:i:s')
        ];
    }
}

🔧 故障排除

1. 安装后没有效果

检查项:

  • 确认配置文件是否生成:config/plugin/openb8/webman-trace/app.php
  • 重启 webman 服务:php start.php restart
  • 检查配置中的 enable 是否为 true

2. 控制台没有彩色输出

解决方法:

// 检查配置文件
'console_output' => [
    'enable' => true,        // 确保启用
    'beautiful' => true,     // 确保开启美化
    'levels' => ['request', 'response', 'exception', 'slow_query'],
],

3. Web界面无法访问

检查项:

  • 访问地址是否正确:http://127.0.0.1:8787/trace
  • Web界面是否启用:'web_interface' => ['enable' => true]
  • 如果开启认证,确认密码:默认 trace123

4. 404页面没有追踪

解决方法:

'handle_404' => [
    'enable' => true,          // 启用404追踪
    'custom_response' => false, // 只记录,不覆盖原有404
],

5. SQL日志没有记录

原因: 需要数据库ORM支持 解决方法:

# 安装支持的ORM
composer require topthink/think-orm
# 或
composer require illuminate/database

6. 日志文件过大

解决方法:

  • 使用Web界面的清理功能
  • 或手动删除:rm runtime/logs/trace.log
  • 配置日志轮转(推荐)

7. 性能影响

优化建议:

// 生产环境配置
'console_output' => ['enable' => false],  // 关闭控制台输出
'log_body' => ['request' => false, 'response' => false],  // 关闭body记录
'debug_only' => true,  // 仅在调试模式记录

🔗 相关链接

🤝 贡献指南

欢迎提交 PR 和 Issue!

  1. Fork 项目
  2. 创建功能分支:git checkout -b feature/new-feature
  3. 提交更改:git commit -m 'Add new feature'
  4. 推送分支:git push origin feature/new-feature
  5. 提交 Pull Request

📄 开源协议

MIT License - 详见 LICENSE 文件

👥 关于作者

OpenB8 - 每个字节,都是起点

OpenB8 是一个专注于高质量开源项目的组织,致力于为开发者提供优秀的工具和解决方案。

  • 🌐 官网: www.openb8.com
  • 📧 邮箱: allen@openb8.com
  • 💬 微信: 扫描下方二维码

微信二维码

🎉 享受愉快的开发体验!
如果这个插件对你有帮助,请给我们一个 ⭐ Star!