Yii2 --日志(原创翻译)

日志

Yii提供了一个高度自定义化和高扩展性的日志框架。根据使用场景的不同,你可以很容易的对各种消息就行记录、过滤、合并,比如说文本文件,数据库文件,邮件。

使用Yii的日志框架包含如下步骤:

  • 调用日志记录的方法
  • 在主应用的配置文件(例如basic下面的web.php)中配置好日志的过滤和导出的设置
  • 检查不同场景下经过过滤之后的日志信息

记录日志

记录日志其实就是简简单单的调用如下的方法:

上面的这些方法虽然根据不同的level和类型来记录信息,但是实际上它们调用的是同一个方法function($message, $category = 'application')。其中$message就是要记录的信息,$category表示的是这个日志的归属类。下面的代码表示在默认的‘application’分类下面记录了一条trace类型的信息。

 Yii::trace('start calculating average revenue');

提示:记录的$message可以是简单的string也可以是复杂的数组、对象。你应该根据不同的场景下日志记录的职责选取合适的$message类型。默认情况下,如果你记录的$message不是String,日志在导出的时候都会调用[[yii\helpers\VarDumper::export()]] 方法来输出一个string类型的消息。

为了更好的组织管理及筛选日志消息,通常情况下应当为每一种日志分配合适的类别。你可以选择一种有明显等级区分意思的分类,用以方便根据不同的目的来筛选不同分类的日志。一种简单且有效的命名方式就是使用PHP的魔术常量METHOD来作为分类的名称。Yii框架里面的核心代码在做日志记录的时候就是这么干的。比如说:

Yii::trace('start calculating average revenue', __METHOD__);

在有常量METHOD出现的地方,它表示的就是当前的方法的名称(并且加上了当前方法所属的类的完整前缀)。举个例子吧,如果在app\controllers\RevenueController这个类里面的calculate方法里面有上面的那行代码,那么此时的METHOD表示的就是‘app\controllers\RevenueController::calculate’。

提示:上面所说的方法事实上只是单例对象[[yii\log\Logger|logger object]] 的[[yii\log\Logger::log()|log()]]方法的简单使用,我们可以通过Yii::getLogger()方法来获得这个单例对象。当我们记录了足够的日志信息或者当前的应用运行结束了,日志对象将调用[yii\log\Dispatcher|message dispatcher]] 方法把记录的日志信息写入到配置的目的位置。

log targets

一个log target是[[yii\log\Target]]或者其子类的实例。它根据严重的等级和分类类过滤日志,然后把日志导出到合适的媒介上面去。就比如说,一个[[yii\log\DbTarget|database target]] 对象就会把过滤之后的日志信息导出到对应数据库。
你可以在应用的配置文件中的日志组件处注册多个log targets,就像下面这样:

return [
// the "log" component must be loaded during bootstrapping time
'bootstrap' => ['log'],

'components' => [
    'log' => [
        'targets' => [
            [
                'class' => 'yii\log\DbTarget',
                'levels' => ['error', 'warning'],
            ],
            [
                'class' => 'yii\log\EmailTarget',
                'levels' => ['error'],
                'categories' => ['yii\db\*'],
                'message' => [
                   'from' => ['log@example.com'],
                   'to' => ['admin@example.com', 'developer@example.com'],
                   'subject' => 'Database errors at example.com',
                ],
            ],
        ],
    ],
],
];
注意:日志组件必须在bootstrap中配置,这样才能把日志信息分发到对应的log target.

上面的代码里面,两个log target注册到了[[yii\log\Dispatcher::targets]]里面。

  • 第一个筛选出错误和警告信息并且把这些信息保存到了数据库。
  • 第二个筛选出分类以yii\db*开头的错误信息,并把这些信息通过邮件发送到admin@example.comdeveloper@example.com.

Yii有下面这些内置的log targets,你可以参考API文档来学习具体怎么去配置和使用它们。

接下来,我们就来看看常见的log target具有的功能。

消息过滤

就每一种log target而言,你可以配置它的 [[yii\log\Target::levels|levels]] 和 [[yii\log\Target::categories|categories]]属性类设置它的严重程度以及归属的分类。
[[yii\log\Target::levels|levels]]属性的采用一个数组里面的一个或者多个值,这个数组包含如下值:

  • error:对应[Yii::error()]记录的消息
  • warning:对应[Yii::warning()]记录的消息
  • info :对应 [Yii::info()]记录的信息
  • trace:对应 [Yii::trace()]记录的信息.
  • profile :对应[[Yii::beginProfile()]] 和 [[Yii::endProfile()]]记录的信息,这种方式下面更多详细信息会被记录。

如果你没有指定[[yii\log\Target::levels|levels]] 的值,那么任何level的信息都会被记录。
[[yii\log\Target::categories|categories]] 属性的值是数组,这个数组里面的值可以是一个具体的分类名称,也可以是类似正则的匹配模式。只有在target能在这个数组里面找到对应的分类名或者符合某一个匹配模式,他才会处理这些消息。这里的匹配模式的组成是在分类的名称后面加上一个号。如果这个分类恰好和这个匹配模式的号前的字符匹配,那么也就是这个分类找到对应匹配值。举个例来说,在类[[yii\db\Command]]中的yii\db\Command::execute和yii \db\Command:: query 方法使用类名类记录相关日志信息,那么这个时候他们都匹配模式yii\db*

同样的,如果我们没有指定[[yii\log\Target::categories|categories]],那么每一种分类的日志信息都会被处理。
除了通过[[yii\log\Target::categories|categories]] 属性来设置分类的白名单外,你也可以通过 [[yii\log\Target::except|except]]属性来设置分类的黑名单。属于黑名单的分类日志信息不会被target处理。

下面的配置指定了一个分类匹配yii\db*或者 yii\web\HttpException:*,但又不包括yii\web\HttpException:404的分类,而且它只处理错误和警告的日志信息。

[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
'categories' => [
    'yii\db\*',
    'yii\web\HttpException:*',
],
'except' => [
    'yii\web\HttpException:404',
],
]
注意:当错误的句柄捕获到HTTP的异常的时候,记录的日志信息会以yii\web\HttpException:ErrorCode的这种格式
记录,例如[[yii\web\NotFoundHttpException]] 就会被记录成yii\web\HttpException:404

消息格式化

日志targets用多种格式来导出日志。举个例子,如果你的日志target是[yii\log\FileTarget],那么你在你的程序中记录日志的时候,应该会找到类似于文件runtime/log/app.log 记录的如下的信息:

2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug

默认情况下,[[yii\log\Target::formatMessage()]]:会帮我们把日志信息格式化成下面的这种格式:

Timestamp [IP address][User ID][Session ID][Severity Level][Category] Message Text

你可以通过给[[yii\log\Target::prefix]] 属性配置一个自定义的回调函数来 自定义日志的前缀。下面的代码就实现了在每条日志信息前面加上了用户的ID(ip地址,sessionId等敏感信息因为个人隐私被去掉了)。

[
'class' => 'yii\log\FileTarget',
'prefix' => function ($message) {
    $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
    $userID = $user ? $user->getId(false) : '-';
    return "[$userID]";
}
]

除了日志消息的前缀,日志的target还把一些上下文信息附加在了每一批的日志记录中。默认情况下,全局的PHP变量包含$_GET, $_POST, $_FILES, $_COOKIE, $_SESSION 和 $_SERVER. 你可以通过配置 [[yii\log\Target::logVars]] 来调整日志记录的全局变量。下面的代码表示的是只记录$_SERVER相关的变量。

[
'class' => 'yii\log\FileTarget',
'logVars' => ['_SERVER'],
]

当 'logVars'为空的时候,表示不记录相关的上下文信息。如果你想自定义上下文信息的提供方式,你可以覆写[[yii\log\Target::getContextMessage()]] 方法。

消息的trace等级

在开发的过程当中,我们总是期望能够知道每一条日志消息是来自哪里。在Yii中你可以通过配置[[yii\log\Dispatcher::traceLevel|traceLevel]] 属性来实现。配置的示例如下:

return [
'bootstrap' => ['log'],
'components' => [
    'log' => [
        'traceLevel' => YII_DEBUG ? 3 : 0,
        'targets' => [...],
    ],
],
];

上面的示例在YII_DEBUG为true的时候将[[yii\log\Dispatcher::traceLevel|traceLevel]] 设置为3,反之设置为0. 意思是什么呢?3表示每一条日志记录都会记录与之相关的三层栈调用信息,0表示不记录任何相关的栈调用信息

提示:没有必要总是记录调用的堆栈信息,比较耗性能。所以,你应该只在你开发的时候或者用于调试的情况下使用该功能。

消息的清空和导出

就如同上面说的,记录的消息以数组的形式保存在[[yii\log\Logger|logger object]]中。为了限制这个数组消耗过多的内存,当这个数组包含的内容大小达到某个量的时候会被对应的target从内存中转移到对应的目标(文件,数据库...)中。你可以通过设置 [[yii\log\Dispatcher::flushInterval|flushInterval]] 的值来决定量的大小。像下面这样:

return [
'bootstrap' => ['log'],
'components' => [
    'log' => [
        'flushInterval' => 100,   // default is 1000
        'targets' => [...],
    ],
],
];
注意:在应用运行结束的时候也会刷新内存,这样做事为了让日志的target能够记录完整的信息。

把日志信息从内存刷到对应存放的地方的这一动作不是立即发生的。事实上,和上面一样,都是当内存中的日志大小达到一定程度才会发生。你可以像下面的示例一样通过配置不同target的[[yii\log\Target::exportInterval|exportInterval]]值,来达到修改的目的:

[
'class' => 'yii\log\FileTarget',
'exportInterval' => 100,  // default is 1000
]

因为清空和导出的设定,默认情况下你调用 Yii::trace() 或者其他的日志记录方法的时候不会在日志target下立马看到日志消息。这对某些长时间运行的控制台程序是一个问题。不过这个问题是可以解决的,方法入下面的代码,你需要把[[yii\log\Dispatcher::flushInterval|flushInterval]] 和[[yii\log\Target::exportInterval|exportInterval]] 的值都设置成1:

return [
'bootstrap' => ['log'],
'components' => [
    'log' => [
        'flushInterval' => 1,
        'targets' => [
            [
                'class' => 'yii\log\FileTarget',
                'exportInterval' => 1,
            ],
        ],
    ],
],
];
注意:如此频繁的清空和导出日志消息会降低系统的性能。

切换日志的targets

你可以通过设置[[yii\log\Target::enabled|enabled]] 属性来禁止日志的target。就如同下面的代码描述的一样:

Yii::$app->log->targets['file']->enabled = false;

上面的代码需要你在配置文件里面有一个下面的配置:

return [
'bootstrap' => ['log'],
'components' => [
    'log' => [
        'targets' => [
            'file' => [
                'class' => 'yii\log\FileTarget',
            ],
            'db' => [
                'class' => 'yii\log\DbTarget',
            ],
        ],
    ],
],
];

创建一个新的target

首先,创建一个新的日志target是很简单的。你主要做的事情是实现[[yii\log\Target::export()]] 方法并且把数组类型的消息[[yii\log\Target::messages]]发送到指定的存储媒介上去就行了。在这个过程中你可以调用[[yii\log\Target::formatMessage()]] 方法来格式化每一条日志消息。至于更多的细节你可以在Yiid的发行版本里找到详细的信息。

性能评测

性能评测是一种比较特别的日志记录。它通常用来获取某些模块执行时间的数据,以此来找到性能的问题所在。比如说,[[yii\db\Command]] 这个类就用性能评测日志来获得每一条sql查询所花费的时间。

要使用该类日志,你首先要做的时确定你要测试的代码范围。然后在每一段代码之间你都应该要保持它们是闭合的,就像下面这个样子:

\Yii::beginProfile('myBenchmark');
...code block being profiled...
\Yii::endProfile('myBenchmark');

myBenchmark只是一个标识,用于你在查看对应日志记录的时候快速定位。
在beginProfile和endProfile之间是可以再嵌套的,但是必须保证正确的闭合关系,如下所示:

\Yii::beginProfile('block1');

// some code to be profiled

\Yii::beginProfile('block2');
    // some other code to be profiled
\Yii::endProfile('block2');

\Yii::endProfile('block1');

如果上面的闭合关系出错了,对应的记录都不会正常工作。

对于每一块被评测的代码,日志的level都是profile。你可以再日志的target中配置这些信息并导出它们。 Yii内建了 Yii debugger来展示评测的结果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,364评论 6 343
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,817评论 1 13
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,908评论 0 6
  • 总角 想成为本领超群的齐天大圣七十二变 腾云驾雾 自由及笄 想成为三毛笔下的那棵树从不依靠 从不寻找 潇洒花信 想...
    劉發發阅读 270评论 0 0