409 lines
10 KiB
PHP
409 lines
10 KiB
PHP
|
<?php
|
||
|
/*
|
||
|
* Copyright 2014 Google Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
if (!class_exists('Google_Client')) {
|
||
|
require_once dirname(__FILE__) . '/../autoload.php';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Abstract logging class based on the PSR-3 standard.
|
||
|
*
|
||
|
* NOTE: We don't implement `Psr\Log\LoggerInterface` because we need to
|
||
|
* maintain PHP 5.2 support.
|
||
|
*
|
||
|
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
|
||
|
*/
|
||
|
abstract class Google_Logger_Abstract
|
||
|
{
|
||
|
/**
|
||
|
* Default log format
|
||
|
*/
|
||
|
const DEFAULT_LOG_FORMAT = "[%datetime%] %level%: %message% %context%\n";
|
||
|
/**
|
||
|
* Default date format
|
||
|
*
|
||
|
* Example: 16/Nov/2014:03:26:16 -0500
|
||
|
*/
|
||
|
const DEFAULT_DATE_FORMAT = 'd/M/Y:H:i:s O';
|
||
|
|
||
|
/**
|
||
|
* System is unusable
|
||
|
*/
|
||
|
const EMERGENCY = 'emergency';
|
||
|
/**
|
||
|
* Action must be taken immediately
|
||
|
*
|
||
|
* Example: Entire website down, database unavailable, etc. This should
|
||
|
* trigger the SMS alerts and wake you up.
|
||
|
*/
|
||
|
const ALERT = 'alert';
|
||
|
/**
|
||
|
* Critical conditions
|
||
|
*
|
||
|
* Example: Application component unavailable, unexpected exception.
|
||
|
*/
|
||
|
const CRITICAL = 'critical';
|
||
|
/**
|
||
|
* Runtime errors that do not require immediate action but should typically
|
||
|
* be logged and monitored.
|
||
|
*/
|
||
|
const ERROR = 'error';
|
||
|
/**
|
||
|
* Exceptional occurrences that are not errors.
|
||
|
*
|
||
|
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||
|
* that are not necessarily wrong.
|
||
|
*/
|
||
|
const WARNING = 'warning';
|
||
|
/**
|
||
|
* Normal but significant events.
|
||
|
*/
|
||
|
const NOTICE = 'notice';
|
||
|
/**
|
||
|
* Interesting events.
|
||
|
*
|
||
|
* Example: User logs in, SQL logs.
|
||
|
*/
|
||
|
const INFO = 'info';
|
||
|
/**
|
||
|
* Detailed debug information.
|
||
|
*/
|
||
|
const DEBUG = 'debug';
|
||
|
|
||
|
/**
|
||
|
* @var array $levels Logging levels
|
||
|
*/
|
||
|
protected static $levels = array(
|
||
|
self::EMERGENCY => 600,
|
||
|
self::ALERT => 550,
|
||
|
self::CRITICAL => 500,
|
||
|
self::ERROR => 400,
|
||
|
self::WARNING => 300,
|
||
|
self::NOTICE => 250,
|
||
|
self::INFO => 200,
|
||
|
self::DEBUG => 100,
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* @var integer $level The minimum logging level
|
||
|
*/
|
||
|
protected $level = self::DEBUG;
|
||
|
|
||
|
/**
|
||
|
* @var string $logFormat The current log format
|
||
|
*/
|
||
|
protected $logFormat = self::DEFAULT_LOG_FORMAT;
|
||
|
/**
|
||
|
* @var string $dateFormat The current date format
|
||
|
*/
|
||
|
protected $dateFormat = self::DEFAULT_DATE_FORMAT;
|
||
|
|
||
|
/**
|
||
|
* @var boolean $allowNewLines If newlines are allowed
|
||
|
*/
|
||
|
protected $allowNewLines = false;
|
||
|
|
||
|
/**
|
||
|
* @param Google_Client $client The current Google client
|
||
|
*/
|
||
|
public function __construct(Google_Client $client)
|
||
|
{
|
||
|
$this->setLevel(
|
||
|
$client->getClassConfig('Google_Logger_Abstract', 'level')
|
||
|
);
|
||
|
|
||
|
$format = $client->getClassConfig('Google_Logger_Abstract', 'log_format');
|
||
|
$this->logFormat = $format ? $format : self::DEFAULT_LOG_FORMAT;
|
||
|
|
||
|
$format = $client->getClassConfig('Google_Logger_Abstract', 'date_format');
|
||
|
$this->dateFormat = $format ? $format : self::DEFAULT_DATE_FORMAT;
|
||
|
|
||
|
$this->allowNewLines = (bool) $client->getClassConfig(
|
||
|
'Google_Logger_Abstract',
|
||
|
'allow_newlines'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the minimum logging level that this logger handles.
|
||
|
*
|
||
|
* @param integer $level
|
||
|
*/
|
||
|
public function setLevel($level)
|
||
|
{
|
||
|
$this->level = $this->normalizeLevel($level);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if the logger should handle messages at the provided level.
|
||
|
*
|
||
|
* @param integer $level
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public function shouldHandle($level)
|
||
|
{
|
||
|
return $this->normalizeLevel($level) >= $this->level;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* System is unusable.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function emergency($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::EMERGENCY, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Action must be taken immediately.
|
||
|
*
|
||
|
* Example: Entire website down, database unavailable, etc. This should
|
||
|
* trigger the SMS alerts and wake you up.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function alert($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::ALERT, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Critical conditions.
|
||
|
*
|
||
|
* Example: Application component unavailable, unexpected exception.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function critical($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::CRITICAL, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Runtime errors that do not require immediate action but should typically
|
||
|
* be logged and monitored.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function error($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::ERROR, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Exceptional occurrences that are not errors.
|
||
|
*
|
||
|
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||
|
* that are not necessarily wrong.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function warning($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::WARNING, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Normal but significant events.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function notice($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::NOTICE, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Interesting events.
|
||
|
*
|
||
|
* Example: User logs in, SQL logs.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function info($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::INFO, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Detailed debug information.
|
||
|
*
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function debug($message, array $context = array())
|
||
|
{
|
||
|
$this->log(self::DEBUG, $message, $context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logs with an arbitrary level.
|
||
|
*
|
||
|
* @param mixed $level The log level
|
||
|
* @param string $message The log message
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
public function log($level, $message, array $context = array())
|
||
|
{
|
||
|
if (!$this->shouldHandle($level)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$levelName = is_int($level) ? array_search($level, self::$levels) : $level;
|
||
|
$message = $this->interpolate(
|
||
|
array(
|
||
|
'message' => $message,
|
||
|
'context' => $context,
|
||
|
'level' => strtoupper($levelName),
|
||
|
'datetime' => new DateTime(),
|
||
|
)
|
||
|
);
|
||
|
|
||
|
$this->write($message);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Interpolates log variables into the defined log format.
|
||
|
*
|
||
|
* @param array $variables The log variables.
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function interpolate(array $variables = array())
|
||
|
{
|
||
|
$template = $this->logFormat;
|
||
|
|
||
|
if (!$variables['context']) {
|
||
|
$template = str_replace('%context%', '', $template);
|
||
|
unset($variables['context']);
|
||
|
} else {
|
||
|
$this->reverseJsonInContext($variables['context']);
|
||
|
}
|
||
|
|
||
|
foreach ($variables as $key => $value) {
|
||
|
if (strpos($template, '%'. $key .'%') !== false) {
|
||
|
$template = str_replace(
|
||
|
'%' . $key . '%',
|
||
|
$this->export($value),
|
||
|
$template
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $template;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reverses JSON encoded PHP arrays and objects so that they log better.
|
||
|
*
|
||
|
* @param array $context The log context
|
||
|
*/
|
||
|
protected function reverseJsonInContext(array &$context)
|
||
|
{
|
||
|
if (!$context) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
foreach ($context as $key => $val) {
|
||
|
if (!$val || !is_string($val) || !($val[0] == '{' || $val[0] == '[')) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$json = @json_decode($val);
|
||
|
if (is_object($json) || is_array($json)) {
|
||
|
$context[$key] = $json;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Exports a PHP value for logging to a string.
|
||
|
*
|
||
|
* @param mixed $value The value to
|
||
|
*/
|
||
|
protected function export($value)
|
||
|
{
|
||
|
if (is_string($value)) {
|
||
|
if ($this->allowNewLines) {
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
return preg_replace('/[\r\n]+/', ' ', $value);
|
||
|
}
|
||
|
|
||
|
if (is_resource($value)) {
|
||
|
return sprintf(
|
||
|
'resource(%d) of type (%s)',
|
||
|
$value,
|
||
|
get_resource_type($value)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if ($value instanceof DateTime) {
|
||
|
return $value->format($this->dateFormat);
|
||
|
}
|
||
|
|
||
|
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||
|
$options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||
|
|
||
|
if ($this->allowNewLines) {
|
||
|
$options |= JSON_PRETTY_PRINT;
|
||
|
}
|
||
|
|
||
|
return @json_encode($value, $options);
|
||
|
}
|
||
|
|
||
|
return str_replace('\\/', '/', @json_encode($value));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts a given log level to the integer form.
|
||
|
*
|
||
|
* @param mixed $level The logging level
|
||
|
* @return integer $level The normalized level
|
||
|
* @throws Google_Logger_Exception If $level is invalid
|
||
|
*/
|
||
|
protected function normalizeLevel($level)
|
||
|
{
|
||
|
if (is_int($level) && array_search($level, self::$levels) !== false) {
|
||
|
return $level;
|
||
|
}
|
||
|
|
||
|
if (is_string($level) && isset(self::$levels[$level])) {
|
||
|
return self::$levels[$level];
|
||
|
}
|
||
|
|
||
|
throw new Google_Logger_Exception(
|
||
|
sprintf("Unknown LogLevel: '%s'", $level)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes a message to the current log implementation.
|
||
|
*
|
||
|
* @param string $message The message
|
||
|
*/
|
||
|
abstract protected function write($message);
|
||
|
}
|