1

Now I'm developing a project on yii2 and the task has arisen to add, in addition to the levels available in the logger, add more fatal and critical.

Actually, I redefined logger.php itself and began to redefine Yii.php, but it pulled a lot of dependencies inside vendor and errors began to appear like:

Fatal error: Uncaught Error: Class 'Yii' not found in /app/vendor/yiisoft/yii2/base/Module.php:183 Stack trace: #0 /app/components/Application.php(14): yii\base\Module::setInstance(Object(app\components\Application)) #1 /app/web/index.php(16): app\components\Application->__construct(Array) #2 {main} thrown in /app/vendor/yiisoft/yii2/base/Module.php on line 183

logger

class Logger extends \yii\log\Logger
{
/**
 * Critical message level. An tracing message is one that reveals the code execution flow.
 */
const LEVEL_CRITICAL = 0x12;

/**
 * Fatal message level. An tracing message is one that reveals the code execution flow.
 */
const LEVEL_FATAL = 0x16;

/**
 * Returns the text display of the specified level.
 * @param int $level the message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]].
 * @return string the text display of the level
 */
public static function getLevelName($level)
{
    static $levels = [
        self::LEVEL_ERROR => 'error',
        self::LEVEL_WARNING => 'warning',
        self::LEVEL_INFO => 'info',
        self::LEVEL_TRACE => 'trace',
        self::LEVEL_CRITICAL => 'critical',
        self::LEVEL_FATAL => 'fatal',
        self::LEVEL_PROFILE_BEGIN => 'profile begin',
        self::LEVEL_PROFILE_END => 'profile end',
        self::LEVEL_PROFILE => 'profile',
    ];

    return isset($levels[$level]) ? $levels[$level] : 'unknown';
}
}

yii.php

defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/components/Yii.php';

$config = require __DIR__ . '/config/console.php';

$application = new yii\console\Application($config);
$exitCode = $application->run();
exit($exitCode);

Yii.php

<?php

namespace app\components;

use app\components\Logger;
use yii\di\Container;

class Yii extends \yii\BaseYii
{
    private static $_logger;

    /**
     * @return Logger message logger
     */
    public static function getLogger()
    {
        if (self::$_logger !== null) {
            return self::$_logger;
        }

        return self::$_logger = static::createObject('app\components\Logger');
    }

    /**
     * Sets the logger object.
     * @param Logger $logger the logger object.
     */
    public static function setLogger($logger)
    {
        self::$_logger = $logger;
    }

    public static function critical($message, $category = 'application')
    {
        static::getLogger()->log($message, Logger::LEVEL_CRITICAL, $category);
    }
    public static function fatal($message, $category = 'application')
    {
        static::getLogger()->log($message, Logger::LEVEL_FATAL, $category);
    }
}

spl_autoload_register(['app\components\Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/../vendor/yiisoft/yii2/classes.php';
Yii::$container = new Container();

Is it possible to make it somehow simpler so as not to redefine the path for each component using it?

2
  • SO is an english speaking site. So please translate your question to english or post it on ru.stackoverflow.com Commented Aug 26, 2019 at 10:14
  • Please translate the question to English language. Commented Aug 26, 2019 at 10:18

1 Answer 1

4

Override the \yii\log\logger with your implementation that adds more levels. You've already done that so i will skip the Logger code.

namespace app\components;

class MyLogger extends \yii\log\Logger
{
    ...
}

Override the log target so it can deal with your extra levels

namespace app\components;

use \yii\log\Logger;
use \yii\helpers\VarDumper;

class MyFileTarget extends \yii\log\FileTarget
{
    private $_levels = 0;

    public function getLevels()
    {
        return $this->_levels;
    }

    public function setLevels($levels)
    {
        static $levelMap = [
            'error' => Logger::LEVEL_ERROR,
            'warning' => Logger::LEVEL_WARNING,
            'info' => Logger::LEVEL_INFO,
            'trace' => Logger::LEVEL_TRACE,
            'critical' => MyLogger::LEVEL_CRITICAL,
            'fatal' => MyLogger::LEVEL_FATAL,
            '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 {
            $bitmapValues = array_reduce($levelMap, function ($carry, $item) {
                return $carry | $item;
            });
            if (!($bitmapValues & $levels) && $levels !== 0) {
                throw new InvalidConfigException("Incorrect $levels value");
            }
            $this->_levels = $levels;
        }
    }

    public function formatMessage($message)
    {
        list($text, $level, $category, $timestamp) = $message;
        $level = MyLogger::getLevelName($level);
        if (!is_string($text)) {
            // exceptions may not be serializable if in the call stack somewhere is a Closure
            if ($text instanceof \Throwable || $text instanceof \Exception) {
                $text = (string) $text;
            } else {
                $text = VarDumper::export($text);
            }
        }
        $traces = [];
        if (isset($message[4])) {
            foreach ($message[4] as $trace) {
                $traces[] = "in {$trace['file']}:{$trace['line']}";
            }
        }
        $prefix = $this->getMessagePrefix($message);
        return $this->getTime($timestamp) . " {$prefix}[$level][$category] $text"
            . (empty($traces) ? '' : "\n    " . implode("\n    ", $traces));
    }
}

More info about log targets

Set the log dispatcher in your component config to use your logger and target

   ...
   'components' => [
        ...
        'log' => [
            'logger' => \app\components\MyLogger::class,
            'targets' => [
                [
                    'class' => \app\components\MyFileTarget::class,
                    'levels' => ['fatal', 'critical'],
                    'categories' => ['app\*'],
                    'file' => '@runtime/logs/critical.log',
                ],
            ],
        ],
        ...
    ],
    ...

More info about log configuration

Then call your logger through log component instead of aliases as Yii::error()

Yii::$app->log->getLogger()->log('msg', MyLogger::LEVEL_CRITICAL, __METHOD__);

Or you can create your own helper for calls similar to those aliases.

Sign up to request clarification or add additional context in comments.

8 Comments

I added the code to my app and it writes messages to log, but shows them tagged as unknown like I haven't changed the target: 2019-08-27 12:14:07 [IP][-][-][unknown][application] fatal \ It is ok with error 2019-08-27 12:14:07 [IP][-][-][error][application] error
I've edited my answer and added override of formatMessage($message) method to MyFileTarget class because that method called Logger statically. Don't forget to add the use \yii\helpers\VarDumper; which is used in formatMessage.
Thanks a lot! I was just picking it at the same time and wanted to add that It is needed to redefine it
@GabrielA.LópezLópez Hello, that's because Yii helper doesn't have methods for the custom logging levels. The Yii helper class is called statically so you can't easily extend it. It is possible to replace it but doing that might hinder maintainability in future. It's better to make your own helper class if you want to call custom logging through aliases. You can still use the Yii helper class for original log levels but it's better to keep the log calls consistent through your app.
@GabrielA.LópezLópez The user ID is already added into log messages when it's formatted in log target. At least it should be unless you've set different prefix. See yii\log\Target::getMessagePrefix()
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.