在研究Magento 2的各种模块时,您会发现日志记录的使用频率比Magento 1少得多。这主要是由于日志记录变得更加困难。 在这里,我想集中讨论该问题的技术方面,即如何记录数据,如何将日志写入自己的文件以及什么是Monolog。
目录
独白
Magento 2中的应用程序功能
实作
使用标准记录器进行记录
使用带有自定义渠道的标准记录器进行记录
使用自己的处理程序写入自定义文件
使用virtualType写入自定义文件
快速数据记录
结论
独白
让我们从最重要的问题开始-什么是Monolog,它来自哪里。
Monolog-这是一个实现PSR-3标准以进行数据记录的库。 Magento 2中使用的是Monolog来记录日志。
反过来,
PSR-3是描述数据记录的通用方法的标准,以及实现具有公共接口的记录器的建议。
PSR-3亮点1.记录器(对象)必须实现\ Psr \ Log \ LoggerInterface接口。
2.我们有以下错误级别(按从高到低的优先级顺序显示):
紧急 -系统无法使用。
警报 -必须立即采取行动。 例如:整个网站关闭,数据库不可用等。
严重-严重情况。 示例:应用程序组件不可用,意外异常。
错误 -运行时错误,不需要立即采取措施,但通常应对其进行监视。
警告 -并非错误的异常事件,例如:使用已弃用的API。
注意 -正常但重要的事件。
信息 -有趣的事件。 示例:用户登录,SQL登录。
DEBUG-详细的调试信息。
3.每个级别都有自己的方法(调试,信息,通知,警告,错误,严重,警报,紧急/紧急情况),并且还应该有一个日志方法,它将错误级别作为第一个参数。
4.这些方法接受字符串或任何实现__toString()的东西(也就是说,您必须为数组手动使用print_r($ message,true),或者将它们传递到下一个参数中)。
5.所有方法都接受$上下文数组来补充日志。
6.可以但不一定要实现将$上下文数组中的数据替换为消息。 在这种情况下,建议使用{name}格式,其中name-> $上下文中数组的键。
Monolog非常易于使用。 让我们看下面的例子。
use Monolog\Logger; use Monolog\Handler\StreamHandler; use Monolog\Formatter\HtmlFormatter;
请记住Monolog的工作重点:
由于PSR-3不强制开发人员强制执行文本中的自动更正值,因此Monolog默认情况下不执行此操作。 如果您编写-> emerg('test 1111 {placeholder}',['placeholder'=>'foo']),您将得到以下内容
[2019-08-12 02:57:52] main.EMERGENCY:test 1111 {placeholder} {“ placeholder”:“ foo”} []
为了使替换正常工作,您需要连接一个附加处理器-\ Monolog \ Processor \ PsrLogMessageProcessor。
值得一提的是,Monolog具有大量开箱即用的Formatter,Processor,Handler。 您可以使用它们,也可以自己编写。
Magento 2中的应用程序功能
在
Magento官方网站上,您可以找到有关如何使用记录仪的一般示例。 不幸的是,所提供的示例并没有揭示所有细节,而且也没有回答“如何将日志写入您自己的文件”这一问题。 因此,让我们更详细地了解所有内容。
在Magento 1时代,可能早晚每个人都使用Mage :: log方法,该方法在代码中随处可见,最简单的日志条目看起来像Mage :: log(“ ALARM!”,Null和“ api.log”)。 结果,我们在var / log / api.log文件中记录了以下格式的记录
2019-08-12T01:00:27+00:00 DEBUG (7): ALARM!
默认格式:%timestamp %% priorityName%(%priority%):%message%。让我们看看如何在Magento 2中最简单的情况下记录数据。通常,您将使用$ this-> _ logger-> info('ALARM!'); (例如,如果对象具有这样的属性,则继承)。
作为这样的调用的结果,我们在var / log / system.log文件中获得以下条目
[2019-08-12 02:56:43] main.INFO: ALARM! [] []
默认格式为[%datetime%]%channel%。%Level_name%:%message %% context %% extra%如果对象不具有这样的属性(_logger或logger),那么我们首先需要将依赖项\ Psr \ Log \ LoggerInterface添加到您的类中,然后将结果对象写入$ logger属性(根据PSR-2第4.2点和Magento网站上的示例 ) 。
与Magento 1不同,这里有更多细微差别。
1.记录参数。考虑对write方法的一般调用
$this->_logger->{level}($message, $context = []);
1)根据PSR-3,{level}是保留用于记录特定错误级别(调试,信息,通知,警告,错误,严重,警报,紧急/紧急情况)的方法之一。
2) $ message-与Magento 1不同,它应该是一个字符串。 即 $ object-> getData()在这里不起作用。 数据数组必须传递给下一个参数。 \ Exception对象是一个例外,因为\ Magento \ Framework \ Logger \ Monolog的实现单独处理它们,并且如果\ Exception对象作为消息传递,则自动将-> getMessage()进一步滚动为$ message。
3) $ context是一个可选参数,一个数组。
2. $ this-> _ logger属性并非在所有类中都可用。出现在:块,助手,模型,集合等。
在以下
版本中
不可用 :ResourceModel,Controller,Comand,Setup等。
了解有关ResourceModel和Collection的更多信息。ResourceModel具有_logger属性,但未在构造函数中填充。 仅使用\ Magento \ Framework \ Model \ ResourceModel \ AbstractResource中的私有getLogger方法填充它。 仅当在commit()方法内写入数据库(在catch块中)时出错时,才调用该方法。 在此之前,资源模型将没有记录器。
public function commit() { $this->getConnection()->commit(); if ($this->getConnection()->getTransactionLevel() === 0) { $callbacks = CallbackPool::get(spl_object_hash($this->getConnection())); try { foreach ($callbacks as $callback) { call_user_func($callback); } } catch (\Exception $e) { $this->getLogger()->critical($e); } } return $this; } … private function getLogger() { if (null === $this->_logger) { $this->_logger = ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); } return $this->_logger; }
Collection从一开始就有记录器。 它是在构造函数\ Magento \ Framework \ Data \ Collection \ AbstractDb中分配的,并在以后继承。
不能不说,但是在控制器中,有一种方法可以使用ObjectManager获取记录器(通过$ this-> _ objectManager属性)。 但这当然不是最正确的方法。
3.默认记录器和处理程序列表。在全局di.xml(应用程序/etc/di.xml)中,您可以找到\ Psr \ Log \ LoggerInterface由\ Magento \ Framework \ Logger \ Monolog类实现,而后者又继承自\ Monolog \ Logger。 记录器名称为main。 在那里也定义了几个处理程序。
… <preference for="Psr\Log\LoggerInterface" type="Magento\Framework\Logger\Monolog" /> ... <type name="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">main</argument> <argument name="handlers" xsi:type="array"> <item name="system" xsi:type="object">Magento\Framework\Logger\Handler\System</item> <item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item> <item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item> </argument> </arguments> </type> ...
一些类与上面列出的类不同(因为它们在Magento \ Developer模块中重新定义):
1) Magento \ Framework \ Logger \ Handler \ System(
监听INFO)2) Magento \开发人员\模型\记录器\处理程序\调试(
侦听DEBUG )
3) Magento \ Developer \模型\ Logger \ Handler \ Syslog(
侦听DEBUG )
在指定的类(Debug和Syslog)中
,添加了禁用日志记录的功能 (分别是dev / debug / debug_logging和dev / syslog / syslog_logging)。
请注意,在处理程序列表中没有写入到exception.log的异常处理程序。 在系统处理程序中调用它。
Magento \ Framework \ Logger \ Handler \系统 ... public function write(array $record) { if (isset($record['context']['exception'])) { $this->exceptionHandler->handle($record); return; } $record['formatted'] = $this->getFormatter()->format($record); parent::write($record); } ...
Magento 2到2.2 的问题是记录器在找到第一个处理程序后无法跳转到另一个处理程序。 此问题是由以下事实引起的:Monolog计算出,所有处理程序都使用数字键组成一个数组,并带有字母键(['system'=>,'debug'=>,...])。 Magento开发人员后来纠正了这种情况-在将哈希传递给Monolog之前,他们使用数字键将哈希转换为常规数组。 Monolog现在也更改了处理程序枚举算法,并使用next()方法。
4.将您的处理程序引入现有处理程序列表中。我们想到了最有趣的事情,它稍微破坏了Magento 2中的实现印象。如果没有...“附加的身体运动”,就不能使用di.xml将自定义处理程序添加到现有处理程序列表中。 这是由于合并配置的原理。
有几种
配置范围 :
1)初始(app /etc/di.xml)
2)全局({moduleDir} /etc/di.xml)
3)特定于区域({moduleDir} / etc / {area} /di.xml,即前端/ adminhtml / crontab / webapi_soap / webapi_rest等)
在第1层内部,配置被合并,但是在下一个层中通过merge重新定义了它们(如果在此也声明了它们)。 这使得不可能在其模块中将处理程序添加到现有列表中,因为它是在初始作用域中声明的。
也许在将来,我们将看到一个实现,在该实现中,处理程序的添加将从初始作用域移至其他模块,从而转移到全局作用域。实作
让我们看一下记录日志的主要方式,这对我们在任务执行中可能很有用。
1.使用标准记录器记录
这种方法使我们可以轻松地在标准日志之一(debug.log,system.log或exception.log)中写入日志。
class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong');
如果我们的类中已经存在继承的记录器依赖项,一切将变得更加简单。
… $this->_logger->info('Something went wrong');
2.使用带有自定义通道的标准记录器进行记录
此方法与前一个方法的不同之处在于,创建了记录器的克隆,并为其分配了另一个通道(名称)。 这将简化日志文件中的搜索。
class RandomClass { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger->withName('api');
要搜索必要的日志,现在就可以在现有system.log,debug.log,exception.log文件中使用“ api”(Magento 2中的默认记录器称为main)进行搜索。 可以使用
grep -rn 'api' var/log/system.log
3.使用自己的处理程序写入自定义文件
让我们创建一个简单的处理程序,将所有Critical级及更高级别的错误记录到单独的文件var / log / critical.log中。 添加了针对给定的错误级别或更高级别阻止所有其他处理程序的功能。 这将避免在debug.log和system.log文件中重复数据。
<?php namespace Oxis\Log\Logger\Handler; use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Logger\Handler\Base; use Monolog\Logger; class Test extends Base { protected $fileName = 'var/log/critical.log'; protected $loggerType = Logger::CRITICAL; public function __construct(DriverInterface $filesystem) { parent::__construct($filesystem,null,null); $this->bubble = false;
在Magento 2 2.2+中的构造函数\ Magento \ Framework \ Logger \ Handler \ Base中,处理日志文件路径的方式已更改
因此,在较旧的处理程序中,您仍然可以在$ fileName的开头找到/。
例如,一些解释是值得给出的。 由于Base不允许您通过构造函数参数设置bubble属性,因此我们要么必须重复Base构造函数中的部分代码,以将输入参数正确地传递给Base类的父类(顺便说一句,它具有用于设置此属性的输入参数)或这样的方法。 我选择了第二个选项。
use Oxis\Log\Logger\Handler\Test; use Psr\Log\LoggerInterface; class RandomClass { private $logger; public function __construct( LoggerInterface $logger, Test $handler ) { $logger->pushHandler($handler);
这种添加处理程序的方法并不理想,但是可以让您摆脱问题的Config Scope的约束,这将要求我们复制di.xml中的所有记录器。 如果目标是用您自己的记录器替换所有记录器,那么最好使用virtualType方法,我们将进一步考虑。
4.使用virtualType写入自定义文件
这种方法使我们可以强制使用di.xml为此所需的类将日志写入指定的日志文件。 您可以在Magento \ Payment和Magento \ Shipping模块中找到类似的方法。 我提请您注意以下事实:该方法从Magento 2 2.2及更高版本开始起作用。
在Magento 2 2.2及更高版本中,向构造函数\ Magento \ Framework \ Logger \ Handler \ Base中添加了新参数,该参数允许您创建虚拟处理程序并通过di.xml指定用于写入日志的文件的相对路径。 以前,需要通过$ filePath指定完整路径,或者创建新的处理程序并将相对路径写入受保护的文件$ fileName属性。
在我们模块的di.xml中,添加以下内容
<virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <type name="Oxis\Log\Model\A"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </type>
将记录器类添加到Oxis \ Log \ Model \A。
namespace Oxis\Log\Model; class A { private $logger; public function __construct(\Psr\Log\LoggerInterface $logger) { $this->logger = $logger; } public function foo() { $this->logger->info('Something went wrong'); } }
现在,绝对会在我们的类中记录的所有日志都将由我们的记录器版本处理,该记录器将使用我们的处理程序将日志写入var /log/api.log文件。
4.1。 如果该类通过$上下文对象而不是通过其构造函数接收记录器。这可能包括\ Magento \ Catalog \ Model \ Product,在其中没有\ Psr \ Log \ LoggerInterface的依赖项,但是有\ Magento \ Framework \ Model \ Context,通过它可以将记录器设置为class属性。 在这种情况下,我们需要使选项稍微复杂一些,并替换位于$上下文对象中的记录器。 为了使这不会影响整个Magento,我们将只使用virtualType替换类的$ context。
<virtualType name="ApiHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> <argument name="fileName" xsi:type="string">var/log/api.log</argument> </arguments> </virtualType> <virtualType name="ApiLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="name" xsi:type="string">api</argument> <argument name="handlers" xsi:type="array"> <item name="default" xsi:type="object">ApiHandler</item> </argument> </arguments> </virtualType> <virtualType name="ApiLogContainingContext" type="Magento\Framework\Model\Context"> <arguments> <argument name="logger" xsi:type="object">ApiLogger</argument> </arguments> </virtualType> <type name="Oxis\Log\Model\A"> <arguments> <argument name="context" xsi:type="object">ApiLogContainingContext</argument> </arguments> </type>
5.快速的数据记录
有时我们需要快速添加日志记录。 通常,在生产服务器上或快速测试中可能需要这样做。
... $log = new \Monolog\Logger('custom', [new \Monolog\Handler\StreamHandler(BP.'/var/log/custom.log')]); $log->error('test'); ...
这种方法的优点:写一个日期,有一个上下文(数组),自动在末尾添加\ n在上面的示例中,\ Monolog \ Logger是专门应用的,而不是\ Magento \ Framework \ Logger \ Monolog对其进行扩展。 事实是,这种用法没有什么区别,但是写的更少(记住起来更容易)。
依次使用\ Monolog \ Handler \ StreamHandler代替\ Magento \ Framework \ Logger \ Handler \ Base,因为由于对第三方类的附加依赖性,使用Base作为代码片段不是很方便。
另一个不能说的方法是旧的file_put_contents。
... file_put_contents(BP.'/var/log/custom.log', 'test',FILE_APPEND); ...
这种方法的优点:写得相对较快,不需要记住类。在这两种情况下,常数
BP都起着主导作用。 她总是指向洋红色(比酒吧高1级)的文件夹,这很方便,而且总是可以帮助我们将日志写到正确的位置。
结论
希望以上信息对您有所帮助。