Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  /*
   3   * Copyright 2014 Google Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *     http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  if (!class_exists('Google_Client')) {
  19    require_once dirname(__FILE__) . '/../autoload.php';
  20  }
  21  
  22  /**
  23   * Abstract logging class based on the PSR-3 standard.
  24   *
  25   * NOTE: We don't implement `Psr\Log\LoggerInterface` because we need to
  26   * maintain PHP 5.2 support.
  27   *
  28   * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
  29   */
  30  abstract class Google_Logger_Abstract
  31  {
  32    /**
  33     * Default log format
  34     */
  35    const DEFAULT_LOG_FORMAT = "[%datetime%] %level%: %message% %context%\n";
  36    /**
  37     * Default date format
  38     *
  39     * Example: 16/Nov/2014:03:26:16 -0500
  40     */
  41    const DEFAULT_DATE_FORMAT = 'd/M/Y:H:i:s O';
  42  
  43    /**
  44     * System is unusable
  45     */
  46    const EMERGENCY = 'emergency';
  47    /**
  48     * Action must be taken immediately
  49     *
  50     * Example: Entire website down, database unavailable, etc. This should
  51     * trigger the SMS alerts and wake you up.
  52     */
  53    const ALERT = 'alert';
  54    /**
  55     * Critical conditions
  56     *
  57     * Example: Application component unavailable, unexpected exception.
  58     */
  59    const CRITICAL = 'critical';
  60    /**
  61     * Runtime errors that do not require immediate action but should typically
  62     * be logged and monitored.
  63     */
  64    const ERROR = 'error';
  65    /**
  66     * Exceptional occurrences that are not errors.
  67     *
  68     * Example: Use of deprecated APIs, poor use of an API, undesirable things
  69     * that are not necessarily wrong.
  70     */
  71    const WARNING = 'warning';
  72    /**
  73     * Normal but significant events.
  74     */
  75    const NOTICE = 'notice';
  76    /**
  77     * Interesting events.
  78     *
  79     * Example: User logs in, SQL logs.
  80     */
  81    const INFO = 'info';
  82    /**
  83     * Detailed debug information.
  84     */
  85    const DEBUG = 'debug';
  86  
  87    /**
  88     * @var array $levels Logging levels
  89     */
  90    protected static $levels = array(
  91        self::EMERGENCY  => 600,
  92        self::ALERT => 550,
  93        self::CRITICAL => 500,
  94        self::ERROR => 400,
  95        self::WARNING => 300,
  96        self::NOTICE => 250,
  97        self::INFO => 200,
  98        self::DEBUG => 100,
  99    );
 100  
 101    /**
 102     * @var integer $level The minimum logging level
 103     */
 104    protected $level = self::DEBUG;
 105  
 106    /**
 107     * @var string $logFormat The current log format
 108     */
 109    protected $logFormat = self::DEFAULT_LOG_FORMAT;
 110    /**
 111     * @var string $dateFormat The current date format
 112     */
 113    protected $dateFormat = self::DEFAULT_DATE_FORMAT;
 114  
 115    /**
 116     * @var boolean $allowNewLines If newlines are allowed
 117     */
 118    protected $allowNewLines = false;
 119  
 120    /**
 121     * @param Google_Client $client  The current Google client
 122     */
 123    public function __construct(Google_Client $client)
 124    {
 125      $this->setLevel(
 126          $client->getClassConfig('Google_Logger_Abstract', 'level')
 127      );
 128  
 129      $format = $client->getClassConfig('Google_Logger_Abstract', 'log_format');
 130      $this->logFormat = $format ? $format : self::DEFAULT_LOG_FORMAT;
 131  
 132      $format = $client->getClassConfig('Google_Logger_Abstract', 'date_format');
 133      $this->dateFormat = $format ? $format : self::DEFAULT_DATE_FORMAT;
 134  
 135      $this->allowNewLines = (bool) $client->getClassConfig(
 136          'Google_Logger_Abstract',
 137          'allow_newlines'
 138      );
 139    }
 140  
 141    /**
 142     * Sets the minimum logging level that this logger handles.
 143     *
 144     * @param integer $level
 145     */
 146    public function setLevel($level)
 147    {
 148      $this->level = $this->normalizeLevel($level);
 149    }
 150  
 151    /**
 152     * Checks if the logger should handle messages at the provided level.
 153     *
 154     * @param  integer $level
 155     * @return boolean
 156     */
 157    public function shouldHandle($level)
 158    {
 159      return $this->normalizeLevel($level) >= $this->level;
 160    }
 161  
 162    /**
 163     * System is unusable.
 164     *
 165     * @param string $message The log message
 166     * @param array $context  The log context
 167     */
 168    public function emergency($message, array $context = array())
 169    {
 170      $this->log(self::EMERGENCY, $message, $context);
 171    }
 172  
 173    /**
 174     * Action must be taken immediately.
 175     *
 176     * Example: Entire website down, database unavailable, etc. This should
 177     * trigger the SMS alerts and wake you up.
 178     *
 179     * @param string $message The log message
 180     * @param array $context  The log context
 181     */
 182    public function alert($message, array $context = array())
 183    {
 184      $this->log(self::ALERT, $message, $context);
 185    }
 186  
 187    /**
 188     * Critical conditions.
 189     *
 190     * Example: Application component unavailable, unexpected exception.
 191     *
 192     * @param string $message The log message
 193     * @param array $context  The log context
 194     */
 195    public function critical($message, array $context = array())
 196    {
 197      $this->log(self::CRITICAL, $message, $context);
 198    }
 199  
 200    /**
 201     * Runtime errors that do not require immediate action but should typically
 202     * be logged and monitored.
 203     *
 204     * @param string $message The log message
 205     * @param array $context  The log context
 206     */
 207    public function error($message, array $context = array())
 208    {
 209      $this->log(self::ERROR, $message, $context);
 210    }
 211  
 212    /**
 213     * Exceptional occurrences that are not errors.
 214     *
 215     * Example: Use of deprecated APIs, poor use of an API, undesirable things
 216     * that are not necessarily wrong.
 217     *
 218     * @param string $message The log message
 219     * @param array $context  The log context
 220     */
 221    public function warning($message, array $context = array())
 222    {
 223      $this->log(self::WARNING, $message, $context);
 224    }
 225  
 226    /**
 227     * Normal but significant events.
 228     *
 229     * @param string $message The log message
 230     * @param array $context  The log context
 231     */
 232    public function notice($message, array $context = array())
 233    {
 234      $this->log(self::NOTICE, $message, $context);
 235    }
 236  
 237    /**
 238     * Interesting events.
 239     *
 240     * Example: User logs in, SQL logs.
 241     *
 242     * @param string $message The log message
 243     * @param array $context  The log context
 244     */
 245    public function info($message, array $context = array())
 246    {
 247      $this->log(self::INFO, $message, $context);
 248    }
 249  
 250    /**
 251     * Detailed debug information.
 252     *
 253     * @param string $message The log message
 254     * @param array $context  The log context
 255     */
 256    public function debug($message, array $context = array())
 257    {
 258      $this->log(self::DEBUG, $message, $context);
 259    }
 260  
 261    /**
 262     * Logs with an arbitrary level.
 263     *
 264     * @param mixed $level    The log level
 265     * @param string $message The log message
 266     * @param array $context  The log context
 267     */
 268    public function log($level, $message, array $context = array())
 269    {
 270      if (!$this->shouldHandle($level)) {
 271        return false;
 272      }
 273  
 274      $levelName = is_int($level) ? array_search($level, self::$levels) : $level;
 275      $message = $this->interpolate(
 276          array(
 277              'message' => $message,
 278              'context' => $context,
 279              'level' => strtoupper($levelName),
 280              'datetime' => new DateTime(),
 281          )
 282      );
 283  
 284      $this->write($message);
 285    }
 286  
 287    /**
 288     * Interpolates log variables into the defined log format.
 289     *
 290     * @param  array $variables The log variables.
 291     * @return string
 292     */
 293    protected function interpolate(array $variables = array())
 294    {
 295      $template = $this->logFormat;
 296  
 297      if (!$variables['context']) {
 298        $template = str_replace('%context%', '', $template);
 299        unset($variables['context']);
 300      } else {
 301        $this->reverseJsonInContext($variables['context']);
 302      }
 303  
 304      foreach ($variables as $key => $value) {
 305        if (strpos($template, '%'. $key .'%') !== false) {
 306          $template = str_replace(
 307              '%' . $key . '%',
 308              $this->export($value),
 309              $template
 310          );
 311        }
 312      }
 313  
 314      return $template;
 315    }
 316  
 317    /**
 318     * Reverses JSON encoded PHP arrays and objects so that they log better.
 319     *
 320     * @param array $context The log context
 321     */
 322    protected function reverseJsonInContext(array &$context)
 323    {
 324      if (!$context) {
 325        return;
 326      }
 327  
 328      foreach ($context as $key => $val) {
 329        if (!$val || !is_string($val) || !($val[0] == '{' || $val[0] == '[')) {
 330          continue;
 331        }
 332  
 333        $json = @json_decode($val);
 334        if (is_object($json) || is_array($json)) {
 335          $context[$key] = $json;
 336        }
 337      }
 338    }
 339  
 340    /**
 341     * Exports a PHP value for logging to a string.
 342     *
 343     * @param mixed $value The value to
 344     */
 345    protected function export($value)
 346    {
 347      if (is_string($value)) {
 348        if ($this->allowNewLines) {
 349          return $value;
 350        }
 351  
 352        return preg_replace('/[\r\n]+/', ' ', $value);
 353      }
 354  
 355      if (is_resource($value)) {
 356        return sprintf(
 357            'resource(%d) of type (%s)',
 358            $value,
 359            get_resource_type($value)
 360        );
 361      }
 362  
 363      if ($value instanceof DateTime) {
 364        return $value->format($this->dateFormat);
 365      }
 366  
 367      if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
 368        $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
 369  
 370        if ($this->allowNewLines) {
 371          $options |= JSON_PRETTY_PRINT;
 372        }
 373  
 374        return @json_encode($value, $options);
 375      }
 376  
 377      return str_replace('\\/', '/', @json_encode($value));
 378    }
 379  
 380    /**
 381     * Converts a given log level to the integer form.
 382     *
 383     * @param  mixed $level   The logging level
 384     * @return integer $level The normalized level
 385     * @throws Google_Logger_Exception If $level is invalid
 386     */
 387    protected function normalizeLevel($level)
 388    {
 389      if (is_int($level) && array_search($level, self::$levels) !== false) {
 390        return $level;
 391      }
 392  
 393      if (is_string($level) && isset(self::$levels[$level])) {
 394        return self::$levels[$level];
 395      }
 396  
 397      throw new Google_Logger_Exception(
 398          sprintf("Unknown LogLevel: '%s'", $level)
 399      );
 400    }
 401  
 402    /**
 403     * Writes a message to the current log implementation.
 404     *
 405     * @param string $message The message
 406     */
 407    abstract protected function write($message);
 408  }