I think I’ve already mentioned this somewhere on the forum but I cannot find it so here it goes again.
First of all, if you are sure which data is exactly logged you can exclude it in the logs like mentioned here.
If you are not sure you can use something what we have written for this in our systems. EmailTarget::getContextMessage() is overwritten like:
class EmailTarget extends \yii\log\EmailTarget
{
protected function getContextMessage()
{
$context = \yii\helpers\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} = " . LogVarDumper::dumpAsString($value);
}
}
return implode("\n\n", $result);
}
}
And LogVarDumper is introduced:
class LogVarDumper extends \yii\helpers\VarDumper
{
private static $_objects;
private static $_output;
private static $_depth;
/**
* Dumps a variable in terms of a string.
* This method achieves the similar functionality as var_dump and print_r
* but is more robust when handling complex objects such as Yii controllers.
* @param mixed $var variable to be dumped
* @param int $depth maximum depth that the dumper should go into the variable. Defaults to 10.
* @param bool $highlight whether the result should be syntax-highlighted
* @return string the string representation of the variable
*/
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('/<\\?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 .= (string) $var;
break;
case 'double':
self::$_output .= (string) $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, stripos($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;
}
}
}
This makes sure that array value is ** PASSWORD HIDDEN **'
when its key is password
.