diff --git a/src/Util/Logger/StreamLogger.php b/src/Util/Logger/StreamLogger.php index 7e52df80f..701d36d2b 100644 --- a/src/Util/Logger/StreamLogger.php +++ b/src/Util/Logger/StreamLogger.php @@ -115,7 +115,6 @@ class StreamLogger extends AbstractLogger $this->checkStream(); - $this->stream = fopen($this->url, 'a'); $formattedLog = $this->formatLog($level, $message, $context); fwrite($this->stream, $formattedLog); } diff --git a/src/Util/Logger/SyslogLogger.php b/src/Util/Logger/SyslogLogger.php index e21e953ac..b1abd5bd5 100644 --- a/src/Util/Logger/SyslogLogger.php +++ b/src/Util/Logger/SyslogLogger.php @@ -4,7 +4,6 @@ namespace Friendica\Util\Logger; use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Util\Introspection; -use Psr\Log\InvalidArgumentException; use Psr\Log\LogLevel; /** @@ -117,7 +116,7 @@ class SyslogLogger extends AbstractLogger public function mapLevelToPriority($level) { if (!array_key_exists($level, $this->logLevels)) { - throw new InvalidArgumentException('LogLevel \'' . $level . '\' isn\'t valid.'); + throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level)); } return $this->logLevels[$level]; @@ -146,7 +145,7 @@ class SyslogLogger extends AbstractLogger throw new InternalServerErrorException('Can\'t open syslog for ident "' . $this->channel . '" and facility "' . $this->logFacility . '""'); } - syslog($priority, $message); + $this->syslogWrapper($priority, $message); } /** @@ -172,4 +171,15 @@ class SyslogLogger extends AbstractLogger return $logMessage; } + + /** + * A syslog wrapper to make syslog functionality testable + * + * @param int $level The syslog priority + * @param string $entry The message to send to the syslog function + */ + protected function syslogWrapper($level, $entry) + { + syslog($level, $entry); + } } diff --git a/tests/src/Util/Logger/AbstractLoggerTest.php b/tests/src/Util/Logger/AbstractLoggerTest.php new file mode 100644 index 000000000..e0c033583 --- /dev/null +++ b/tests/src/Util/Logger/AbstractLoggerTest.php @@ -0,0 +1,143 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +use Friendica\Test\MockedTest; +use Friendica\Util\Introspection; +use Mockery\MockInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +abstract class AbstractLoggerTest extends MockedTest +{ + use LoggerDataTrait; + + const LOGLINE = '/.* \[.*\]: .* \{.*\"file\":\".*\".*,.*\"line\":\d*,.*\"function\":\".*\".*,.*\"uid\":\".*\".*}/'; + + const FILE = 'test'; + const LINE = 666; + const FUNC = 'myfunction'; + + /** + * @var Introspection|MockInterface + */ + protected $introspection; + + /** + * Returns the content of the current logger instance + * + * @return string + */ + abstract protected function getContent(); + + /** + * Returns the current logger instance + * + * @param string $level the default loglevel + * + * @return LoggerInterface + */ + abstract protected function getInstance($level = LogLevel::DEBUG); + + protected function setUp() + { + parent::setUp(); + + $this->introspection = \Mockery::mock(Introspection::class); + $this->introspection->shouldReceive('getRecord')->andReturn([ + 'file' => self::FILE, + 'line' => self::LINE, + 'function' => self::FUNC + ]); + } + + public function assertLogline($string) + { + $this->assertRegExp(self::LOGLINE, $string); + } + + public function assertLoglineNums($assertNum, $string) + { + $this->assertEquals($assertNum, preg_match_all(self::LOGLINE, $string)); + } + + /** + * Test if the logger works correctly + */ + public function testNormal() + { + $logger = $this->getInstance(); + $logger->emergency('working!'); + $logger->alert('working too!'); + $logger->debug('and now?'); + $logger->notice('message', ['an' => 'context']); + + $text = $this->getContent(); + $this->assertLogline($text); + $this->assertLoglineNums(4, $text); + } + + /** + * Test if a log entry is correctly interpolated + */ + public function testPsrInterpolate() + { + $logger = $this->getInstance(); + + $logger->emergency('A {psr} test', ['psr' => 'working']); + $logger->alert('An {array} test', ['array' => ['it', 'is', 'working']]); + $text = $this->getContent(); + $this->assertContains('A working test', $text); + $this->assertContains('An ["it","is","working"] test', $text); + } + + /** + * Test if a log entry contains all necessary information + */ + public function testContainsInformation() + { + $logger = $this->getInstance(); + $logger->emergency('A test'); + + $text = $this->getContent(); + $this->assertContains('"file":"' . self::FILE . '"', $text); + $this->assertContains('"line":' . self::LINE, $text); + $this->assertContains('"function":"' . self::FUNC . '"', $text); + } + + /** + * Test if the minimum level is working + */ + public function testMinimumLevel() + { + $logger = $this->getInstance(LogLevel::NOTICE); + + $logger->emergency('working'); + $logger->alert('working'); + $logger->error('working'); + $logger->warning('working'); + $logger->notice('working'); + $logger->info('not working'); + $logger->debug('not working'); + + $text = $this->getContent(); + + $this->assertLoglineNums(5, $text); + } + + /** + * Test with different logging data + * @dataProvider dataTests + */ + public function testDifferentTypes($function, $message, array $context) + { + $logger = $this->getInstance(); + $logger->$function($message, $context); + + $text = $this->getContent(); + + $this->assertLogline($text); + + $this->assertContains(@json_encode($context), $text); + } +} diff --git a/tests/src/Util/Logger/LoggerDataTrait.php b/tests/src/Util/Logger/LoggerDataTrait.php new file mode 100644 index 000000000..1267098a8 --- /dev/null +++ b/tests/src/Util/Logger/LoggerDataTrait.php @@ -0,0 +1,52 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +trait LoggerDataTrait +{ + public function dataTests() + { + return [ + 'emergency' => [ + 'function' => 'emergency', + 'message' => 'test', + 'context' => ['a' => 'context'], + ], + 'alert' => [ + 'function' => 'alert', + 'message' => 'test {test}', + 'context' => ['a' => 'context', 2 => 'so', 'test' => 'works'], + ], + 'critical' => [ + 'function' => 'critical', + 'message' => 'test crit 2345', + 'context' => ['a' => 'context', 'wit' => ['more', 'array']], + ], + 'error' => [ + 'function' => 'error', + 'message' => 2.554, + 'context' => [], + ], + 'warning' => [ + 'function' => 'warning', + 'message' => 'test warn', + 'context' => ['a' => 'context'], + ], + 'notice' => [ + 'function' => 'notice', + 'message' => 2346, + 'context' => ['a' => 'context'], + ], + 'info' => [ + 'function' => 'info', + 'message' => null, + 'context' => ['a' => 'context'], + ], + 'debug' => [ + 'function' => 'debug', + 'message' => true, + 'context' => ['a' => false], + ], + ]; + } +} diff --git a/tests/src/Util/Logger/ProfilerLoggerTest.php b/tests/src/Util/Logger/ProfilerLoggerTest.php new file mode 100644 index 000000000..848191a4d --- /dev/null +++ b/tests/src/Util/Logger/ProfilerLoggerTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +use Friendica\Test\MockedTest; +use Friendica\Util\Logger\ProfilerLogger; +use Friendica\Util\Profiler; +use Mockery\MockInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +class ProfilerLoggerTest extends MockedTest +{ + use LoggerDataTrait; + + /** + * @var LoggerInterface|MockInterface + */ + private $logger; + /** + * @var Profiler|MockInterface + */ + private $profiler; + + protected function setUp() + { + parent::setUp(); + + $this->logger = \Mockery::mock(LoggerInterface::class); + $this->profiler = \Mockery::mock(Profiler::class); + } + + /** + * Test if the profiler is profiling data + * @dataProvider dataTests + */ + public function testProfiling($function, $message, array $context) + { + $logger = new ProfilerLogger($this->logger, $this->profiler); + + $this->logger->shouldReceive($function)->with($message, $context)->once(); + $this->profiler->shouldReceive('saveTimestamp')->with(\Mockery::any(), 'file', \Mockery::any())->once(); + $logger->$function($message, $context); + } + + /** + * Test the log() function + */ + public function testProfilingLog() + { + $logger = new ProfilerLogger($this->logger, $this->profiler); + + $this->logger->shouldReceive('log')->with(LogLevel::WARNING, 'test', ['a' => 'context'])->once(); + $this->profiler->shouldReceive('saveTimestamp')->with(\Mockery::any(), 'file', \Mockery::any())->once(); + + $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']); + } +} diff --git a/tests/src/Util/Logger/StreamLoggerTest.php b/tests/src/Util/Logger/StreamLoggerTest.php index 38706231c..bbf94419a 100644 --- a/tests/src/Util/Logger/StreamLoggerTest.php +++ b/tests/src/Util/Logger/StreamLoggerTest.php @@ -2,126 +2,89 @@ namespace Friendica\Test\src\Util\Logger; -use Friendica\Test\MockedTest; use Friendica\Test\Util\VFSTrait; -use Friendica\Util\Introspection; use Friendica\Util\Logger\StreamLogger; -use Mockery\MockInterface; use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamFile; use Psr\Log\LogLevel; -class StreamLoggerTest extends MockedTest +class StreamLoggerTest extends AbstractLoggerTest { - const LOGLINE = '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} .* \[.*\]: .* \{.*\"file\":\".*\".*,.*\"line\":\d*,.*\"function\":\".*\".*,.*\"uid\":\".*\".*,.*\"process_id\":\d*.*\}/'; - - const FILE = 'test'; - const LINE = 666; - const FUNC = 'myfunction'; - use VFSTrait; /** - * @var Introspection|MockInterface + * @var StreamLogger */ - private $introspection; + private $logger; + + /** + * @var vfsStreamFile + */ + private $logfile; protected function setUp() { parent::setUp(); $this->setUpVfsDir(); - - $this->introspection = \Mockery::mock(Introspection::class); - $this->introspection->shouldReceive('getRecord')->andReturn([ - 'file' => self::FILE, - 'line' => self::LINE, - 'function' => self::FUNC - ]); - } - - public function assertLogline($string) - { - $this->assertRegExp(self::LOGLINE, $string); - } - - public function assertLoglineNums($assertNum, $string) - { - $this->assertEquals($assertNum, preg_match_all(self::LOGLINE, $string)); - } - - public function testNormal() - { - $logfile = vfsStream::newFile('friendica.log') - ->at($this->root); - - $logger = new StreamLogger('test', $logfile->url(), $this->introspection); - $logger->emergency('working!'); - $logger->alert('working too!'); - $logger->debug('and now?'); - $logger->notice('message', ['an' => 'context']); - - $text = $logfile->getContent(); - $this->assertLogline($text); - $this->assertLoglineNums(4, $text); } /** - * Test if a log entry is correctly interpolated + * {@@inheritdoc} */ - public function testPsrInterpolate() + protected function getInstance($level = LogLevel::DEBUG) { - $logfile = vfsStream::newFile('friendica.log') + $this->logfile = vfsStream::newFile('friendica.log') ->at($this->root); - $logger = new StreamLogger('test', $logfile->url(), $this->introspection); + $this->logger = new StreamLogger('test', $this->logfile->url(), $this->introspection, $level); - $logger->emergency('A {psr} test', ['psr' => 'working']); - $logger->alert('An {array} test', ['array' => ['it', 'is', 'working']]); - $text = $logfile->getContent(); - $this->assertContains('A working test', $text); - $this->assertContains('An ["it","is","working"] test', $text); + return $this->logger; } /** - * Test if a log entry contains all necessary information + * {@inheritdoc} */ - public function testContainsInformation() + protected function getContent() { - $logfile = vfsStream::newFile('friendica.log') - ->at($this->root); - - $logger = new StreamLogger('test', $logfile->url(), $this->introspection); - - $logger->emergency('A test'); - - $text = $logfile->getContent(); - $this->assertContains('"process_id":' . getmypid(), $text); - $this->assertContains('"file":"' . self::FILE . '"', $text); - $this->assertContains('"line":' . self::LINE, $text); - $this->assertContains('"function":"' . self::FUNC . '"', $text); + return $this->logfile->getContent(); } /** - * Test if the minimum level is working + * Test if a stream is working */ - public function testMinimumLevel() + public function testStream() { $logfile = vfsStream::newFile('friendica.log') ->at($this->root); - $logger = new StreamLogger('test', $logfile->url(), $this->introspection, LogLevel::NOTICE); + $filehandler = fopen($logfile->url(), 'ab'); + $logger = new StreamLogger('test', $filehandler, $this->introspection); $logger->emergency('working'); - $logger->alert('working'); - $logger->error('working'); - $logger->warning('working'); - $logger->notice('working'); - $logger->info('not working'); - $logger->debug('not working'); $text = $logfile->getContent(); - $this->assertLoglineNums(5, $text); + $this->assertLogline($text); + } + + /** + * Test if the close statement is working + */ + public function testClose() + { + $logfile = vfsStream::newFile('friendica.log') + ->at($this->root); + + $logger = new StreamLogger('test', $logfile->url(), $this->introspection); + $logger->emergency('working'); + $logger->close(); + // close doesn't affect + $logger->emergency('working too'); + + $text = $logfile->getContent(); + + $this->assertLoglineNums(2, $text); } /** @@ -187,4 +150,14 @@ class StreamLoggerTest extends MockedTest $logger->log('NOPE', 'a test'); } + + /** + * Test when the file is null + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage A stream must either be a resource or a string. + */ + public function testWrongFile() + { + $logger = new StreamLogger('test', null, $this->introspection); + } } diff --git a/tests/src/Util/Logger/SyslogLoggerTest.php b/tests/src/Util/Logger/SyslogLoggerTest.php new file mode 100644 index 000000000..545f76b32 --- /dev/null +++ b/tests/src/Util/Logger/SyslogLoggerTest.php @@ -0,0 +1,62 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +use Friendica\Util\Logger\SyslogLogger; +use Psr\Log\LogLevel; + +class SyslogLoggerTest extends AbstractLoggerTest +{ + /** + * @var SyslogLoggerWrapper + */ + private $logger; + + protected function setUp() + { + parent::setUp(); + + $this->introspection->shouldReceive('addClasses')->with([SyslogLogger::class]); + } + + /** + * {@inheritdoc} + */ + protected function getContent() + { + return $this->logger->getContent(); + } + + /** + * {@inheritdoc} + */ + protected function getInstance($level = LogLevel::DEBUG) + { + $this->logger = new SyslogLoggerWrapper('test', $this->introspection, $level); + + return $this->logger; + } + + + /** + * Test when the minimum level is not valid + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The level ".*" is not valid./ + */ + public function testWrongMinimumLevel() + { + $logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE'); + } + + /** + * Test when the minimum level is not valid + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /The level ".*" is not valid./ + */ + public function testWrongLogLevel() + { + $logger = new SyslogLoggerWrapper('test', $this->introspection); + + $logger->log('NOPE', 'a test'); + } +} diff --git a/tests/src/Util/Logger/SyslogLoggerWrapper.php b/tests/src/Util/Logger/SyslogLoggerWrapper.php new file mode 100644 index 000000000..63ad53690 --- /dev/null +++ b/tests/src/Util/Logger/SyslogLoggerWrapper.php @@ -0,0 +1,37 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +use Friendica\Util\Introspection; +use Friendica\Util\Logger\SyslogLogger; +use Psr\Log\LogLevel; + +class SyslogLoggerWrapper extends SyslogLogger +{ + private $content; + + public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER) + { + parent::__construct($channel, $introspection, $level, $logOpts, $logFacility); + + $this->content = ''; + } + + /** + * Gets the content from the wrapped Syslog + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * {@inheritdoc} + */ + protected function syslogWrapper($level, $entry) + { + $this->content .= $entry . PHP_EOL; + } +} diff --git a/tests/src/Util/Logger/VoidLoggerTest.php b/tests/src/Util/Logger/VoidLoggerTest.php new file mode 100644 index 000000000..4c436d697 --- /dev/null +++ b/tests/src/Util/Logger/VoidLoggerTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Friendica\Test\src\Util\Logger; + +use Friendica\Test\MockedTest; +use Friendica\Util\Logger\VoidLogger; +use Psr\Log\LogLevel; + +class VoidLoggerTest extends MockedTest +{ + use LoggerDataTrait; + + /** + * Test if the profiler is profiling data + * @dataProvider dataTests + */ + public function testNormal($function, $message, array $context) + { + $logger = new VoidLogger(); + $logger->$function($message, $context); + } + + /** + * Test the log() function + */ + public function testProfilingLog() + { + $logger = new VoidLogger(); + $logger->log(LogLevel::WARNING, 'test', ['a' => 'context']); + } +}