Miles' Blog

天涯何處無幹話,何必要講實務話

Handler 的繼承關係如下(使用 YAML 表示):

- HandlerInterface:
- AbstractHandler:
- AbstractProcessingHandler:
- AbstractSyslogHandler:
- SyslogHandler
- SyslogUdpHandler
- AmqpHandler
- BrowserConsoleHandler
- ChromePHPHandler
- CouchDBHandler
- CubeHandler
- DoctrineCouchDBHandler
- DynamoDbHandler
- ElasticSearchHandler
- ErrorLogHandler
- FirePHPHandler
- GelfHandler
- IFTTTHandler
- LogglyHandler
- MailHandler:
- MandrillHandler
- NativeMailerHandler
- SwiftMailerHandler
- MongoDBHandler
- NewRelicHandler
- PHPConsoleHandler
- RavenHandler
- RedisHandler
- RollbarHandler
- SlackbotHandler
- SlackWebhookHandler
- SocketHandler:
- FleepHookHandler
- FlowdockHandler
- HipChatHandler
- LogEntriesHandler
- PushoverHandler
- SlackHandler
- StreamHandler:
- RotatingFileHandler
- TestHandler
- ZendMonitorHandler
- BufferHandler:
- DeduplicationHandler
- FilterHandler
- FingersCrossedHandler
- GroupHandler:
- WhatFailureGroupHandler
- NullHandler
- PsrHandler
- SamplingHandler
- HandlerWrapper

洋洋灑灑列出一長串的 Class 名稱,大部分是單純實作服務的串接,有另一小部分的 Handler 是有特殊用途的,撿幾個來介紹。

TestHandler

這個 Handler 是設計用來做測試的,比方說:

$logger = new \Monolog\Logger('name');

$handler = new \Monolog\Handler\TestHandler();

$logger->pushHandler($handler);

$logger->warning('test');

var_dump($handler->hasAlertRecords());
var_dump($handler->hasWarningRecords());

這樣輸出的結果會是:

bool(false)
bool(true)

它可以作為一個 spy,去確認寫到 Logger 的內容是正確的。

它的設計方法也很單純,在 write 去寫入一個陣列:

protected function write(array $record)
{
$this->recordsByLevel[$record['level']][] = $record;
$this->records[] = $record;
}

而在 assertion 的方法去找陣列有沒有對應的值即可,非常厲害。

GroupHandler

這可以把多個 Handler 集合成一個 Group,而對這個 Group 操作,就等於對全部的 Handler 操作。

NullHandler

它的說明很有趣:

Blackhole

所有的 Log 將會像是遇到無底洞一樣,全部被這個 Handler 吃光光。

這也是設計用來測試用的。

PsrHandler

覺得 Monolog 不好,但有中意其他 PSR Logger。雖然傷心寂寞覺得冷,但還是可以考慮用 Monolog 的 PsrHandler 包裝其他 PSR Logger 哦。

昨天提到 AbstractHandler 會實作存在 Processor 的方法,但實質上 AbstractHandler 是不會使用 Processor 的。

Monolog 的設計是另外寫一個 AbstractProcessingHandler 來繼承 AbstractHandler,在裡面處理 Processor

protected function processRecord(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}

return $record;
}

實作 AbstractProcessingHandler 只要實作 write 方法即可。

其他都是因實作功能需求,把迭代 Processor 的任務放到 handle 的開始、中間、或是後面。

比方說 BufferHandler 是把所有記錄全都放到記憶體裡,直到程序結束後,再一次性的往外送。這種情況下,就會需要在實際 Handler 裡,拿到真正的 record 後,才能跑 Processor,再存 buffer。

昨天了解 Formatter 的運作方法了,而資料夾還有另一個角色 Processor,今天來看看它到底裡面賣的是什麼藥。

從使用它到了解它

Processor 有兩個地方可以使用,分別在 Logger 實作與 HandlerInterface 定義,都有 Processor 的影子。

先來看看 Logger 實作的介面:

class Logger implements LoggerInterface
{
public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);

return $this;
}

public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}

return array_shift($this->processors);
}
}

這裡可以看到,它跟 Handler 一樣是使用 Array Stack 實作儲存。而要成為 Processor 的一員條件是 is_callable

HandlerInterface 的定義也相差不遠:

interface HandlerInterface
{
public function pushProcessor($callback);

public function popProcessor();
}

HandlerInterface 可以依需求實作不同的邏輯,所以我們先來看 Logger 已經寫好的實作。Logger 是這樣使用 Processor 的:

public function addRecord($level, $message, array $context = array())
{
// ...

foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}

// ...
}

Processor 只要實作 Magic Function __invoke 即可當成 callable 來用,比方說 Processor\MemoryUsageProcessor 的內容如下:

class MemoryUsageProcessor extends MemoryProcessor
{
public function __invoke(array $record)
{
$bytes = memory_get_usage($this->realUsage);
$formatted = $this->formatBytes($bytes);

$record['extra']['memory_usage'] = $formatted;

return $record;
}
}

這樣我們就能動態為 $record 加上額外需要的系統資訊了。

而在 Logger 裡,是先跑 Processor,才跑 Handlerhandle 方法,因此在 Logger 的 Processor,實際上會作用在全部的 Handler。

Handler 的實作

使用 IDE 可以簡單找得到,實作的地方在 AbstractHandlerHandlerWrapper。後者是使用類似 Proxy Pattern 的方法在包裝其他的 Handler,所以本質上還是在使用 AbstractHandler 的實作。

AbstractHandler 的實作如下:

public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);

return $this;
}

public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}

return array_shift($this->processors);
}

事實上與 Logger 完全一樣,重點會是在 $this->processors 如何被使用。不同的 Handler 的用法都有點不大一樣,我們留到明天再詳解吧。

做完交付後,下一個目標就是要做部署了!不過部署做簡單一點,在 Docker 上能跑就行了!

閱讀全文 »
0%