pear / log
PEAR 日志框架
Requires
- php: >=7.4
- pear/pear_exception: 1.0.1 || 1.0.2
Requires (Dev)
Suggests
- pear/db: Install optionally via your project's composer.json
README
用户文档
内容
- 1 使用日志处理器
- 2 标准日志处理器
- 3 组合处理器
- 4 日志观察者
- 5 从标准错误处理器进行日志记录
- 6 自定义处理器
1 使用日志处理器
Log 包实现为一个框架,该框架支持特定后端日志处理器的概念。基本日志对象(由 Log 类 定义)主要是当前配置的处理器的抽象接口。
Log 包附带了许多处理器,如果您找不到满足应用程序需求的处理器,可以轻松地 编写自己的处理器。
1.1 创建日志对象
创建日志对象有三种方式
- 使用
Log::factory()
方法- 使用
Log::singleton()
方法- 直接实例化
1.1.1 工厂方法
Log::factory()
方法实现了 工厂模式。它允许在运行时以参数化的方式构建具体的 Log 实例。传递给 Log::factory()
方法的第一个参数指定了要创建的具体处理器名称。其余的参数将传递到处理器的构造函数中(参见下文的 配置处理器)。
返回的是新的 Log
实例的引用。
require_once 'Log.php'; $console = Log::factory('console', '', 'TEST'); $console->log('Logging to the console.'); $file = Log::factory('file', 'out.log', 'TEST'); $file->log('Logging to out.log.');
1.1.2 单例方法
Log::singleton()
方法实现了 单例模式。单例模式确保给定日志类型和配置的实例只创建一个。这有两个好处:首先,它防止了重复创建 Log
实例;其次,它让所有代码都可以访问相同的 Log
实例。后一点在记录到文件时尤为重要,因为只需要管理一个文件处理器。
Log::singleton()
方法的参数与 Log::factory()
方法相同。返回的是新的 Log
实例的引用。
require_once 'Log.php'; /* Same construction parameters */ $a = Log::singleton('console', '', 'TEST'); $b = Log::singleton('console', '', 'TEST'); if ($a === $b) { echo '$a and $b point to the same Log instance.' . "\n"; } /* Different construction parameters */ $c = Log::singleton('console', '', 'TEST1'); $d = Log::singleton('console', '', 'TEST2'); if ($c !== $d) { echo '$c and $d point to different Log instances.' . "\n"; }
1.1.3 直接实例化
也可以直接实例化具体的 Log
处理器实例。然而,这种方法并不推荐,因为它将应用程序代码与 Log 包之间的耦合度变得更紧密,超过了必要的程度。建议使用 工厂方法 或 单例方法。
1.2 配置处理器
日志处理器的配置由其构造时使用的参数确定。以下是这些参数的概述
/* Using the factory method ... */ Log::factory($handler, $name, $ident, $conf, $maxLevel); /* Using the singleton method ... */ Log::singleton($handler, $name, $ident, $conf, $maxLevel); /* Using direct instantiation ... */ new Log_handler($name, $ident, $conf, $maxLevel);
1.3 记录事件
事件使用 log()
方法进行记录
$logger->log('Message', PEAR_LOG_NOTICE);
第一个参数包含日志事件的消息。尽管事件始终以字符串的形式记录,但可以向 log()
方法传递一个对象。如果对象实现了 getString()
方法、toString()
方法或 Zend Engine 2 的特殊 __toString()
类型转换方法,它将被用来确定对象的字符串表示形式。否则,将记录对象的序列化形式。
第二个可选参数指定日志事件的优先级。请参见 日志级别 表以获取完整列表。默认优先级为 PEAR_LOG_INFO。
如果事件成功记录,log()
方法将返回 true
。
还提供了用于在特定日志级别记录事件的“快捷”方法。请参见 日志级别 表以获取完整列表。
1.4 日志级别
该表按最高优先级(PEAR_LOG_EMERG
)到最低优先级(PEAR_LOG_DEBUG
)的顺序排列。
1.5 日志级别掩码
定义一个日志级别掩码可以让你包括和/或排除特定级别的日志事件。构造参数 $level
(参见 配置处理器)使用这种机制来排除低于一定优先级的日志事件,一旦构建了 Log 对象,就可以定义更复杂的掩码。
每个优先级都有一个特定的掩码与之相关联。要计算一个优先级的掩码,请使用静态 Log::MASK()
方法。
$mask = Log::MASK(PEAR_LOG_INFO);
要计算到一定级别(包括该级别)的所有优先级的掩码,请使用静态方法 Log::MAX()
。
$mask = Log::MAX(PEAR_LOG_INFO);
要计算大于或等于一定级别的所有优先级的掩码,请使用静态方法 Log::MIN()
。
$mask = Log::MIN(PEAR_LOG_INFO);
应用掩码时,请使用 setMask()
方法。
$logger->setMask($mask);
可以使用位运算符组合掩码。要仅将记录限制为标记为 PEAR_LOG_NOTICE
或 PEAR_LOG_DEBUG
的事件
$mask = Log::MASK(PEAR_LOG_NOTICE) | Log::MASK(PEAR_LOG_DEBUG); $logger->setMask($mask);
为了方便,预定义了两个特殊掩码:PEAR_LOG_NONE
和PEAR_LOG_ALL
。 PEAR_LOG_ALL
特别适用于排除特定优先级。
$mask = PEAR_LOG_ALL ^ Log::MASK(PEAR_LOG_NOTICE); $logger->setMask($mask);
还可以检索和修改日志对象的现有掩码。
$mask = $logger->getMask() | Log::MASK(PEAR_LOG_INFO); $logger->setMask($mask);
1.6 日志行格式
大多数日志处理器支持可配置的行格式。以下是在运行时使用与日志事件相关的上下文信息展开的特殊标记列表。每个标记还有一个简写符号。
1.7 刷新日志事件
某些日志处理器(如控制台处理器)支持显式的“缓冲”。当启用缓冲时,日志事件实际上不会写入输出流,直到处理器关闭。其他处理器(如文件处理器)由于使用操作系统的IO例程,可能缓冲输出,因此支持隐式缓冲。
然而,可以通过调用其flush()
方法强制这些处理器刷新其输出。
$conf = ['buffering' => true]; $logger = Log::singleton('console', '', 'test', $conf); for ($i = 0; $i < 10; $i++) { $logger->log('This event will be buffered.'); } /* Flush all of the buffered log events. */ $logger->flush(); for ($i = 0; $i < 10; $i++) { $logger->log('This event will be buffered.'); } /* Implicitly flush the buffered events on close. */ $logger->close();
目前,flush()
方法仅由控制台处理器、文件处理器、Firebug处理器和邮件处理器实现。
2 标准日志处理器
2.1 控制台处理器
控制台处理器将日志事件直接输出到控制台。它支持输出缓冲和可配置的字符串格式。
2.1.1 配置
2.1.2 示例
$logger = Log::singleton('console', '', 'ident'); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.2 显示处理器
显示处理器简单地将日志事件打印回浏览器。它尊重error_prepend_string
和error_append_string
错误处理值,并且当从标准错误处理器进行日志记录时非常有用。
2.2.1 配置
2.2.2 示例
$conf = ['error_prepend' => '<font color="#ff0000"><tt>', 'error_append' => '</tt></font>']; $logger = Log::singleton('display', '', '', $conf, PEAR_LOG_DEBUG); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.3 错误日志处理器
错误日志处理器将日志事件发送到PHP的error_log()函数。
2.3.1 配置
2.3.2 Error_Log 类型
PHP手册中详细介绍了所有可用的日志类型。为了方便起见,日志包还定义了以下常量,可以用于$name
处理器构造参数。
2.3.3 示例
$logger = Log::singleton('error_log', PEAR_LOG_TYPE_SYSTEM, 'ident'); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.4 文件处理器
文件处理器使用可配置的字符串格式将日志事件写入文本文件。
2.4.1 配置
如果文件处理器负责创建文件,则它将尝试设置mode
值。
2.4.2 示例
$conf = ['mode' => 0600, 'timeFormat' => '%X %x']; $logger = Log::singleton('file', 'out.log', 'ident', $conf); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.5 Firebug 处理器
Firebug处理器将日志事件输出到Firebug控制台。它支持输出缓冲和可配置的字符串格式。
2.5.1 配置
2.5.2 示例
$logger = Log::singleton('firebug', '', 'ident'); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.6 邮件处理器
邮件处理器将会话的日志事件聚合并使用PEAR Mail包或PHP的本地mail()函数将其发送到邮件消息体。
如果指定了空的mailBackend
值,则将使用mail()函数而不是PEAR Mail包。
可以通过在$name
构造参数中用逗号分隔电子邮件地址来指定多个收件人。
2.6.1 配置
2.6.2 示例
$conf = ['subject' => 'Important Log Events']; $logger = Log::singleton('mail', '[email protected]', 'ident', $conf); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.7 MDB2 处理器
MDB2处理器类似于SQL(DB)处理器,但它使用的是MDB2数据库抽象包而不是PEAR DB包。
2.7.1 配置
2.8 空处理器
空处理器简单地消耗日志事件(类似于将它们发送到/dev/null
)。会尊重日志级别掩码,事件仍将被发送到任何已注册的日志观察者。
2.8.1 示例
$logger = Log::singleton('null'); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.9 SQL (数据库) 处理器
SQL处理器使用PEAR的DB抽象层将日志事件发送到数据库。
注意:由于默认数据库模式的限制,SQL处理器将$ident
字符串的长度限制为十六(16)个字符。此限制可以使用identLimit
配置参数进行调整。
2.9.1 日志表
此处理器默认使用的SQL表如下所示
CREATE TABLE log_table ( id INT NOT NULL, logtime TIMESTAMP NOT NULL, ident CHAR(16) NOT NULL, priority INT NOT NULL, message VARCHAR(200), PRIMARY KEY (id) );
这是应该适用于所有SQL兼容数据库的“最低公共标准”。然而,您可能需要根据特定需求对该模式进行数据库或站点特定的更改。例如,PostgreSQL用户可能更喜欢使用TEXT
类型来表示message
字段。
2.9.2 配置
将日志条目写入的数据库表名称使用$name
构造参数指定(参见配置处理器)。
2.9.3 示例
使用数据源名称创建新的数据库连接
$conf = ['dsn' => 'pgsql://jon@localhost+unix/logs']; $logger = Log::singleton('sql', 'log_table', 'ident', $conf); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
使用现有的DB对象
require_once 'DB.php'; $db = &DB::connect('pgsql://jon@localhost+unix/logs'); $conf['db'] = $db; $logger = Log::singleton('sql', 'log_table', 'ident', $conf); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.10 SQLite 处理器
SQLite处理器使用原生PHP sqlite函数将日志事件发送到SQLite数据库。
它比SQL(DB)处理器更快,因为请求是直接发送到数据库而没有使用抽象层。值得注意的是,SQLite数据库文件可以在您的系统上像其他文件一样移动、复制和删除,这使得日志管理更加容易。最后但同样重要的是,使用数据库来记录事件允许您使用SQL查询来创建报告和统计数据。
当使用数据库并记录大量事件时,建议将数据库分割成更小的数据库。这是SQLite所允许的,您以后可以使用SQLite的ATTACH语句全局查询日志数据库文件。
如果在打开日志时数据库不存在,sqlite将尝试自动创建它。如果日志表不存在,它也将自动创建。表创建使用以下SQL请求
CREATE TABLE log_table ( id INTEGER PRIMARY KEY NOT NULL, logtime NOT NULL, ident CHAR(16) NOT NULL, priority INT NOT NULL, message );
2.10.1 配置
也可以将已打开的数据库连接作为参数传递,而不是上面的配置。在这种情况下,关闭数据库连接由用户负责。
2.10.2 示例
使用配置创建新的数据库连接
$conf = ['filename' => 'log.db', 'mode' => 0666, 'persistent' => true]; $logger = Log::factory('sqlite', 'log_table', 'ident', $conf); $logger->log('logging an event', PEAR_LOG_WARNING);
使用现有连接
$db = sqlite_open('log.db', 0666, $error); $logger = Log::factory('sqlite', 'log_table', 'ident', $db); $logger->log('logging an event', PEAR_LOG_WARNING); sqlite_close($db);
2.11 Syslog 处理器
Syslog处理器将日志事件发送到系统日志服务(Unix-like环境中的syslog或在Windows系统中的事件日志)。事件是通过PHP的syslog()函数发送的。
2.11.1 配置
2.11.2 设施
2.11.3 示例
$logger = Log::singleton('syslog', LOG_LOCAL0, 'ident'); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
2.12 Windows 处理器
Window处理器将日志事件发送到单独的浏览器窗口。这个处理器的原始想法受到了Craig Davis的"JavaScript Power PHP Debugging"文章的启发。
2.12.1 配置
注意:当PHP的输出缓冲系统启用时,Window处理器可能无法可靠地工作。
2.12.2 示例
$conf = ['title' => 'Sample Log Output']; $logger = Log::singleton('win', 'LogWindow', 'ident', $conf); for ($i = 0; $i < 10; $i++) { $logger->log("Log entry $i"); }
3 组合处理器
将事件记录到多个处理器通常很有用。日志包提供了一个组合系统,这使得这项任务变得简单。
首先创建单个日志处理器
$console = Log::factory('console', '', 'TEST'); $file = Log::factory('file', 'out.log', 'TEST');
然后,构建一个组合处理器并将单个处理器作为组合的子处理器添加
$composite = Log::singleton('composite'); $composite->addChild($console); $composite->addChild($file);
组合处理器实现了标准的Log
接口,因此您可以使用它就像使用其他任何处理器一样
$composite->log('This event will be logged to both handlers.');
当子项不再需要时,可以从组合中移除它们
$composite->removeChild($file);
4 日志观察者
日志观察者提供了观察者模式的实现。[观察者模式](http://wikipedia.org/wiki/Observer_pattern "观察者模式")。在日志包的内容中,它们提供了一种机制,可以检查(即观察)每个日志事件。这允许根据日志事件的内容实现特殊行为。例如,如果日志事件包含字符串PANIC
,观察者代码可以发送警报电子邮件。
创建日志观察者需要实现Log_observer
类的子类。子类必须重写基类的notify()
方法。此方法接收一个包含事件优先级和事件的哈希表。子类的实现可以自由地以任何方式处理这些信息。
日志观察者通过attach()
方法附加到Log
实例上
$observer = Log_observer::factory('yourType'); $logger->attach($observer);
可以使用detach()
方法断开观察者的连接
$logger->detach($observer);
目前,Log包中没有提供具体的Log_observer
实现
5 从标准错误处理器进行日志记录
5.1 记录 PHP 错误
可以使用set_error_handler()函数覆盖PHP的默认错误处理器。自定义的错误处理函数可以使用全局日志实例来记录PHP错误。
注意:目前无法通过自定义错误处理器处理致命的PHP错误。
function errorHandler($code, $message, $file, $line) { global $logger; /* Map the PHP error to a Log priority. */ switch ($code) { case E_WARNING: case E_USER_WARNING: $priority = PEAR_LOG_WARNING; break; case E_NOTICE: case E_USER_NOTICE: $priority = PEAR_LOG_NOTICE; break; case E_ERROR: case E_USER_ERROR: $priority = PEAR_LOG_ERR; break; default: $priority = PEAR_LOG_INFO; } $logger->log($message . ' in ' . $file . ' at line ' . $line, $priority); } set_error_handler('errorHandler'); trigger_error('This is an information log message.', E_USER_NOTICE);
5.2 记录 PHP 断言
PHP允许用户定义assert()回调处理器。断言回调通过assert_options()函数配置。
function assertCallback($file, $line, $message) { global $logger; $logger->log($message . ' in ' . $file . ' at line ' . $line, PEAR_LOG_ALERT); } assert_options(ASSERT_CALLBACK, 'assertCallback'); assert(false);
5.3 记录 PHP 异常
PHP 5及以后版本支持异常的概念。可以使用set_exception_handler()函数分配自定义异常处理器。
function exceptionHandler($exception) { global $logger; $logger->log($exception->getMessage(), PEAR_LOG_ALERT); } set_exception_handler('exceptionHandler'); throw new Exception('Uncaught Exception');
5.4 记录 PEAR 错误
可以通过编写使用全局日志实例的错误处理函数,使用PEAR::setErrorHandling()
的PEAR_ERROR_CALLBACK
机制来使用Log包。[这里是一个例子](#)。
function errorHandler($error) { global $logger; $message = $error->getMessage(); if (!empty($error->backtrace[1]['file'])) { $message .= ' (' . $error->backtrace[1]['file']; if (!empty($error->backtrace[1]['line'])) { $message .= ' at line ' . $error->backtrace[1]['line']; } $message .= ')'; } $logger->log($message, $error->code); } PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'errorHandler'); PEAR::raiseError('This is an information log message.', PEAR_LOG_INFO);
6 自定义处理器
有时标准处理器与您的需求并不完全匹配。在这些情况下,解决方案可能是编写自定义处理器。
6.1 使用自定义处理器
使用自定义日志处理器非常简单。一旦编写完成(见下文中的[编写新处理器](#)和[扩展现有处理器](#)),您可以选择将文件放在PEAR安装的Log/
主目录中(通常类似/usr/local/lib/php/Log
或C:\php\pear\Log
),这样它就可以被系统上的任何PHP应用程序找到并使用,或者将其放在应用程序的本地层次结构中的某个位置,并在构建自定义日志对象之前包含它。
6.1.1 方法 1:标准位置的处理程序
将处理器文件复制到PEAR安装的Log/
目录后,只需将处理器视为标准分发的部分即可。如果您的处理器命名为custom
(因此由名为Log_custom
的类实现)
require_once 'Log.php'; $logger = Log::factory('custom', '', 'CUSTOM');
6.1.2 方法 2:自定义位置的处理程序
如果您更喜欢将处理器存储在应用程序的本地层次结构中,您需要在其上创建日志实例之前包含该文件。
require_once 'Log.php'; require_once 'LocalHandlers/custom.php'; $logger = Log::factory('custom', '', 'CUSTOM');
6.2 编写新处理器
编写新的日志处理器就像编写一个新的扩展Log
类的类,并实现一组相对标准的函数。
为了使日志包能够识别,每个处理器的类名必须以Log_
开头。
class Log_custom extends Log
处理器的构造函数将使用四个参数调用。这些值在[配置处理器](#)部分中详细讨论。
Log_custom($name, $ident = '', $conf = [], $level = PEAR_LOG_DEBUG)
构造函数负责根据这些值配置处理程序。特定于处理程序的自定义参数作为$conf
数组的一部分传递。至少,处理程序的构造函数必须设置由Log
基类定义的以下值。
$this->id = md5(microtime().rand()); $this->name = $name; $this->ident = $ident; $this->mask = Log::MAX($level);
下面的处理程序方法部分详细说明了可以由日志处理程序实现的多种标准方法。在实用方法部分中,描述了一些由Log
基类提供的有用实用方法,这些方法在实现日志处理程序时可能很有用。
6.3 扩展现有处理器
扩展现有处理程序与编写新处理程序非常相似,只是处理程序不是直接从Log
基类继承,而是扩展现有处理程序的类。这是一种在不完全编写新类的情况下添加一些自定义行为到现有处理程序的有用方式(遵循面向对象编程的精神)。
例如,可以将邮件处理程序扩展以支持发送带有MIME编码附件的消息,只需创建一个符合规定的构造函数和自定义的log()
方法的Log_mail_mime
新类。其余的标准方法将回退到Log_mail
基类的实现。
显然,扩展现有处理程序的具体细节需要对处理程序的实现有良好的理解。
6.4 处理器方法
6.4.1 bool open()
调用open()
方法以打开日志资源进行输出。处理程序可以在构造时立即调用open()
,或者在运行时延迟调用(例如,当接收到第一个日志事件时)。
Log
基类提供一个受保护的$_opened
成员变量,当日志处理程序打开时应将其设置为true
,关闭时应设置为false
。处理程序方法可以检查此值以确定处理程序是否当前打开且准备输出。
如果open()
方法未能为输出准备处理程序,则应返回false
并将$this-_opened
设置为false
。
6.4.2 bool close()
调用close()
方法以关闭日志资源。此方法与open()
方法类似。即使处理程序未打开以进行输出,也可以安全地调用close()
。
如果close()
方法未能关闭处理程序,则应返回false
。否则,应返回true
。还应适当地更新$this->opened
标志。
6.4.3 bool flush()
flush()
方法清除任何缓存的日志事件,如清除日志事件中所述。此方法的实现将很大程度上取决于处理程序。如果处理程序不支持缓冲输出,则不需要实现此方法;将调用Log
类的flush()
方法。
6.4.4 bool log($message, $priority = null)
log()
方法是每个日志处理程序的核心。当用户希望记录一个事件时,会调用此方法。此方法的具体实现非常依赖于处理程序。它应返回true
或false
,取决于消息是否成功由处理程序记录。
log()
实现应确保在事件成功记录时始终调用announce()。
6.5 实用方法
这些实用方法由Log
基类提供,并为处理程序实现提供常见的、有用的功能。
6.5.1 string extractMessage($message)
此方法返回提供消息数据的字符串表示形式。
如果$message
是一个对象,_extractMessage()
将尝试使用已知方法(例如PEAR_Error对象的getMessage()方法)提取消息文本。如果找不到已知方法,将返回对象的序列化表示形式。
如果消息数据已经是字符串,则将其返回不变。
6.5.2 string format($format, $timestamp, $priority, $message)
此方法基于格式字符串和一组代表当前日志记录和状态的令牌生成格式化的日志行。
6.5.3 bool isMasked($priority)
此方法检查给定的优先级是否包含在处理器当前级别掩码中。这对于确定是否将日志事件写入处理器日志很有用。
6.5.4 void announce($event)
此方法通知任何已注册的日志观察者已记录新事件。$event
是一个包含两个命名元素的数组
['priority' => $priority, 'message' => $message]
announce()
应从处理器的方法中调用,例如在事件成功记录时调用log()
方法。否则,注册的观察者将永远不会意识到该事件。