diff --git a/main/inc/lib/autoload.class.php b/main/inc/lib/autoload.class.php index 35a3ed42cb..49f06b2044 100644 --- a/main/inc/lib/autoload.class.php +++ b/main/inc/lib/autoload.class.php @@ -221,6 +221,7 @@ class Autoload $result['LinkCategory'] = '/main/coursecopy/classes/LinkCategory.class.php'; $result['LinkFactory'] = '/main/gradebook/lib/be/linkfactory.class.php'; $result['LinkForm'] = '/main/gradebook/lib/fe/linkform.class.php'; + $result['Log'] = '/main/inc/lib/log.class.php'; $result['Login'] = '/main/inc/lib/login.lib.php'; $result['LoginRedirection'] = '/main/inc/lib/login_redirection.class.php'; $result['Matching'] = '/main/exercice/matching.class.php'; diff --git a/main/inc/lib/log.class.php b/main/inc/lib/log.class.php new file mode 100644 index 0000000000..80a8a0d67c --- /dev/null +++ b/main/inc/lib/log.class.php @@ -0,0 +1,149 @@ + for the Univesity of Geneva + */ +class Log +{ + + private static function register_autoload() + { + static $has_run = false; + if ($has_run) { + return true; + } + + $directory = api_get_path(LIBRARY_PATH) . 'symfony'; + if (!class_exists('Doctrine\Common\ClassLoader', false)) { + require_once $directory . '/doctrine/Common/ClassLoader.php'; + } + + $loader = new Doctrine\Common\ClassLoader('Monolog', $directory); + $loader->register(); + + $has_run = true; + } + + /** + * + * @return \Monolog\Logger + */ + public static function logger() + { + static $result = null; + if (empty($result)) { + self::register_autoload(); + $name = Chamilo::name(); + $result = new Logger($name); + $handler = new Monolog\Handler\StreamHandler('php://stderr'); + $handler->setFormatter(new Monolog\Formatter\LineFormatter('[%datetime%] [%level_name%] [%channel%]: %message% %context% %extra%' . PHP_EOL, 'Y-m-d H:i:s')); + $result->pushHandler($handler); + //$result->pushProcessor(new \Monolog\Processor\WebProcessor()); + } + return $result; + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function debug($message, array $context = array()) + { + return self::logger()->debug($message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function info($message, array $context = array()) + { + return self::logger()->info($message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function notice($message, array $context = array()) + { + return self::logger()->notice($message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function warning($message, array $context = array()) + { + return self::logger()->warn($message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function error($message, array $context = array()) + { + return self::logger()->err($message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function crit($message, array $context = array()) + { + return self::logger()->crit($message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public static function alert($message, array $context = array()) + { + return self::logger()->alert($message, $context); + } + +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Formatter/FormatterInterface.php b/main/inc/lib/symfony/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000000..77891de80b --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + function formatBatch(array $records); +} diff --git a/main/inc/lib/symfony/Monolog/Formatter/JsonFormatter.php b/main/inc/lib/symfony/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000000..ab2017938e --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter implements FormatterInterface +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return json_encode($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + return json_encode($records); + } +} diff --git a/main/inc/lib/symfony/Monolog/Formatter/LineFormatter.php b/main/inc/lib/symfony/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000000..53100b7923 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter implements FormatterInterface +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $format; + protected $dateFormat; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($format = null, $dateFormat = null) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = $record; + $vars['datetime'] = $vars['datetime']->format($this->dateFormat); + + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->convertToString($val), $output); + unset($vars['extra'][$var]); + } + } + foreach ($vars as $var => $val) { + $output = str_replace('%'.$var.'%', $this->convertToString($val), $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + return stripslashes(json_encode($this->normalize($data))); + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + return $data; + } + + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + foreach ($data as $key => $value) { + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if (is_resource($data)) { + return '[resource]'; + } + + return sprintf("[object] (%s: %s)", get_class($data), json_encode($data)); + } +} diff --git a/main/inc/lib/symfony/Monolog/Formatter/WildfireFormatter.php b/main/inc/lib/symfony/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000000..4c393a9400 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + // Create JSON object describing the appearance of the message in the console + $json = json_encode(array( + array( + 'Type' => $this->logLevels[$record['level']], + 'File' => $file, + 'Line' => $line, + 'Label' => $record['channel'], + ), + $message, + )); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/AbstractHandler.php b/main/inc/lib/symfony/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000000..6dbf77a7cb --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = false; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->level = $level; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param integer $level + */ + public function setLevel($level) + { + $this->level = $level; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return integer + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble True means that bubbling is not permitted. + * False means that this handler allows bubbling. + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean True means that bubbling is not permitted. + * False means that this handler allows bubbling. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + $this->close(); + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/AbstractProcessingHandler.php b/main/inc/lib/symfony/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000000..9babe037ea --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/BufferHandler.php b/main/inc/lib/symfony/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000000..7031607f19 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/BufferHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize; + protected $buffer = array(); + + /** + * @param HandlerInterface $handler Handler. + * @param integer $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(HandlerInterface $handler, $bufferSize = 0, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferSize = $bufferSize; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->handler->handleBatch($this->buffer); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/FingersCrossedHandler.php b/main/inc/lib/symfony/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000000..3086c73514 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $actionLevel; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + + /** + * @param callback|HandlerInterface $handler Handler or factory callback($record, $fingersCrossedHandler). + * @param int $actionLevel The minimum logging level at which this handler will be triggered + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + */ + public function __construct($handler, $actionLevel = Logger::WARNING, $bufferSize = 0, $bubble = true, $stopBuffering = true) + { + $this->handler = $handler; + $this->actionLevel = $actionLevel; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($record['level'] >= $this->actionLevel) { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + $this->handler = call_user_func($this->handler, $record, $this); + } + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callback should return a HandlerInterface"); + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/FirePHPHandler.php b/main/inc/lib/symfony/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000000..3ca392d7e5 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && $this->sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + $this->sendHeaders = $this->headersAccepted(); + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + + self::$initialized = true; + } + + $header = $this->createRecordHeader($record); + $this->sendHeader(key($header), current($header)); + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + return !isset($_SERVER['HTTP_USER_AGENT']) || preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT']); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/GroupHandler.php b/main/inc/lib/symfony/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000000..c94c52f3f8 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/GroupHandler.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/HandlerInterface.php b/main/inc/lib/symfony/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000000..24f82d7efe --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * @return Boolean + */ + function isHandling(array $record); + + /** + * Handles a record. + * + * The return value of this function controls the bubbling process of the handler stack. + * + * @param array $record The record to handle + * @return Boolean True means that this handler handled the record, and that bubbling is not permitted. + * False means the record was either not processed or that this handler allows bubbling. + */ + function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + */ + function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + function getFormatter(); +} diff --git a/main/inc/lib/symfony/Monolog/Handler/MailHandler.php b/main/inc/lib/symfony/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000000..9e9ab5638f --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/MailHandler.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages)); + } + } + + /** + * Send a mail with the given content + * + * @param string $content + */ + abstract protected function send($content); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted']); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/NativeMailerHandler.php b/main/inc/lib/symfony/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000000..56b7666289 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + */ +class NativeMailerHandler extends MailHandler +{ + protected $to; + protected $subject; + protected $headers; + + /** + * @param string $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + $this->to = $to; + $this->subject = $subject; + $this->headers = sprintf("From: %s\r\nContent-type: text/plain; charset=utf-8\r\n", $from); + } + + /** + * {@inheritdoc} + */ + protected function send($content) + { + mail($this->to, $this->subject, wordwrap($content, 70), $this->headers); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/NullHandler.php b/main/inc/lib/symfony/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000000..7756d9e572 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/NullHandler.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/RotatingFileHandler.php b/main/inc/lib/symfony/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000000..ebecd56952 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + */ +class RotatingFileHandler extends StreamHandler +{ + protected $filename; + protected $maxFiles; + protected $mustRotate; + + /** + * @param string $filename + * @param integer $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + + $fileInfo = pathinfo($this->filename); + $timedFilename = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-'.date('Y-m-d'); + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + // disable rotation upfront if files are unlimited + if (0 === $this->maxFiles) { + $this->mustRotate = false; + } + + parent::__construct($timedFilename, $level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + $fileInfo = pathinfo($this->filename); + $glob = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-*'; + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + $iterator = new \GlobIterator($glob); + $count = $iterator->count(); + if ($this->maxFiles >= $count) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + $array = iterator_to_array($iterator); + usort($array, function($a, $b) { + return strcmp($b->getFilename(), $a->getFilename()); + }); + + foreach (array_slice($array, $this->maxFiles) as $file) { + if ($file->isWritable()) { + unlink($file->getRealPath()); + } + } + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/StreamHandler.php b/main/inc/lib/symfony/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000000..3268ae48ba --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/StreamHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\SimpleFormatter; +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + + /** + * @param string $stream + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } else { + $this->url = $stream; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (null === $this->stream) { + if (!$this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->stream = @fopen($this->url, 'a'); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened; it may be invalid or not writable.', $this->url)); + } + } + fwrite($this->stream, (string) $record['formatted']); + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/SwiftMailerHandler.php b/main/inc/lib/symfony/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000000..e6fef494c6 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + protected $message; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callback|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + $this->mailer = $mailer; + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callback returning it'); + } + $this->message = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content) + { + $message = clone $this->message; + $message->setBody($content); + + $this->mailer->send($message); + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Handler/SyslogHandler.php b/main/inc/lib/symfony/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000000..b381503cee --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\SimpleFormatter; +use Monolog\Logger; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to syslog log priorities. + */ + private $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + ); + + /** + * List of valid log facility names. + */ + private $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param string $ident + * @param mixed $facility + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } else if (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + if (!openlog($ident, LOG_PID, $facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$ident.'" and facility "'.$facility.'"'); + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } +} diff --git a/main/inc/lib/symfony/Monolog/Handler/TestHandler.php b/main/inc/lib/symfony/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000000..8f478557e8 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Handler/TestHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function hasAlert($record) + { + return $this->hasRecord($record, Logger::ALERT); + } + + public function hasCritical($record) + { + return $this->hasRecord($record, Logger::CRITICAL); + } + + public function hasError($record) + { + return $this->hasRecord($record, Logger::ERROR); + } + + public function hasWarning($record) + { + return $this->hasRecord($record, Logger::WARNING); + } + + public function hasInfo($record) + { + return $this->hasRecord($record, Logger::INFO); + } + + public function hasDebug($record) + { + return $this->hasRecord($record, Logger::DEBUG); + } + + public function hasAlertRecords() + { + return isset($this->recordsByLevel[Logger::ALERT]); + } + + public function hasCriticalRecords() + { + return isset($this->recordsByLevel[Logger::CRITICAL]); + } + + public function hasErrorRecords() + { + return isset($this->recordsByLevel[Logger::ERROR]); + } + + public function hasWarningRecords() + { + return isset($this->recordsByLevel[Logger::WARNING]); + } + + public function hasInfoRecords() + { + return isset($this->recordsByLevel[Logger::INFO]); + } + + public function hasDebugRecords() + { + return isset($this->recordsByLevel[Logger::DEBUG]); + } + + protected function hasRecord($record, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + if (is_array($record)) { + $record = $record['message']; + } + + foreach ($this->recordsByLevel[$level] as $rec) { + if ($rec['message'] === $record) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } +} \ No newline at end of file diff --git a/main/inc/lib/symfony/Monolog/Logger.php b/main/inc/lib/symfony/Monolog/Logger.php new file mode 100644 index 0000000000..618c9523a1 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Logger.php @@ -0,0 +1,394 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Exceptional occurences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + protected static $levels = array( + 100 => 'DEBUG', + 200 => 'INFO', + 300 => 'WARNING', + 400 => 'ERROR', + 500 => 'CRITICAL', + 550 => 'ALERT', + ); + + protected $name; + + /** + * The handler stack + * + * @var array of Monolog\Handler\HandlerInterface + */ + protected $handlers = array(); + + protected $processors = array(); + + /** + * @param string $name The logging channel + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Pushes an handler on the stack. + * + * @param HandlerInterface $handler + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + } + + /** + * Pops an handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + return array_shift($this->handlers); + } + + /** + * Adds a processor in the stack. + * + * @param callable $callback + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + + /** + * Adds a log record. + * + * @param integer $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', self::DEBUG)); + } + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => self::getLevelName($level), + 'channel' => $this->name, + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + // check if any message will handle this message + $handlerKey = null; + foreach ($this->handlers as $key => $handler) { + if ($handler->isHandling($record)) { + $handlerKey = $key; + break; + } + } + // none found + if (null === $handlerKey) { + return false; + } + // found at least one, process message and dispatch it + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + while (isset($this->handlers[$handlerKey]) && + false === $this->handlers[$handlerKey]->handle($record)) { + $handlerKey++; + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(self::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(self::INFO, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(self::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(self::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(self::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(self::ALERT, $message, $context); + } + + /** + * Gets the name of the logging level. + * + * @param integer $level + * @return string + */ + public static function getLevelName($level) + { + return self::$levels[$level]; + } + + // ZF Logger Compat + + /** + * Adds a log record at the DEBUG level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(self::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(self::INFO, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(self::INFO, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(self::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(self::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(self::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(self::ALERT, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows to have an easy ZF compatibility. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(self::ALERT, $message, $context); + } +} diff --git a/main/inc/lib/symfony/Monolog/Processor/IntrospectionProcessor.php b/main/inc/lib/symfony/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000000..f03e34723f --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $trace = debug_backtrace(); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + while (isset($trace[$i]['class']) && false !== strpos($trace[$i]['class'], 'Monolog\\')) { + $i++; + } + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i-1]['file']) ? $trace[$i-1]['file'] : null, + 'line' => isset($trace[$i-1]['line']) ? $trace[$i-1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } +} diff --git a/main/inc/lib/symfony/Monolog/Processor/MemoryPeakUsageProcessor.php b/main/inc/lib/symfony/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000000..77e0324022 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_peak_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/main/inc/lib/symfony/Monolog/Processor/MemoryProcessor.php b/main/inc/lib/symfony/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000000..7a41238603 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + protected $realUsage; + + /** + * @param boolean $realUsage + */ + public function __construct($realUsage = true) + { + $this->realUsage = (boolean) $realUsage; + } + + /** + * Formats bytes into a human readable string + * + * @param int $bytes + * @return string + */ + protected static function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if ($bytes > 1024*1024) { + return round($bytes/1024/1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes/1024, 2).' KB'; + } + + return $bytes . ' B'; + } + +} diff --git a/main/inc/lib/symfony/Monolog/Processor/MemoryUsageProcessor.php b/main/inc/lib/symfony/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000000..0867459fc8 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/main/inc/lib/symfony/Monolog/Processor/WebProcessor.php b/main/inc/lib/symfony/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000000..035606c724 --- /dev/null +++ b/main/inc/lib/symfony/Monolog/Processor/WebProcessor.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + protected $serverData; + + /** + * @param mixed $serverData array or object w/ ArrayAccess that provides access to the $_SERVER data + */ + public function __construct($serverData = null) + { + if (null === $serverData) { + $this->serverData =& $_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = array_merge( + $record['extra'], + array( + 'url' => $this->serverData['REQUEST_URI'], + 'ip' => $this->serverData['REMOTE_ADDR'], + 'http_method' => $this->serverData['REQUEST_METHOD'], + ) + ); + + return $record; + } +} diff --git a/main/install/index.php b/main/install/index.php index 7023998220..ae3e72a07d 100644 --- a/main/install/index.php +++ b/main/install/index.php @@ -42,7 +42,9 @@ session_start(); // Including necessary libraries. require_once '../inc/lib/main_api.lib.php'; require_once api_get_path(LIBRARY_PATH).'database.lib.php'; +require_once api_get_path(LIBRARY_PATH).'log.class.php'; require_once 'install.lib.php'; +require_once 'install.class.php'; // The function api_get_setting() might be called within the installation scripts. // We need to provide some limited support for it through initialization of the diff --git a/main/install/install.class.php b/main/install/install.class.php new file mode 100644 index 0000000000..06340cf715 --- /dev/null +++ b/main/install/install.class.php @@ -0,0 +1,16 @@ + for the Univesity of Geneva + */ +class Install +{ + static function message($message) + { + echo '
' . $message . '
'; + } + +} \ No newline at end of file diff --git a/main/install/update-db-1.8.8-1.9.0.inc.php b/main/install/update-db-1.8.8-1.9.0.inc.php index 0f982c14af..15c3042595 100755 --- a/main/install/update-db-1.8.8-1.9.0.inc.php +++ b/main/install/update-db-1.8.8-1.9.0.inc.php @@ -80,21 +80,21 @@ if (defined('SYSTEM_INSTALLATION')) { * without a database name */ if (strlen($dbNameForm) > 40) { - error_log('Database name '.$dbNameForm.' is too long, skipping', 0); + Log::error('Database name '.$dbNameForm.' is too long, skipping'); } elseif (!in_array($dbNameForm, $dblist)) { - error_log('Database '.$dbNameForm.' was not found, skipping', 0); + Log::error('Database '.$dbNameForm.' was not found, skipping'); } else { Database::select_db($dbNameForm); foreach ($m_q_list as $query){ if ($only_test) { - error_log("Database::query($dbNameForm,$query)", 0); + Log::notice("Database::query($dbNameForm,$query)"); } else { $res = Database::query($query); if ($log) { - error_log("In $dbNameForm, executed: $query", 0); + Log::notice("In $dbNameForm, executed: $query"); } if ($res === false) { - error_log('Error in '.$query.': '.Database::error()); + Log::error('Error in '.$query.': '.Database::error()); } } } @@ -110,22 +110,22 @@ if (defined('SYSTEM_INSTALLATION')) { * without a database name */ if (strlen($dbStatsForm) > 40) { - error_log('Database name '.$dbStatsForm.' is too long, skipping', 0); + Log::error('Database name '.$dbStatsForm.' is too long, skipping'); } elseif (!in_array($dbStatsForm, $dblist)){ - error_log('Database '.$dbStatsForm.' was not found, skipping', 0); + Log::error('Database '.$dbStatsForm.' was not found, skipping'); } else { Database::select_db($dbStatsForm); foreach ($s_q_list as $query) { if ($only_test) { - error_log("Database::query($dbStatsForm,$query)", 0); + Log::notice("Database::query($dbStatsForm,$query)"); } else { $res = Database::query($query); if ($log) { - error_log("In $dbStatsForm, executed: $query", 0); + Log::notice("In $dbStatsForm, executed: $query"); } if ($res === false) { - error_log('Error in '.$query.': '.Database::error()); + Log::error('Error in '.$query.': '.Database::error()); } } } @@ -183,19 +183,19 @@ if (defined('SYSTEM_INSTALLATION')) { * without a database name */ if (strlen($dbUserForm) > 40) { - error_log('Database name '.$dbUserForm.' is too long, skipping', 0); + Log::error('Database name '.$dbUserForm.' is too long, skipping'); } elseif (!in_array($dbUserForm,$dblist)) { - error_log('Database '.$dbUserForm.' was not found, skipping', 0); + Log::error('Database '.$dbUserForm.' was not found, skipping'); } else { Database::select_db($dbUserForm); foreach ($u_q_list as $query) { if ($only_test) { - error_log("Database::query($dbUserForm,$query)", 0); - error_log("In $dbUserForm, executed: $query", 0); + Log::notice("Database::query($dbUserForm,$query)"); + Log::notice("In $dbUserForm, executed: $query"); } else { $res = Database::query($query); if ($res === false) { - error_log('Error in '.$query.': '.Database::error()); + Log::error('Error in '.$query.': '.Database::error()); } } } @@ -233,14 +233,14 @@ if (defined('SYSTEM_INSTALLATION')) { // Get the courses databases queries list (c_q_list) $c_q_list = get_sql_file_contents('migrate-db-'.$old_file_version.'-'.$new_file_version.'-pre.sql', 'course'); - error_log('Starting migration: '.$old_file_version.' - '.$new_file_version); + Log::notice('Starting migration: '.$old_file_version.' - '.$new_file_version); if (count($c_q_list) > 0) { // Get the courses list if (strlen($dbNameForm) > 40) { - error_log('Database name '.$dbNameForm.' is too long, skipping', 0); + Log::error('Database name '.$dbNameForm.' is too long, skipping'); } elseif(!in_array($dbNameForm, $dblist)) { - error_log('Database '.$dbNameForm.' was not found, skipping', 0); + Log::error('Database '.$dbNameForm.' was not found, skipping'); } else { Database::select_db($dbNameForm); $res = Database::query("SELECT id, code,db_name,directory,course_language FROM course WHERE target_course_code IS NULL ORDER BY code"); @@ -269,14 +269,14 @@ if (defined('SYSTEM_INSTALLATION')) { $query = preg_replace('/^(UPDATE|ALTER TABLE|CREATE TABLE|DROP TABLE|INSERT INTO|DELETE FROM)\s+(\w*)(.*)$/', "$1 $prefix{$row_course['db_name']}_$2$3", $query); } if ($only_test) { - error_log("Database::query(".$row_course['db_name'].",$query)", 0); + Log::notice("Database::query(".$row_course['db_name'].",$query)", 0); } else { $res = Database::query($query); if ($log) { - error_log("In ".$row_course['db_name'].", executed: $query", 0); + Log::notice("In ".$row_course['db_name'].", executed: $query", 0); } if ($res === false) { - error_log('Error in '.$query.': '.Database::error()); + Log::error('Error in '.$query.': '.Database::error()); } } } @@ -372,7 +372,8 @@ if (defined('SYSTEM_INSTALLATION')) { 'wiki_mailcue' ); - error_log('<<<------- Loading DB course '.$row_course['db_name'].' -------->>'); + Log::notice('<<<------- Loading DB course '.$row_course['db_name'].' -------->>'); + Install::message('<<<------- Loading DB course '.$row_course['db_name'].' -------->>'); $count = $old_count = 0; foreach ($table_list as $table) { @@ -399,9 +400,9 @@ if (defined('SYSTEM_INSTALLATION')) { $row = Database::fetch_row($result); $old_count = $row[0]; } else { - error_log("Seems that the table $old_table doesn't exists "); + Log::error("Seems that the table $old_table doesn't exists "); } - error_log("#rows in $old_table: $old_count"); + Log::notice("# rows in $old_table: $old_count"); $sql = "SELECT * FROM $old_table"; $result = Database::query($sql); @@ -417,15 +418,16 @@ if (defined('SYSTEM_INSTALLATION')) { $errors[$old_table][] = $row; } } - error_log("# rows inserted in $new_table: $count"); + Log::notice("# rows inserted in $new_table: $count"); if ($old_count != $count) { - error_log("ERROR count of new and old table doesn't match: $old_count - $new_table"); - error_log("Check the results: "); + Log::error("ERROR count of new and old table doesn't match: $old_count - $new_table"); + Log::error("Check the results: "); + Log::error(print_r($errors, 1)); error_log(print_r($errors, 1)); } } - error_log('<<<------- end -------->>'); + Log::notice('<<<------- end -------->>'); } } }