Nickolay
Nickolay

Reputation: 45

yii2 exclude variable from logging

I need to make path for yii2 logging. This path should avoid logging variables like password, etc. According to documentation (yii\log\Target) for excluding variable I need to configure logVars.

In config:

 'targets' => [
            [
                'class' => yii\log\FileTarget::className(),
                'levels' => ['error', 'warning'],
                'maxLogFiles' => 1000,
                'maxFileSize' => 102400,
                'logVars' => '!_POST[LoginForm[password]]',
            ],

But LoginForm password still appear in log files. How to configure logVars properly.

P.S. I have tried !LoginForm.password, !_POST.LoginForm.password

Upvotes: 0

Views: 1316

Answers (2)

Bizley
Bizley

Reputation: 18021

I prepared the code for the same purpose - maybe you will find it useful.

Override getContextMessage() in chosen target class like:

<?php

namespace your\namespace\here;

use yii\helpers\ArrayHelper;

class FileTarget extends \yii\log\FileTarget
{
    protected function getContextMessage()
    {
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
        $result = [];
        foreach ($context as $key => $value) {
            if (\is_string($value) && stripos($key, 'password') !== false) {
                $result[] = "\${$key} = '** PASSWORD HIDDEN **'";
            } else {
                $result[] = "\${$key} = " . \your\namespace\here\LogVarDumper::dumpAsString($value);
            }
        }
        return implode("\n\n", $result);
    }
}

Override VarDumper class with LogVarDumper (used above):

<?php

namespace your\namespace\here;

use yii\base\InvalidValueException;

/**
 * Class LogVarDumper
 * Extended to handle logs password fields darkening.
 */
class LogVarDumper extends \yii\helpers\VarDumper
{
    private static $_objects;
    private static $_output;
    private static $_depth;

    public static function dumpAsString($var, $depth = 10, $highlight = false)
    {
        self::$_output = '';
        self::$_objects = [];
        self::$_depth = $depth;
        self::dumpInternal($var, 0);
        if ($highlight) {
            $result = highlight_string("<?php\n" . self::$_output, true);
            self::$_output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
        }

        return self::$_output;
    }

    /**
     * @param mixed $var variable to be dumped
     * @param int $level depth level
     * @param bool $passwordKey whether password related key was present in previous iteration
     */
    private static function dumpInternal($var, $level, $passwordKey = false)
    {
        switch (gettype($var)) {
            case 'boolean':
                self::$_output .= $var ? 'true' : 'false';
                break;
            case 'integer':
                self::$_output .= "$var";
                break;
            case 'double':
                self::$_output .= "$var";
                break;
            case 'string':
                if ($passwordKey) {
                    self::$_output .= "'** PASSWORD HIDDEN **'";
                } else {
                    self::$_output .= "'" . addslashes($var) . "'";
                }
                break;
            case 'resource':
                self::$_output .= '{resource}';
                break;
            case 'NULL':
                self::$_output .= 'null';
                break;
            case 'unknown type':
                self::$_output .= '{unknown}';
                break;
            case 'array':
                if (self::$_depth <= $level) {
                    self::$_output .= '[...]';
                } elseif (empty($var)) {
                    self::$_output .= '[]';
                } else {
                    $keys = array_keys($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= '[';
                    foreach ($keys as $key) {
                        self::$_output .= "\n" . $spaces . '    ';
                        self::dumpInternal($key, 0);
                        self::$_output .= ' => ';
                        self::dumpInternal($var[$key], $level + 1, strpos(strtolower($key), 'password') !== false);
                    }
                    self::$_output .= "\n" . $spaces . ']';
                }
                break;
            case 'object':
                if (($id = array_search($var, self::$_objects, true)) !== false) {
                    self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)';
                } elseif (self::$_depth <= $level) {
                    self::$_output .= get_class($var) . '(...)';
                } else {
                    $id = array_push(self::$_objects, $var);
                    $className = get_class($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= "$className#$id\n" . $spaces . '(';
                    if ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__debugInfo')) {
                        $dumpValues = $var->__debugInfo();
                        if (!is_array($dumpValues)) {
                            throw new InvalidValueException('__debuginfo() must return an array');
                        }
                    } else {
                        $dumpValues = (array) $var;
                    }
                    foreach ($dumpValues as $key => $value) {
                        $keyDisplay = strtr(trim($key), "\0", ':');
                        self::$_output .= "\n" . $spaces . "    [$keyDisplay] => ";
                        self::dumpInternal($value, $level + 1);
                    }
                    self::$_output .= "\n" . $spaces . ')';
                }
                break;
        }
    }
}

And use your\namespace\here\FileTarget in configuration for logs. You can use all properties available for the selected target and now all fields containing password in their name with field value being string will log ** PASSWORD HIDDEN ** instead of actual password.

Upvotes: 5

Ed209
Ed209

Reputation: 821

The default target component only filters the keys one level deep.

So you can do !_POST.LoginForm to filter all fields of the LoginForm but not filter specific fields.

'logVars' = [
    '_GET',
    '_POST',
    '_FILES',
    '_COOKIE',
    '_SESSION',
    '_SERVER',
    '!_POST.LoginForm'
    ],

Will give you all the default logging except for any fields posted in LoginForm.

Upvotes: 0

Related Questions