LoggerTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of the Monolog package.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Monolog;
  11. use Monolog\Handler\HandlerInterface;
  12. use Monolog\Processor\WebProcessor;
  13. use Monolog\Handler\TestHandler;
  14. use Monolog\Test\TestCase;
  15. use PHPUnit\Framework\Attributes\DataProvider;
  16. class LoggerTest extends TestCase
  17. {
  18. /**
  19. * @covers Logger::getName
  20. */
  21. public function testGetName()
  22. {
  23. $logger = new Logger('foo');
  24. $this->assertEquals('foo', $logger->getName());
  25. }
  26. /**
  27. * @covers Logger::withName
  28. */
  29. public function testWithName()
  30. {
  31. $first = new Logger('first', [$handler = new TestHandler()]);
  32. $second = $first->withName('second');
  33. $this->assertSame('first', $first->getName());
  34. $this->assertSame('second', $second->getName());
  35. $this->assertSame($handler, $second->popHandler());
  36. }
  37. /**
  38. * @covers Logger::toMonologLevel
  39. */
  40. public function testConvertPSR3ToMonologLevel()
  41. {
  42. $this->assertEquals(Logger::toMonologLevel('debug'), Level::Debug);
  43. $this->assertEquals(Logger::toMonologLevel('info'), Level::Info);
  44. $this->assertEquals(Logger::toMonologLevel('notice'), Level::Notice);
  45. $this->assertEquals(Logger::toMonologLevel('warning'), Level::Warning);
  46. $this->assertEquals(Logger::toMonologLevel('error'), Level::Error);
  47. $this->assertEquals(Logger::toMonologLevel('critical'), Level::Critical);
  48. $this->assertEquals(Logger::toMonologLevel('alert'), Level::Alert);
  49. $this->assertEquals(Logger::toMonologLevel('emergency'), Level::Emergency);
  50. }
  51. /**
  52. * @covers Monolog\Logger::addRecord
  53. * @covers Monolog\Logger::log
  54. */
  55. public function testConvertRFC5424ToMonologLevelInAddRecordAndLog()
  56. {
  57. $logger = new Logger('test');
  58. $handler = new TestHandler;
  59. $logger->pushHandler($handler);
  60. foreach ([
  61. 7 => 100,
  62. 6 => 200,
  63. 5 => 250,
  64. 4 => 300,
  65. 3 => 400,
  66. 2 => 500,
  67. 1 => 550,
  68. 0 => 600,
  69. ] as $rfc5424Level => $monologLevel) {
  70. $handler->reset();
  71. $logger->addRecord($rfc5424Level, 'test');
  72. $logger->log($rfc5424Level, 'test');
  73. $records = $handler->getRecords();
  74. self::assertCount(2, $records);
  75. self::assertSame($monologLevel, $records[0]['level']);
  76. self::assertSame($monologLevel, $records[1]['level']);
  77. }
  78. }
  79. /**
  80. * @covers Logger::__construct
  81. */
  82. public function testChannel()
  83. {
  84. $logger = new Logger('foo');
  85. $handler = new TestHandler;
  86. $logger->pushHandler($handler);
  87. $logger->warning('test');
  88. list($record) = $handler->getRecords();
  89. $this->assertEquals('foo', $record->channel);
  90. }
  91. /**
  92. * @covers Logger::addRecord
  93. */
  94. public function testLogPreventsCircularLogging()
  95. {
  96. $logger = new Logger(__METHOD__);
  97. $loggingHandler = new LoggingHandler($logger);
  98. $testHandler = new TestHandler();
  99. $logger->pushHandler($loggingHandler);
  100. $logger->pushHandler($testHandler);
  101. $logger->addRecord(Level::Alert, 'test');
  102. $records = $testHandler->getRecords();
  103. $this->assertCount(3, $records);
  104. $this->assertSame('ALERT', $records[0]->level->getName());
  105. $this->assertSame('DEBUG', $records[1]->level->getName());
  106. $this->assertSame('WARNING', $records[2]->level->getName());
  107. }
  108. /**
  109. * @covers Monolog\Logger::addRecord
  110. */
  111. public function testLog()
  112. {
  113. $logger = new Logger(__METHOD__);
  114. $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
  115. $handler->expects($this->never())->method('isHandling');
  116. $handler->expects($this->once())->method('handle');
  117. $logger->pushHandler($handler);
  118. $this->assertTrue($logger->addRecord(Level::Warning, 'test'));
  119. }
  120. /**
  121. * @covers Logger::addRecord
  122. */
  123. public function testLogAlwaysHandledIfNoProcessorsArePresent()
  124. {
  125. $logger = new Logger(__METHOD__);
  126. $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
  127. $handler->expects($this->never())->method('isHandling');
  128. $handler->expects($this->once())->method('handle');
  129. $logger->pushHandler($handler);
  130. $this->assertTrue($logger->addRecord(Level::Warning, 'test'));
  131. }
  132. /**
  133. * @covers Logger::addRecord
  134. */
  135. public function testLogNotHandledIfProcessorsArePresent()
  136. {
  137. $logger = new Logger(__METHOD__);
  138. $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
  139. $handler->expects($this->once())->method('isHandling')->willReturn(false);
  140. $handler->expects($this->never())->method('handle');
  141. $logger->pushProcessor(fn (LogRecord $record) => $record);
  142. $logger->pushHandler($handler);
  143. $this->assertFalse($logger->addRecord(Level::Warning, 'test'));
  144. }
  145. public function testHandlersInCtor()
  146. {
  147. $handler1 = new TestHandler;
  148. $handler2 = new TestHandler;
  149. $logger = new Logger(__METHOD__, [$handler1, $handler2]);
  150. $this->assertEquals($handler1, $logger->popHandler());
  151. $this->assertEquals($handler2, $logger->popHandler());
  152. }
  153. public function testProcessorsInCtor()
  154. {
  155. $processor1 = new WebProcessor;
  156. $processor2 = new WebProcessor;
  157. $logger = new Logger(__METHOD__, [], [$processor1, $processor2]);
  158. $this->assertEquals($processor1, $logger->popProcessor());
  159. $this->assertEquals($processor2, $logger->popProcessor());
  160. }
  161. /**
  162. * @covers Logger::pushHandler
  163. * @covers Logger::popHandler
  164. */
  165. public function testPushPopHandler()
  166. {
  167. $logger = new Logger(__METHOD__);
  168. $handler1 = new TestHandler;
  169. $handler2 = new TestHandler;
  170. $logger->pushHandler($handler1);
  171. $logger->pushHandler($handler2);
  172. $this->assertEquals($handler2, $logger->popHandler());
  173. $this->assertEquals($handler1, $logger->popHandler());
  174. $this->expectException(\LogicException::class);
  175. $logger->popHandler();
  176. }
  177. /**
  178. * @covers Logger::setHandlers
  179. */
  180. public function testSetHandlers()
  181. {
  182. $logger = new Logger(__METHOD__);
  183. $handler1 = new TestHandler;
  184. $handler2 = new TestHandler;
  185. $logger->pushHandler($handler1);
  186. $logger->setHandlers([$handler2]);
  187. // handler1 has been removed
  188. $this->assertEquals([$handler2], $logger->getHandlers());
  189. $logger->setHandlers([
  190. "AMapKey" => $handler1,
  191. "Woop" => $handler2,
  192. ]);
  193. // Keys have been scrubbed
  194. $this->assertEquals([$handler1, $handler2], $logger->getHandlers());
  195. }
  196. /**
  197. * @covers Logger::pushProcessor
  198. * @covers Logger::popProcessor
  199. */
  200. public function testPushPopProcessor()
  201. {
  202. $logger = new Logger(__METHOD__);
  203. $processor1 = new WebProcessor;
  204. $processor2 = new WebProcessor;
  205. $logger->pushProcessor($processor1);
  206. $logger->pushProcessor($processor2);
  207. $this->assertEquals($processor2, $logger->popProcessor());
  208. $this->assertEquals($processor1, $logger->popProcessor());
  209. $this->expectException(\LogicException::class);
  210. $logger->popProcessor();
  211. }
  212. /**
  213. * @covers Logger::addRecord
  214. */
  215. public function testProcessorsAreExecuted()
  216. {
  217. $logger = new Logger(__METHOD__);
  218. $handler = new TestHandler;
  219. $logger->pushHandler($handler);
  220. $logger->pushProcessor(function ($record) {
  221. $record->extra['win'] = true;
  222. return $record;
  223. });
  224. $logger->error('test');
  225. list($record) = $handler->getRecords();
  226. $this->assertTrue($record->extra['win']);
  227. }
  228. /**
  229. * @covers Logger::addRecord
  230. */
  231. public function testProcessorsAreCalledOnlyOnce()
  232. {
  233. $logger = new Logger(__METHOD__);
  234. $handler = $this->createMock('Monolog\Handler\HandlerInterface');
  235. $handler->expects($this->any())
  236. ->method('isHandling')
  237. ->willReturn(true);
  238. $handler->expects($this->any())
  239. ->method('handle')
  240. ->willReturn(true);
  241. $logger->pushHandler($handler);
  242. $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor')
  243. ->disableOriginalConstructor()
  244. ->onlyMethods(['__invoke'])
  245. ->getMock()
  246. ;
  247. $processor->expects($this->once())
  248. ->method('__invoke')
  249. ->willReturnArgument(0)
  250. ;
  251. $logger->pushProcessor($processor);
  252. $logger->error('test');
  253. }
  254. /**
  255. * @covers Logger::addRecord
  256. */
  257. public function testProcessorsNotCalledWhenNotHandled()
  258. {
  259. $logger = new Logger(__METHOD__);
  260. $handler = $this->createMock('Monolog\Handler\HandlerInterface');
  261. $handler->expects($this->once())
  262. ->method('isHandling')
  263. ->willReturn(false);
  264. $logger->pushHandler($handler);
  265. $that = $this;
  266. $logger->pushProcessor(function ($record) use ($that) {
  267. $that->fail('The processor should not be called');
  268. });
  269. $logger->alert('test');
  270. }
  271. /**
  272. * @covers Logger::addRecord
  273. */
  274. public function testHandlersNotCalledBeforeFirstHandlingWhenProcessorsPresent()
  275. {
  276. $logger = new Logger(__METHOD__);
  277. $logger->pushProcessor(fn ($record) => $record);
  278. $handler1 = $this->createMock('Monolog\Handler\HandlerInterface');
  279. $handler1->expects($this->never())
  280. ->method('isHandling')
  281. ->willReturn(false);
  282. $handler1->expects($this->once())
  283. ->method('handle')
  284. ->willReturn(false);
  285. $logger->pushHandler($handler1);
  286. $handler2 = $this->createMock('Monolog\Handler\HandlerInterface');
  287. $handler2->expects($this->once())
  288. ->method('isHandling')
  289. ->willReturn(true);
  290. $handler2->expects($this->once())
  291. ->method('handle')
  292. ->willReturn(false);
  293. $logger->pushHandler($handler2);
  294. $handler3 = $this->createMock('Monolog\Handler\HandlerInterface');
  295. $handler3->expects($this->once())
  296. ->method('isHandling')
  297. ->willReturn(false);
  298. $handler3->expects($this->never())
  299. ->method('handle')
  300. ;
  301. $logger->pushHandler($handler3);
  302. $logger->debug('test');
  303. }
  304. /**
  305. * @covers Logger::addRecord
  306. */
  307. public function testHandlersNotCalledBeforeFirstHandlingWhenProcessorsPresentWithAssocArray()
  308. {
  309. $handler1 = $this->createMock('Monolog\Handler\HandlerInterface');
  310. $handler1->expects($this->never())
  311. ->method('isHandling')
  312. ->willReturn(false);
  313. $handler1->expects($this->once())
  314. ->method('handle')
  315. ->willReturn(false);
  316. $handler2 = $this->createMock('Monolog\Handler\HandlerInterface');
  317. $handler2->expects($this->once())
  318. ->method('isHandling')
  319. ->willReturn(true);
  320. $handler2->expects($this->once())
  321. ->method('handle')
  322. ->willReturn(false);
  323. $handler3 = $this->createMock('Monolog\Handler\HandlerInterface');
  324. $handler3->expects($this->once())
  325. ->method('isHandling')
  326. ->willReturn(false);
  327. $handler3->expects($this->never())
  328. ->method('handle')
  329. ;
  330. $logger = new Logger(__METHOD__, ['last' => $handler3, 'second' => $handler2, 'first' => $handler1]);
  331. $logger->pushProcessor(fn ($record) => $record);
  332. $logger->debug('test');
  333. }
  334. /**
  335. * @covers Logger::addRecord
  336. */
  337. public function testBubblingWhenTheHandlerReturnsFalse()
  338. {
  339. $logger = new Logger(__METHOD__);
  340. $handler1 = $this->createMock('Monolog\Handler\HandlerInterface');
  341. $handler1->expects($this->any())
  342. ->method('isHandling')
  343. ->willReturn(true);
  344. $handler1->expects($this->once())
  345. ->method('handle')
  346. ->willReturn(false);
  347. $logger->pushHandler($handler1);
  348. $handler2 = $this->createMock('Monolog\Handler\HandlerInterface');
  349. $handler2->expects($this->any())
  350. ->method('isHandling')
  351. ->willReturn(true);
  352. $handler2->expects($this->once())
  353. ->method('handle')
  354. ->willReturn(false);
  355. $logger->pushHandler($handler2);
  356. $logger->debug('test');
  357. }
  358. /**
  359. * @covers Logger::addRecord
  360. */
  361. public function testNotBubblingWhenTheHandlerReturnsTrue()
  362. {
  363. $logger = new Logger(__METHOD__);
  364. $handler1 = $this->createMock('Monolog\Handler\HandlerInterface');
  365. $handler1->expects($this->any())
  366. ->method('isHandling')
  367. ->willReturn(true);
  368. $handler1->expects($this->never())
  369. ->method('handle')
  370. ;
  371. $logger->pushHandler($handler1);
  372. $handler2 = $this->createMock('Monolog\Handler\HandlerInterface');
  373. $handler2->expects($this->any())
  374. ->method('isHandling')
  375. ->willReturn(true);
  376. $handler2->expects($this->once())
  377. ->method('handle')
  378. ->willReturn(true);
  379. $logger->pushHandler($handler2);
  380. $logger->debug('test');
  381. }
  382. /**
  383. * @covers Logger::isHandling
  384. */
  385. public function testIsHandling()
  386. {
  387. $logger = new Logger(__METHOD__);
  388. $handler1 = $this->createMock('Monolog\Handler\HandlerInterface');
  389. $handler1->expects($this->any())
  390. ->method('isHandling')
  391. ->willReturn(false);
  392. $logger->pushHandler($handler1);
  393. $this->assertFalse($logger->isHandling(Level::Debug));
  394. $handler2 = $this->createMock('Monolog\Handler\HandlerInterface');
  395. $handler2->expects($this->any())
  396. ->method('isHandling')
  397. ->willReturn(true);
  398. $logger->pushHandler($handler2);
  399. $this->assertTrue($logger->isHandling(Level::Debug));
  400. }
  401. /**
  402. * @covers Level::Debug
  403. * @covers Level::Info
  404. * @covers Level::Notice
  405. * @covers Level::Warning
  406. * @covers Level::Error
  407. * @covers Level::Critical
  408. * @covers Level::Alert
  409. * @covers Level::Emergency
  410. */
  411. #[DataProvider('logMethodProvider')]
  412. public function testLogMethods(string $method, Level $expectedLevel)
  413. {
  414. $logger = new Logger('foo');
  415. $handler = new TestHandler;
  416. $logger->pushHandler($handler);
  417. $logger->{$method}('test');
  418. list($record) = $handler->getRecords();
  419. $this->assertEquals($expectedLevel, $record->level);
  420. }
  421. public static function logMethodProvider()
  422. {
  423. return [
  424. // PSR-3 methods
  425. ['debug', Level::Debug],
  426. ['info', Level::Info],
  427. ['notice', Level::Notice],
  428. ['warning', Level::Warning],
  429. ['error', Level::Error],
  430. ['critical', Level::Critical],
  431. ['alert', Level::Alert],
  432. ['emergency', Level::Emergency],
  433. ];
  434. }
  435. /**
  436. * @covers Logger::setTimezone
  437. */
  438. #[DataProvider('setTimezoneProvider')]
  439. public function testSetTimezone($tz)
  440. {
  441. $logger = new Logger('foo');
  442. $logger->setTimezone($tz);
  443. $handler = new TestHandler;
  444. $logger->pushHandler($handler);
  445. $logger->info('test');
  446. list($record) = $handler->getRecords();
  447. $this->assertEquals($tz, $record->datetime->getTimezone());
  448. }
  449. public static function setTimezoneProvider()
  450. {
  451. return array_map(
  452. function ($tz) {
  453. return [new \DateTimeZone($tz)];
  454. },
  455. \DateTimeZone::listIdentifiers()
  456. );
  457. }
  458. /**
  459. * @covers Logger::setTimezone
  460. * @covers JsonSerializableDateTimeImmutable::__construct
  461. */
  462. public function testTimezoneIsRespectedInUTC()
  463. {
  464. foreach ([true, false] as $microseconds) {
  465. $logger = new Logger('foo');
  466. $logger->useMicrosecondTimestamps($microseconds);
  467. $tz = new \DateTimeZone('America/New_York');
  468. $logger->setTimezone($tz);
  469. $handler = new TestHandler;
  470. $logger->pushHandler($handler);
  471. $dt = new \DateTime('now', $tz);
  472. $logger->info('test');
  473. list($record) = $handler->getRecords();
  474. $this->assertEquals($tz, $record->datetime->getTimezone());
  475. $this->assertEquals($dt->format('Y/m/d H:i'), $record->datetime->format('Y/m/d H:i'), 'Time should match timezone with microseconds set to: '.var_export($microseconds, true));
  476. }
  477. }
  478. /**
  479. * @covers Logger::setTimezone
  480. * @covers JsonSerializableDateTimeImmutable::__construct
  481. */
  482. public function testTimezoneIsRespectedInOtherTimezone()
  483. {
  484. date_default_timezone_set('CET');
  485. foreach ([true, false] as $microseconds) {
  486. $logger = new Logger('foo');
  487. $logger->useMicrosecondTimestamps($microseconds);
  488. $tz = new \DateTimeZone('America/New_York');
  489. $logger->setTimezone($tz);
  490. $handler = new TestHandler;
  491. $logger->pushHandler($handler);
  492. $dt = new \DateTime('now', $tz);
  493. $logger->info('test');
  494. list($record) = $handler->getRecords();
  495. $this->assertEquals($tz, $record->datetime->getTimezone());
  496. $this->assertEquals($dt->format('Y/m/d H:i'), $record->datetime->format('Y/m/d H:i'), 'Time should match timezone with microseconds set to: '.var_export($microseconds, true));
  497. }
  498. }
  499. public function tearDown(): void
  500. {
  501. date_default_timezone_set('UTC');
  502. }
  503. /**
  504. * @covers Logger::useMicrosecondTimestamps
  505. * @covers Logger::addRecord
  506. */
  507. #[DataProvider('useMicrosecondTimestampsProvider')]
  508. public function testUseMicrosecondTimestamps($micro, $assert, $assertFormat)
  509. {
  510. if (PHP_VERSION_ID === 70103) {
  511. $this->markTestSkipped();
  512. }
  513. $logger = new Logger('foo');
  514. $logger->useMicrosecondTimestamps($micro);
  515. $handler = new TestHandler;
  516. $logger->pushHandler($handler);
  517. $logger->info('test');
  518. list($record) = $handler->getRecords();
  519. $this->{$assert}('000000', $record->datetime->format('u'));
  520. $this->assertSame($record->datetime->format($assertFormat), (string) $record->datetime);
  521. }
  522. public static function useMicrosecondTimestampsProvider()
  523. {
  524. return [
  525. // this has a very small chance of a false negative (1/10^6)
  526. 'with microseconds' => [true, 'assertNotSame', 'Y-m-d\TH:i:s.uP'],
  527. // php 7.1 always includes microseconds, so we keep them in, but we format the datetime without
  528. 'without microseconds' => [false, 'assertNotSame', 'Y-m-d\TH:i:sP'],
  529. ];
  530. }
  531. public function testProcessorsDoNotInterfereBetweenHandlers()
  532. {
  533. $logger = new Logger('foo');
  534. $logger->pushHandler($t1 = new TestHandler());
  535. $logger->pushHandler($t2 = new TestHandler());
  536. $t1->pushProcessor(function (LogRecord $record) {
  537. $record->extra['foo'] = 'bar';
  538. return $record;
  539. });
  540. $logger->error('Foo');
  541. self::assertSame([], $t2->getRecords()[0]->extra);
  542. }
  543. /**
  544. * @covers Logger::setExceptionHandler
  545. */
  546. public function testSetExceptionHandler()
  547. {
  548. $logger = new Logger(__METHOD__);
  549. $this->assertNull($logger->getExceptionHandler());
  550. $callback = function ($ex) {
  551. };
  552. $logger->setExceptionHandler($callback);
  553. $this->assertEquals($callback, $logger->getExceptionHandler());
  554. }
  555. /**
  556. * @covers Logger::handleException
  557. */
  558. public function testDefaultHandleException()
  559. {
  560. $logger = new Logger(__METHOD__);
  561. $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
  562. $handler->expects($this->any())
  563. ->method('isHandling')
  564. ->willReturn(true);
  565. $handler->expects($this->any())
  566. ->method('handle')
  567. ->will($this->throwException(new \Exception('Some handler exception')))
  568. ;
  569. $this->expectException(\Exception::class);
  570. $logger->pushHandler($handler);
  571. $logger->info('test');
  572. }
  573. /**
  574. * @covers Logger::handleException
  575. * @covers Logger::addRecord
  576. */
  577. public function testCustomHandleException()
  578. {
  579. $logger = new Logger(__METHOD__);
  580. $that = $this;
  581. $logger->setExceptionHandler(function ($e, $record) use ($that) {
  582. $that->assertEquals($e->getMessage(), 'Some handler exception');
  583. $that->assertInstanceOf(LogRecord::class, $record);
  584. $that->assertEquals($record->message, 'test');
  585. });
  586. $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
  587. $handler->expects($this->any())
  588. ->method('isHandling')
  589. ->willReturn(true);
  590. $handler->expects($this->any())
  591. ->method('handle')
  592. ->will($this->throwException(new \Exception('Some handler exception')))
  593. ;
  594. $logger->pushHandler($handler);
  595. $logger->info('test');
  596. }
  597. public function testSerializable()
  598. {
  599. $logger = new Logger(__METHOD__);
  600. $copy = unserialize(serialize($logger));
  601. self::assertInstanceOf(Logger::class, $copy);
  602. self::assertSame($logger->getName(), $copy->getName());
  603. self::assertSame($logger->getTimezone()->getName(), $copy->getTimezone()->getName());
  604. self::assertSame($logger->getHandlers(), $copy->getHandlers());
  605. }
  606. public function testReset()
  607. {
  608. $logger = new Logger('app');
  609. $testHandler = new Handler\TestHandler();
  610. $testHandler->setSkipReset(true);
  611. $bufferHandler = new Handler\BufferHandler($testHandler);
  612. $groupHandler = new Handler\GroupHandler([$bufferHandler]);
  613. $fingersCrossedHandler = new Handler\FingersCrossedHandler($groupHandler);
  614. $logger->pushHandler($fingersCrossedHandler);
  615. $processorUid1 = new Processor\UidProcessor(10);
  616. $uid1 = $processorUid1->getUid();
  617. $groupHandler->pushProcessor($processorUid1);
  618. $processorUid2 = new Processor\UidProcessor(5);
  619. $uid2 = $processorUid2->getUid();
  620. $logger->pushProcessor($processorUid2);
  621. $getProperty = function ($object, $property) {
  622. $reflectionProperty = new \ReflectionProperty(\get_class($object), $property);
  623. return $reflectionProperty->getValue($object);
  624. };
  625. $assertBufferOfBufferHandlerEmpty = function () use ($getProperty, $bufferHandler) {
  626. self::assertEmpty($getProperty($bufferHandler, 'buffer'));
  627. };
  628. $assertBuffersEmpty = function () use ($assertBufferOfBufferHandlerEmpty, $getProperty, $fingersCrossedHandler) {
  629. $assertBufferOfBufferHandlerEmpty();
  630. self::assertEmpty($getProperty($fingersCrossedHandler, 'buffer'));
  631. };
  632. $logger->debug('debug1');
  633. $logger->reset();
  634. $assertBuffersEmpty();
  635. $this->assertFalse($testHandler->hasDebugRecords());
  636. $this->assertFalse($testHandler->hasErrorRecords());
  637. $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
  638. $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
  639. $logger->debug('debug2');
  640. $logger->error('error2');
  641. $logger->reset();
  642. $assertBuffersEmpty();
  643. $this->assertTrue($testHandler->hasRecordThatContains('debug2', Level::Debug));
  644. $this->assertTrue($testHandler->hasRecordThatContains('error2', Level::Error));
  645. $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
  646. $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
  647. $logger->info('info3');
  648. $this->assertNotEmpty($getProperty($fingersCrossedHandler, 'buffer'));
  649. $assertBufferOfBufferHandlerEmpty();
  650. $this->assertFalse($testHandler->hasInfoRecords());
  651. $logger->reset();
  652. $assertBuffersEmpty();
  653. $this->assertFalse($testHandler->hasInfoRecords());
  654. $this->assertNotSame($uid1, $uid1 = $processorUid1->getUid());
  655. $this->assertNotSame($uid2, $uid2 = $processorUid2->getUid());
  656. $logger->notice('notice4');
  657. $logger->emergency('emergency4');
  658. $logger->reset();
  659. $assertBuffersEmpty();
  660. $this->assertFalse($testHandler->hasInfoRecords());
  661. $this->assertTrue($testHandler->hasRecordThatContains('notice4', Level::Notice));
  662. $this->assertTrue($testHandler->hasRecordThatContains('emergency4', Level::Emergency));
  663. $this->assertNotSame($uid1, $processorUid1->getUid());
  664. $this->assertNotSame($uid2, $processorUid2->getUid());
  665. }
  666. /**
  667. * @covers Logger::addRecord
  668. */
  669. public function testLogWithDateTime()
  670. {
  671. foreach ([true, false] as $microseconds) {
  672. $logger = new Logger(__METHOD__);
  673. $loggingHandler = new LoggingHandler($logger);
  674. $testHandler = new TestHandler();
  675. $logger->pushHandler($loggingHandler);
  676. $logger->pushHandler($testHandler);
  677. $datetime = (new JsonSerializableDateTimeImmutable($microseconds))->modify('2022-03-04 05:06:07');
  678. $logger->addRecord(Level::Debug, 'test', [], $datetime);
  679. list($record) = $testHandler->getRecords();
  680. $this->assertEquals($datetime->format('Y-m-d H:i:s'), $record->datetime->format('Y-m-d H:i:s'));
  681. }
  682. }
  683. public function testLogCycleDetectionWithFibersWithoutCycle()
  684. {
  685. $logger = new Logger(__METHOD__);
  686. $fiberSuspendHandler = new FiberSuspendHandler();
  687. $testHandler = new TestHandler();
  688. $logger->pushHandler($fiberSuspendHandler);
  689. $logger->pushHandler($testHandler);
  690. $fibers = [];
  691. for ($i = 0; $i < 10; $i++) {
  692. $fiber = new \Fiber(static function () use ($logger) {
  693. $logger->info('test');
  694. });
  695. $fiber->start();
  696. // We need to keep a reference here, because otherwise the fiber gets automatically cleaned up
  697. $fibers[] = $fiber;
  698. }
  699. self::assertCount(10, $testHandler->getRecords());
  700. }
  701. public function testLogCycleDetectionWithFibersWithCycle()
  702. {
  703. $logger = new Logger(__METHOD__);
  704. $fiberSuspendHandler = new FiberSuspendHandler();
  705. $loggingHandler = new LoggingHandler($logger);
  706. $testHandler = new TestHandler();
  707. $logger->pushHandler($fiberSuspendHandler);
  708. $logger->pushHandler($loggingHandler);
  709. $logger->pushHandler($testHandler);
  710. $fiber = new \Fiber(static function () use ($logger) {
  711. $logger->info('test');
  712. });
  713. $fiber->start();
  714. self::assertCount(3, $testHandler->getRecords());
  715. }
  716. }
  717. class LoggingHandler implements HandlerInterface
  718. {
  719. /**
  720. * @var Logger
  721. */
  722. private $logger;
  723. public function __construct(Logger $logger)
  724. {
  725. $this->logger = $logger;
  726. }
  727. public function isHandling(LogRecord $record): bool
  728. {
  729. return true;
  730. }
  731. public function handle(LogRecord $record): bool
  732. {
  733. $this->logger->debug('Log triggered while logging');
  734. return false;
  735. }
  736. public function handleBatch(array $records): void
  737. {
  738. }
  739. public function close(): void
  740. {
  741. }
  742. }
  743. class FiberSuspendHandler implements HandlerInterface
  744. {
  745. public function isHandling(LogRecord $record): bool
  746. {
  747. return true;
  748. }
  749. public function handle(LogRecord $record): bool
  750. {
  751. \Fiber::suspend();
  752. return true;
  753. }
  754. public function handleBatch(array $records): void
  755. {
  756. }
  757. public function close(): void
  758. {
  759. }
  760. }