Log

Testing Is Documentation

tests/Log/LogTest.phpLog - 图1

日志记录统一由日志组件完成,通常我们使用代理 \Leevel\Log\Proxy\Log 类进行静态调用。

内置支持的 log 驱动类型包括 file、syslog,未来可能增加其他驱动。

TIP

日志遵循 PSR-3 规范,用法与主流框架完全一致。

使用方式

使用容器 logs 服务

  1. \App::make('logs')->emergency(string $message, array $context = []): void;
  2. \App::make('logs')->alert(string $message, array $context = []): void;
  3. \App::make('logs')->critical(string $message, array $context = []): void;
  4. \App::make('logs')->error(string $message, array $context = []): void;
  5. \App::make('logs')->warning(string $message, array $context = []): void;
  6. \App::make('logs')->notice(string $message, array $context = []): void;
  7. \App::make('logs')->info(string $message, array $context = []): void;
  8. \App::make('logs')->debug(string $message, array $context = []): void;
  9. \App::make('logs')->log(string $level, string $message, array $context = []): void;

依赖注入

  1. class Demo
  2. {
  3. private \Leevel\Log\Manager $log;
  4. public function __construct(\Leevel\Log\Manager $log)
  5. {
  6. $this->log = $log;
  7. }
  8. }

使用静态代理

  1. \Leevel\Log\Proxy\Log::emergency(string $message, array $context = []): void;
  2. \Leevel\Log\Proxy\Log::alert(string $message, array $context = []): void;
  3. \Leevel\Log\Proxy\Log::critical(string $message, array $context = []): void;
  4. \Leevel\Log\Proxy\Log::error(string $message, array $context = []): void;
  5. \Leevel\Log\Proxy\Log::warning(string $message, array $context = []): void;
  6. \Leevel\Log\Proxy\Log::notice(string $message, array $context = []): void;
  7. \Leevel\Log\Proxy\Log::info(string $message, array $context = []): void;
  8. \Leevel\Log\Proxy\Log::debug(string $message, array $context = []): void;
  9. \Leevel\Log\Proxy\Log::log(string $level, string $message, array $context = []): void;

log 配置

系统的 log 配置位于应用下面的 option/log.php 文件。

可以定义多个日志连接,并且支持切换,每一个连接支持驱动设置。

  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the your app package.
  5. *
  6. * The PHP Application For Code Poem For You.
  7. * (c) 2018-2099 http://yourdomian.com All rights reserved.
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. return [
  13. /*
  14. * ---------------------------------------------------------------
  15. * 默认日志驱动
  16. * ---------------------------------------------------------------
  17. *
  18. * 系统为所有日志提供了统一的接口,在使用上拥有一致性
  19. */
  20. 'default' => Leevel::env('LOG_DRIVER', 'file'),
  21. /*
  22. * ---------------------------------------------------------------
  23. * 允许记录的日志级别
  24. * ---------------------------------------------------------------
  25. *
  26. * 默认为 debug、info、notice、warning、error、critical、alert 和 emergency
  27. */
  28. 'levels' => [
  29. 'debug',
  30. 'info',
  31. 'notice',
  32. 'warning',
  33. 'error',
  34. 'critical',
  35. 'alert',
  36. 'emergency',
  37. ],
  38. /*
  39. * ---------------------------------------------------------------
  40. * 频道
  41. * ---------------------------------------------------------------
  42. *
  43. * 隔离不同环境的日志
  44. */
  45. 'channel' => Leevel::env('ENVIRONMENT', 'development'),
  46. /*
  47. * ---------------------------------------------------------------
  48. * 是否启用缓冲
  49. * ---------------------------------------------------------------
  50. *
  51. * 启用缓冲可以降低磁盘或网络的 IO 请求以提升性能
  52. */
  53. 'buffer' => true,
  54. /*
  55. * ---------------------------------------------------------------
  56. * 缓冲数量
  57. * ---------------------------------------------------------------
  58. *
  59. * 日志数量达到缓冲数量会执行一次 IO 操作
  60. */
  61. 'buffer_size' => 100,
  62. /*
  63. * ---------------------------------------------------------------
  64. * 日志连接参数
  65. * ---------------------------------------------------------------
  66. *
  67. * 这里为所有的日志的连接参数,每一种不同的驱动拥有不同的配置
  68. * 虽然有不同的驱动,但是在日志使用上却有着一致性
  69. */
  70. 'connect' => [
  71. 'file' => [
  72. // driver
  73. 'driver' => 'file',
  74. // 频道
  75. 'channel' => null,
  76. // 日志文件名时间格式化
  77. 'name' => 'Y-m-d H',
  78. // 默认的日志路径
  79. 'path' => Leevel::runtimePath('log'),
  80. // 日志行时间格式化,支持微秒
  81. 'format' => 'Y-m-d H:i:s u',
  82. // 日志文件权限
  83. 'file_permission' => null,
  84. // 是否使用锁
  85. 'use_locking' => false,
  86. ],
  87. 'syslog' => [
  88. // driver
  89. 'driver' => 'syslog',
  90. // 频道
  91. 'channel' => null,
  92. // 存储 @see \Monolog\Handler\AbstractSyslogHandler
  93. 'facility' => LOG_USER,
  94. // 等级
  95. 'level' => 'debug',
  96. // 日志行事件格式化,支持微秒
  97. 'format' => 'Y-m-d H:i:s u',
  98. ],
  99. ],
  100. ];

log 参数根据不同的连接会有所区别,通用的 log 参数如下:

配置项配置描述
levels允许记录的日志级别
channel频道
buffer是否启用缓冲
buffer_size日志数量达到缓冲数量会执行一次 IO 操作

注意

QueryPHP 的日志如果启用了缓冲,会在日志数量达到缓冲数量会执行一次 IO 操作。

Uses

  1. <?php
  2. use Leevel\Filesystem\Helper;
  3. use Leevel\Log\File;
  4. use Leevel\Log\ILog;
  5. use Monolog\Logger;

log 基本使用

除了 PSR-3 支持的方法外,系统还提供了一些额外方法。

支持的日志类型

  1. # Tests\Log\LogTest::baseUseProvider
  2. public function baseUseProvider(): array
  3. {
  4. return [
  5. ['emergency'],
  6. ['alert'],
  7. ['critical'],
  8. ['error'],
  9. ['warning'],
  10. ['notice'],
  11. ['info'],
  12. ['debug'],
  13. ];
  14. }

获取日志记录数量

  1. # Leevel\Log\ILog::count
  2. /**
  3. * 获取日志记录数量.
  4. */
  5. public function count(?string $level = null): int;

获取当前日志记录

  1. # Leevel\Log\ILog::all
  2. /**
  3. * 获取当前日志记录.
  4. *
  5. * - 每次 IO 写入后会执行一次清理
  6. */
  7. public function all(?string $level = null): array;

清理日志记录

  1. # Leevel\Log\ILog::clear
  2. /**
  3. * 清理日志记录.
  4. */
  5. public function clear(?string $level = null): void;
  1. public function testBaseUse(string $level): void
  2. {
  3. $log = $this->createFileConnect();
  4. $this->assertInstanceof(ILog::class, $log);
  5. $this->assertNull($log->{$level}('foo', ['hello', 'world']));
  6. $this->assertSame([$level => [[$level, 'foo', ['hello', 'world']]]], $log->all());
  7. $this->assertSame([[$level, 'foo', ['hello', 'world']]], $log->all($level));
  8. $this->assertSame(1, $log->count());
  9. $this->assertSame(1, $log->count($level));
  10. $this->assertNull($log->clear($level));
  11. $this->assertSame([], $log->all($level));
  12. $this->assertNull($log->clear());
  13. $this->assertSame([], $log->all());
  14. $this->assertSame([], $log->all($level));
  15. $this->assertInstanceOf(Logger::class, $log->getMonolog());
  16. Helper::deleteDirectory(__DIR__.'/cacheLog');
  17. }

日志支持等级过滤

  1. public function testLogFilterLevel(): void
  2. {
  3. $log = $this->createFileConnect(['levels' => [ILog::INFO]]);
  4. $log->log(ILog::INFO, 'foo', ['hello', 'world']);
  5. $log->log(ILog::DEBUG, 'foo', ['hello', 'world']);
  6. $this->assertSame([ILog::INFO => [[ILog::INFO, 'foo', ['hello', 'world']]]], $log->all());
  7. }

日志支持默认等级 debug

  1. public function testLogLevelNotFoundWithDefaultLevel(): void
  2. {
  3. $log = $this->createFileConnect(['levels' => [ILog::DEBUG]]);
  4. $log->log('notfound', 'foo', ['hello', 'world']);
  5. $this->assertSame([ILog::DEBUG => [[ILog::DEBUG, 'foo', ['hello', 'world']]]], $log->all());
  6. $log->flush();
  7. }

日志支持消息分类

系统提供的等级 level 无法满足大型项目的日志需求,于是对消息 message 定义了一套规则来满足更精细的分类。

日志消息分类规则

  1. # Leevel\Log\Log::parseMessageCategory
  2. public static function parseMessageCategory(string $message): string
  3. {
  4. if (preg_match('/^\[([a-zA-Z_0-9\-:.\/]+)\]/', $message, $matches)) {
  5. return str_replace(':', '/', $matches[1]);
  6. }
  7. return '';
  8. }

TIP

消息开头满足 [大小写字母|数字|下划线|中横线|点号|斜杆|冒号] 会被识别为消息分类,其中冒号会被转化为斜杆。

目前消息分类会作为文件类日志目录,支持无限层级目录。

  1. public function testLogMessageCategory(): void
  2. {
  3. $log = $this->createFileConnect();
  4. $log->log(ILog::INFO, '[SQL] foo', ['hello', 'world']);
  5. $log->log(ILog::INFO, '[SQL:FAILED] foo', ['hello', 'world']);
  6. $this->assertSame([
  7. ILog::INFO => [
  8. [ILog::INFO, '[SQL] foo', ['hello', 'world']],
  9. [ILog::INFO, '[SQL:FAILED] foo', ['hello', 'world']],
  10. ],
  11. ], $log->all());
  12. $log->flush();
  13. }