1<?php
2/**
3 * Zend Framework
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category   Zend
16 * @package    Zend_Log
17 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license    http://framework.zend.com/license/new-bsd     New BSD License
19 * @version    $Id$
20 */
21
22/**
23 * @category   Zend
24 * @package    Zend_Log
25 * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
26 * @license    http://framework.zend.com/license/new-bsd     New BSD License
27 * @version    $Id$
28 *
29 * Convenience methods for log [@see Zend_Log::__call()]:
30 *
31 * @method emerg(string $message, $extras = null)
32 * @method alert(string $message, $extras = null)
33 * @method crit(string $message, $extras = null)
34 * @method err(string $message, $extras = null)
35 * @method warn(string $message, $extras = null)
36 * @method notice(string $message, $extras = null)
37 * @method info(string $message, $extras = null)
38 * @method debug(string $message, $extras = null)
39 */
40class Zend_Log
41{
42    const EMERG   = 0;  // Emergency: system is unusable
43    const ALERT   = 1;  // Alert: action must be taken immediately
44    const CRIT    = 2;  // Critical: critical conditions
45    const ERR     = 3;  // Error: error conditions
46    const WARN    = 4;  // Warning: warning conditions
47    const NOTICE  = 5;  // Notice: normal but significant condition
48    const INFO    = 6;  // Informational: informational messages
49    const DEBUG   = 7;  // Debug: debug messages
50
51    /**
52     * @var array of priorities where the keys are the
53     * priority numbers and the values are the priority names
54     */
55    protected $_priorities = array();
56
57    /**
58     * @var array of Zend_Log_Writer_Abstract
59     */
60    protected $_writers = array();
61
62    /**
63     * @var array of Zend_Log_Filter_Interface
64     */
65    protected $_filters = array();
66
67    /**
68     * @var array of extra log event
69     */
70    protected $_extras = array();
71
72    /**
73     *
74     * @var string
75     */
76    protected $_defaultWriterNamespace = 'Zend_Log_Writer';
77
78    /**
79     *
80     * @var string
81     */
82    protected $_defaultFilterNamespace = 'Zend_Log_Filter';
83
84    /**
85     *
86     * @var string
87     */
88    protected $_defaultFormatterNamespace = 'Zend_Log_Formatter';
89
90    /**
91     *
92     * @var callback
93     */
94    protected $_origErrorHandler       = null;
95
96    /**
97     *
98     * @var boolean
99     */
100    protected $_registeredErrorHandler = false;
101
102    /**
103     *
104     * @var array|boolean
105     */
106    protected $_errorHandlerMap        = false;
107
108    /**
109     *
110     * @var string
111     */
112    protected $_timestampFormat        = 'c';
113
114    /**
115     * Class constructor.  Create a new logger
116     *
117     * @param Zend_Log_Writer_Abstract|null  $writer  default writer
118     */
119    public function __construct(Zend_Log_Writer_Abstract $writer = null)
120    {
121        $r = new ReflectionClass($this);
122        $this->_priorities = array_flip($r->getConstants());
123
124        if ($writer !== null) {
125            $this->addWriter($writer);
126        }
127    }
128
129    /**
130     * Factory to construct the logger and one or more writers
131     * based on the configuration array
132     *
133     * @param  array|Zend_Config Array or instance of Zend_Config
134     * @return Zend_Log
135     * @throws Zend_Log_Exception
136     */
137    static public function factory($config = array())
138    {
139        if ($config instanceof Zend_Config) {
140            $config = $config->toArray();
141        }
142
143        if (!is_array($config) || empty($config)) {
144            /** @see Zend_Log_Exception */
145            throw new Zend_Log_Exception('Configuration must be an array or instance of Zend_Config');
146        }
147
148        if (array_key_exists('className', $config)) {
149            $class = $config['className'];
150            unset($config['className']);
151        } else {
152            $class = __CLASS__;
153        }
154
155        $log = new $class;
156
157        if (!$log instanceof Zend_Log) {
158            /** @see Zend_Log_Exception */
159            throw new Zend_Log_Exception('Passed className does not belong to a descendant of Zend_Log');
160        }
161
162        if (array_key_exists('timestampFormat', $config)) {
163            if (null != $config['timestampFormat'] && '' != $config['timestampFormat']) {
164                $log->setTimestampFormat($config['timestampFormat']);
165            }
166            unset($config['timestampFormat']);
167        }
168
169        if (!is_array(current($config))) {
170            $log->addWriter(current($config));
171        } else {
172            foreach($config as $writer) {
173                $log->addWriter($writer);
174            }
175        }
176
177        return $log;
178    }
179
180
181    /**
182     * Construct a writer object based on a configuration array
183     *
184     * @param  array $config config array with writer spec
185     * @return Zend_Log_Writer_Abstract
186     * @throws Zend_Log_Exception
187     */
188    protected function _constructWriterFromConfig($config)
189    {
190        $writer = $this->_constructFromConfig('writer', $config, $this->_defaultWriterNamespace);
191
192        if (!$writer instanceof Zend_Log_Writer_Abstract) {
193            $writerName = is_object($writer)
194                        ? get_class($writer)
195                        : 'The specified writer';
196            /** @see Zend_Log_Exception */
197            throw new Zend_Log_Exception("{$writerName} does not extend Zend_Log_Writer_Abstract!");
198        }
199
200        if (isset($config['filterName'])) {
201            $filter = $this->_constructFilterFromConfig($config);
202            $writer->addFilter($filter);
203        }
204
205        if (isset($config['formatterName'])) {
206            $formatter = $this->_constructFormatterFromConfig($config);
207            $writer->setFormatter($formatter);
208        }
209
210        return $writer;
211    }
212
213    /**
214     * Construct filter object from configuration array or Zend_Config object
215     *
216     * @param  array|Zend_Config $config Zend_Config or Array
217     * @return Zend_Log_Filter_Interface
218     * @throws Zend_Log_Exception
219     */
220    protected function _constructFilterFromConfig($config)
221    {
222        $filter = $this->_constructFromConfig('filter', $config, $this->_defaultFilterNamespace);
223
224        if (!$filter instanceof Zend_Log_Filter_Interface) {
225             $filterName = is_object($filter)
226                         ? get_class($filter)
227                         : 'The specified filter';
228            /** @see Zend_Log_Exception */
229            throw new Zend_Log_Exception("{$filterName} does not implement Zend_Log_Filter_Interface");
230        }
231
232        return $filter;
233    }
234
235   /**
236    * Construct formatter object from configuration array or Zend_Config object
237    *
238    * @param  array|Zend_Config $config Zend_Config or Array
239    * @return Zend_Log_Formatter_Interface
240    * @throws Zend_Log_Exception
241    */
242    protected function _constructFormatterFromConfig($config)
243    {
244        $formatter = $this->_constructFromConfig('formatter', $config, $this->_defaultFormatterNamespace);
245
246        if (!$formatter instanceof Zend_Log_Formatter_Interface) {
247             $formatterName = is_object($formatter)
248                         ? get_class($formatter)
249                         : 'The specified formatter';
250            /** @see Zend_Log_Exception */
251            throw new Zend_Log_Exception($formatterName . ' does not implement Zend_Log_Formatter_Interface');
252        }
253
254        return $formatter;
255    }
256
257    /**
258     * Construct a filter or writer from config
259     *
260     * @param string $type 'writer' of 'filter'
261     * @param mixed $config Zend_Config or Array
262     * @param string $namespace
263     * @return object
264     * @throws Zend_Log_Exception
265     */
266    protected function _constructFromConfig($type, $config, $namespace)
267    {
268        if ($config instanceof Zend_Config) {
269            $config = $config->toArray();
270        }
271
272        if (!is_array($config) || empty($config)) {
273            throw new Zend_Log_Exception(
274                'Configuration must be an array or instance of Zend_Config'
275            );
276        }
277
278        $params    = isset($config[ $type .'Params' ]) ? $config[ $type .'Params' ] : array();
279        $className = $this->getClassName($config, $type, $namespace);
280        if (!class_exists($className)) {
281            Zend_Loader::loadClass($className);
282        }
283
284        $reflection = new ReflectionClass($className);
285        if (!$reflection->implementsInterface('Zend_Log_FactoryInterface')) {
286            throw new Zend_Log_Exception(
287                $className . ' does not implement Zend_Log_FactoryInterface and can not be constructed from config.'
288            );
289        }
290
291        return call_user_func(array($className, 'factory'), $params);
292    }
293
294    /**
295     * Get the writer or filter full classname
296     *
297     * @param array $config
298     * @param string $type filter|writer
299     * @param string $defaultNamespace
300     * @return string full classname
301     * @throws Zend_Log_Exception
302     */
303    protected function getClassName($config, $type, $defaultNamespace)
304    {
305        if (!isset($config[$type . 'Name'])) {
306            throw new Zend_Log_Exception("Specify {$type}Name in the configuration array");
307        }
308
309        $className = $config[$type . 'Name'];
310        $namespace = $defaultNamespace;
311
312        if (isset($config[$type . 'Namespace'])) {
313            $namespace = $config[$type . 'Namespace'];
314        }
315
316        // PHP >= 5.3.0 namespace given?
317        if (substr($namespace, -1) == '\\') {
318            return $namespace . $className;
319        }
320
321        // empty namespace given?
322        if (strlen($namespace) === 0) {
323            return $className;
324        }
325
326        return $namespace . '_' . $className;
327    }
328
329    /**
330     * Packs message and priority into Event array
331     *
332     * @param  string   $message   Message to log
333     * @param  integer  $priority  Priority of message
334     * @return array Event array
335     */
336    protected function _packEvent($message, $priority)
337    {
338        return array_merge(array(
339            'timestamp'    => date($this->_timestampFormat),
340            'message'      => $message,
341            'priority'     => $priority,
342            'priorityName' => $this->_priorities[$priority]
343            ),
344            $this->_extras
345        );
346    }
347
348    /**
349     * Class destructor.  Shutdown log writers
350     *
351     * @return void
352     */
353    public function __destruct()
354    {
355        /** @var Zend_Log_Writer_Abstract $writer */
356        foreach($this->_writers as $writer) {
357            $writer->shutdown();
358        }
359    }
360
361    /**
362     * Undefined method handler allows a shortcut:
363     *   $log->priorityName('message')
364     *     instead of
365     *   $log->log('message', Zend_Log::PRIORITY_NAME)
366     *
367     * @param  string  $method  priority name
368     * @param  string  $params  message to log
369     * @return void
370     * @throws Zend_Log_Exception
371     */
372    public function __call($method, $params)
373    {
374        $priority = strtoupper($method);
375        if (($priority = array_search($priority, $this->_priorities)) !== false) {
376            switch (count($params)) {
377                case 0:
378                    /** @see Zend_Log_Exception */
379                    throw new Zend_Log_Exception('Missing log message');
380                case 1:
381                    $message = array_shift($params);
382                    $extras = null;
383                    break;
384                default:
385                    $message = array_shift($params);
386                    $extras  = array_shift($params);
387                    break;
388            }
389            $this->log($message, $priority, $extras);
390        } else {
391            /** @see Zend_Log_Exception */
392            throw new Zend_Log_Exception('Bad log priority');
393        }
394    }
395
396    /**
397     * Log a message at a priority
398     *
399     * @param  string   $message   Message to log
400     * @param  integer  $priority  Priority of message
401     * @param  mixed    $extras    Extra information to log in event
402     * @return void
403     * @throws Zend_Log_Exception
404     */
405    public function log($message, $priority, $extras = null)
406    {
407        // sanity checks
408        if (empty($this->_writers)) {
409            /** @see Zend_Log_Exception */
410            throw new Zend_Log_Exception('No writers were added');
411        }
412
413        if (! isset($this->_priorities[$priority])) {
414            /** @see Zend_Log_Exception */
415            throw new Zend_Log_Exception('Bad log priority');
416        }
417
418        // pack into event required by filters and writers
419        $event = $this->_packEvent($message, $priority);
420
421        // Check to see if any extra information was passed
422        if (!empty($extras)) {
423            $info = array();
424            if (is_array($extras)) {
425                foreach ($extras as $key => $value) {
426                    if (is_string($key)) {
427                        $event[$key] = $value;
428                    } else {
429                        $info[] = $value;
430                    }
431                }
432            } else {
433                $info = $extras;
434            }
435            if (!empty($info)) {
436                $event['info'] = $info;
437            }
438        }
439
440        // abort if rejected by the global filters
441        /** @var Zend_Log_Filter_Interface $filter */
442        foreach ($this->_filters as $filter) {
443            if (! $filter->accept($event)) {
444                return;
445            }
446        }
447
448        // send to each writer
449        /** @var Zend_Log_Writer_Abstract $writer */
450        foreach ($this->_writers as $writer) {
451            $writer->write($event);
452        }
453    }
454
455    /**
456     * Add a custom priority
457     *
458     * @param  string  $name     Name of priority
459     * @param  integer $priority Numeric priority
460     * @return $this
461     * @throws Zend_Log_Exception
462     */
463    public function addPriority($name, $priority)
464    {
465        // Priority names must be uppercase for predictability.
466        $name = strtoupper($name);
467
468        if (isset($this->_priorities[$priority])
469            || false !== array_search($name, $this->_priorities)) {
470            /** @see Zend_Log_Exception */
471            throw new Zend_Log_Exception('Existing priorities cannot be overwritten');
472        }
473
474        $this->_priorities[$priority] = $name;
475        return $this;
476    }
477
478    /**
479     * Add a filter that will be applied before all log writers.
480     * Before a message will be received by any of the writers, it
481     * must be accepted by all filters added with this method.
482     *
483     * @param  int|Zend_Config|array|Zend_Log_Filter_Interface $filter
484     * @return $this
485     * @throws Zend_Log_Exception
486     */
487    public function addFilter($filter)
488    {
489        if (is_int($filter)) {
490            /** @see Zend_Log_Filter_Priority */
491            $filter = new Zend_Log_Filter_Priority($filter);
492
493        } elseif ($filter instanceof Zend_Config || is_array($filter)) {
494            $filter = $this->_constructFilterFromConfig($filter);
495
496        } elseif(! $filter instanceof Zend_Log_Filter_Interface) {
497            /** @see Zend_Log_Exception */
498            throw new Zend_Log_Exception('Invalid filter provided');
499        }
500
501        $this->_filters[] = $filter;
502        return $this;
503    }
504
505    /**
506     * Add a writer.  A writer is responsible for taking a log
507     * message and writing it out to storage.
508     *
509     * @param  mixed $writer Zend_Log_Writer_Abstract or Config array
510     * @return Zend_Log
511     * @throws Zend_Log_Exception
512     */
513    public function addWriter($writer)
514    {
515        if (is_array($writer) || $writer instanceof  Zend_Config) {
516            $writer = $this->_constructWriterFromConfig($writer);
517        }
518
519        if (!$writer instanceof Zend_Log_Writer_Abstract) {
520            /** @see Zend_Log_Exception */
521            throw new Zend_Log_Exception(
522                'Writer must be an instance of Zend_Log_Writer_Abstract'
523                . ' or you should pass a configuration array'
524            );
525        }
526
527        $this->_writers[] = $writer;
528        return $this;
529    }
530
531    /**
532     * Set an extra item to pass to the log writers.
533     *
534     * @param  string $name    Name of the field
535     * @param  string $value   Value of the field
536     * @return Zend_Log
537     */
538    public function setEventItem($name, $value)
539    {
540        $this->_extras = array_merge($this->_extras, array($name => $value));
541        return $this;
542    }
543
544    /**
545     * Register Logging system as an error handler to log php errors
546     * Note: it still calls the original error handler if set_error_handler is able to return it.
547     *
548     * Errors will be mapped as:
549     *   E_NOTICE, E_USER_NOTICE => NOTICE
550     *   E_WARNING, E_CORE_WARNING, E_USER_WARNING => WARN
551     *   E_ERROR, E_USER_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR => ERR
552     *   E_DEPRECATED, E_STRICT, E_USER_DEPRECATED => DEBUG
553     *   (unknown/other) => INFO
554     *
555     * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler
556     *
557     * @return Zend_Log
558     */
559    public function registerErrorHandler()
560    {
561        // Only register once.  Avoids loop issues if it gets registered twice.
562        if ($this->_registeredErrorHandler) {
563            return $this;
564        }
565
566        $this->_origErrorHandler = set_error_handler(array($this, 'errorHandler'));
567
568        // Contruct a default map of phpErrors to Zend_Log priorities.
569        // Some of the errors are uncatchable, but are included for completeness
570        $this->_errorHandlerMap = array(
571            E_NOTICE            => Zend_Log::NOTICE,
572            E_USER_NOTICE       => Zend_Log::NOTICE,
573            E_WARNING           => Zend_Log::WARN,
574            E_CORE_WARNING      => Zend_Log::WARN,
575            E_USER_WARNING      => Zend_Log::WARN,
576            E_ERROR             => Zend_Log::ERR,
577            E_USER_ERROR        => Zend_Log::ERR,
578            E_CORE_ERROR        => Zend_Log::ERR,
579            E_RECOVERABLE_ERROR => Zend_Log::ERR,
580            E_STRICT            => Zend_Log::DEBUG,
581        );
582        // PHP 5.3.0+
583        if (defined('E_DEPRECATED')) {
584            $this->_errorHandlerMap['E_DEPRECATED'] = Zend_Log::DEBUG;
585        }
586        if (defined('E_USER_DEPRECATED')) {
587            $this->_errorHandlerMap['E_USER_DEPRECATED'] = Zend_Log::DEBUG;
588        }
589
590        $this->_registeredErrorHandler = true;
591        return $this;
592    }
593
594    /**
595     * Error Handler will convert error into log message, and then call the original error handler
596     *
597     * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler
598     * @param int $errno
599     * @param string $errstr
600     * @param string $errfile
601     * @param int $errline
602     * @param array $errcontext
603     * @return boolean
604     */
605    public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
606    {
607        $errorLevel = error_reporting();
608
609        if ($errorLevel & $errno) {
610            if (isset($this->_errorHandlerMap[$errno])) {
611                $priority = $this->_errorHandlerMap[$errno];
612            } else {
613                $priority = Zend_Log::INFO;
614            }
615            $this->log($errstr, $priority, array('errno'=>$errno, 'file'=>$errfile, 'line'=>$errline, 'context'=>$errcontext));
616        }
617
618        if ($this->_origErrorHandler !== null) {
619            return call_user_func($this->_origErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext);
620        }
621        return false;
622    }
623
624    /**
625     * Set timestamp format for log entries.
626     *
627     * @param string $format
628     * @return Zend_Log
629     */
630    public function setTimestampFormat($format)
631    {
632        $this->_timestampFormat = $format;
633        return $this;
634    }
635
636    /**
637     * Get timestamp format used for log entries.
638     *
639     * @return string
640     */
641    public function getTimestampFormat()
642    {
643        return $this->_timestampFormat;
644    }
645}
646