zalora/punyan

Bunyan 的 PHP 实现

1.0.1 2016-12-20 08:04 UTC

README

Punyan 是 Bunyan Logger 的 PHP 实现,最初是为 node.js 编写的

需求

  • Linux / OS X(也许有人想尝试 Windows?)
  • PHP 7.x
  • Composer
  • NPM

安装

$ composer require zalora/punyan

简洁的日志记录器

{
  "writers": [
    {
      "stream": {
        "url": "/tmp/myproject.log",
        "filters": [{ "priority": { "priority": "info" } }]
      }
    }
  ]
}
$config = [
    'writers' => [
        ['stream' => [
            'url' => '/tmp/myproject.log',
            'filters' => [
                ['priority' => ['priority' => 'info']]
            ]
        ]]
    ]
];
<?php
use Zalora\Punyan\Logger;
use Zalora\Punyan\ZLog;

ZLog::setInstance(new Logger('MyAppName', $config));

...

ZLog::info('Informative log message', ['param1' => 'stuffs']);
$ sudo npm install -g bunyan
$ tail -f /tmp/myproject.log | bunyan -o short -L

Bunyan 实际上是少数几个无需警告或错误即可安装的 npm 模块之一!

配置文件

示例配置是 JSON 格式编写的,只要表示相同的结构,其他格式也可以工作。记录器本身期望一个具有相同结构的数组。

{
  "mute": false,
  "filters": [
    { "priority": {"priority": "info", "operator": ">="} }
  ],
  "writers": [
    {
      "Stream": {
        "mute": false,
        "url": "php://stdout",
        "filters": [
          { "priority": { "priority": "warn" } },
          { "ns": { "namespace": "Service", "searchMethod": "contains" } }
        ]
      }
    }
  ]
}

顶级选项

  • mute:顶级 mute 会静音整个记录器,writer 中的 mute 仅适用于此特定 writer
  • filters:顶级 filter 适用于整个记录器,writer 中的 filter 仅适用于特定 writer
  • writers:每个记录器可以有零个或多个 writer(当然,多于零个 writer 更有趣...)
  • exceptionHandler:为了确保我们可以屏蔽敏感数据,我们需要实现自己的特殊 exceptionHandler,所以我们使其可配置。想想看,您不想在日志文件中包含密码或其他敏感数据。

过滤器

  • 回调
  • DiscoBouncer
  • NoFilter
  • 命名空间
  • 优先级
  • 正则表达式

要添加过滤器,您必须将此结构添加到过滤器数组中

{ "<filter_name>": { "option1": "value1", "option2": "value2" } }

回调

回调过滤器必须返回(true|false),并将 LogEvent 对象传递以提供一些评估数据。

唯一的选项是

  • function:请确保它是可调用的

如果您不返回任何内容,则假定返回 false。当然,这也取决于您确保类已加载或存在自动加载器来执行此操作。

示例

{ "callback": { "function": "MyClass::myStaticMethod" }}

DiscoBouncer

名字已经说明了一切:它将过滤所有内容。这个过滤器没有选项,目前用于单元测试。如果您找到实际用例,请告诉我。我很感兴趣。

示例

{ "discoBouncer": {} }

NoFilter

DiscoBouncer 的相反,允许一切通过。

示例

{ "noFilter": {} }

命名空间(Ns)

此过滤器应用于您的类名(包括命名空间),因此您可以使用它为您的模块分配单独的日志文件。正则表达式在记录器初始化时进行验证。

选项

  • namespace:在命名空间中搜索什么
  • searchMethod:(可选,默认 'startsWith')以下之一:(startsWith|contains|regexp)

如果您使用 regexp 作为 searchMethod,则 namespace 包含您的正则表达式

示例

这只会接受包含标记 'Service' 的类的日志

{ "ns": { "namespace": "Service", "searchMethod": "contains" } }

我的正则表达式 foo 非常低,对此表示歉意。我希望这个匹配以 Hello 结尾的每个类

{ "ns": { "namespace": "/Hello$/", "searchMethod": "regexp" } }

优先级

优先级过滤器根据优先级进行过滤(惊喜...),例如,如果过滤器级别设置为 'info',则一切从 'info' 开始通过。我想没有太多要解释的。

以下是完整选项列表

  • priority:以下之一(trace|debug|info|warn|error|fatal)
  • 操作符:(可选,默认 >=) 你不需要修改它...

示例

{ "priority": { "priority": "warn", "operator": ">=" } }

正则表达式

将字段(默认为日志消息)与你的正则表达式模式进行匹配。以下是过滤器的选项

  • pattern:(必需)你的正则表达式
  • field:(可选,默认 'msg') 在上下文中运行正则表达式的字段。要访问嵌套值,请使用点作为分隔符,例如 request.src 来访问 $context['request']['src']
  • returnValueOnMissingField:(可选,默认 false) 如果字段不存在,则返回 true 或 false

示例

{ "regexp": { "pattern": "/^https/", "field": "request.url", "returnValueOnMissingField": false } }

匹配所有以 https 开头的 URL,如果没有记录任何 URL,则丢弃日志条目。

写入器

目前只有一个写入器(StreamWriter),在未来的版本中,我将添加几个以支持 FirePHP 和 Slack。每个写入器都有三个选项

  • mute (true|false) 默认是 false
  • origin (true|false) 默认是 true
  • bubble (true|false) 默认是 true

静音

这将(明显)静音写入器并抑制任何潜在的输出

原始

原始添加一个包含日志调用触发位置信息的数组,它包含以下字段

  • file
  • line
  • class
  • function

例如,对于命名空间过滤器这是必需的。如果你将原始设置为 false 并添加 Ns-过滤器,则过滤器将始终返回 false。

冒泡

一旦写入器记录了消息,则日志事件不会被发送到其他写入器。示例

{
  "filters": [
    { "priority": { "priority": "info" } }
  ],
  "writers": [
    {
      "Stream": {
        "origin": true,
        "bubble": false,
        "url": "services.log",
        "filters": [
          { "ns": { "namespace": "Service", "searchMethod": "contains" } }
        ]
      }
    },
    {
      "Stream": {
        "origin": true,
        "url": "common.log",
        "filters": []
      }
    }
  ]
}

The services.log 保留所有来自包含单词 Service 的类名的类的日志事件。如果没有冒泡,则日志消息将出现在两个文件中,因为 common.log 的流写入器没有任何过滤器。所以冒泡可以用来防止重复的日志消息。请注意,当你使用冒泡时,定义你的写入器的顺序很重要!

免责声明:我从 Monolog 借用了“冒泡”这个词

StreamWriter

StreamWriter 支持所有可写流(https://php.ac.cn/manual/en/wrappers.php),例如

  • php://memory
  • file:///tmp/myproject.log
  • php://stdout

选项

  • url:你想要写入(追加)到的 url
  • filters:过滤器是可选的,但至少必须存在一个空数组
  • mute:(可选,默认 false) 与每个写入器一样,它可以被静音

在初始化过程中将打开流,因此对于文件,你可以很早就知道它是否可写

处理器

我又从 Monolog 借用了这个名字,因为我找不到更好的名字...它们也做同样的事情:为每个日志事件添加额外的数据。

当然你可以手动做这件事,但这需要很多工作,并且会让代码变得混乱,因为即使日志消息本身被过滤掉,它也会被执行。所以你将这段代码移动到处理器中,并将其附加到写入器。从那时起,它将自动收集所有所需的信息。

处理器有一个共同字段

  • onDemand (true|false) 默认是 false

附加数据存储在键 'proc' 下,该键在 Zalora\Punyan\IProcessor 中定义。

示例

{ "Web": { "onDemand": true }}{ "Web": { }}

Web 处理器

以下数据添加到每个日志事件的 proc 数组中

  • url:$_SERVER['REQUEST_URI']
  • ip:$_SERVER['REMOTE_ADDR']
  • http_method:$_SERVER['REQUEST_METHOD']
  • server:$_SERVER['SERVER_NAME']
  • referrer:$_SERVER['HTTP_REFERER']

已知问题

  • 仅支持 Bunyan 格式;这并不是真正的问题,而是一个设计决策。如果有人感兴趣,我可能会放宽这个限制,以便支持其他基于 JSON 的服务...

常见问题解答

为什么要重新发明轮子?

实际上,我没有找到我喜欢且不实现PSR-3接口的日志记录器。最初我以为为Monolog实现一个格式化器应该可以解决问题,但是日志级别匹配不正确,使用映射看起来太像黑客手段了。

由于我们想使用bunyan项目提供的工具,我们必须保持格式兼容。这里又有另一个PHP日志记录器。

计划是什么?

配置存储

为了更改日志级别,你必须更改文件,甚至更糟糕的是更改代码。我想提供一个小的命令行程序,该程序可以在无需可写文件或重新部署的情况下更改配置。这可以通过将配置存储在键值存储中实现

新过滤器

  • 速率限制:关闭冒泡功能后,你可以避免在夜间发送五亿封电子邮件;-)
  • 采样器:过滤掉一定比例的事件

新写入器

  • AMQP:将日志发送到RabbitMQ
  • Slack:你最喜欢的消息传递应用
  • FirePHP:在开发期间将日志事件发送到浏览器
  • Responsys:通过电子邮件发送重要的日志(注意速率限制...)
  • New Relic:你最喜欢的应用程序监控工具
  • Redis:将日志持久化到Redis列表