Target.php 10.5 KB
Newer Older
w  
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
w  
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

Qiang Xue committed
8
namespace yii\log;
w  
Qiang Xue committed
9

Qiang Xue committed
10
use Yii;
Qiang Xue committed
11
use yii\base\Component;
Qiang Xue committed
12
use yii\base\InvalidConfigException;
13
use yii\helpers\VarDumper;
14
use yii\web\Request;
Qiang Xue committed
15

w  
Qiang Xue committed
16 17 18
/**
 * Target is the base class for all log target classes.
 *
w  
Qiang Xue committed
19 20 21
 * A log target object will filter the messages logged by [[Logger]] according
 * to its [[levels]] and [[categories]] properties. It may also export the filtered
 * messages to specific destination defined by the target, such as emails, files.
w  
Qiang Xue committed
22
 *
Qiang Xue committed
23 24 25
 * Level filter and category filter are combinatorial, i.e., only messages
 * satisfying both filter conditions will be handled. Additionally, you
 * may specify [[except]] to exclude messages of certain categories.
w  
Qiang Xue committed
26
 *
27
 * @property integer $levels The message levels that this target is interested in. This is a bitmap of level
28 29
 * values. Defaults to 0, meaning  all available levels. Note that the type of this property differs in getter
 * and setter. See [[getLevels()]] and [[setLevels()]] for details.
Qiang Xue committed
30
 *
w  
Qiang Xue committed
31 32 33
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
Qiang Xue committed
34
abstract class Target extends Component
w  
Qiang Xue committed
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
    /**
     * @var boolean whether to enable this log target. Defaults to true.
     */
    public $enabled = true;
    /**
     * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories.
     * You can use an asterisk at the end of a category so that the category may be used to
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
     * categories starting with 'yii\db\', such as 'yii\db\Connection'.
     */
    public $categories = [];
    /**
     * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages.
     * If this property is not empty, then any category listed here will be excluded from [[categories]].
     * You can use an asterisk at the end of a category so that the category can be used to
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
     * categories starting with 'yii\db\', such as 'yii\db\Connection'.
     * @see categories
     */
    public $except = [];
    /**
     * @var array list of the PHP predefined variables that should be logged in a message.
     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged.
     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`.
     */
    public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
62
    /**
63 64 65 66 67 68
     * @var callable a PHP callable that returns a string to be prefixed to every exported message.
     *
     * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information
     * such as user IP, user ID and session ID.
     *
     * The signature of the callable should be `function ($message)`.
69 70
     */
    public $prefix;
71 72 73 74 75 76 77 78 79 80 81 82 83 84
    /**
     * @var integer how many messages should be accumulated before they are exported.
     * Defaults to 1000. Note that messages will always be exported when the application terminates.
     * Set this property to be 0 if you don't want to export messages until the application terminates.
     */
    public $exportInterval = 1000;
    /**
     * @var array the messages that are retrieved from the logger so far by this log target.
     * Please refer to [[Logger::messages]] for the details about the message structure.
     */
    public $messages = [];

    private $_levels = 0;

85

86 87 88 89 90 91 92 93 94 95
    /**
     * Exports log [[messages]] to a specific destination.
     * Child classes must implement this method.
     */
    abstract public function export();

    /**
     * Processes the given log messages.
     * This method will filter the given messages with [[levels]] and [[categories]].
     * And if requested, it will also export the filtering result to specific medium (e.g. email).
96 97 98
     * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
     * of each message.
     * @param boolean $final whether this method is called at the end of the current application
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
     */
    public function collect($messages, $final)
    {
        $this->messages = array_merge($this->messages, $this->filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
        $count = count($this->messages);
        if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
            if (($context = $this->getContextMessage()) !== '') {
                $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
            }
            $this->export();
            $this->messages = [];
        }
    }

    /**
     * Generates the context information to be logged.
     * The default implementation will dump user information, system variables, etc.
     * @return string the context information. If an empty string, it means no context information.
     */
    protected function getContextMessage()
    {
        $context = [];
        foreach ($this->logVars as $name) {
            if (!empty($GLOBALS[$name])) {
123
                $context[] = "\${$name} = " . VarDumper::dumpAsString($GLOBALS[$name]);
124 125 126 127 128 129 130 131
            }
        }

        return implode("\n\n", $context);
    }

    /**
     * @return integer the message levels that this target is interested in. This is a bitmap of
132
     * level values. Defaults to 0, meaning  all available levels.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
     */
    public function getLevels()
    {
        return $this->_levels;
    }

    /**
     * Sets the message levels that this target is interested in.
     *
     * The parameter can be either an array of interested level names or an integer representing
     * the bitmap of the interested level values. Valid level names include: 'error',
     * 'warning', 'info', 'trace' and 'profile'; valid level values include:
     * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]],
     * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]].
     *
     * For example,
     *
     * ~~~
     * ['error', 'warning']
     * // which is equivalent to:
     * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING
     * ~~~
     *
156
     * @param array|integer $levels message levels that this target is interested in.
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
     * @throws InvalidConfigException if an unknown level name is given
     */
    public function setLevels($levels)
    {
        static $levelMap = [
            'error' => Logger::LEVEL_ERROR,
            'warning' => Logger::LEVEL_WARNING,
            'info' => Logger::LEVEL_INFO,
            'trace' => Logger::LEVEL_TRACE,
            'profile' => Logger::LEVEL_PROFILE,
        ];
        if (is_array($levels)) {
            $this->_levels = 0;
            foreach ($levels as $level) {
                if (isset($levelMap[$level])) {
                    $this->_levels |= $levelMap[$level];
                } else {
                    throw new InvalidConfigException("Unrecognized level: $level");
                }
            }
        } else {
            $this->_levels = $levels;
        }
    }

    /**
     * Filters the given messages according to their categories and levels.
184 185
     * @param array $messages messages to be filtered.
     * The message structure follows that in [[Logger::messages]].
186 187 188 189 190
     * @param integer $levels the message levels to filter by. This is a bitmap of
     * level values. Value 0 means allowing all levels.
     * @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
     * @param array $except the message categories to exclude. If empty, it means all categories are allowed.
     * @return array the filtered messages.
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
     */
    public static function filterMessages($messages, $levels = 0, $categories = [], $except = [])
    {
        foreach ($messages as $i => $message) {
            if ($levels && !($levels & $message[1])) {
                unset($messages[$i]);
                continue;
            }

            $matched = empty($categories);
            foreach ($categories as $category) {
                if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) {
                    $matched = true;
                    break;
                }
            }

            if ($matched) {
                foreach ($except as $category) {
                    $prefix = rtrim($category, '*');
                    if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
                        $matched = false;
                        break;
                    }
                }
            }

            if (!$matched) {
                unset($messages[$i]);
            }
        }
        return $messages;
    }

    /**
226
     * Formats a log message for display as a string.
227
     * @param array $message the log message to be formatted.
228
     * The message structure follows that in [[Logger::messages]].
229 230 231 232 233 234 235
     * @return string the formatted message
     */
    public function formatMessage($message)
    {
        list($text, $level, $category, $timestamp) = $message;
        $level = Logger::getLevelName($level);
        if (!is_string($text)) {
Carsten Brandt committed
236
            $text = VarDumper::export($text);
237 238
        }

239
        $prefix = $this->getMessagePrefix($message);
240
        return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text";
241 242 243 244
    }

    /**
     * Returns a string to be prefixed to the given message.
245
     * If [[prefix]] is configured it will return the result of the callback.
246
     * The default implementation will return user IP, user ID and session ID as a prefix.
247 248
     * @param array $message the message being exported.
     * The message structure follows that in [[Logger::messages]].
249 250 251 252
     * @return string the prefix string
     */
    public function getMessagePrefix($message)
    {
253 254 255 256
        if ($this->prefix !== null) {
            return call_user_func($this->prefix, $message);
        }

257 258
        $request = Yii::$app->getRequest();
        $ip = $request instanceof Request ? $request->getUserIP() : '-';
259

260
        /* @var $user \yii\web\User */
Qiang Xue committed
261
        $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
262
        $userID = $user ? $user->getId(false) : '-';
263

264
        /* @var $session \yii\web\Session */
Qiang Xue committed
265
        $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
266
        $sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
267

268
        return "[$ip][$userID][$sessionID]";
269
    }
w  
Qiang Xue committed
270
}