Monolog sends your logs to files, sockets, inboxes, databases and various web services.
Monolog 發送你的日誌到檔案、到sockets、到郵箱、到資料庫或(和)者其他網路儲存服務(雲)。Monolog可以做到同時儲存到一個或多個儲存介質(後面的棧冒泡處理)。
安裝
$ composer require monolog/monolog
基本用法 (初步印象)
pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));// add records to the log$log->warning('Foo');$log->error('Bar');
核心概念 ¶官方解釋
Every Loggerinstance has a channel (name) and a stack of handlers. Whenever you add a record to the logger, it traverses the handler stack. Each handler decides whether it fully handled the record, and if so, the propagation of the record ends there.
每一個 Logger執行個體都有一個 通道(也就是一個唯一的名稱)和一個有由一個或多個處理常式組成的 棧。當我們添加一個記錄到 Logger的時候,它會遍曆這個處理常式棧。每一個處理常式決定是否去充分處理這個記錄,如果是,則處理到此為止(停止冒泡)。這裡的 充分指的是我們想不想了,想的話就繼續,不想就停止。
這就允許我們靈活的設定日誌了。比如我們有一個 StreamHandler,它在 棧的最底部,它會把記錄都儲存到硬碟上,在它上面有一個 MailHandler,它會在錯誤訊息被記錄的時候發送郵件。 Handlers都有一個 $bubble屬性,用來定義當某個處理常式在處理記錄的時候是否阻塞處理(阻塞的話,就是這個記錄到我這裡就算處理完畢了,不要冒泡處理了,聽話)。在這個例子中,我們設定 MailHandler的 $bubble為 false,意思就是說記錄都會被 MailHandler處理,不會冒泡到 StreamHandler了。
補充一下:這裡提到了棧,也提到了冒泡,乍一看有點暈,因為我們理解冒泡是自下而上的過程,棧就是一個類似杯子的容器,然後上面又說底部是 StreamHandler,上面是 MailHandler,結果是 MailHandler處理了,停止冒泡到 StreamHandler了,給人的感覺是這個泡是從上往下冒的,666,這能叫冒泡嗎? 英雄時刻:堆棧啥的,我也偶爾混淆,這裡再次謹記,堆是先進先出( First- In/ First- Out),想想[自來]水管;棧就是先進後出( First- In/ Last- Out),想想一個有N層顏色的冰淇淋裝在一個杯子裡,下面是黃色的,…,最上面是粉紅的,所以,你先吃得是粉紅色的( MailHandler),後吃的是黃色的( StreamHandler),實際上,這個泡冒的沒錯,確切的說,這個泡冒在了一個倒立的杯子中,這樣理解就形象了。
繼續…
我們可以建立很多 Logger,每個 Logger定義一個通道(e.g.:db,request,router,…),每個通道可結合多個Handler,Handler可以被寫成可通用的或者不可通用的。通道,同日誌中日期時間一樣,它是一個名稱,在日誌中就是一個字串被記錄下來,大概是這樣 2016-04-25 12:33:00 通道名稱 記錄內容,具體格式看設定了,可以用來識別或者過濾。
每一個Handler都有一個 Formatter,用來格式化日誌了。不詳細介紹了。
自訂日誌等級在monolog中不可用,只有8種 RFC 5424等級,即 debug, info, notice, warning, error, critical, alert, emergency。但是如果我們真的有特殊需求的話,比如歸類等,我們可以添加 Processors到 Logger,當然是在日誌訊息被處理之前。 我這估計這輩子都不會添加`Processors`。
日誌等級
- DEBUG (100): Detailed debug information.詳細的Debug資訊
- INFO (200): Interesting events. Examples: User logs in, SQL logs.感興趣的事件或資訊,如使用者登入資訊,SQL日誌資訊
- NOTICE (250): Normal but significant events.普通但重要的事件資訊
- WARNING (300): Exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
- ERROR (400): Runtime errors that do not require immediate action but should typically be logged and monitored.
- CRITICAL (500): Critical conditions. Example: Application component unavailable, unexpected exception.
- ALERT (550): Action must be taken immediately. Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
- EMERGENCY (600): Emergency: system is unusable.
配置一個Logger
Here is a basic setup to log to a file and to firephp on the DEBUG level:
pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG));$logger->pushHandler(new FirePHPHandler());// You can now use your logger$logger->addInfo('My logger is now ready');
我們來分析一下這個配置。 The first step is to create the logger instance which will be used in your code. The argument is a channel name, which is useful when you use several loggers (see below for more details about it). 第一步,建立 Logger執行個體,參數即通道名字。
The logger itself does not know how to handle a record. It delegates it to some handlers. The code above registers two handlers in the stack to allow handling records in two different ways. Logger本身不知道如何處理記錄,它將處理委託給 Handler[s],上面的代碼註冊了兩個 Handlers,這樣就可以用兩種方法來處理記錄。
Note that the FirePHPHandler is called first as it is added on top of the stack. This allows you to temporarily add a logger with bubbling disabled if you want to override other configured loggers. 提示: FirePHPHandler最先被調用,因為它被添加在棧的頂部。這就允許你臨時添加一個阻塞的 Logger,如果你想覆蓋其他 Logger[s]的話。
添加額外的資料到記錄
Monolog 提供兩種方法來添加額外的資訊到簡單的文本資訊(along the simple textual message)。
使用日誌上下文
第一種,即當前日誌上下文,允許傳遞一個數組作為第二個參數,這個數組的資料是額外的資訊:
addInfo('Adding a new user', array('username' => 'Seldaek'));
簡單的Handler(SteamHandler)會簡單的將數組格式化為字串,功能豐富點的Handler(FirePHP)可以搞得更好看。
使用 processors
Processors 可以是任何可調用的方法(回調)。它們接受 $record作為參數,然後返回它( $record),返回之前,即是我們添加 額外資訊的操作,在這裡,這個操作是改變 $record的 extrakey的值。像這樣:
pushProcessor(function ($record) { $record['extra']['dummy'] = 'Hello world!'; return $record;});
Monolog 提供了一些內建的 processors。看 dedicated chapter收回我說的話,我可能很快就會用到 Processors的。
使用通道
通道是識別record記錄的是程式哪部分的好方法(當然,關鍵詞匹配啊),這在大型應用中很有用,如 MonologBundle in Symfony2。
想象一下,兩個 Logger共用一個 Handler,通過這個 Handler將記錄寫入一個檔案。這時使用通道能夠讓我們識別出是哪一個 Logger處理的。我們可簡單的在這個檔案中過濾這個或者那個通道。
pushHandler($stream);$logger->pushHandler($firephp);// Create a logger for the security-related stuff with a different channel$securityLogger = new Logger('security');$securityLogger->pushHandler($stream);$securityLogger->pushHandler($firephp);// Or clone the first one to only change the channel$securityLogger = $logger->withName('security');
自訂日誌格式
在 Monolog 中個人化日誌是很easy的。大部分 Handler 使用 $record['formatted']的值。這個值依賴於 formatter 的設定。我們可以選擇預定義的 formatter 類或者編寫自己的。
配置一個預定義的 formatter 類,只需要將其設定成 Handler 的欄位(屬性)即可:
// the default format is "Y-m-d H:i:s"$dateFormat = "Y n j, g:i a";// the default output format is [%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"$output = "%datetime% > %level_name% > %message% %context% %extra%\n";$formatter = new LineFormatter($output, $dateFormat);// Create a handler$stream = new StreamHandler(__DIR__ . 'my_app.log', Logger:DEBUG);$stream->setFormatter($formatter);// bind it to a logger object$securityLogger = new Logger('security');$securityLogger->pushHandler($stream);
formatter 是可以在N個 Handler 之間複用的,並且可在N個 Logger 之間共用 Handler。
Handlers
記錄日誌到檔案與系統日誌(syslog)
- StreamHandler:記錄日誌到任何 PHP stream,用它來記錄到檔案。
- RotatingFileHandler: 每天一個檔案,會自動刪除比 $maxFiles老的檔案,這隻是一個很隨意的方案,You should use logrotate for high profile setups though。
- SyslogHandler: 記錄到系統日誌
- ErrorLogHandler: Logs records to PHP’s error_log() function.
擴充一下…
發送提醒與郵件
看看就能理解
- NativeMailerHandler: Sends emails using PHP’s mail() function.
- SwiftMailerHandler: Sends emails using a Swift_Mailer instance.
- PushoverHandler: Sends mobile notifications via the Pushover API.
- HipChatHandler: Logs records to a HipChat chat room using its API.
- FlowdockHandler: Logs records to a Flowdock account.
- SlackHandler: Logs records to a Slack account.
- MandrillHandler: Sends emails via the Mandrill API using a Swift_Message instance.
- FleepHookHandler: Logs records to a Fleep conversation using Webhooks.
- IFTTTHandler: Notifies an IFTTT trigger with the log channel, level name and message.
記錄到指定server與部落格
接著看看
SocketHandler: Logs records to sockets, use this for UNIX and TCP sockets. See an example. AmqpHandler: Logs records to an amqpcompatible server. Requires the php-amqpextension (1.0+). GelfHandler: Logs records to a Graylog2server. CubeHandler: Logs records to a Cubeserver. RavenHandler: Logs records to a Sentryserver using raven. ZendMonitorHandler: Logs records to the Zend Monitor present in Zend Server. NewRelicHandler: Logs records to a NewRelicapplication. LogglyHandler: Logs records to a Logglyaccount. RollbarHandler: Logs records to a Rollbaraccount. SyslogUdpHandler: Logs records to a remote Syslogdserver. LogEntriesHandler: Logs records to a LogEntriesaccount.
開發環境中,利用瀏覽器延伸
裝擴充
FirePHPHandler: Handler for FirePHP, providing inline console messages within FireBug. ChromePHPHandler: Handler for ChromePHP, providing inline console messages within Chrome. BrowserConsoleHandler: Handler to send logs to browser’s Javascript console with no browser extension required. Most browsers supporting console API are supported. PHPConsoleHandler: Handler for PHP Console, providing inline console and notification popup messages within Chrome.
記錄到資料庫
顧名思義
RedisHandler: Logs records to a redisserver. MongoDBHandler: Handler to write records in MongoDB via a Mongoextension connection. CouchDBHandler: Logs records to a CouchDB server. DoctrineCouchDBHandler: Logs records to a CouchDB server via the Doctrine CouchDB ODM. ElasticsearchHandler: Logs records to an Elastic Search server. DynamoDbHandler: Logs records to a DynamoDB table with the AWS SDK.
特殊的Handler
慢慢看
FingersCrossedHandler: A very interesting wrapper. It takes a logger as parameter and will accumulate log records of all levels until a record exceeds the defined severity level. At which point it delivers all records, including those of lower severity, to the handler it wraps. This means that until an error actually happens you will not see anything in your logs, but when it happens you will have the full information, including debug and info records. This provides you with all the information you need, but only when you need it.
DeduplicationHandler: Useful if you are sending notifications or emails when critical errors occur. It takes a logger as parameter and will accumulate log records of all levels until the end of the request (or flush() is called). At that point it delivers all records to the handler it wraps, but only if the records are unique over a given time period (60seconds by default). If the records are duplicates they are simply discarded. The main use of this is in case of critical failure like if your database is unreachable for example all your requests will fail and that can result in a lot of notifications being sent. Adding this handler reduces the amount of notifications to a manageable level.
WhatFailureGroupHandler: This handler extends the GroupHandlerignoring exceptions raised by each child handler. This allows you to ignore issues where a remote tcp connection may have died but you do not want your entire application to crash and may wish to continue to log to other handlers.
BufferHandler: This handler will buffer all the log records it receives until close() is called at which point it will callhandleBatch() on the handler it wraps with all the log messages at once. This is very useful to send an email with all records at once for example instead of having one mail for every log record.
GroupHandler: This handler groups other handlers. Every record received is sent to all the handlers it is configured with.
FilterHandler: This handler only lets records of the given levels through to the wrapped handler.
SamplingHandler: Wraps around another handler and lets you sample records if you only want to store some of them.
NullHandler: Any record it can handle will be thrown away. This can be used to put on top of an existing handler stack to disable it temporarily.
PsrHandler: Can be used to forward log records to an existing PSR-3 logger
TestHandler: Used for testing, it records everything that is sent to it and has accessors to read out the information.
HandlerWrapper: A simple handler wrapper you can inherit from to create your own wrappers easily.
Formatters
✪ 為常用
- LineFormatter: Formats a log record into a one-line string. ✪
- HtmlFormatter: Used to format log records into a human readable html table, mainly suitable for emails.✪
- NormalizerFormatter: Normalizes objects/resources down to strings so a record can easily be serialized/encoded.
- ScalarFormatter: Used to format log records into an associative array of scalar values.
- JsonFormatter: Encodes a log record into json.✪
- WildfireFormatter: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.
- ChromePHPFormatter: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.
- GelfMessageFormatter: Used to format log records into Gelf message instances, only useful for the GelfHandler.
- LogstashFormatter: Used to format log records into logstash event json, useful for any handler listed under inputs here.
- ElasticaFormatter: Used to format log records into an Elastica\Document object, only useful for the ElasticsearchHandler.
- LogglyFormatter: Used to format log records into Loggly messages, only useful for the LogglyHandler.
- FlowdockFormatter: Used to format log records into Flowdock messages, only useful for the FlowdockHandler.
- MongoDBFormatter: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler.
Processors
-
PsrLogMessageProcessor: Processes a log record’s message according to PSR-3 rules, replacing {foo} with the value from $context[‘foo’].
-
IntrospectionProcessor: Adds the line/file/class/method from which the log call originated.
-
WebProcessor: Adds the current request URI, request method and client IP to a log record.
-
MemoryUsageProcessor: Adds the current memory usage to a log record.
-
MemoryPeakUsageProcessor: Adds the peak memory usage to a log record.
-
ProcessIdProcessor: Adds the process id to a log record.
-
UidProcessor: Adds a unique identifier to a log record.
-
GitProcessor: Adds the current git branch and commit to a log record.
-
TagProcessor: Adds an array of predefined tags to a log record.
Utilities
-
Registry: The Monolog\Registryclass lets you configure global loggers that you can then statically access from anywhere. It is not really a best practice but can help in some older codebases or for ease of use. Monolog\Registry允許我們配置全域的 logger,並且我們可以全域靜態訪問,這雖然不是最佳實務,但可以在某些老的程式碼程式庫中提供一些協助或者僅僅只是簡單使用。
-
ErrorHandler: The Monolog\ErrorHandlerclass allows you to easily register a Logger instance as an exception handler, error handler or fatal error handler. Monolog\ErrorHandler允許我們註冊一個 Logger執行個體作為一個異常處理控制代碼,錯誤處理控制代碼或者致命錯誤處理控制代碼。
-
ErrorLevelActivationStrategy: Activates a FingersCrossedHandler when a certain log level is reached. 當達到某個日誌等級的時候啟用 FingersCrossedHandler。
-
ChannelLevelActivationStrategy: Activates a FingersCrossedHandler when a certain log level is reached, depending on which channel received the log record. 當達到某個日誌等級的時候啟用 FingersCrossedHandler,取決於哪個通道收到日誌資訊。
Extending Monolog
Monolog 是完全可以擴充的。可以輕鬆讓logger適用於我們的需求。
編寫自己的 Handler
雖然 Monolog 提供了很多內建的 Handler,但是我們依然可能沒有找到我們想要的那個,這時我們就要來編寫並使用自己的了。僅需 implement Monolog\Handler\HandlerInterface。
來寫個 PDOHandler,用來把日誌存到資料庫,我們繼承 Monolog 提供的抽象類別,以堅守 Don’t Rpeat Yourself原則。
pdo = $pdo; parent::__construct($level, $bubble); } protected function write(array $record) { if (!$this->initialized) { $this->initialize(); } $this->statement->execute(array( 'channel' => $record['channel'], 'level' => $record['level'], 'message' => $record['formatted'], 'time' => $record['datetime']->format('U'), )); } private function initialize() { $this->pdo->exec( 'CREATE TABLE IF NOT EXISTS monolog ' .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' ); $this->statement = $this->pdo->prepare( 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' ); $this->initialized = true; } }
現在就可以在Logger中使用這個Handler了:
$logger->pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')));// You can now use your logger$logger->addInfo('My logger is now ready');
Monolog\Handler\AbstractProcessingHandler提供了Handler需要的大部分邏輯,包括processors的使用以及record的格式化(which is why we use $record['formatted']instead of $record['message'])。