使用Monolog做日志

3.3 version
维护中的版本

Symfony自带了一个外部库 - 叫做 Monolog - 它允许你创建“可以存放在各种不同地方”的日志。

记录一条信息 

要记录一条信息,从控制器中的容器取出 logger 服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function indexAction()
{
    $logger = $this->get('logger');
    $logger->info('I just got the logger');
    $logger->error('An error occurred');
 
    $logger->critical('I left the oven on!', array(
        // include extra "context" info in your logs / 在日志中附带额外的“上下文”信息
        'cause' => 'in_hurry',
    ));
 
    // ...
}

logger 服务针对不同的日志等级/优先级拥有不同的方法。你可以基于信息的 等级 来配置logger去做不同的事(如 有错误发生时发送邮件)。

参考 LoggerInterface 以了解logger的全部方法列表。

日志被存在哪里 

对于把日志存放于 何处 的配置信息,位于特定的 environment 配置文件: config_dev.ymlconfig_prod.yml 之中。

当你处于 dev 环境时,日志文件入口被默认写进了 var/logs/dev.log 文件。在 prod 环境下,日志被写入 var/logs/prod.log,但却 在请求过程中遇到错误或是遇到高优先级日志时才会发生(如 error() , critical(), alert()emergency())。

要控制存放位置,你要配置不同的 handlers(控制器)来控制日志入口点(entries),必要时修改它们,最终存储它们。

Handlers: 把日志写入不同的位置 

logger自带了一组 handlers,每一种都用于把日志入口写入到不同的位置(如,文件,数据库,Slack等)。

能配置日志的 "channels"(频道),这就像分类。每个频道可以有其 自己的 handlers,这意味着你可以把不同的信息记录到不同的地方。参考 如何把日志信息写入不同的文件中

Symfony在 config_dev.ymlconfig_prod.yml 文件中预置了一些handlers。要了解真实用例不妨查看它们。

下例使用了 两个 handlers: stream (用于写入文件) 和 syslog,后者使用 syslog 函数来完成日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# app/config/config.yml
monolog:
    handlers:
        # this "file_log" key could be anything
        # 这个 "file_log" 键可以是任何(合法)字符
        file_log:
            type: stream
            # log to var/logs/(environment).log
            # 写入到 var/logs/(environment).log
            path: "%kernel.logs_dir%/%kernel.environment%.log"
            # log *all* messages (debug is lowest level)
            # 把 *全部* 信息写入(debug是最低级别)
            level: debug

        syslog_handler:
            type: syslog
            # log error-level messages and higher
            # 记录 error-level(ERROR级别)或更高级别(的信息)
            level: error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:monolog="http://symfony.com/schema/dic/monolog"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/monolog
        http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
 
    <monolog:config>
        <monolog:handler
            name="file_log"
            type="stream"
            path="%kernel.logs_dir%/%kernel.environment%.log"
            level="debug"
        />
        <monolog:handler
            name="syslog_handler"
            type="syslog"
            level="error"
        />
    </monolog:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// app/config/config.php
$container->loadFromExtension('monolog', array(
    'handlers' => array(
        'file_log' => array(
            'type'  => 'stream',
            'path'  => '%kernel.logs_dir%/%kernel.environment%.log',
            'level' => 'debug',
        ),
        'syslog_handler' => array(
            'type'  => 'syslog',
            'level' => 'error',
        ),
    ),
));

本例定义了一 handler,它们会按照各自被定义的顺序被调用。

可修改日志入口的handlers 

并非将日志写入某处的文件, 一些 handlers可用于“在将日志发送到 其他 handlers之前”过滤和修改它们。一个内置的名为 fingers_crossed 的强大handleris默认用于 prod 环境下。它在请求过程中存储 全部 日志信息,却 只是 将它们传入第二个handler,如果某条信息达到 action_level 级别的话。看以下例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# app/config/config.yml
monolog:
    handlers:
        filter_for_errors:
            type: fingers_crossed
            # if *one* log is error or higher, pass *all* to file_log
            # 如果 *一条* 日志是error或更高(的级别),把它们 *全部* 传入file_log
            action_level: error
            handler: file_log
 
        # now passed *all* logs, but only if one log is error or higher
        # 现在传入了 *全部* 日志,但只是那些error或更高级别的
        file_log:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.log"
 
        # still passed *all* logs, and still only logs error or higher
        # 仍然传入了 *全部* 日志,并且仍然都是error或更高级别的
        syslog_handler:
            type: syslog
            level: error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:monolog="http://symfony.com/schema/dic/monolog"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/monolog
        http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
 
    <monolog:config>
        <monolog:handler
            name="filter_for_errors"
            type="fingers_crossed"
            action-level="error"
            handler="file_log"
        />
        <monolog:handler
            name="file_log"
            type="stream"
            path="%kernel.logs_dir%/%kernel.environment%.log"
            level="debug"
        />
        <monolog:handler
            name="syslog_handler"
            type="syslog"
            level="error"
        />
    </monolog:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app/config/config.php
$container->loadFromExtension('monolog', array(
    'handlers' => array(
        'filter_for_errors' => array(
            'type'         => 'fingers_crossed',
            'action_level' => 'error',
            'handler'      => 'file_log',
        ),
        'file_log' => array(
            'type'  => 'stream',
            'path'  => '%kernel.logs_dir%/%kernel.environment%.log',
            'level' => 'debug',
        ),
        'syslog_handler' => array(
            'type'  => 'syslog',
            'level' => 'error',
        ),
    ),
));

现在,如果某一日志入口(log entry,译注:即“位于某处的日志文件”)遇到了 error 或更高级别的(信息),则该请求的 所有 日志入口将通过 file_log handler被存成一个文件。这意味着你的日志文件将包含问题请求的 全部 相关细节 - 这令调试变得更容易!

名为 "file_log" 的handler将不包括在控制器堆栈中,因为它是作为 fingers_crossed handler的嵌套控制器来使用。

如果你希望通过另一个配置文件来覆写 monolog 配置信息,你需要重新定义整个 handlers stack(控制器堆栈)。这两个文件中的配置信息不能被合并,因为(handler的)顺序很关键,一旦合并就无法控制顺序。

所有的内置handler 

Monolog内置了 许多 用于发送日志邮件的handlers,把它们发送到Loggly,或者在Slack中通知你。这些功能在MonologBundle中都有文档。完整的列表请参考 Monolog Configuration

如何翻转你的日志文件 

时间一长,不管是开发环境还是生产环境,日志文件会变得 极为巨大。解决方案中的一个最佳实践是使用诸如 logrotate 这种Linux command来在日志文件巨型化之前反转之。

另一个选项是使用rotating_file handler来令Monolog去反转日志文件。这个控制器每天会创建新的日志文件,并自动删除旧的。要使用它,把控制器中的 type 选项设为 rotating_file即可:

1
2
3
4
5
6
7
8
9
10
11
# app/config/config_dev.yml
monolog:
    handlers:
        main:
            type:  rotating_file
            path:  '%kernel.logs_dir%/%kernel.environment%.log'
            level: debug
            # max number of log files to keep
            # defaults to zero, which means infinite files
            # 要保留的日志文件的最大数量,默认是零,即,无限个文件
            max_files: 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- app/config/config_dev.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:monolog="http://symfony.com/schema/dic/monolog"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/monolog
        http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
 
    <monolog:config>
        <!-- "max_files": max number of log files to keep
             defaults to zero, which means infinite files -->
        <monolog:handler name="main"
            type="rotating_file"
            path="%kernel.logs_dir%/%kernel.environment%.log"
            level="debug"
            max_files="10"
        />
    </monolog:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
// app/config/config_dev.php
$container->loadFromExtension('monolog', array(
    'handlers' => array(
        'main' => array(
            'type'  => 'rotating_file',
            'path'  => "%kernel.logs_dir%/%kernel.environment%.log",
            'level' => 'debug',
            // max number of log files to keep
            // defaults to zero, which means infinite files
            'max_files' => 10,
        ),
    ),
));

向每个日志添加额外数据(如,一个唯一的请求token) 

参考 如何通过Processor向日志信息添加额外数据 可了解细节。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)