luyadev/luya-module-admin

View on GitHub
src/models/Logger.php

Summary

Maintainability
A
1 hr
Test Coverage
F
50%
<?php

namespace luya\admin\models;

use luya\admin\aws\DetailViewActiveWindow;
use luya\admin\Module;
use luya\admin\ngrest\base\NgRestModel;
use yii\base\Arrayable;
use yii\helpers\Json;

/**
 * Application Logger.
 *
 * Logger to store information when working in controllers and actions.
 *
 * + {{luya\admin\models\Logger::info()}}
 * + {{luya\admin\models\Logger::success()}}
 * + {{luya\admin\models\Logger::success()}}
 * + {{luya\admin\models\Logger::error()}}
 *
 * Sometimes its usefull to trace data from an action or controller in order to find out what happens an critical areas runing trough the system.
 *
 * An example of how to use the logger inside an order action:
 *
 * ```php
 * public function actionPaymentOrder($id)
 * {
 *     Logger::info('loading the payment model: ' . $id);
 *
 *     $model = MyModel::findOne($id);
 *
 *     if (!$model) {
 *         Logger::error('Unable to find the model id: ' . $id);
 *         throw new \Exception("abort");
 *     }
 *
 *     if ($model->amount < 0) {
 *         Logger::warning('Maybe something is wrong with model is amount is less then 0');
 *     }
 *
 *     if ($model->save()) {
 *         Logger::success("The model has been saved and validatet successful");
 *     }
 * }
 * ```
 *
 * If there are multiple log informations on the same site and you like to seperate them use the group attribute.
 *
 * ```php
 * public function actionLogin()
 * {
 *     Yii::$app->user->login();
 *     Logger::info('User is logging in', 'user');
 *
 *     Yii::$app->user->addToBasket('Product 1');
 *     Logger::info('Add Product to users Cart', 'basket');
 *
 *     Logger::info('Redirect the user to basket', 'user');
 *     Yii::$app->user->redirect();
 * }
 * ```
 *
 * @property integer $id
 * @property integer $time
 * @property string $message
 * @property integer $type
 * @property string $trace_file
 * @property string $trace_line
 * @property string $trace_function
 * @property string $trace_function_args
 * @property string $get
 * @property string $post
 * @property string $session
 * @property string $server
 * @property string $group_identifier If provided the group_identifier is used to group multiple logging informations into one trace in order to see what messages should be display togher (trace behavior).
 * @property integer $group_identifier_index
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
final class Logger extends NgRestModel
{
    /**
     * @var integer Type level info.
     */
    public const TYPE_INFO = 1;

    /**
     * @var integer Type level warning.
     */
    public const TYPE_WARNING = 2;

    /**
     * @var integer Type level error.
     */
    public const TYPE_ERROR = 3;

    /**
     * @var integer Type level success.
     */
    public const TYPE_SUCCESS = 4;

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%admin_logger}}';
    }

    /**
     * @inheritdoc
     */
    public static function ngRestApiEndpoint()
    {
        return 'api-admin-logger';
    }

    /**
     * @inheritdoc
     */
    public function ngRestAttributeTypes()
    {
        return [
            'time' => 'datetime',
            'message' => 'text',
            'group_identifier' => 'text',
            'group_identifier_index' => 'number',
        ];
    }

    /**
     * @inheritdoc
     */
    public function ngRestExtraAttributeTypes()
    {
        return [
            'typeBadge' => ['html', 'sortField' => 'type'],
        ];
    }

    /**
     * Get the html badge for a status type in order to display inside the admin module.
     *
     * @return string Type value evaluated as a badge.
     */
    public function getTypeBadge()
    {
        return match ($this->type) {
            self::TYPE_INFO => '<span class="badge badge-info">info</span>',
            self::TYPE_WARNING => '<span class="badge badge-warning">Warning</span>',
            self::TYPE_ERROR => '<span class="badge badge-danger">Error</span>',
            self::TYPE_SUCCESS => '<span class="badge badge-success">success</span>',
            default => '',
        };
    }

    public function ngRestGroupByField()
    {
        return 'group_identifier';
    }

    /**
     * @inheritdoc
     */
    public function extraFields()
    {
        return ['typeBadge'];
    }

    /**
     * @inheritdoc
     */
    public function ngRestConfig($config)
    {
        $config->aw->load([
            'class' => DetailViewActiveWindow::class,
            'attributes' => [
                'id',
                'time:datetime',
                'message',
                'typeBadge:html:Type',
                'trace_file',
                'trace_line',
                'trace_function',
                'trace_function_args',
                'get:ntext',
                'post:ntext',
                'session:ntext',
                'server:ntext',
            ],
        ]);
        $this->ngRestConfigDefine($config, ['list'], ['message', 'typeBadge', 'time', 'group_identifier', 'group_identifier_index']);
        $config->delete = true;
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['time', 'message', 'type'], 'required'],
            [['time', 'type', 'group_identifier_index'], 'integer'],
            [['message', 'get', 'post', 'session', 'server', 'group_identifier'], 'string'],
            [['trace_file', 'trace_line', 'trace_function', 'trace_function_args'], 'safe'],
        ];
    }

    private static array $identiferIndex = [];

    private static $requestIdentifier;

    /**
     * Generate request identifier.
     *
     * @return string
     */
    private static function getRequestIdentifier()
    {
        if (self::$requestIdentifier === null) {
            self::$requestIdentifier = uniqid('logger', true);
        }

        return self::$requestIdentifier;
    }

    /**
     * Get array index based on identifiers.
     *
     * @param string $message
     * @param string $groupIdentifier
     * @return string[]|mixed[]
     */
    private static function getHashArray($message, $groupIdentifier)
    {
        $hash = md5(static::getRequestIdentifier() . Json::encode((array) $groupIdentifier));
        if (isset(self::$identiferIndex[$hash])) {
            self::$identiferIndex[$hash]++;
        } else {
            self::$identiferIndex[$hash] = 1;
        }

        $hashIndex =  self::$identiferIndex[$hash];

        return [
            'hash' => $hash,
            'index' => $hashIndex,
        ];
    }

    /**
     * Internal generate log message.
     *
     * @param string $type
     * @param string $message
     * @param string $trace
     * @param string $groupIdentifier
     * @return boolean
     */
    private static function log($type, $message, $trace, $groupIdentifier)
    {
        $hashArray = static::getHashArray($message, $groupIdentifier);

        $file = 'unknown';
        $line = 'unknown';
        $fn = 'unknown';
        $fnArgs = [];

        if (isset($trace[0])) {
            $file = !isset($trace[0]['file']) ?: $trace[0]['file'];
            $line = !isset($trace[0]['line']) ?: $trace[0]['line'];
            $fn = !isset($trace[0]['function']) ?: $trace[0]['function'];
            $fnArgs = !isset($trace[0]['args']) ?: $trace[0]['args'];
        }

        if (isset($trace[1])) {
            $fn = !isset($trace[1]['function']) ?: $trace[1]['function'];
            $fnArgs = !isset($trace[1]['args']) ?: $trace[1]['args'];
        }

        if ($message instanceof Arrayable) {
            $message = $message->toArray();
        }

        $model = new self();
        $model->attributes = [
            'time' => time(),
            'message' => is_array($message) ? Json::encode($message) : $message,
            'type' => $type,
            'trace_file' => $file,
            'trace_line' => $line,
            'trace_function' => $fn,
            'trace_function_args' => Json::encode($fnArgs),
            'get' => (isset($_GET)) ? Json::encode($_GET) : '{}',
            'post' => (isset($_POST)) ? Json::encode($_POST) : '{}',
            'session' => (isset($_SESSION)) ? Json::encode($_SESSION) : '{}',
            'server' => (isset($_SERVER)) ? Json::encode($_SERVER) : '{}',
            'group_identifier' => $hashArray['hash'],
            'group_identifier_index' => $hashArray['index'],
        ];

        return $model->save(false);
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Module::t('model_pk_id'),
            'time' => Module::t('model_logger_time'),
            'message' => Module::t('model_logger_message'),
            'type' => Module::t('model_logger_type'),
            'trace_file' => Module::t('model_logger_trace_file'),
            'trace_line' => Module::t('model_logger_trace_line'),
            'trace_function' => Module::t('model_logger_trace_function'),
            'trace_function_args' => Module::t('model_logger_trace_function_args'),
            'get' => Module::t('model_logger_get'),
            'post' => Module::t('model_logger_post'),
            'session' => Module::t('model_logger_session'),
            'server' => Module::t('model_logger_server'),
            'group_identifier' => Module::t('model_logger_group_identifier'),
            'group_identifier_index' => Module::t('model_logger_group_identifier_index'),
            'typeBadge' => Module::t('model_logger_badgetype'),
        ];
    }

    /**
     * Log a success message.
     *
     * @param string $message The message to log for this current request event.
     * @param string $groupIdentifier If multiple logger events are in the same action and you want to seperate them, define an additional group identifier.
     * @return boolean
     */
    public static function success($message, $groupIdentifier = null)
    {
        return static::log(self::TYPE_SUCCESS, $message, debug_backtrace(false, 2), $groupIdentifier);
    }

    /**
     * Log an error message.
     *
     * @param string $message The message to log for this current request event.
     * @param string $groupIdentifier If multiple logger events are in the same action and you want to seperate them, define an additional group identifier.
     * @return boolean
     */
    public static function error($message, $groupIdentifier = null)
    {
        return static::log(self::TYPE_ERROR, $message, debug_backtrace(false, 2), $groupIdentifier);
    }

    /**
     * Log an info message.
     *
     * @param string $message The message to log for this current request event.
     * @param string $groupIdentifier If multiple logger events are in the same action and you want to seperate them, define an additional group identifier.
     * @return boolean
     */
    public static function info($message, $groupIdentifier = null)
    {
        return static::log(self::TYPE_INFO, $message, debug_backtrace(false, 2), $groupIdentifier);
    }

    /**
     * Log a warning message.
     *
     * @param string $message The message to log for this current request event.
     * @param string $groupIdentifier If multiple logger events are in the same action and you want to seperate them, define an additional group identifier.
     * @return boolean
     */
    public static function warning($message, $groupIdentifier = null)
    {
        return static::log(self::TYPE_WARNING, $message, debug_backtrace(false, 2), $groupIdentifier);
    }
}