Replace debug log with respecting log priorities
refs #5638 fixes #5522
This commit is contained in:
parent
c5f016b220
commit
8edf421c45
|
@ -6,35 +6,50 @@ indexController = "dashboard"
|
|||
dateFormat = "d/m/Y"
|
||||
timeFormat = "g:i A"
|
||||
|
||||
; The directory that contains the symlinks to all enabled directories.
|
||||
; The directory that contains the symlinks to all enabled directories
|
||||
moduleFolder = "@icingaweb_config_path@/enabledModules"
|
||||
|
||||
; Contains the directories that will be searched for available modules. Modules that
|
||||
; don't exist in these directories can still be symlinked in the module folder, but
|
||||
; won't show up in the list of disabled modules.
|
||||
; won't show up in the list of disabled modules
|
||||
; modulePath = "/vagrant/modules:/usr/share/icingaweb/modules"
|
||||
|
||||
[logging]
|
||||
; General log
|
||||
enable = "1"
|
||||
;enable = false
|
||||
; Writing to a Stream
|
||||
type = "stream"
|
||||
verbose = "1"
|
||||
; Write data to the following file
|
||||
target = "@icingaweb_log_path@/icingaweb.log"
|
||||
; Write data to a PHP stream
|
||||
;target = "php://output"
|
||||
|
||||
; For development and debug purposes: Logs additional (non critical) events to a
|
||||
; seperate log
|
||||
debug.enable = "1"
|
||||
debug.type = "stream"
|
||||
debug.target = "@icingaweb_log_path@/icingaweb.debug.log"
|
||||
;; Writing to the System Log
|
||||
;type = "syslog"
|
||||
;; Prefix all syslog messages generated with the string "Icinga Web"
|
||||
;application = "Icinga Web"
|
||||
;facility = "LOG_USER"
|
||||
|
||||
priority = 4
|
||||
; The default priority is WARN, which means that only events of this priority and above will be tracked.
|
||||
; Priority numbers descend in order of importance where EMERG (0) is the most important priority and DEBUG (7) is the
|
||||
; least important priority:
|
||||
;
|
||||
; EMERG = 0 - Emergency: system is unusable
|
||||
; ALERT = 1 - Alert: action must be taken immediately
|
||||
; CRIT = 2 - Critical: critical conditions
|
||||
; ERR = 3 - Error: error conditions
|
||||
; WARN = 4 - Warning: warning conditions
|
||||
; NOTICE = 5 - Notice: normal but significant condition
|
||||
; INFO = 6 - Informational: informational messages
|
||||
; DEBUG = 7 - Debug: debug messages
|
||||
|
||||
; Use ini store to store preferences on local disk
|
||||
[preferences]
|
||||
; Use INI file storage to save preferences to a local disk
|
||||
type = "ini"
|
||||
|
||||
; Use database to store preference into mysql or postgres
|
||||
;[preferences]
|
||||
;type=db
|
||||
;resource=icingaweb-mysql
|
||||
|
||||
configPath = "@icingaweb_config_path@/preferences"
|
||||
|
||||
; Use database storage to save preferences in either a MySQL or PostgreSQL database
|
||||
;type = db
|
||||
;resource = icingaweb-mysql
|
||||
|
||||
|
||||
|
|
|
@ -29,14 +29,13 @@
|
|||
|
||||
namespace Icinga\Application;
|
||||
|
||||
use \DateTimeZone;
|
||||
use \Exception;
|
||||
use \Icinga\Application\Modules\Manager as ModuleManager;
|
||||
use \Icinga\Exception\ConfigurationError;
|
||||
use \Icinga\Util\DateTimeFactory;
|
||||
use \Icinga\Util\Translator;
|
||||
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use Icinga\Application\Modules\Manager as ModuleManager;
|
||||
use Icinga\Data\ResourceFactory;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Util\DateTimeFactory;
|
||||
use Icinga\Util\Translator;
|
||||
|
||||
/**
|
||||
* This class bootstraps a thin Icinga application layer
|
||||
|
@ -325,10 +324,7 @@ abstract class ApplicationBootstrap
|
|||
try {
|
||||
$this->moduleManager->loadEnabledModules();
|
||||
} catch (Exception $e) {
|
||||
Logger::fatal(
|
||||
'Could not load modules. An exception was thrown during bootstrap: %s',
|
||||
$e->getMessage()
|
||||
);
|
||||
Logger::exception(new Exception('Cannot load enabled modules. An exception was thrown:', 0, $e));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -29,12 +29,14 @@
|
|||
|
||||
namespace Icinga\Application;
|
||||
|
||||
use \Zend_Config;
|
||||
use \Zend_Log;
|
||||
use \Zend_Log_Filter_Priority;
|
||||
use \Zend_Log_Writer_Abstract;
|
||||
use \Zend_Log_Exception;
|
||||
use Exception;
|
||||
use Zend_Config;
|
||||
use Zend_Log;
|
||||
use Zend_Log_Exception;
|
||||
use Zend_Log_Filter_Priority;
|
||||
use Zend_Log_Writer_Abstract;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Util\File;
|
||||
|
||||
/**
|
||||
* Zend_Log wrapper
|
||||
|
@ -42,22 +44,7 @@ use Icinga\Exception\ConfigurationError;
|
|||
class Logger
|
||||
{
|
||||
/**
|
||||
* Default log type
|
||||
*/
|
||||
const DEFAULT_LOG_TYPE = "stream";
|
||||
|
||||
/**
|
||||
* Default log target
|
||||
*/
|
||||
const DEFAULT_LOG_TARGET = "./var/log/icingaweb.log";
|
||||
|
||||
/**
|
||||
* Default debug target
|
||||
*/
|
||||
const DEFAULT_DEBUG_TARGET = "./var/log/icingaweb.debug.log";
|
||||
|
||||
/**
|
||||
* Array of writers
|
||||
* Writers attached to the instance of Zend_Log
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -71,37 +58,39 @@ class Logger
|
|||
private $logger;
|
||||
|
||||
/**
|
||||
* Singleton instance
|
||||
* Singleton Logger instance
|
||||
*
|
||||
* @var Logger
|
||||
* @var self
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Queue of unwritten messages
|
||||
*
|
||||
* @var array
|
||||
* Format for logging exceptions
|
||||
*/
|
||||
private static $queue = array();
|
||||
const LOG_EXCEPTION_FORMAT = <<<'EOD'
|
||||
%s: %s
|
||||
|
||||
Stacktrace
|
||||
----------
|
||||
%s
|
||||
EOD;
|
||||
|
||||
/**
|
||||
* Flag indicate that errors occurred in the past
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $errorsOccurred = false;
|
||||
|
||||
/**
|
||||
* Create a new logger object
|
||||
* Create a new Logger
|
||||
*
|
||||
* @param Zend_Config $config
|
||||
*/
|
||||
public function __construct(Zend_Config $config)
|
||||
{
|
||||
$this->overwrite($config);
|
||||
$this->logger = new Zend_Log();
|
||||
if ((bool) $config->get('enable', true) === true) {
|
||||
$this->addWriter($config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the writers attached to the instance of Zend_Log
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getWriters()
|
||||
|
@ -110,131 +99,59 @@ class Logger
|
|||
}
|
||||
|
||||
/**
|
||||
* Overwrite config to initiated logger
|
||||
* Add writer to the Zend_Log instance
|
||||
*
|
||||
* @param Zend_Config $config
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function overwrite(Zend_Config $config)
|
||||
{
|
||||
$this->clearLog();
|
||||
try {
|
||||
if ($config->debug && $config->debug->enable == '1') {
|
||||
$this->setupDebugLog($config);
|
||||
}
|
||||
} catch (ConfigurationError $e) {
|
||||
$this->warn('Could not create debug log: ' . $e->getMessage());
|
||||
}
|
||||
if ($config->get('enable', '1') != '0') {
|
||||
$this->setupLog($config);
|
||||
}
|
||||
$this->flushQueue();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure debug log
|
||||
*
|
||||
* @param Zend_Config $config
|
||||
*/
|
||||
private function setupDebugLog(Zend_Config $config)
|
||||
{
|
||||
$type = $config->debug->get("type", self::DEFAULT_LOG_TYPE);
|
||||
$target = $config->debug->get("target", self::DEFAULT_LOG_TARGET);
|
||||
if ($target == self::DEFAULT_LOG_TARGET) {
|
||||
$type = self::DEFAULT_LOG_TYPE;
|
||||
}
|
||||
$this->addWriter($type, $target, Zend_Log::DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure log
|
||||
*
|
||||
* @param Zend_Config $config
|
||||
*/
|
||||
private function setupLog(Zend_Config $config)
|
||||
{
|
||||
$type = $config->get("type", self::DEFAULT_LOG_TYPE);
|
||||
$target = $config->get("target", self::DEFAULT_DEBUG_TARGET);
|
||||
if ($target == self::DEFAULT_DEBUG_TARGET) {
|
||||
$type = self::DEFAULT_LOG_TYPE;
|
||||
}
|
||||
$level = Zend_Log::WARN;
|
||||
if ($config->get("verbose", 0) == 1) {
|
||||
$level = Zend_Log::INFO;
|
||||
}
|
||||
$this->addWriter($type, $target, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add writer to log instance
|
||||
*
|
||||
* @param string $type Type, e.g. stream
|
||||
* @param string $target Target, e.g. filename
|
||||
* @param int $priority Value of Zend::* constant
|
||||
* @throws ConfigurationError
|
||||
*/
|
||||
private function addWriter($type, $target, $priority)
|
||||
public function addWriter($config)
|
||||
{
|
||||
$type[0] = strtoupper($type[0]);
|
||||
$writerClass = "Zend_Log_Writer_" . $type;
|
||||
|
||||
if (($type = $config->type) === null) {
|
||||
throw new ConfigurationError('Logger configuration is missing the type directive');
|
||||
}
|
||||
$type = ucfirst(strtolower($type));
|
||||
$writerClass = 'Zend_Log_Writer_' . $type;
|
||||
if (!@class_exists($writerClass)) {
|
||||
self::fatal(
|
||||
'Could not add log writer of type "%s". Type does not exist.',
|
||||
$type
|
||||
);
|
||||
return;
|
||||
throw new ConfigurationError('Cannot add log writer of type "' . $type . '". Type does not exist');
|
||||
}
|
||||
try {
|
||||
|
||||
$target = Config::resolvePath($target);
|
||||
// Make sure the permissions for log target file are correct
|
||||
if ($type === 'Stream') {
|
||||
$writer = new $writerClass($target);
|
||||
if (substr($target, 0, 6) !== 'php://' && !file_exists($target)) {
|
||||
touch($target);
|
||||
chmod($target, 0664);
|
||||
}
|
||||
} elseif ($type === 'Syslog') {
|
||||
$writer = new $writerClass();
|
||||
switch ($type) {
|
||||
case 'Stream':
|
||||
if (($target = $config->target) === null) {
|
||||
throw new ConfigurationError(
|
||||
'Logger configuration is missing the target directive for type stream'
|
||||
);
|
||||
}
|
||||
$target = Config::resolvePath($target);
|
||||
$writer = new $writerClass($target);
|
||||
if (substr($target, 0, 6) !== 'php://' && !file_exists($target)) {
|
||||
File::create($target);
|
||||
}
|
||||
break;
|
||||
case 'Syslog':
|
||||
$writer = new $writerClass($config->toArray());
|
||||
break;
|
||||
default:
|
||||
throw new ConfigurationError('Logger configuration defines an invalid log type "' . $type . '"');
|
||||
}
|
||||
if (($priority = $config->priority) === null) {
|
||||
$priority = Zend_Log::WARN;
|
||||
} else {
|
||||
self::fatal('Got invalid lot type "%s"', $type);
|
||||
$priority = (int) $priority;
|
||||
}
|
||||
$writer->addFilter(new Zend_Log_Filter_Priority($priority));
|
||||
|
||||
$this->logger->addWriter($writer);
|
||||
$this->writers[] = $writer;
|
||||
} catch (Zend_Log_Exception $e) {
|
||||
self::fatal(
|
||||
'Could not add log writer of type %s. An exception was thrown: %s',
|
||||
$type,
|
||||
$e->getMessage()
|
||||
throw new ConfigurationError(
|
||||
'Cannot not add log writer of type "' . $type . '". An exception was thrown: '. $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush pending messages to writer
|
||||
*/
|
||||
public function flushQueue()
|
||||
{
|
||||
try {
|
||||
foreach (self::$queue as $msgTypePair) {
|
||||
$this->logger->log($msgTypePair[0], $msgTypePair[1]);
|
||||
}
|
||||
} catch (Zend_Log_Exception $e) {
|
||||
self::fatal(
|
||||
'Could not flush logs to output. An exception was thrown: %s',
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format output message
|
||||
* Format a message
|
||||
*
|
||||
* @param array $argv
|
||||
*
|
||||
|
@ -260,28 +177,13 @@ class Logger
|
|||
}
|
||||
|
||||
/**
|
||||
* Reset object configuration
|
||||
*/
|
||||
public function clearLog()
|
||||
{
|
||||
$this->logger = null;
|
||||
$this->writers = array();
|
||||
$this->logger = new Zend_Log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance
|
||||
* Create/overwrite the internal Logger instance
|
||||
*
|
||||
* @param Zend_Config $config
|
||||
*
|
||||
* @return Logger
|
||||
* @param Zend_Config $config
|
||||
*/
|
||||
public static function create(Zend_Config $config)
|
||||
{
|
||||
if (self::$instance) {
|
||||
return self::$instance->overwrite($config);
|
||||
}
|
||||
return self::$instance = new Logger($config);
|
||||
self::$instance = new static($config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -317,63 +219,36 @@ class Logger
|
|||
}
|
||||
|
||||
/**
|
||||
* Log message with severity fatal
|
||||
*/
|
||||
public static function fatal()
|
||||
{
|
||||
self::log(self::formatMessage(func_get_args()), Zend_Log::EMERG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
* Log a message at a priority
|
||||
*
|
||||
* @param string $msg Message
|
||||
* @param int $level Log level
|
||||
* @param string $message Message to log
|
||||
* @param int $priority Priority of message
|
||||
*/
|
||||
private static function log($msg, $level = Zend_Log::INFO)
|
||||
private static function log($message, $priority = Zend_Log::INFO)
|
||||
{
|
||||
$logger = self::$instance;
|
||||
|
||||
if ($level < Zend_Log::WARN && self::$errorsOccurred === false) {
|
||||
self::$errorsOccurred =true;
|
||||
// Swallow messages if the Logger hast not been created
|
||||
if (self::$instance !== null && count(self::$instance->getWriters()) > 0) {
|
||||
self::$instance->logger->log($message, $priority);
|
||||
}
|
||||
|
||||
if (!$logger || !count($logger->getWriters())) {
|
||||
array_push(self::$queue, array($msg, $level));
|
||||
return;
|
||||
}
|
||||
|
||||
$logger->logger->log($msg, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag if messages > warning occurred
|
||||
* Log a exception at a priority
|
||||
*
|
||||
* @return bool
|
||||
* @param Exception $e Exception to log
|
||||
* @param int $priority Priority of message
|
||||
*/
|
||||
public static function hasErrorsOccurred()
|
||||
public static function exception(Exception $e, $priority = Zend_Log::ERR)
|
||||
{
|
||||
return self::$errorsOccurred;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the log queue
|
||||
*
|
||||
* The log queue holds messages that could not be written to output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getQueue()
|
||||
{
|
||||
return self::$queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset object state
|
||||
*/
|
||||
public static function reset()
|
||||
{
|
||||
self::$queue = array();
|
||||
self::$instance = null;
|
||||
$message = array();
|
||||
do {
|
||||
$message[] = self::formatMessage(
|
||||
array(self::LOG_EXCEPTION_FORMAT, get_class($e), $e->getMessage(), $e->getTraceAsString())
|
||||
);
|
||||
} while($e = $e->getPrevious());
|
||||
self::log(
|
||||
implode(PHP_EOL, $message),
|
||||
$priority
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,7 +310,7 @@ class Manager
|
|||
$this->persistCurrentUser();
|
||||
}
|
||||
|
||||
Logger::info('User "%s" logged in successfully', $credentials->getUsername());
|
||||
Logger::info('User "%s" logged in', $credentials->getUsername());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -33,164 +33,52 @@ namespace Tests\Icinga\Application;
|
|||
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Test/BaseTestCase.php');
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
use \Zend_Config;
|
||||
use Zend_Config;
|
||||
use Zend_Log;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
/**
|
||||
* Test class for Logger
|
||||
*
|
||||
* @backupStaticAttributes enabled
|
||||
**/
|
||||
class LoggerTest extends BaseTestCase
|
||||
{
|
||||
private $tempDir;
|
||||
|
||||
private $logTarget;
|
||||
|
||||
private $debugTarget;
|
||||
|
||||
public function setUp()
|
||||
/**
|
||||
* @backupStaticAttributes enabled
|
||||
*/
|
||||
public function testLogfileCreation()
|
||||
{
|
||||
$this->tempDir = tempnam(sys_get_temp_dir(), 'icingaweb-log');
|
||||
unlink($this->tempDir); // tempnam create the file automatically
|
||||
|
||||
if (!is_dir($this->tempDir)) {
|
||||
mkdir($this->tempDir, 0755);
|
||||
}
|
||||
|
||||
$this->debugTarget = $this->tempDir . '/debug.log';
|
||||
$this->logTarget = $this->tempDir . '/application.log';
|
||||
|
||||
$loggingConfigurationArray = array(
|
||||
'enable' => 1,
|
||||
'type' => 'stream',
|
||||
'verbose' => 1,
|
||||
'target' => $this->logTarget,
|
||||
|
||||
'debug' => array(
|
||||
'enable' => 1,
|
||||
'type' => 'stream',
|
||||
'target' => $this->debugTarget
|
||||
$target = tempnam(sys_get_temp_dir(), 'log');
|
||||
unlink($target);
|
||||
Logger::create(
|
||||
new Zend_Config(
|
||||
array(
|
||||
'type' => 'stream',
|
||||
'target' => $target
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$loggingConfiguration = new Zend_Config($loggingConfigurationArray);
|
||||
|
||||
Logger::reset();
|
||||
Logger::create($loggingConfiguration);
|
||||
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if (file_exists($this->debugTarget)) {
|
||||
unlink($this->debugTarget);
|
||||
}
|
||||
|
||||
if (file_exists($this->logTarget)) {
|
||||
unlink($this->logTarget);
|
||||
}
|
||||
|
||||
rmdir($this->tempDir);
|
||||
}
|
||||
|
||||
private function getLogData()
|
||||
{
|
||||
return array(
|
||||
explode(PHP_EOL, file_get_contents($this->logTarget)),
|
||||
explode(PHP_EOL, file_get_contents($this->debugTarget))
|
||||
);
|
||||
$this->assertFileExists($target, 'Logger did not create the log file');
|
||||
unlink($target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test error messages
|
||||
* @backupStaticAttributes enabled
|
||||
* @depends testLogfileCreation
|
||||
*/
|
||||
public function testLoggingErrorMessages()
|
||||
{
|
||||
Logger::error('test-error-1');
|
||||
Logger::error('test-error-2');
|
||||
|
||||
$this->assertFileExists($this->logTarget);
|
||||
$this->assertFileExists($this->debugTarget);
|
||||
|
||||
list($main, $debug) = $this->getLogData();
|
||||
|
||||
$this->assertCount(3, $main);
|
||||
$this->assertCount(3, $debug);
|
||||
|
||||
$this->assertContains(' ERR (3): test-error-1', $main[0]);
|
||||
$this->assertContains(' ERR (3): test-error-2', $main[1]);
|
||||
|
||||
$this->assertContains(' ERR (3): test-error-1', $debug[0]);
|
||||
$this->assertContains(' ERR (3): test-error-2', $debug[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test debug log and difference between error and debug messages
|
||||
*/
|
||||
public function testLoggingDebugMessages()
|
||||
{
|
||||
Logger::debug('test-debug-1');
|
||||
Logger::error('test-error-1');
|
||||
Logger::debug('test-debug-2');
|
||||
|
||||
$this->assertFileExists($this->logTarget);
|
||||
$this->assertFileExists($this->debugTarget);
|
||||
|
||||
list($main, $debug) = $this->getLogData();
|
||||
|
||||
$this->assertCount(2, $main);
|
||||
$this->assertCount(4, $debug);
|
||||
|
||||
$this->assertContains(' ERR (3): test-error-1', $main[0]);
|
||||
|
||||
$this->assertContains(' DEBUG (7): test-debug-1', $debug[0]);
|
||||
$this->assertContains(' ERR (3): test-error-1', $debug[1]);
|
||||
$this->assertContains(' DEBUG (7): test-debug-2', $debug[2]);
|
||||
}
|
||||
|
||||
public function testLoggingQueueIfNoWriterAvailable()
|
||||
{
|
||||
Logger::reset();
|
||||
|
||||
Logger::error('test-error-1');
|
||||
Logger::debug('test-debug-1');
|
||||
Logger::error('test-error-2');
|
||||
|
||||
list($main, $debug) = $this->getLogData();
|
||||
|
||||
$this->assertCount(1, $main);
|
||||
$this->assertCount(1, $debug);
|
||||
|
||||
$this->assertTrue(Logger::hasErrorsOccurred());
|
||||
|
||||
$queue = Logger::getQueue();
|
||||
|
||||
$this->assertCount(3, $queue);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'test-error-1',
|
||||
3
|
||||
),
|
||||
$queue[0]
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'test-debug-1',
|
||||
7
|
||||
),
|
||||
$queue[1]
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'test-error-2',
|
||||
3
|
||||
),
|
||||
$queue[2]
|
||||
$target = tempnam(sys_get_temp_dir(), 'log');
|
||||
unlink($target);
|
||||
Logger::create(
|
||||
new Zend_Config(
|
||||
array(
|
||||
'type' => 'stream',
|
||||
'priority' => Zend_Log::ERR,
|
||||
'target' => $target
|
||||
)
|
||||
)
|
||||
);
|
||||
Logger::error('This is a test error');
|
||||
$log = file_get_contents($target);
|
||||
unlink($target);
|
||||
$this->assertContains('This is a test error', $log, 'Log does not contain the error "This is a test error"');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue