summaryrefslogtreecommitdiff
path: root/framework/Log/EventLog/log.php
blob: a99569ddb456de2f144d1dcc11eb42f860a0965b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
<?php
/**
 * File containing the ezcLog class.
 *
 * @package EventLog
 * @version //autogentag//
 * @copyright Copyright (C) 2005, 2006 eZ systems as. All rights reserved.
 * @license http://ez.no/licenses/new_bsd New BSD License
 */

/**
 * The ezcLog class records log messages and audit trails to one or multiple
 * writers.
 *
 * Available writers are:
 * - {@link ezcLogWriterUnixFile Unix File} writer
 * - {@link ezcLogWriterDatabase Database} writer
 *
 * Extra writers can be added by implementing the the {@link ezcLogWriter} interface.
 * 
 * Use the {@link map()} method to attach an additional writer to the ezcLog 
 * class. This method filters incoming log messages with the {@link ezcLogFilter}.
 * Log messages that are accepted, match with the filter, are sent to the 
 * {@link ezcLogWriter}.
 *
 * The following example demonstrates how all log messages, except for the 
 * audit trailing and debug messages, are written to a file.
 * <code>
 * $filter = new ezcLogFilter();
 * $filter->severity = ezcLog::INFO | ezcLog::NOTICE | ezcLog::WARNING | ezcLog::ERROR | ezcLog::FATAL;
 * 
 * $log = ezcLog::getInstance();
 * $log->map( $filter, new ezcLogWriterUnixFile( "/tmp/logs/", "error.log" ) );
 * </code>
 *
 * The log messages with the severity: INFO, NOTICE, WARNING, ERROR, and FATAL will
 * be written to the file: "/tmp/logs/error.log". See {@link ezcLogWriterUnixFile} for
 * the description of the file format.
 *
 * The following example will write the audit trails to the database:
 * <code>
 * $filter = new ezcLogFilter();
 * $filter->severity = ezcLog::SUCCESS_AUDIT | ezcLog::FAILED_AUDIT;
 * 
 * $log = ezcLog::getInstance();
 * $log->map( $filter, new ezcLogWriterDatabase( "audits" ) );
 * </code>
 *
 * The audit trails will be stored in the table "audits". See {@link ezcLogWriterDatabase}
 * for creating the appropriate tables and setting up the database.
 *
 * It is also possible to exclude messages from going to a specific writer. 
 * This is done via the {@link unmap()} method. The following example shows
 * how all messages, except for the DEBUG severity, are written to a file:
 * <code>
 * $fileWriter = ezcLogWriterUnixFile( "/tmp/logs/", "all.log" );
 * $filter = new ezcLogFilter();
 * $ezcLog::getInstance()->map( $filter, $fileWriter );  // All severities.
 *
 * $filter->severity = ezcLog::DEBUG;
 * ezcLog::getInstance()->unmap( $filter, $fileWriter ); // Remove DEBUG severity.
 * </code>
 *
 * Use the {@link log()} method to log messages at the specified writers. This
 * method expects a:
 * - Message, contains a single log message. 
 * - Severity, indicates the level of importance. 
 * - Extra attributes (optional).
 *
 * Although the interpretation of the severity levels are up to the programmer, 
 * the most common interpretations are:
 * - DEBUG: Records information about the progress in the program and references 
 *   source code functions. Knowledge of the source code is needed to interpret
 *   this log message.
 * - INFO: Informative logging at a detailed level. This logging method produces a
 *   high level of logging, which is unmanageable on a production environment.
 *   Usually INFO logging is only enabled to help by analysing a problem. 
 * - NOTICE: Informative logging at a lower detail level than INFO logging.
 *   Only major stages are recorded and is useful to monitor a low volume system.
 * - WARNING: Something unexpected happened, but did not cause any loss of service.
 * - ERROR: An error occured, which may cause partial loss of service. Usually the
 *   system can recover.
 * - FATAL: An serious error occured and the system is unlikely to recover. 
 * - SUCCESS_AUDIT: Informative logging about a successful completion of work by
 *   a module completed. Useful to trace system changes directly or indirectly 
 *   done by a user.
 * - FAILED_AUDIT: Informative logging about an action from a module
 *   with a negative result. A failed login will most likely added to this severity.
 *   
 * The next example logs a fatal error and has no extra attributes:
 * <code>
 * ezcLog::getInstance()->log( "Cannot open ini file: <$file>", ezcLog::FATAL );
 * </code>
 * 
 * The log message will get by default the category and source: "default". The
 * default values can be modified by changing, respectively, the properties:
 * category and source. Their use is as follows:
 * - Source, definition of the global location where the log message comes from. 
 *   Some examples are: module, source file, extension, etc. The source depends
 *   also on the severity of the message. For DEBUG messages is the source file
 *   more important whereas for a FATAL error the module is sufficient.
 * - Category, definition of the message group. Again the category is related to
 *   the severity. The non audit trails can group the log messages like: Database
 *   (or even the database types), Templates, etc. For audit trails it makes
 *   much sense to categorize the actions. For example: security, modified content,
 *   published content, shop, etc.
 *
 * An example of a Payment checker is as follows:
 * <code>
 * // The start of the Payment module.
 * $log = ezcLog::getInstance();
 * $log->source = "Payment checker"; // Change the default source.
 *
 * $log->log( "Checking the received amount", ezcLog::INFO, array( "shop" ) );
 *
 * if( !$eZPay->receivedAmount() != $requiredAmount )
 * {
 *     $log->log( "Received amount: <".$eZPay->receivedAmount()."> expected: <$requiredAmount>.", 
 *                 ezcLog::DEBUG,
 *                 array( "category" => "shop", "file" => __FILE__, "line" => __LINE )
 *              );
 * 
 *     $log->log( "Insufficient amount.",
 *                ezcLog::FAILED_AUDIT,
 *                array( "UserName" => getCurrentUser(), category => "Payment" )
 *              )
 *
 *     $log->log( "Rollback amount not implemented, cannot recover, ezcLog::FATAL );
 *     exit();
 * }
 * </code>
 *
 * Sometimes information repeats for specific severities or categories. For example that
 * for the audit trails an username is required. Convenience methods like: 
 * {@link setSeverityAttributes()} and {@link setSourceAttributes()} exist to append 
 * information automatically to the log message.
 *   
 * The ezcLog class provides a {@link trigger_error()} log handler: {@link ezcLog::LogHandler()}.
 * Using the trigger_error method makes your code less Log package dependent and 
 * produces less overhead when logging is disabled.
 *
 * See the {@link ezcLog::LogHandler()} method for more information about how to set up the 
 * trigger_error functionality. 
 *
 * See the {@link ezcDebug} package for more detailed information about writing DEBUG
 * messages.
 *
 * @package EventLog
 * @version //autogentag//
 */
class ezcLog
{
    /**
     * Debug severity constant.
     */
     const DEBUG          = 1;

    /**
     * Success audit severity constant.
     */
     const SUCCESS_AUDIT  = 2;

    /**
     * Failed audit severity constant.
     */
     const FAILED_AUDIT   = 4;

     /**
      * Info severity constant.
      */
     const INFO           = 8;

     /**
      * Notice severity constant.
      */
     const NOTICE         = 16;

     /**
      * Warning severity constant.
      */
     const WARNING        = 32;

     /**
      * Error severity constant.
      */
     const ERROR          = 64;

     /**
      * Fatal severity constant.
      */
     const FATAL          = 128;

    /**
     * Holds the properties of this class.
     *
     * @var array(string=>mixed)
     */
    private $properties = array();

    /**
     * Contains the logic of mapping an incoming log message to the writer.
     *
     * @var ezcLogMap
     */
    protected $writers;

    /**
     * Stores the attributes from the eventTypes and eventSources.
     *
     * $var ezcLogContext
     */
    protected $context;

    /**
     * Stores the instance of this class.
     *
     * @var ezcLog
     */
    private static $instance = null;

    /**
     * Stores the setting whether writer exceptions should be thrown.
     *
     * @var bool
     */
    private $throwWriterExceptions = true;

    /**
     * Constructs an empty ezcLog instance.
     *
     * This constructor is private as this class should be used as a
     * singleton. Use the getInstance() method instead to get an ezcLog instance.
     */
    private function __construct()
    {
        $this->reset();
    }

    /**
     * Returns the instance of the class.
     *
     * @return ezcLog
     */
    public static function getInstance()
    {
        if ( is_null( self::$instance ) )
        {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Sets the property $name to $value.
     *
     * @throws ezcBasePropertyNotFoundException if the property does not exist.
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public function __set( $name, $value )
    {
        switch ( $name )
        {
            case "source":   
            case "category": 
                $this->properties[$name] = $value; 
                return;
        }

        throw new ezcBasePropertyNotFoundException( $name );
    }

   /**
     * Returns the property $name.
     *
     * @throws ezcBasePropertyNotFoundException if the property does not exist.
     * @param string $name
     * @return mixed
     */
    public function __get( $name )
    {
        switch ( $name )
        {
            case "source":
            case "category": 
                return $this->properties[$name];
        }

        throw new ezcBasePropertyNotFoundException( $name );
    }

    /**
     * Resets the log instance to its initial state.
     *
     * All sourceAttributes, severityAttributes, and writers will be removed.
     * The default source and category are also reset.
     *
     * @return void
     */
    public function reset()
    {
        $this->writers = new ezcLogMap();
        $this->context = new ezcLogContext();

        $this->setDefaults();
    }

    /**
     * Sets the source and category defaults to "default".
     *
     * @return void
     */
    protected function setDefaults()
    {
        $this->source = "default";
        $this->category = "default";
    }

    /**
     * Enables or disables writer exceptions with the boolean $enable.
     *
     * Typically you want to have exceptions enabled while developing your application
     * in order to catch potential problems. A live server however, should not throw
     * a deadly exception when a relatively unimportant debug message could not be written to
     * the log file. For these setups you can disable writer exceptions.
     *
     * @param bool $enable
     * @return void
     */
    public function throwWriterExceptions( $enable )
    {
        $this->throwWriterExceptions = $enable;
    }

    /**
     * Write the message $message with additional information to one or multiple log writers.
     *
     * The log message $message, severity $severity, and extra attributes $attributes are sent to
     * the writers that matches with the {@link ezcLogFilter}. The following parameters are 
     * taken in the comparation with the ezcLogFilter:
     * - $severity: the severity of the log message.
     * - $attributes[ "source" ]: the source from where the log message comes.
     * - $attributes[ "category" ]: the category of the log message.
     * 
     * See for more information about filter matching the classes {@link ezcLog} and 
     * {@link ezcLogFilter}. 
     *
     * The message $message describes what happened. The severity $severity is one of the ezcLog constants:
     * - DEBUG: Records information about the progress in the program and references 
     *   source code functions. Knowledge of the source code is needed to interpret
     *   this log message.
     * - INFO: Informative logging at a detailed level. This logging method produces a
     *   high level of logging, which is unmanageable on a production environment.
     *   Usually INFO logging is only enabled to help by analysing a problem. 
     * - NOTICE: Informative logging at a lower detail level than INFO logging.
     *   Only major stages are recorded and is useful to monitor a low volume system.
     * - WARNING: Something unexpected happened, but did not cause any loss of service.
     * - ERROR: An error occured, which may cause partial loss of service. Usually the
     *   system can recover.
     * - FATAL: An serious error occured and the system is unlikely to recover. 
     * - SUCCESS_AUDIT: Informative logging about a successful completion of work by
     *   a module completed. Useful to trace system changes directly or indirectly 
     *   done by a user.
     * - FAILED_AUDIT: Informative logging about an action from a module
     *   with a negative result. A failed login will most likely added to this severity.
     * 
     * The attributes array $attributes can have one or multiple attributes that will
     * be added to the log. If source and category are given, they will override the default
     * source or category given as property to this object. Further more it is up to the 
     * application what to include in the log. It may be useful to add the
     * file and linenumber to the attributes array. Use the magic PHP constants: {@link __FILE__}
     * and {@link __LINE__}  for this purpose. The next example adds an warning to the log.
     *
     * <code>
     * ezcLog::getInstance()->source = "templateEngine"; // Set the default source.
     * ezcLog::getInstance()->log( "ezcPersistentObject <$obj> does not exist.", 
     *     ezcLog::WARNING, 
     *     array( "category" => "Database", "line" => __LINE__, "file" => __FILE__, "code" => 123 )
     *     );
     * </code>
     * 
     * The methods {@link setSeverityAttributes()} and {@link setSourceAttributes()} can automatically
     * add attributes to log messages based on, respectively, the severity and source.
     *
     * See also {@link LogHandler()} on how to use {@link trigger_error()} to write log messages.
     *
     * @throws ezcLogWriterException if {@link throwWriterExceptions} are enabled and a log entry 
     *                               could not be written. 
     *
     * @param string $message
     * @param int $severity  One of the following severity constants: 
     *                       DEBUG, SUCCES_AUDIT, FAIL_AUDIT, INFO, NOTICE, WARNING, ERROR, or FATAL.
     * @param array(string=>string) $attributes
     * @return void
     */
    public function log( $message, $severity, $attributes = array() )
    {
        $source = ( isset( $attributes["source"] ) ? $attributes["source"] : $this->properties["source"] );
        $category = ( isset( $attributes["category"] ) ? $attributes["category"] : $this->properties["category"] );

        unset( $attributes["source"] );
        unset( $attributes["category"] );

        $writers = $this->writers->get( $severity, $source, $category );
        foreach ( $writers as $writer )
        {
            try
            {
                $writer->writeLogMessage( $message, $severity, $source, $category, $attributes );
            }
            catch ( ezcLogWriterException $e )
            {
                if ( $this->throwWriterExceptions )
                {
                    throw $e;
                }
            }
        }
    }

    /**
     * Attaches the writer $writer with the filter $logFilter to this ezcLog.
     *
     * The log filter $logFilter describes which severities, categories, and
     * sources are accepted by the writer $writer. Those messages that
     * match are send to the writer.
     *
     * Multiple logFilters with their writer can be attached to the ezcLog class.
     * Every log message will be sent to the writer for which the log filter matches.
     *
     * Available writers are:
     * - {@link ezcLogWriterUnixFile Unix File} writer
     * - {@link ezcLogWriterDatabase Database} writer
     *
     * Extra writers can be added by implementing the the {@link ezcLogWriter} interface.
     *
     * The following example maps the Unix file writer to all the log messages that
     * are from the category "template", source "content_module" and severities
     * WARNING, ERROR, or FATAL.
     * <code>
     * $f = new ezclogFilter();
     * $f->severity = ezcLog::WARNING | ezcLog::ERROR | ezcLog::FATAL;
     * $f->source = "template";
     * $f->category = "content_module";
     * 
     * $w = new ezcLogWriterUnixFile("/tmp/logs/content_module/", "template.log" );
     * 
     * $ezcLog::getInstance()->map( $f, $w ); 
     * </code>
     *
     * See also {@link unmap()}
     *
     * @param ezcLogFilter $logFilter
     * @param ezcLogWriter $writer
     * @return void
     */
    public function map( ezcLogFilter $logFilter, ezcLogWriter $writer )
    {
        $this->writers->map( $logFilter->severity, $logFilter->source, $logFilter->category, $writer );
    }

    /**
     * Detaches the writer $writer for specific log messages, specified by the log filter $logFilter. 
     * 
     * The log filter $logFilter describes which severities, categories, and
     * sources are no longer accepted by the writer $writer. 
     * 
     * See the {@link map()} method for information about attaching a filter.
     *
     * The following example shows how to log all messages, except the debug message:
     * <code>
     * $fileWriter = ezcLogWriterUnixFile( "/tmp/logs/", "all.log" );
     * $filter = new ezcLogFilter();
     * $ezcLog::getInstance()->map( $filter, $fileWriter );  // All severities.
     *
     * $filter->severity = ezcLog::DEBUG;
     * ezcLog::getInstance()->unmap( $filter, $fileWriter ); // Remove DEBUG severity.
     * </code>
     *
     * See also {@link map()}
     *
     * @param ezcLogFilter $logFilter
     * @param ezcLogWriter $writer
     * @return void
     */
    public function unmap( ezcLogFilter $logFilter, ezcLogWriter $writer )
    {
        $this->writers->unmap( $logFilter->severity, $logFilter->source, $logFilter->category, $writer );
    }

    /**
     * Sets the attributes $attributes for a group of severities $severityMask. 
     *
     * The severities are specified with a bit mask. These attributes will be
     * added to the log message when the log severity is the same as specified
     * here.
     *
     * Example:
     * <code>
     * ezcLog::getInstance()->setSeverityAttributes( 
     *     ezcLog::SUCCESS_AUDIT | ezcLog::FAILED_AUDIT
     *     array( "username" => "Jan K. Doodle" )
     * );
     * </code>
     *
     * Every log message that has the severity SUCCESS_AUDIT or FAILED_AUDIT 
     * includes the user name: "Jan K. Doodle".
     *
     * @param integer $severityMask Multiple severities are specified with a logic-or. 
     * @param array(string=>string) $attributes
     * @return void
     */
    public function setSeverityAttributes( $severityMask, $attributes )
    {
        $this->context->setSeverityContext( $severityMask, $attributes );
    }

    /**
     * Sets the attributes $attributes for a group of sources $sources. 
     *
     * The sources are specified in an array. These attributes will be added to the
     * log message when it matches with the given $sources.
     *
     * Example:
     * <code>
     * ezcLog::getInstance()->setSourceAttributes( 
     *     array( "Paynet", "Bibit", "Paypal" ),
     *     array( "MerchantID" => $merchantID )
     * );
     * </code>
     *
     * Every log message that comes from the payment module: Paynet, Bibit, or Paypal
     * includes the Merchant ID. 
     *
     * @param array(string) $sources
     * @param array(string => string) $attributes
     * @return void
     */
    public function setSourceAttributes ( $sources, $attributes )
    {
        $this->context->setSourceContext( $sources, $attributes );
    }

	/**
	 * This method can be set as error_handler to log using {@link trigger_error()}.
	 *
     * This method can be assigned with the {@link set_error_handler()} to handle the
     * trigger_error calls. This method will get the log instance and forward the
     * message. But includes the following information: 
     * - The file and linenumber are automatically added. 
     * - Source and category can be 'encoded' in the message.
     *
     * The message format is as follows:
     * <pre>
     * [ source, category ] Message 
     * </pre>
     *
     * When one name is given between the brackets, the category will be set and the message has a default source:
     * <pre>
     * [ category ] Message
     * </pre>
     *
     * Without any names between the brackets, the default category and source are used:
     * <pre>
     * Message
     * </pre>
     * 
     * The following example creates manually an error handler and forwards the 
     * ERROR, WARNING and NOTICE severities. 
     * <code>
     * public function MyLogHandler($errno, $errstr, $errfile, $errline)
     * {
     *     switch ($errno)
     *     {
     *         case E_USER_ERROR:
     *         case E_USER_WARNING:
     *         case E_USER_NOTICE:
     *             if ( $loggingEnabled )
     *             {   // Forward the message to the log handler. 
     *                 ezcLog::LogHandler( $errno, $errstr, $errfile, $errline );
     *             }
     *             break;
     *
     *         default:
     *             print( "$errstr in $errfile on line $errline\n" ); 
     *             break;
     *     }
     * }
     * 
     * // Register MyLogHandler
     * set_error_handler( "MyLogHandler" );
     *
     * // Write an warning to the log.
     * trigger_error( "[paynet, transaction] Didn't get a callback from the Paynet service", E_USER_WARNING );
     *
     * // Add a notice.
     * trigger_error( "Getting paynet status information", E_USER_NOTICE );
     *
     * </code>
     * 
     * Notice that the ezcLog component is not loaded at all when the logging is disabled. 
     *
     * @param int $errno
     * @param int $erstr
     * @param string $errfile
     * @param int $errline
     * @return void
     */
     public static function logHandler( $errno, $errstr, $errfile, $errline )
     {
         $log = ezcLog::getInstance();
         $lm = new ezcLogMessage( $errstr, $errno, $log->source, $log->category );
         $log->log(
             $lm->message, $lm->severity,
             array( "source" => $lm->source, "category" => $lm->category, "file" => $errfile, "line" => $errline )
         );
     }

    /**
     * Translates the severity constant to a string and returns this.
     *
     * Null is returned when the severity constant is invalid.
     *
     * @param int $severity
     * @return string
     */
    public static function translateSeverityName( $severity )
    {
        switch ( $severity )
        {
            case self::DEBUG:           return "Debug";
            case self::SUCCESS_AUDIT:   return "Success audit";
            case self::FAILED_AUDIT:    return "Failed audit";
            case self::INFO:            return "Info";
            case self::NOTICE:          return "Notice";
            case self::WARNING:         return "Warning";
            case self::ERROR:           return "Error";
            case self::FATAL:           return "Fatal";
            default:                    return null;
        }
    }
}
?>