# hyperf-guzzle-logger **Repository Path**: khyyyzd/hyperf-guzzle-logger ## Basic Information - **Project Name**: hyperf-guzzle-logger - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-05 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # khyzd/hyperf-guzzle-logger Hyperf 3 的 Guzzle 二次封装。所有通过本包发出的 HTTP 请求都会自动记录到数据库,并支持按月 / 按年自动分表。 - 默认按月分表:`guzzle_log_202611` - 可切换为按年分表:`guzzle_log_2026` - 也可关闭分表:`guzzle_log` - 调用时通过 `note` 参数写备注,`note` 不会发送给上游 - 自动记录 HTTP 状态码、响应体、耗时、请求参数 - 请求参数按原样记录(`json`、`form_params`、`multipart`、`headers`、`query` 等) - 自动脱敏 `Authorization`、`Cookie`、`token`、`password` 等,以及 URL query 中的敏感参数 - HTTP 4xx/5xx 和网络异常都会记录 - 读取日志响应体不会破坏业务方拿到的响应 - 数据库写入失败默认不阻断业务请求 - 支持同步、异步(Guzzle Promise)请求 - 支持自动建表、迁移和预建分表命令 ## 环境要求 | 依赖 | 版本 | | --- | --- | | PHP | >= 8.0 | | Hyperf | 3.0 / 3.1 / 3.2 | | Guzzle | >= 7.5 | | 数据库 | MySQL 5.7 / 8.0(自动建表仅支持 MySQL) | | PHP 扩展 | `ext-json`、`ext-mbstring` | ## 安装 ```bash # 安装最新的 3.1.x 正式版 composer require khyzd/hyperf-guzzle-logger:^3.1 ``` > 如果需要固定到具体版本,可以使用 `3.1.0`。 > 本地开发、私有部署或 Composer 找不到包时,请参考下面的 path/vcs repository 配置。 ### 本地 / 私有包安装 > 必须在**业务 Hyperf 项目根目录**执行 `composer require`,不能在 `hyperf-guzzle-logger` 包目录里执行。 如果不是从 Packagist 安装,在项目 `composer.json` 中添加: ```json { "repositories": [ { "type": "path", "url": "/path/to/hyperf-guzzle-logger", "options": { "symlink": true, "versions": { "khyzd/hyperf-guzzle-logger": "3.1.0" } } } ] } ``` 然后执行: ```bash composer require khyzd/hyperf-guzzle-logger:^3.1 ``` 如果不设置 `versions`,也可以直接安装开发版本: ```bash composer require khyzd/hyperf-guzzle-logger:@dev ``` 发布到私有 Git 仓库后,则可以改用 vcs repository,并正常使用 `^3.1` 版本约束。 Hyperf 会通过 `extra.hyperf.config` 自动注册 `ConfigProvider`,无需手动添加。 发布配置文件和基础表迁移: ```bash php bin/hyperf.php vendor:publish khyzd/hyperf-guzzle-logger ``` 发布后会得到: - `config/autoload/guzzle_logger.php` - `migrations/2026_11_05_000000_create_guzzle_log_table.php` 执行迁移(创建基础表 `guzzle_log`): ```bash php bin/hyperf.php migrate ``` > 默认 `auto_create_table = true`,即使不执行迁移,第一次写入时也会自动创建目标分表。 > 如果需要关闭自动建表,请设置 `auto_create_table = false` 并自行创建分表。 ## 配置 `config/autoload/guzzle_logger.php`: ```php env('GUZZLE_LOGGER_ENABLED', true), // config/autoload/databases.php 中的连接名 'connection' => env('GUZZLE_LOGGER_DB_CONNECTION', 'default'), // 基础表名 'table' => env('GUZZLE_LOGGER_TABLE', 'guzzle_log'), // none | monthly | yearly 'partition' => env('GUZZLE_LOGGER_PARTITION', GuzzleLoggerConfig::PARTITION_MONTHLY), // 自动建表(仅 MySQL) 'auto_create_table' => env('GUZZLE_LOGGER_AUTO_CREATE_TABLE', true), // 分表和 created_at / updated_at 使用的时区,null 表示 PHP 默认时区 'timezone' => env('GUZZLE_LOGGER_TIMEZONE', null), // 传递给 new GuzzleHttp\Client() 的默认配置,可按客户端覆盖 'client' => [ // 'timeout' => 10.0, // 'connect_timeout' => 5.0, ], // 这些 key 的值在 options 和 url query 中会替换为 *** 'sensitive_keys' => [ 'authorization', 'proxy-authorization', 'cookie', 'set-cookie', 'x-api-key', 'api-key', 'apikey', 'token', 'access_token', 'refresh_token', 'auth', 'password', 'passwd', 'secret', 'client_secret', ], // 字节数上限,0 表示不截断;options 是 TEXT,建议不要超过 65535 'max_options_length' => env('GUZZLE_LOGGER_MAX_OPTIONS_LENGTH', 65535), 'max_response_length' => env('GUZZLE_LOGGER_MAX_RESPONSE_LENGTH', 1048576), // time 字段保留的小数位数(单位:秒) 'time_precision' => 6, // 日志写入失败时是否抛出异常;默认 false,仅记录 warning 'throw_on_log_failure' => env('GUZZLE_LOGGER_THROW_ON_LOG_FAILURE', false), // 是否对 url 中命中 sensitive_keys 的 query 参数值脱敏 'redact_url_query' => env('GUZZLE_LOGGER_REDACT_URL_QUERY', true), ]; ``` ### 分表规则 分表依据「请求开始时间」和 `timezone` 配置决定: | `partition` | 表名示例 | | --- | --- | | `none` | `guzzle_log` | | `monthly` | `guzzle_log_202611` | | `yearly` | `guzzle_log_2026` | - 默认是 `monthly`。 - 表名后缀格式为 `Ym`(月)或 `Y`(年)。 - 如果 `databases.php` 配置了表前缀(`prefix`),包会自动加上前缀。 - 切换分表模式后,旧数据仍保留在原表中,新请求写入新规则对应的表。 ### 自动建表与预建分表 默认开启自动建表,每个 worker 首次写入某个分表时会检查并执行: ```sql CREATE TABLE IF NOT EXISTS `guzzle_log_202611` (...); ``` 多 worker 并发执行也是安全的(`IF NOT EXISTS`)。 也可以提前建表: ```bash # 创建当前月 + 下个月 php bin/hyperf.php guzzle-logger:create-table # 以 2026-12 为基准,创建 2026-12、2027-01、2027-02 三个月的表 php bin/hyperf.php guzzle-logger:create-table --date=2026-12-01 --next=2 # 同时创建基础表 guzzle_log php bin/hyperf.php guzzle-logger:create-table --base ``` 如果数据库账号没有 `CREATE TABLE` 权限,请关闭自动建表并由 DBA 建表: ```sql CREATE TABLE guzzle_log_202611 LIKE guzzle_log; ``` ## 数据表字段 | 字段 | 说明 | | --- | --- | | `method` | 请求方法,自动大写,最多 50 字符 | | `url` | 实际请求地址(包含 query string),命中脱敏规则的 query 值会替换为 `***`,最多 255 字符 | | `options` | 原始 Guzzle options 的 JSON,不包含 `note`,敏感值已脱敏 | | `return_code` | HTTP 状态码;网络异常时为 `0` | | `response` | 响应体;请求异常时为「异常信息 + 响应体」 | | `time` | 请求耗时,单位秒,默认保留 6 位小数,例如 `0.123456` | | `note` | 调用时传入的备注,可为空,最多 255 字符 | | `created_at` | 请求开始时间(按配置时区) | | `updated_at` | 请求结束、日志写入时间(按配置时区) | ### 关于 `options` Guzzle 在真正发请求前会把 `headers`、`json`、`form_params`、`multipart` 等选项转换并移除。 本包通过自定义的 `LoggingClient` 在转换前保存原始 options,因此日志里能看到调用方实际传的参数,例如: ```php $this->http->post('https://api.example.com/orders', [ 'json' => ['order_id' => 1001], 'headers' => ['Authorization' => 'Bearer secret'], 'timeout' => 5, 'note' => '创建订单', ]); ``` 写入的 `options` 大致为: ```json { "json": {"order_id": 1001}, "headers": {"Authorization": "***"}, "timeout": 5 } ``` - `note` 会被单独写入 `note` 字段,不会出现在 `options` 中,也不会传给 Guzzle / 上游。 - 记录的是本次调用传入的 options;通过 `ClientFactory` 创建客户端时设置的默认 headers 等客户端级配置不会合并进 `options`。 - `StreamInterface`、resource、闭包等无法 JSON 化的值会被转换成描述信息。 - 超过 `max_options_length` 时按字节截断,并追加 `...[truncated]`。 ### 关于响应体 - 成功响应:记录响应体;读取后会将 stream 指针还原,业务代码仍可正常读取。 - 非 seekable 响应:会复制为内存流,业务代码仍能读取。 - `stream = true` 或指定了 `sink` 的请求:不读取响应体,记录 `[response body not recorded for a streaming/sink request]`。 - HTTP 4xx/5xx:默认 `http_errors = true` 时 Guzzle 抛出异常,包会记录状态码和响应体后继续抛出原异常。 - 网络异常(连接失败、超时等):`return_code = 0`,`response` 记录异常信息和可用响应体。 ## 使用 ### 依赖注入 `GuzzleLoggerInterface` 已绑定到默认实现,直接注入即可: ```php http->post('https://api.example.com/orders', [ 'json' => $payload, 'headers' => [ 'Authorization' => 'Bearer your-token', ], 'timeout' => 10, 'note' => '创建订单', ]); return json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR); } } ``` 或者使用 Hyperf 注解: ```php use Hyperf\Di\Annotation\Inject; use Khyzd\HyperfGuzzleLogger\GuzzleLoggerInterface; #[Inject] protected GuzzleLoggerInterface $http; ``` ### 可用方法 ```php $http->request('GET', $url, $options); $http->get($url, $options); $http->head($url, $options); $http->post($url, $options); $http->put($url, $options); $http->patch($url, $options); $http->delete($url, $options); $http->options($url, $options); $http->send($psr7Request, $options); $http->sendAsync($psr7Request, $options); $http->requestAsync('POST', $url, $options); $http->getAsync($url, $options); $http->postAsync($url, $options); // ... ``` 异步请求会在 Promise 完成时写日志: ```php $response = $http->postAsync('https://api.example.com/orders', [ 'json' => $payload, 'note' => '异步创建订单', ])->wait(); ``` ### 直接使用 Guzzle Client ```php $client = $http->client(); $response = $client->get('https://api.example.com/users', [ 'note' => '拉取用户列表', ]); ``` `client()` 返回的是带日志中间件的 `LoggingClient`,直接通过它发起的请求同样会记录。 ### 自定义 Client 需要独立的 base URI、超时、代理等配置时,可以注入 `ClientFactory`: ```php use Khyzd\HyperfGuzzleLogger\Client\ClientFactory; final class ApiClient { public function __construct(private ClientFactory $clientFactory) { } public function client(): \GuzzleHttp\ClientInterface { return $this->clientFactory->create([ 'base_uri' => 'https://api.example.com', 'timeout' => 5.0, 'headers' => [ 'X-App' => 'demo', ], ]); } } ``` 传入的配置会与 `guzzle_logger.client` 合并,日志中间件会自动附加。 > 注意:直接 `new \GuzzleHttp\Client()` 不会经过本包,也不会记录日志。 > 请使用 `GuzzleLoggerInterface`、`ClientFactory` 或 `$http->client()`。 ## 日志写入失败处理 默认 `throw_on_log_failure = false`: - 数据库断开、表不存在、字段超长等日志异常不会影响 HTTP 响应。 - 包会通过 Hyperf 框架内置的 `StdoutLoggerInterface` 输出一条 `warning`(通常由 Supervisor/Docker 收集到 stdout)。 如果希望日志失败时让业务请求也失败,设置: ```env GUZZLE_LOGGER_THROW_ON_LOG_FAILURE=true ``` ## 性能与异步落库 默认在请求完成的 Promise 回调中同步写数据库,日志写入发生在业务方拿到响应之前。每次写入约增加一次 insert 的网络往返。 如果 QPS 较高,可以实现自己的 `LogRecorderInterface`,把日志投递到队列/Redis,由消费者异步落库: ```php method / url / options / returnCode / response / time / note // $entry->createdAt / updatedAt // 例如:投递到 Hyperf 异步队列 } } ``` 在 `config/autoload/dependencies.php` 中替换绑定: ```php return [ \Khyzd\HyperfGuzzleLogger\Contract\LogRecorderInterface::class => \App\Recorder\QueueLogRecorder::class, ]; ``` ## 事务注意事项 - 日志写入使用 `guzzle_logger.connection` 指定的当前数据库连接。如果业务代码处于事务中,日志 insert 会加入该事务;业务事务回滚时,日志也会一起回滚。 - 自动建表执行 `CREATE TABLE`,MySQL 会隐式提交当前事务。如果业务大量使用事务,建议: 1. 设置 `auto_create_table = false`; 2. 通过定时任务提前执行 `guzzle-logger:create-table` 创建当月/下月分表; 3. 或使用自定义异步 `LogRecorderInterface` 独立落库。 ## 安全说明 - `sensitive_keys` 中的 key 在 `options` 和 `url` query string 中会被替换为 `***`。 - 匹配不区分大小写,并忽略 `-`、`_`、空格,因此 `Authorization`、`X-Api-Key`、`access_token` 等都能命中。 - 支持嵌套参数、header、query、form_params 和 multipart 字段。 - 手动传入的原始 `body` 字符串不会解析其中的敏感内容,只对结构化字段脱敏。 - 默认脱敏 `authorization`、`cookie`、`token`、`password`、`secret` 以及 Guzzle 的 `auth` 选项。 - 如果不需要对 URL 中的 query 参数脱敏,可设置 `redact_url_query = false`。 - 日志表可能包含用户隐私或业务敏感数据,请按实际合规要求配置保留时长和脱敏字段。 ## 在 Hyperf 项目中做冒烟测试 `composer test` 只能验证包内逻辑,要确认真实 Hyperf 项目中的 DI、建表和写入都正常,建议做一次端到端冒烟测试。 ### 1. 用 path repository 临时安装 把下面的 `url` 换成这个包的实际路径,然后在 Hyperf 项目的 `composer.json` 中加入: ```json { "repositories": [ { "type": "path", "url": "/path/to/hyperf-guzzle-logger", "options": { "symlink": true } } ] } ``` 然后执行: ```bash composer require khyzd/hyperf-guzzle-logger:@dev composer dump-autoload ``` 确认包已经安装: ```bash composer show khyzd/hyperf-guzzle-logger ``` ### 2. 发布并修改配置 ```bash php bin/hyperf.php vendor:publish khyzd/hyperf-guzzle-logger ``` 第一次验证建议先用单表,避免查错表: ```php // config/autoload/guzzle_logger.php return [ 'connection' => 'default', 'table' => 'guzzle_log', 'partition' => 'none', 'auto_create_table' => true, 'timezone' => 'Asia/Shanghai', ]; ``` 确保 `config/autoload/databases.php` 中的 `default` 连接可正常使用。 ### 3. 添加临时测试命令 创建 `app/Command/GuzzleLoggerSmokeCommand.php`: ```php http->get('https://example.com', [ 'headers' => [ 'Authorization' => 'Bearer smoke-secret', ], 'query' => [ 'access_token' => 'query-secret', 'page' => 1, ], 'note' => 'hyperf-smoke-test', 'timeout' => 10, ]); $this->line('HTTP status: ' . $response->getStatusCode()); $this->line('Response: ' . substr((string) $response->getBody(), 0, 200)); return self::SUCCESS; } } ``` 把命令加入 `config/autoload/commands.php`: ```php return [ \App\Command\GuzzleLoggerSmokeCommand::class, ]; ``` ### 4. 执行并检查数据库 ```bash php bin/hyperf.php test:guzzle-logger ``` 然后查询: ```sql SHOW TABLES LIKE 'guzzle_log%'; SELECT id, method, url, return_code, note, time, created_at FROM guzzle_log ORDER BY id DESC LIMIT 1; SELECT options, response FROM guzzle_log ORDER BY id DESC LIMIT 1\G ``` 检查以下内容: - 命令正常输出 HTTP 200,且没有 DI 报错。 - 表中新增了一行。 - `method` 为 `GET`,`url` 为 `https://example.com?...`。 - `return_code` 为 `200`,`note` 为 `hyperf-smoke-test`。 - `options` 中包含 `headers`、`query`,不包含 `note`。 - `Authorization` 和 `access_token` 的值显示为 `***`。 - `time` 为数字,`created_at` 为当前时间。 ### 5. 再验证分表 把配置改为: ```php 'partition' => 'monthly', ``` 再次执行命令,然后查询当前月份的表,例如: ```sql SHOW TABLES LIKE 'guzzle_log_%'; SELECT id, url, return_code, note FROM guzzle_log_202611 ORDER BY id DESC LIMIT 1; ``` 表名按请求时间和 `timezone` 生成,实际月份以运行时为准。 ### 6. 没有写入数据库时怎么排查 - 确认 `enabled` 为 `true`,且修改的是 `config/autoload/guzzle_logger.php`。 - 确认 `connection` 指向 `config/autoload/databases.php` 中真实存在的连接。 - 确认查询的是正确的表:`partition=none` 查 `guzzle_log`,`monthly` 查 `guzzle_log_YYYYMM`。 - 如果 `auto_create_table=true` 但数据库账号没有 `CREATE TABLE` 权限,执行: ```bash php bin/hyperf.php guzzle-logger:create-table ``` 或关闭自动建表后手动创建/迁移。 - 查看命令输出和 `runtime/logs`,日志写入失败会输出 `Guzzle request log could not be written...` warning。 - 修改代码后执行 `composer dump-autoload`,并重启 Hyperf 进程。 ### 7. 验证异常记录 把测试地址换成会返回 500 的接口(或项目内一个 500 路由),此时 Guzzle 会抛异常: ```php try { $this->http->get('https://httpbin.org/status/500', ['note' => 'error-test']); } catch (\Throwable $e) { $this->line('Exception: ' . $e->getMessage()); } ``` 数据库中应出现 `return_code = 500` 的记录,`response` 包含异常信息和响应体。 ## 测试 ```bash composer install composer test ``` 当前测试覆盖配置解析、分表规则、DDL、参数脱敏、响应体保护、成功/异常/异步请求、日志失败降级等。 测试环境为 PHP 8.3 + Hyperf 3.2;代码同时兼容 PHP 8.0 / Hyperf 3.0。 ## 限制 - 自动建表和迁移使用内置的 MySQL DDL;如需自定义索引/字段,请设置 `auto_create_table = false` 并自行建表。 - 自动建表仅支持 MySQL;其他数据库请设置 `auto_create_table = false` 并自行建表。 - `time` 字段以「秒」为单位的字符串保存。 - `created_at` / `updated_at` 按配置时区格式化。如果 MySQL 会话时区与 PHP 时区不同,请注意 `timestamp` 字段的转换行为。 - `options` 列是 `TEXT`,上限 65535 字节;默认会截断。 - `response` 默认最多记录 1 MB,可通过 `max_response_length` 调整,`0` 表示不限制。 - `stream` / `sink` 请求不会记录响应体,避免破坏流式响应。 - 日志写入是同步的;高并发场景建议使用自定义异步 Recorder。 - 迁移回滚只会删除基础表;已经创建的分表不会被自动删除。 ## License MIT