后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载hyperf/logger是 Hyperf 框架基于 PSR-3 规范psr/logger实现的日志组件默认以monolog/monolog作为底层驱动为常驻内存的协程服务提供了一套完整、可扩展的日志记录方案。本文将以官方文档为主线结合仓库源码深入讲解如何安装与配置日志通道Channel、如何通过LoggerFactory获取与使用Logger、如何理解 Monolog 的Handler/Formatter/Processor核心概念以及如何落地按日期轮转、多 Handler 分流、统一请求级日志、环境差异化格式等实战方案帮助你写出协程安全、生产可用的日志代码。组件定位与设计概述hyperf/logger组件基于psr/loggerPSR-3接口规范实现默认使用monolog/monolog作为驱动。在hyperf-skeleton骨架项目中组件会默认提供一套日志配置默认使用Monolog\Handler\StreamHandler将日志写入文件。这里有一个与协程密切相关的前提由于 Swoole 已经对fopen、fwrite等文件操作函数进行了协程化处理只要不将StreamHandler的useLocking参数设置为true日志写入就是协程安全的。也就是说在协程环境下多个协程并发写同一日志文件时不会因为文件锁flock阻塞或产生交叉错乱。从源码结构看该组件的核心代码位于 src/logger/src主要包含LoggerFactory.php日志工厂负责根据配置解析并创建Logger实例Logger.phpHyperf 对 MonologLogger的扩展实现同时实现了StdoutLoggerInterfaceHandler/StreamHandler.php适配协程环境的流式处理器Aspect/UdpSocketAspect.php对 Monolog UDP Socket 的协程化 AOP 切面publish/logger.php可发布到项目中的默认配置模板。组件依赖的第三方库可在 src/logger/composer.json 中查看要求php 8.2、monolog/monolog ^3.1、psr/log ^2.0 || ^3.0。安装在 Hyperf 项目中通过 Composer 安装composer require hyperf/logger安装完成后组件通过ConfigProvider见 src/logger/src/ConfigProvider.php自动完成两件事将Psr\Log\LoggerInterface绑定到容器工厂LoggerInterface::class fn ($container) $container-get(LoggerFactory::class)-make()这意味着你可以在任意位置直接依赖注入Psr\Log\LoggerInterface获取一个默认 Logger注册可发布配置将publish/logger.php发布为config/autoload/logger.php同时声明Hyperf\Logger\LoggerFactory的容器依赖。配置默认情况下日志配置文件为项目根目录下的config/autoload/logger.php官方文档给出的最简示例如下?php return [ default [ handler [ class \Monolog\Handler\StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf.log, level \Monolog\Level::Debug, ], ], formatter [ class \Monolog\Formatter\LineFormatter::class, constructor [ format null, dateFormat null, allowInlineLineBreaks true, ] ], ], ];这是一个通道Channel名 通道配置的结构每个通道由handler处理器与formatter格式化器两部分组成。constructor中的参数会原样传递给对应类通过 Hyperf 容器的make()创建支持依赖注入。仓库内置的默认配置模板实际安装组件时发布出来的配置比上面的示例更完整。仓库中 src/logger/publish/logger.php 展示了组件官方推荐的多通道配置模板其结构与上面示例略有不同这是 3.x 版本的新式配置风格引入了default与channels两级结构?php use Monolog\Formatter\LineFormatter; use Monolog\Formatter\SyslogFormatter; use Monolog\Handler\NullHandler; use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\StreamHandler; use Monolog\Handler\SyslogHandler; use Monolog\Level; use Monolog\Processor\PsrLogMessageProcessor; use function Hyperf\Support\env; return [ // 默认日志通道 default env(LOG_CHANNEL, stack), // 日志通道 channels [ stack [ handlers explode(,, (string) env(LOG_STACK, single)), ], single [ handler [ class StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf.log, level Level::Debug, ], ], formatter [ class LineFormatter::class, constructor [], ], processors [], ], daily [ handler [ class RotatingFileHandler::class, constructor [ filename BASE_PATH . /runtime/logs/hyperf.log, level Level::Debug, ], ], formatter [ class LineFormatter::class, constructor [], ], processors [], ], stderr [ handler [ class StreamHandler::class, constructor [ stream php://stderr, level Level::Debug, ], ], formatter [ class LineFormatter::class, constructor [], ], processors [ PsrLogMessageProcessor::class, ], ], syslog [ handler [ class SyslogHandler::class, constructor [ level Level::Debug, facility env(LOG_SYSLOG_FACILITY, LOG_USER), ], ], formatter [ class SyslogFormatter::class, constructor [], ], processors [], ], null [ handler [class NullHandler::class], ], ], ];这个模板体现了几点值得掌握的配置能力default指定默认通道可通过环境变量LOG_CHANNEL切换如LOG_CHANNELdailystack通道通过handlers引用其他通道名用逗号分隔多个形成栈式组合默认引用singlesingle单文件、daily按日轮转、stderr标准错误输出、syslog系统日志、null丢弃日志覆盖了最常见的落地场景每个通道都可以配置独立的processors例如stderr通道挂载了PsrLogMessageProcessor用于将 PSR-3 风格的占位符如{user}替换为上下文中的实际值。新旧两种配置风格的兼容LoggerFactory的构造函数见 src/logger/src/LoggerFactory.php对配置做了兼容处理如果配置中不存在logger.channels键或者logger.default本身是一个数组即旧式配置把所有通道直接挂在logger下会自动将其转换为$this-config-set(logger, [ default default, channels $this-config-get(logger), ]);也就是说文档示例中的default [...]这种旧式写法default既是默认通道名又是通道配置数组也能正常工作LoggerFactory会把它重写为channels.default。可调用Callable配置从源码的make()方法src/logger/src/LoggerFactory.php可以看到通道配置除了可以是数组还可以是可调用对象闭包$config $this-config-get($key, []); if (is_callable($config)) { $config $config($name); }闭包会接收到日志的$name参数据此可以动态生成与 name 绑定的配置。这一特性在 src/logger/tests/LoggerFactoryTest.php 的testInvokeLoggerByCallableConfigFromFactory用例中有直接验证测试中通过闭包配置把日志流指向BASE_PATH . /runtime/logs/ . $name . .log使每个 Logger name 对应独立日志文件。使用在业务类中通过依赖注入LoggerFactory获取指定 name 与通道的Logger实例?php declare(strict_types1); namespace App\Service; use Psr\Log\LoggerInterface; use Hyperf\Logger\LoggerFactory; class DemoService { protected LoggerInterface $logger; public function __construct(LoggerFactory $loggerFactory) { // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key $this-logger $loggerFactory-get(log, default); } public function method() { // Do something. $this-logger-info(Your log message.); } }参数说明第一个参数$name日志名即 Monolog 中的 Channel 名会出现在日志记录里用于区分来源第二个参数$channel对应config/autoload/logger.php中的通道 key即logger.channels.channel省略时使用default指定的默认通道。从LoggerFactory::get()的实现src/logger/src/LoggerFactory.php可以看到同一namechannel组合的Logger会被缓存在$loggers[$channel][$name]中重复获取返回同一个实例避免了每次调用都重建 Handler 与文件流public function get(string $name hyperf, ?string $channel null): LoggerInterface { $channel $channel ?? $this-config-get(logger.default, default); if (isset($this-loggers[$channel][$name]) $this-loggers[$channel][$name] instanceof Logger) { return $this-loggers[$channel][$name]; } return $this-loggers[$channel][$name] $this-make($name, $channel); }Hyperf 的Logger类src/logger/src/Logger.php继承自Monolog\Logger同时实现了Hyperf\Contract\StdoutLoggerInterface并在构造时调用了useLoggingLoopDetection(false)关闭 Monolog 的日志循环检测以规避协程并发场景下的误判。你拿到的实例天然兼容 PSR-3 的emergency / alert / critical / error / warning / notice / info / debug全套日志级别方法。注意默认容器里Psr\Log\LoggerInterface的绑定来自 ConfigProvider等价于LoggerFactory::make()即使用默认通道与默认 name可直接构造注入使用。关于 Monolog 的基础知识要熟练使用 Hyperf 日志组件先要理解 Monolog 的核心概念。官方文档用一段代码完整演示了Logger、Handler、Formatter、Processor的协作关系use Monolog\Formatter\LineFormatter; use Monolog\Handler\FirePHPHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; // 创建一个 Channel参数 log 即为 Channel 的名字 $log new Logger(log); // 创建两个 Handler对应变量 $stream 和 $fire $stream new StreamHandler(test.log, Logger::WARNING); $fire new FirePHPHandler(); // 定义时间格式为 Y-m-d H:i:s $dateFormat Y n j, g:i a; // 定义日志格式为 [%datetime%] %channel%.%level_name%: %message% %context% %extra%\n $output %datetime%||%channel||%level_name%||%message%||%context%||%extra%\n; // 根据 时间格式 和 日志格式创建一个 Formatter $formatter new LineFormatter($output, $dateFormat); // 将 Formatter 设置到 Handler 里面 $stream-setFormatter($formatter); // 将 Handler 推入到 Channel 的 Handler 队列内 $log-pushHandler($stream); $log-pushHandler($fire); // clone new log channel $log2 $log-withName(log2); // add records to the log $log-warning(Foo); // add extra data to record // 1. log context $log-error(a new user, [username daydaygo]); // 2. processor $log-pushProcessor(function ($record) { $record[extra][dummy] hello; return $record; }); $log-pushProcessor(new \Monolog\Processor\MemoryPeakUsageProcessor()); $log-alert(czl);结合这段代码可以梳理出 Monolog 的核心工作流Logger与 Channel实例化一个Logger并命名名字即channel用于标识日志来源如hyperf、appHandler处理日志一个Logger可以绑定多个Handler打日志时由Logger依次交给所有Handler处理推入 Handler 队列级别过滤Handler可以指定自己处理哪些日志级别例如new StreamHandler(test.log, Logger::WARNING)表示只处理级别 WARNING的日志更低的INFO/DEBUG会被该 Handler 忽略Formatter格式化日志文本由Formatter负责排版先创建 Formatter 再通过setFormatter()绑定到对应的Handler上日志记录的结构一条日志通常包含datetime、channel、level_name、message、context、extra等部分可通过格式化字符串定制如%datetime%||%channel||%level_name%||%message%||%context%||%extra%\ncontext与extra的区别context由调用者打日志时额外传入更灵活随条日志变化如[username daydaygo]extra由绑定在Logger上的Processor固定追加适合收集一些通用信息如内存占用、请求 ID。在 Hyperf 组件中这套机制被LoggerFactory的handlers()/processors()方法src/logger/src/LoggerFactory.php自动化了它读取配置中的handlers或单个handler与processors通过容器make()创建 Handler 与 Formatter 实例并自动执行$handler-setFormatter($formatter)仅当 Handler 实现了FormattableHandlerInterface。同时它内置了默认值未配置 handler 时默认使用StreamHandlerBASE_PATH/runtime/logs/hyperf.logDEBUG级别见getDefaultHandlerConfig()未配置 formatter 时默认使用LineFormatter见getDefaultFormatterConfig()。更多用法封装Log类如果你更习惯Log::info()这种多数框架的静态调用方式可以在App命名空间下创建一个Log门面类通过容器获取LoggerFactory并转发调用namespace App; use Hyperf\Logger\LoggerFactory; use Hyperf\Context\ApplicationContext; class Log { public static function get(string $name app) { return ApplicationContext::getContainer()-get(LoggerFactory::class)-get($name); } }默认使用 Channel 名为app的 Logger 记录日志你也可以通过Log::get($name)获取其他 Channel 的Logger。由于Logger本身继承自Monolog\Logger直接调用Log::get(app)-info(...)即可记录各级别日志容器Container负责了实例的获取与复用。⚠️重要提醒官方文档明确警告在使用时不要让$name与请求挂钩比如把$request_id当作 Logger name 使用。由于LoggerFactory会缓存$loggers[$channel][$name]这会导致请求级别的日志对象被永久保存在 Factory 中引发严重的内存泄漏。正确的做法是让$name只代表固定的业务模块如app、sys、order而把请求级信息通过context或Processor写入日志内容。stdout 日志框架组件如服务启动、请求分发等默认输出的日志由Hyperf\Contract\StdoutLoggerInterface接口的实现类Hyperf\Framework\Logger\StdoutLogger提供支持。该实现类的源码位于 src/framework/src/Logger/StdoutLogger.php它通过Symfony\Component\Console\Output\ConsoleOutput将格式化后的信息输出到标准输出stdout即启动 Hyperf 的终端Terminal上因此它并没有使用 Monolog。StdoutLogger还支持按级别过滤它读取config/autoload/logger.php中Hyperf\Contract\StdoutLoggerInterface::class [log_level [...]]的配置源码 src/framework/src/Logger/StdoutLogger.php只有log_level数组中列出的级别才会被输出。其默认绑定关系在 src/framework/src/ConfigProvider.php 中声明为StdoutLoggerInterface::class StdoutLogger::class。如果你希望 stdout 日志也统一走 Monolog保持格式一致、便于收集仍然可以通过强大的容器Container替换默认实现首先实现一个StdoutLoggerFactory类关于 Factory 的用法可在 依赖注入 章节获得更多详细说明?php declare(strict_types1); namespace App; use Psr\Container\ContainerInterface; class StdoutLoggerFactory { public function __invoke(ContainerInterface $container) { return Log::get(sys); } }然后声明依赖让所有依赖StdoutLoggerInterface的地方都使用StdoutLoggerFactory实例化的类// config/autoload/dependencies.php return [ \Hyperf\Contract\StdoutLoggerInterface::class \App\StdoutLoggerFactory::class, ];之后框架层面的 stdout 日志就与业务日志一样统一由配置了sys通道推荐使用php://stdout流的 Monolog Logger 输出。不同环境下输出不同格式的日志Handler与Formatter可以按运行环境灵活组合。例如在开发dev环境输出人类可读的多行文本在生产环境输出 JSON 以便投递到第三方日志服务// config/autoload/logger.php $appEnv env(APP_ENV, dev); if ($appEnv dev) { $formatter [ class \Monolog\Formatter\LineFormatter::class, constructor [ format ||%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n, allowInlineLineBreaks true, includeStacktraces true, ], ]; } else { $formatter [ class \Monolog\Formatter\JsonFormatter::class, constructor [], ]; } return [ default [ handler [ class \Monolog\Handler\StreamHandler::class, constructor [ stream php://stdout, level \Monolog\Level::Info, ], ], formatter $formatter, ], ];要点梳理配置中默认提供名为default的Handler并包含该Handler及其Formatter的信息获取Logger时如果没有指定Handler底层会自动把default这一Handler绑定到Logger上对应LoggerFactory::handlers()中$handlerConfigs $config[handlers] ?? [[]]的兜底逻辑未显式配置时使用默认 Handler 配置dev 环境日志通过php://stdout输出到标准输出且Formatter设置allowInlineLineBreaks方便查看多行日志同时开启includeStacktraces输出堆栈非 dev 环境日志使用JsonFormatter被格式化为 JSON方便结构化采集与投递到第三方日志服务。日志文件按日期轮转如果希望日志文件按日期轮转可直接使用 Monolog 已提供的Monolog\Handler\RotatingFileHandler。修改config/autoload/logger.php将Handler改为RotatingFileHandler并把stream字段改为filename即可?php return [ default [ handler [ class Monolog\Handler\RotatingFileHandler::class, constructor [ filename BASE_PATH . /runtime/logs/hyperf.log, level Monolog\Level::Debug, ], ], formatter [ class Monolog\Formatter\LineFormatter::class, constructor [ format null, dateFormat null, allowInlineLineBreaks true, ], ], ], ];RotatingFileHandler会按天生成带日期的日志文件如hyperf.log-2026-10-11并自动清理过期文件。仓库内置模板中的daily通道src/logger/publish/logger.php正是这种配置。如果你希望进行更细粒度的日志切割也可以继承Monolog\Handler\RotatingFileHandler类并重新实现rotate()方法实现自定义的切割策略如按小时、按大小。配置多个Handler一个日志组可以配置多个Handler实现同一日志按级别分流到不同文件等效果。官方文档给出了两种写法。写法一在handlers内联多个 Handler 配置。例如下面配置中当用户投递一个INFO级别及以上的日志时会同时写入hyperf.log和hyperf-debug.log当投递DEBUG级别日志时只会写入hyperf-debug.log因为hyperf.log对应的 Handler 级别是INFO?php declare(strict_types1); use Monolog\Handler; use Monolog\Formatter; use Monolog\Level; return [ default [ handlers [ [ class Handler\StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf.log, level Level::Info, ], formatter [ class Formatter\LineFormatter::class, constructor [ format null, dateFormat null, allowInlineLineBreaks true, ], ], ], [ class Handler\StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf-debug.log, level Level::Info, ], formatter [ class Formatter\JsonFormatter::class, constructor [ batchMode Formatter\JsonFormatter::BATCH_MODE_JSON, appendNewline true, ], ], ], ], ], ];写法二通过字符串引用其他通道。handlers内的元素也可以是字符串此时它表示logger.channels下的另一个通道名LoggerFactory会读取该通道的handler与formatter配置见 src/logger/src/LoggerFactory.php?php declare(strict_types1); use Monolog\Handler; use Monolog\Formatter; use Monolog\Level; return [ default [ handlers [single, daily], ], single [ handler [ class Handler\StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf.log, level Level::Info, ], ], formatter [ class Formatter\LineFormatter::class, constructor [ format null, dateFormat null, allowInlineLineBreaks true, ], ], ], daily [ handler [ class Handler\StreamHandler::class, constructor [ stream BASE_PATH . /runtime/logs/hyperf-debug.log, level Level::Info, ], ], formatter [ class Formatter\JsonFormatter::class, constructor [ batchMode Formatter\JsonFormatter::BATCH_MODE_JSON, appendNewline true, ], ], ], ];两种写法对应的输出效果如下hyperf.log使用LineFormatter输出纯文本行hyperf-debug.log使用JsonFormatter输出 JSON runtime/logs/hyperf.log [2019-11-08 11:11:35] hyperf.INFO: 5dc4dce791690 [] [] runtime/logs/hyperf-debug.log {message:5dc4dce791690,context:[],level:200,level_name:INFO,channel:hyperf,datetime:{date:2019-11-08 11:11:35.597153,timezone_type:3,timezone:Asia/Shanghai},extra:[]} {message:xxxx,context:[],level:100,level_name:DEBUG,channel:hyperf,datetime:{date:2019-11-08 11:11:35.597635,timezone_type:3,timezone:Asia/Shanghai},extra:[]}从 JSON 记录可以看出level字段是 Monolog 内部的级别数值INFO为 200、DEBUG为 100channel记录了 Logger 的 namedatetime记录了完整时间与时区信息。统一请求级别日志在微服务与协程场景下我们经常需要把同一个请求产生的所有日志关联起来。可以通过实现一个自定义Processor利用 Hyperf 的协程上下文Hyperf\Context\Context与协程 IDHyperf\Coroutine\Coroutine为每条日志追加request_id与coroutine_id?php declare(strict_types1); namespace App\Kernel\Log; use Hyperf\Context\Context; use Hyperf\Coroutine\Coroutine; use Monolog\LogRecord; use Monolog\Processor\ProcessorInterface; class AppendRequestIdProcessor implements ProcessorInterface { public const REQUEST_ID log.request.id; public function __invoke(array|LogRecord $record) { $record[extra][request_id] Context::getOrSet(self::REQUEST_ID, uniqid()); $record[extra][coroutine_id] Coroutine::id(); return $record; } }然后将该 Processor 配置到logger.php的processors中?php declare(strict_types1); use App\Kernel\Log; return [ default [ // 删除其他配置 processors [ [ class Log\AppendRequestIdProcessor::class, ], ], ], ];实现要点说明Context::getOrSet()会在协程上下文中缓存request_id保证同一请求内包括跨协程传递的上下文日志携带相同的request_id方便按请求链路聚合检索Coroutine::id()记录当前协程 ID便于排查并发问题配置项processors支持类 可选 constructor 参数的形式LoggerFactory::processors()会通过make($value[class], $value[constructor] ?? [])创建实例同时也支持直接写闭包见 src/logger/tests/LoggerFactoryTest.php 的testProcessor用例其中混合使用了两个类 Processor 与一个闭包 Processor验证了三种写法均可生效配置兼容processor单数与processors复数两种写法未设置processors而设置了processor时会被自动包装为数组src/logger/src/LoggerFactory.php。协程安全深入 StreamHandler 与 UDP Socket官方文档特别强调只要不将useLocking参数设置为true就是协程安全的其依据可以从 Hyperf 的StreamHandler源码src/logger/src/Handler/StreamHandler.php中看出端倪构造参数useLocking默认为false此时write()方法直接通过fwrite()写入流不执行flock()加锁源码第 176-200 行而 Swoole 已将fopen/fwrite等函数协程化I/O 期间会自动让出协程调度因此并发写入是安全的当useLocking true时write()会先flock($stream, LOCK_EX)再写入、写入后flock($stream, LOCK_UN)虽然保证了跨进程/跨协程的强一致性但会引入阻塞开销因此默认不开启该 Handler 还针对长驻进程做了增强每次write()前通过 inode 比对检测日志文件是否被外部轮转hasUrlInodeWasChanged()若 inode 发生变化如被logrotate切割则自动重开文件流避免写入已删除的文件写入失败时还会自动关闭并重试一次retrying机制写入前会自动创建目录createDir()使用mkdir(..., 0777, true)并且 chunk size 会根据 PHPmemory_limit动态调整默认最大 10MB 分块避免大日志记录撑爆内存。此外Monolog 的SyslogUdp\UdpSocket在协程环境下会共享底层 socket 资源Hyperf 通过 AOP 切面 src/logger/src/Aspect/UdpSocketAspect.php 对其getSocket()方法进行拦截在协程内为每个协程维护独立的 socket存于WeakMap协程外仍使用非协程 socket从而避免 socket 句柄在协程间串用导致的异常。测试验证该组件的核心行为均有单元测试覆盖可作为理解与验证实现细节的参考src/logger/tests/LoggerFactoryTest.php验证LoggerFactory的实例化、get()获取 Logger、可调用闭包配置、字符串引用的多 Handler、handlers数组多 Handler、processors/processor配置的解析与执行包括类实例与闭包两种形式src/logger/tests/LoggerTest.php验证Hyperf\Logger\Logger同时是Monolog\Logger、StdoutLoggerInterface、Psr\Log\LoggerInterface的实例支持直接记录Throwable对象并通过parallel()并行写入验证关闭日志循环检测后在协程并发下不会产生重复日志src/framework/tests/StdoutLoggerTest.php验证StdoutLogger的log_level配置过滤行为。总结hyperf/logger组件以 PSR-3 规范为契约、Monolog 为引擎通过LoggerFactory将配置即通道、通道即 Handler 组合的理念落地你可以用一套配置文件管理单文件、按日轮转、stdout/stderr、syslog、null 等多种输出自由组合多个 Handler 实现日志分级分流借助 Formatter 实现开发/生产环境的差异化格式通过 Processor 为日志统一注入请求 ID 与协程 ID。在协程层面组件通过协程安全的StreamHandler与 UDP Socket 切面保证了高并发长驻进程下的写入可靠性与资源隔离。无论你是刚接触 Hyperf 的新手还是需要精细化治理日志的老手都可以从config/autoload/logger.php出发按本文提供的配置模板与源码依据逐步定制属于自己的日志方案。赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐Hyperf 日志组件实战指南基于 Monolog 的协程安全日志体系与多 Handler 配置Hyperf 日志组件实战指南基于 Monolog 的协程安全日志体系与多 Handler 配置 本篇技术指南围绕 Hyperf 框架的 hyperf/log后端微服务Hyperf 日志组件实战指南基于 Monolog 构建协程安全的日志体系Hyperf 日志组件实战指南基于 Monolog 构建协程安全的日志体系 导读 本文以 Hyperf 官方文档中「日誌」章节 docs/zh tw/log后端微服务Hyperf Logger 组件实战指南基于 Monolog 的协程安全日志体系配置与扩展Hyperf Logger 组件实战指南基于 Monolog 的协程安全日志体系配置与扩展 hyperf/logger 是 Hyperf 框架的日志组件它基后端Web框架微服务RPC框架异步编程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考