      1: <?php
      2: /*************************************************************************************/
      3: /*                                                                                   */
      4: /*      Thelia                                                                       */
      5: /*                                                                                   */
      6: /*      Copyright (c) OpenStudio                                                     */
      7: /*  email : info@thelia.net                                                      */
      8: /*      web : http://www.thelia.net                                                  */
      9: /*                                                                                   */
     10: /*      This program is free software; you can redistribute it and/or modify         */
     11: /*      it under the terms of the GNU General Public License as published by         */
     12: /*      the Free Software Foundation; either version 3 of the License                */
     13: /*                                                                                   */
     14: /*      This program is distributed in the hope that it will be useful,              */
     15: /*      but WITHOUT ANY WARRANTY; without even the implied warranty of               */
     16: /*      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                */
     17: /*      GNU General Public License for more details.                                 */
     18: /*                                                                                   */
     19: /*      You should have received a copy of the GNU General Public License            */
     20: /*      along with this program. If not, see <http://www.gnu.org/licenses/>.     */
     21: /*                                                                                   */
     22: /*************************************************************************************/
     23: namespace Thelia\Log;
     24:
     25: use Thelia\Model\ConfigQuery;
     26: use Psr\Log\LoggerInterface;
     27:
     28: /**
     29:  *
     30:  * Thelia Logger
     31:  *
     32:  * Allow to define different level and output.
     33:  *
     34:  * @author Franck Allimant <franck@cqfdev.fr>
     35:  * @author Manuel Raynaud <mraynaud@openstudio.fr>
     36:  */
     37: class Tlog Implements LoggerInterface
     38: {
     39:     // Nom des variables de configuration
     40:     const VAR_LEVEL         = "tlog_level";
     41:     const VAR_DESTINATIONS  = "tlog_destinations";
     42:     const VAR_PREFIXE       = "tlog_prefix";
     43:     const VAR_FILES         = "tlog_files";
     44:     const VAR_IP                = "tlog_ip";
     45:     const VAR_SHOW_REDIRECT     = "tlog_show_redirect";
     46:
     47:     // all level of trace
     48:     const DEBUG                 = 100;
     49:     const INFO                  = 200;
     50:     const NOTICE                = 300;
     51:     const WARNING               = 400;
     52:     const ERROR                 = 500;
     53:     const CRITICAL              = 600;
     54:     const ALERT                 = 700;
     55:     const EMERGENCY             = 800;
     56:     const MUET                  = PHP_INT_MAX;
     57:
     58:     protected $levels = array(
     59:         100 => "DEBUG",
     60:         200 => "INFO",
     61:         300 => "NOTICE",
     62:         400 => "WARNING",
     63:         500 => "ERROR",
     64:         600 => "CRITICAL",
     65:         700 => "ALERT",
     66:         800 => "EMERGENCY"
     67:     );
     68:
     69:     // default values
     70:     const DEFAULT_LEVEL         = self::DEBUG;
     71:     const DEFAUT_DESTINATIONS   = "Thelia\Log\Destination\TlogDestinationFile";
     72:     const DEFAUT_PREFIXE    = "#NUM: #NIVEAU [#FICHIER:#FONCTION()] {#LIGNE} #DATE #HEURE: ";
     73:     const DEFAUT_FILES      = "*";
     74:     const DEFAUT_IP         = "";
     75:     const DEFAUT_SHOW_REDIRECT  = 0;
     76:
     77:     /**
     78:      *
     79:      * @var \Thelia\Log\Tlog
     80:      */
     81:     private static $instance = false;
     82:
     83:     /**
     84:      *
     85:      * @var array containing class of destination handler
     86:      */
     87:     protected $destinations = array();
     88:
     89:     protected $mode_back_office = false;
     90:     protected $level = self::MUET;
     91:     protected $prefixe = "";
     92:     protected $files = array();
     93:     protected $all_files = false;
     94:     protected $show_redirect = false;
     95:
     96:     private $linecount = 0;
     97:
     98:     protected static $done = false;
     99:
    100:     // directories where are the Destinations Files
    101:     public $dir_destinations = array();
    102:
    103:     /**
    104:      *
    105:      */
    106:     private function __construct(){}
    107:
    108:     /**
    109:      *
    110:      * @return \Thelia\Log\Tlog
    111:      */
    112:     public static function getInstance() {
    113:         if (self::$instance == false) {
    114:             self::$instance = new Tlog();
    115:
    116:             // On doit placer les initialisations à ce level pour pouvoir
    117:             // utiliser la classe Tlog dans les classes de base (Cnx, BaseObj, etc.)
    118:             // Les placer dans le constructeur provoquerait une boucle
    119:             self::$instance->init();
    120:         }
    121:
    122:         return self::$instance;
    123:     }
    124:
    125:     /**
    126:      * initialize default configuration
    127:      */
    128:     protected function init()
    129:     {
    130:
    131:         $this->setLevel(ConfigQuery::read(self::VAR_LEVEL, self::DEFAULT_LEVEL));
    132:
    133:         $this->dir_destinations = array(
    134:                 __DIR__.'/Destination'
    135:                 //, __DIR__.'/../client/tlog/destinations'
    136:         );
    137:
    138:         $this->setPrefix(ConfigQuery::read(self::VAR_PREFIXE, self::DEFAUT_PREFIXE));
    139:         $this->setFiles(ConfigQuery::read(self::VAR_FILES, self::DEFAUT_FILES));
    140:         $this->setIp(ConfigQuery::read(self::VAR_IP, self::DEFAUT_IP));
    141:         $this->setDestinations(ConfigQuery::read(self::VAR_DESTINATIONS, self::DEFAUT_DESTINATIONS));
    142:         $this->setShowRedirect(ConfigQuery::read(self::VAR_SHOW_REDIRECT, self::DEFAUT_SHOW_REDIRECT));
    143:
    144:         // Au cas ou il y aurait un exit() quelque part dans le code.
    145:         register_shutdown_function(array($this, 'writeOnExit'));
    146:     }
    147:
    148:     // Configuration
    149:     // -------------
    150:
    151:     /**
    152:      *
    153:      * @param string $destinations
    154:      */
    155:     public function setDestinations($destinations)
    156:     {
    157:         if (! empty($destinations)) {
    158:
    159:             $this->destinations = array();
    160:
    161:             $classes_destinations = explode(';', $destinations);
    162:             $this->loadDestinations($this->destinations, $classes_destinations);
    163:         }
    164:     }
    165:
    166:     /**
    167:      *
    168:      * change the debug level. Use Tlog constant : \Thelia\Log\Tlog::DEBUG set level to Debug
    169:      *
    170:      * @param int $level
    171:      */
    172:     public function setLevel($level)
    173:     {
    174:             $this->level = $level;
    175:     }
    176:
    177:     public function setPrefix($prefixe)
    178:     {
    179:             $this->prefixe = $prefixe;
    180:     }
    181:
    182:     public function setFiles($files)
    183:     {
    184:             $this->files = explode(";", $files);
    185:
    186:             $this->all_files = in_array('*', $this->files);
    187:     }
    188:
    189:     public function setIp($ips)
    190:     {
    191:             // isset($_SERVER['REMOTE_ADDR']) if we are in cli mode
    192:             if (! empty($ips) && isset($_SERVER['REMOTE_ADDR']) && ! in_array($_SERVER['REMOTE_ADDR'], explode(";", $ips))) $this->level = self::MUET;
    193:     }
    194:
    195:     public function setShowRedirect($bool)
    196:     {
    197:             $this->show_redirect = $bool;
    198:     }
    199:
    200:     // Configuration d'une destination
    201:     public function setConfig($destination, $param, $valeur)
    202:     {
    203:             if (isset($this->destinations[$destination])) {
    204:                     $this->destinations[$destination]->setConfig($param, $valeur);
    205:             }
    206:     }
    207:
    208:     // Configuration d'une destination
    209:     public function getConfig($destination, $param)
    210:     {
    211:             if (isset($this->destinations[$destination])) {
    212:                     return $this->destinations[$destination]->getConfig($param);
    213:             }
    214:
    215:             return false;
    216:     }
    217:
    218:     // Methodes d'accès aux traces
    219:     // ---------------------------
    220:
    221:     /**
    222:      * Detailed debug information.
    223:      *
    224:      * @param string $message
    225:      * @param array $context
    226:      * @return null
    227:      */
    228:     public function debug($message, array $context = array())
    229:     {
    230:         $this->log(self::DEBUG, $message, $context);
    231:     }
    232:
    233:     /**
    234:      *
    235:      * Alias of debug method. With this method you can put all parameter you want
    236:      *
    237:      * ex : Tlog::getInstance()->addDebug($arg1, $arg2, $arg3);
    238:      *
    239:      */
    240:     public function addDebug()
    241:     {
    242:         $args = func_get_args();
    243:
    244:         foreach ($args as $arg) {
    245:             $this->log(self::DEBUG, $arg);
    246:         }
    247:     }
    248:
    249:     /**
    250:      * Interesting events.
    251:      *
    252:      * Example: User logs in, SQL logs.
    253:      *
    254:      * @param string $message
    255:      * @param array $context
    256:      * @return null
    257:      */
    258:     public function info($message, array $context = array())
    259:     {
    260:         $this->log(self::INFO, $message, $context);
    261:     }
    262:
    263:     /**
    264:      *
    265:      * Alias of info method. With this method you can put all parameter you want
    266:      *
    267:      * ex : Tlog::getInstance()->addInfo($arg1, $arg2, $arg3);
    268:      *
    269:      */
    270:     public function addInfo()
    271:     {
    272:         $args = func_get_args();
    273:
    274:         foreach ($args as $arg) {
    275:             $this->log(self::INFO, $arg);
    276:         }
    277:     }
    278:
    279:     /**
    280:      * Normal but significant events.
    281:      *
    282:      * @param string $message
    283:      * @param array $context
    284:      * @return null
    285:      */
    286:     public function notice($message, array $context = array())
    287:     {
    288:         $this->log(self::NOTICE, $message, $context);
    289:     }
    290:
    291:     /**
    292:      *
    293:      * Alias of notice method. With this method you can put all parameter you want
    294:      *
    295:      * ex : Tlog::getInstance()->addNotice($arg1, $arg2, $arg3);
    296:      *
    297:      */
    298:     public function addNotice()
    299:     {
    300:         $args = func_get_args();
    301:
    302:         foreach ($args as $arg) {
    303:             $this->log(self::NOTICE, $arg);
    304:         }
    305:     }
    306:
    307:     /**
    308:      * Exceptional occurrences that are not errors.
    309:      *
    310:      * Example: Use of deprecated APIs, poor use of an API, undesirable things
    311:      * that are not necessarily wrong.
    312:      *
    313:      * @param string $message
    314:      * @param array $context
    315:      * @return null
    316:      */
    317:     public function warning($message, array $context = array())
    318:     {
    319:         $this->log(self::WARNING, $message, $context);
    320:     }
    321:
    322:     /**
    323:      *
    324:      * Alias of warning method. With this method you can put all parameter you want
    325:      *
    326:      * ex : Tlog::getInstance()->addWarning($arg1, $arg2, $arg3);
    327:      *
    328:      */
    329:     public function addWarning()
    330:     {
    331:         $args = func_get_args();
    332:
    333:         foreach ($args as $arg) {
    334:             $this->log(self::WARNING, $arg);
    335:         }
    336:     }
    337:
    338:     /**
    339:      * Runtime errors that do not require immediate action but should typically
    340:      * be logged and monitored.
    341:      *
    342:      * @param string $message
    343:      * @param array $context
    344:      * @return null
    345:      */
    346:     public function error($message, array $context = array())
    347:     {
    348:         $this->log(self::ERROR, $message, $context);
    349:     }
    350:
    351:     /**
    352:      *
    353:      * Alias of error method. With this method you can put all parameter you want
    354:      *
    355:      * ex : Tlog::getInstance()->addError($arg1, $arg2, $arg3);
    356:      *
    357:      */
    358:     public function addError()
    359:     {
    360:         $args = func_get_args();
    361:
    362:         foreach ($args as $arg) {
    363:             $this->log(self::ERROR, $arg);
    364:         }
    365:     }
    366:
    367:     /**
    368:      *
    369:      * @see error()
    370:      */
    371:     public function err($message, array $context = array())
    372:     {
    373:         $this->error($message, $context);
    374:     }
    375:
    376:     /**
    377:      * Critical conditions.
    378:      *
    379:      * Example: Application component unavailable, unexpected exception.
    380:      *
    381:      * @param string $message
    382:      * @param array $context
    383:      * @return null
    384:      */
    385:     public function critical($message, array $context = array())
    386:     {
    387:         $this->log(self::CRITICAL, $message, $context);
    388:     }
    389:
    390:     /**
    391:      *
    392:      * Alias of critical method. With this method you can put all parameter you want
    393:      *
    394:      * ex : Tlog::getInstance()->addCritical($arg1, $arg2, $arg3);
    395:      *
    396:      */
    397:     public function addCritical()
    398:     {
    399:         $args = func_get_args();
    400:
    401:         foreach ($args as $arg) {
    402:             $this->log(self::CRITICAL, $arg);
    403:         }
    404:     }
    405:
    406:     /**
    407:      *
    408:      * @see critical()
    409:      */
    410:     public function crit($message, array $context = array())
    411:     {
    412:         $this->critical($message, $context);
    413:     }
    414:
    415:     /**
    416:      * Action must be taken immediately.
    417:      *
    418:      * Example: Entire website down, database unavailable, etc. This should
    419:      * trigger the SMS alerts and wake you up.
    420:      *
    421:      * @param string $message
    422:      * @param array $context
    423:      * @return null
    424:      */
    425:     public function alert($message, array $context = array())
    426:     {
    427:         $this->log(self::ALERT, $message, $context);
    428:     }
    429:
    430:     /**
    431:      *
    432:      * Alias of alert method. With this method you can put all parameter you want
    433:      *
    434:      * ex : Tlog::getInstance()->addAlert($arg1, $arg2, $arg3);
    435:      *
    436:      */
    437:     public function addAlert()
    438:     {
    439:         $args = func_get_args();
    440:
    441:         foreach ($args as $arg) {
    442:             $this->log(self::ALERT, $arg);
    443:         }
    444:     }
    445:
    446:     /**
    447:      * System is unusable.
    448:      *
    449:      * @param string $message
    450:      * @param array $context
    451:      * @return null
    452:      */
    453:     public function emergency($message, array $context = array())
    454:     {
    455:         $this->log(self::EMERGENCY, $message, $context);
    456:     }
    457:
    458:     /**
    459:      *
    460:      * Alias of emergency method. With this method you can put all parameter you want
    461:      *
    462:      * ex : Tlog::getInstance()->addEmergency($arg1, $arg2, $arg3);
    463:      *
    464:      */
    465:     public function addEmergency()
    466:     {
    467:         $args = func_get_args();
    468:
    469:         foreach ($args as $arg) {
    470:             $this->log(self::EMERGENCY, $arg);
    471:         }
    472:     }
    473:
    474:     /**
    475:      * Logs with an arbitrary level.
    476:      *
    477:      * @param mixed $level
    478:      * @param string $message
    479:      * @param array $context
    480:      * @return null
    481:      */
    482:     public function log($level, $message, array $context = array()) {
    483:         if($this->level > $level || array_key_exists($level, $this->levels) === false)
    484:             return;
    485:
    486:         $this->out($this->levels[$level], $message, $context);
    487:     }
    488:
    489:
    490:
    491:     // Mode back office
    492:     public static function SetBackOfficeMode($booleen)
    493:     {
    494:             foreach (Tlog::getInstance()->destinations as $dest) {
    495:                     $dest->SetBackOfficeMode($booleen);
    496:             }
    497:     }
    498:
    499:     /**
    500:      *
    501:      * final end method. Write log for each destination handler
    502:      *
    503:      * @param string $res
    504:      * @return void
    505:      */
    506:     public function write(&$res)
    507:     {
    508:             self::$done = true;
    509:
    510:             // Muet ? On ne fait rien
    511:             if ($this->level == self::MUET) return;
    512:
    513:             foreach ($this->destinations as $dest) {
    514:                     $dest->write($res);
    515:             }
    516:     }
    517:
    518:     /**
    519:      * @see write()
    520:      */
    521:     public function writeOnExit()
    522:     {
    523:         // Si les infos de debug n'ont pas été ecrites, le faire maintenant
    524:         if (self::$done === false) {
    525:
    526:                 $res = "";
    527:
    528:                 $this->write($res);
    529:
    530:                 echo $res;
    531:         }
    532:     }
    533:
    534:     public function showRedirect($url)
    535:     {
    536:         if ($this->level != self::MUET && $this->show_redirect) {
    537:                 echo "
    538: <html>
    539: <head><title>Redirection...</title></head>
    540: <body>
    541: <a href=\"$url\">Redirection vers $url</a>
    542: </body>
    543: </html>
    544:         ";
    545:
    546:                 return true;
    547:         } else {
    548:                 return false;
    549:         }
    550:     }
    551:
    552:     /**
    553:      *
    554:      * check if level is activated and control if current file is activated
    555:      *
    556:      * @param int $level
    557:      * @return boolean
    558:      */
    559:     public function isActivated($level)
    560:     {
    561:         if ($this->level <= $level) {
    562:
    563:             $origin = $this->findOrigin();
    564:
    565:             $file = basename($origin['file']);
    566:
    567:             if ($this->isActivedFile($file)) {
    568:                     return true;
    569:             }
    570:         }
    571:
    572:         return false;
    573:     }
    574:
    575:     /**
    576:      *
    577:      * check if $file is in authorized files
    578:      *
    579:      * @param string $file
    580:      * @return boolean
    581:      */
    582:     public function isActivedFile($file)
    583:     {
    584:         return ($this->all_files || in_array($file, $this->files)) && ! in_array("!$file", $this->files);
    585:     }
    586:
    587:     /* -- Methodes privees ---------------------------------------- */
    588:
    589:
    590:     private function findOrigin()
    591:     {
    592:         $origine = array();
    593:
    594:         if (function_exists('debug_backtrace')) {
    595:
    596:             $trace = debug_backtrace();
    597:             $prevHop = null;
    598:             // make a downsearch to identify the caller
    599:             $hop = array_pop($trace);
    600:
    601:             while ($hop !== null) {
    602:                 if (isset($hop['class'])) {
    603:                     // we are sometimes in functions = no class available: avoid php warning here
    604:                     $className = $hop['class'];
    605:
    606:                     if (! empty($className) and ($className == ltrim(__CLASS__,'\\') or strtolower(get_parent_class($className)) == ltrim(__CLASS__,'\\'))) {
    607:                             $origine['line'] = $hop['line'];
    608:                             $origine['file'] = $hop['file'];
    609:                             break;
    610:                     }
    611:                 }
    612:                 $prevHop = $hop;
    613:                 $hop = array_pop($trace);
    614:             }
    615:
    616:             $origine['class'] = isset($prevHop['class']) ? $prevHop['class'] : 'main';
    617:
    618:             if(isset($prevHop['function']) and
    619:                 $prevHop['function'] !== 'include' and
    620:                 $prevHop['function'] !== 'include_once' and
    621:                 $prevHop['function'] !== 'require' and
    622:                 $prevHop['function'] !== 'require_once') {
    623:
    624:                 $origine['function'] = $prevHop['function'];
    625:             } else {
    626:                 $origine['function'] = 'main';
    627:             }
    628:         }
    629:
    630:         return $origine;
    631:     }
    632:
    633:     protected function interpolate($message, array $context = array())
    634:     {
    635:         // build a replacement array with braces around the context keys
    636:         $replace = array();
    637:         foreach ($context as $key => $val) {
    638:             $replace['{' . $key . '}'] = $val;
    639:         }
    640:
    641:         // interpolate replacement values into the message and return
    642:         return strtr($message, $replace);
    643:     }
    644:
    645:     private function out($level, $message, array $context = array())
    646:     {
    647:         $text = '';
    648:
    649:         /*if (is_array($message)) {
    650:             foreach ($message as $arg) {
    651:                 $text .= is_scalar($arg) ? $arg : print_r($arg, true);
    652:             }
    653:         } else */ if (is_scalar($message) === false) {
    654:             $text = print_r($message, true);
    655:         } else {
    656:             $text = $message;
    657:         }
    658:
    659:         $text = $this->interpolate($text, $context);
    660:
    661:         $origine = $this->findOrigin();
    662:
    663:         $file = basename($origine['file']);
    664:
    665:         if ($this->isActivedFile($file)) {
    666:
    667:             $function = $origine['function'];
    668:             $line = $origine['line'];
    669:
    670:             $prefixe = str_replace(
    671:                 array("#NUM", "#NIVEAU", "#FICHIER", "#FONCTION", "#LIGNE", "#DATE", "#HEURE"),
    672:                 array(1+$this->linecount, $level, $file, $function, $line, date("Y-m-d"), date("G:i:s")),
    673:                 $this->prefixe
    674:             );
    675:
    676:             $trace = $prefixe . $text;
    677:
    678:             foreach ($this->destinations as $dest) {
    679:                 $dest->add($trace);
    680:             }
    681:
    682:             $this->linecount++;
    683:         }
    684:     }
    685:
    686:     /**
    687:      *
    688:      * @param type  $destinations
    689:      * @param array $actives      array containing classes instanceof AbstractTlogDestination
    690:      */
    691:     protected function loadDestinations(&$destinations, array $actives = NULL)
    692:     {
    693:         foreach ($actives as $active) {
    694:             if (class_exists($active)) {
    695:                 $class = new $active();
    696:
    697:                 if (!$class instanceof AbstractTlogDestination) {
    698:                     throw new \UnexpectedValueException($active." must extends Thelia\Tlog\AbstractTlogDestination");
    699:                 }
    700:
    701:                 $destinations[$active] = $class;
    702:             }
    703:         }
    704:     }
    705: }
    706: