# Логирование ¶

Yii предоставляет мощную, гибко настраиваемую и легко расширяемую систему логирования. Эта система логирования позволяет удобным способом сохранять сообщения разных типов и фильтровать их. Сообщения могут быть сохранены в файлы, базы данных или отправлены на email.

Использование Системы логирования Yii включает следующие шаги:

- Запись сообщений лога в различных частях кода приложения;
- Настройка целей лога в конфигурации приложения;
- Изучение отфильтрованных сообщений лога, например, при помощи Отладчика Yii.

В данном разделе будем рассматривать первые два шага.

## Сообщения лога ¶

Запись сообщений лога осуществляется вызовом одного из следующих методов:

- Yii::debug(): записывает сообщения для отслеживания выполнения кода приложения. Используется, в основном, при разработке.
- Yii::info(): записывает сообщение, содержащее какую-либо полезную информацию.
- Yii::warning(): записывает тревожное сообщение при возникновении неожиданного события.
- Yii::error(): записывает критическую ошибку, на которую нужно, как можно скорее, обратить внимание.

Эти методы позволяют записывать сообщения разных уровней важности и категорий. Они имеют одинаковое описание функции `function ($message, $category = 'application')`, где `$message` передает сообщение для записи, а `$category` - категорию сообщения. В следующем примере будет записано trace сообщение с категорией по умолчанию `application`:

    Yii::debug('начало вычисления среднего дохода');

> Примечание: Сообщение может быть как строкой, так и объектом или массивом. За корректную работу с содержимым сообщения отвечают цели лога. По умолчанию, если сообщение не является строкой, оно будет приведено к строковому типу при помощи yii\helpers\VarDumper::export().

Для упрощения работы с сообщениями лога и их фильтрации, рекомендуется явно указывать подходящую категорию для каждого сообщения. Возможно использование иерархической системы именования категорий, что значительно упростит целям лога фильтрацию сообщений по категориям. Простым и эффективным способом именования категорий является использование магической PHP-константы `__METHOD__`. Такой подход используется в ядре фреймворка Yii. Например,

    Yii::debug('начало вычисления среднего дохода', __METHOD__);

Константа `__METHOD__` вычисляется как имя метода (включая полное имя класса), в котором она использована. Например, её значение будет вычислено как `'app\controllers\RevenueController::calculate'`, если показанный выше код вызывается в соответствующем методе.

> Информация: методы логирования, описанные выше являются, на самом деле, ярлыками для метода log() объекта логгера, который доступен как синглтон `Yii::getLogger()`. При определенном количестве записанных сообщений или завершении приложения, объект логгера вызывает message dispatcher для отправки записанных сообщений зарегистрированным целям логов.

## Цели логов ¶

Цель логов - это экземпляр класса yii\log\Target или класса, унаследованного от него. Цель фильтрует сообщения логов по уровню важности и категории, а затем выгружает их в соответствующее хранилище. Например, database target выгружает отфильтрованные сообщения логов в таблицу базы данных, а email target отправляет сообщения логов на заданные адреса email.

При помощи компонента приложения `log` возможна регистрация нескольких целей логов. Пример конфигурации приложения:

    return [
        // Компонент "log" должен быть загружен на этапе предзагрузки
        'bootstrap' => ['log'],
        // Компонент "log" обрабатывает сообщения с меткой времени timestamp.
        // Задайте временную зону для создания корректных меток времени.
        'timeZone' => 'Europe/Moscow',
        'components' => [
            'log' => [
                'targets' => [
                    [
                        'class' => 'yii\log\DbTarget',
                        'levels' => ['error', 'warning'],
                    ],
                    [
                        'class' => 'yii\log\EmailTarget',
                        'levels' => ['error'],
                        'categories' => ['yii\db\*'],
                        'message' => [
                           'from' => ['log@example.com'],
                           'to' => ['admin@example.com', 'developer@example.com'],
                           'subject' => 'Ошибки базы данных на сайте example.com',
                        ],
                    ],
                ],
            ],
        ],
    ];

> Примечание: Компонент `log` должен быть загружен в процессе предзагрузки, тогда он сможет оперативно передавать сообщения целям логов. Поэтому он указан в массиве `bootstrap`.

В приведенном выше коде в свойстве yii\log\Dispatcher::$targets зарегистрированы две цели логов:

- первая цель выбирает ошибки и предупреждения и сохраняет их в базу данных;
- вторая цель выбирает ошибки с категорией, имя которой начинается с `yii\db\` и шлет сразу на два адреса email `admin@example.com` и `developer@example.com`.

На данный момент, Yii содержит следующие встроенные цели логов. В документации по API подробно описана настройка и использование этих классов.

- yii\log\DbTarget: сохраняет сообщения логов в таблицу базы данных.
- yii\log\EmailTarget: шлет сообщения логов на заранее указанный email.
- yii\log\FileTarget: сохраняет сообщения логов в файлы.
- yii\log\SyslogTarget: сохраняет сообщения логов в системный лог используя функцию PHP `syslog()`.

Дальше рассмотрим общие для этих четырех классов возможности.

### Фильтрация сообщений ¶

Для каждой цели можно настроить свойства levels и categories, которые указывают уровни важности и категории сообщений логов, которые цель должна обрабатывать.

Свойство levels принимает массив, содержащий одно или несколько следующих значений:

- `error`: соответствует сообщениям, сохраненным методом Yii::error().
- `warning`: соответствует сообщениям, сохраненным методом Yii::warning().
- `info`: соответствует сообщениям, сохраненным методом Yii::info().
- `trace`: соответствует сообщениям, сохраненным методом Yii::debug().
- `profile`: соответствует сообщениям, сохраненным методами Yii::beginProfile() и Yii::endProfile(), подробнее о которых написано в подразделе Профилирование производительности.

Если свойство levels не задано, цель логов будет обрабатывать сообщения с любым уровнем важности.

Свойство categories принимает массив, содержащий имена категорий или шаблоны. Цель будет обрабатывать только те сообщения, категория которых совпадает с одним из значений или шаблонов этого массива. Шаблон категории должен состоять из префикса имени категории и звездочки `*` на конце. Имя категории совпадает с шаблоном, если оно начинается с префикса шаблона. Например, `yii\db\Command::execute` и `yii\db\Command::query` используются в качестве имен категорий сообщений, записанных в классе yii\db\Command. Оба они совпадают с шаблоном `yii\db\*`.

Если свойство categories не задано, цель будет обрабатывать сообщения любой категории.

Кроме списка включаемый категорий, заданного свойством categories, при помощи свойства except возможно задать список исключаемых категорий. Если категория сообщения совпадает со значением или шаблоном из списка исключаемых категорий, такое сообщение не будет обработано.

В следующем примере показан вариант конфигурации цели логов, которая должна обрабатывать только сообщения об ошибках и предупреждениях в категориях `yii\db\*` и `yii\web\HttpException:*`, за исключением `yii\web\HttpException:404`.

    [
        'class' => 'yii\log\FileTarget',
        'levels' => ['error', 'warning'],
        'categories' => [
            'yii\db\*',
            'yii\web\HttpException:*',
        ],
        'except' => [
            'yii\web\HttpException:404',
        ],
    ]

> Примечание: При обработке HTTP-исключения обработчиком ошибок, сообщение будет сохранено с категорией вида `yii\web\HttpException:ErrorCode`. Например, исключение yii\web\NotFoundHttpException вызовет сообщение об ошибке с категорией `yii\web\HttpException:404`.

### Форматирование сообщений ¶

Цели логов выгружают отфильтрованные сообщения в определенном формате. Например, цель класса yii\log\FileTarget сохранит сообщение следующего формата в файле `runtime/log/app.log`:

    2014-10-04 18:10:15 [::1][][-][trace][yii\base\Module::getModule] Loading module: debug

По умолчанию сообщения логов форматируются методом yii\log\Target::formatMessage():

    Временная метка [IP-адрес][ID пользователя][ID сессии][Уровень важности][Категория] Текст сообщения

Этот формат может быть изменен при помощи свойства yii\log\Target::$prefix, которое получает анонимную функцию, возвращающую нужный префикс сообщения. Например, следующий код позволяет настроить вывод идентификатор текущего пользователя в качестве префикса для всех сообщений.

    [
        'class' => 'yii\log\FileTarget',
        'prefix' => function ($message) {
            $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
            $userID = $user ? $user->getId(false) : '-';
            return "[$userID]";
        }
    ]

Кроме префиксов сообщений, также возможно добавление общей информации для каждого набора сообщений лога. По умолчанию включаются значения следующих глобальных PHP-переменных: `$_GET`, `$_POST`, `$_FILES`, `$_COOKIE`, `$_SESSION` и `$_SERVER`. Эта возможность настраивается при помощи свойства yii\log\Target::$logVars, содержащего массив имен переменных, которые необходимо включить в лог. Например, следующий код позволяет настроить цель логов так, чтобы к сообщениям присоединялось только содержимое переменной `$_SERVER`.

    [
        'class' => 'yii\log\FileTarget',
        'logVars' => ['_SERVER'],
    ]

При задании значением свойства `logVars` пустого массива, общая информация не будет выводиться. Для определения собственного алгоритма подключения общей информации, следует переопределить метод yii\log\Target::getContextMessage().

### Уровень отслеживания выполнения кода ¶

В процессе разработки, часто бывает необходимость видеть источники сообщений. Для этого нужно использовать свойство traceLevel компонента `log`. Например,

    return [
        'bootstrap' => ['log'],
        'components' => [
            'log' => [
                'traceLevel' => YII_DEBUG ? 3 : 0,
                'targets' => [...],
            ],
        ],
    ];

При такой настройке свойство traceLevel будет равно 3 при `YII_DEBUG` равном `true` и 0 при `YII_DEBUG` равном `false`. Это означает, что при включенном `YII_DEBUG`, каждое сообщение лога будет содержать до трех уровней стека вызовов, а при выключенном `YII_DEBUG` информация о стеке вызовов не будет включаться в лог.

> Информация: Получение информации стека вызовов является не простым процессом. Поэтому такую возможность следует использовать только при разработке или отладке приложения.

### Передача на обработку и выгрузка сообщений ¶

Как упоминалось выше, сообщения логов обрабатываются в массиве объектом логгера. Для ограничения объема памяти, занятого этим массивом, при накоплении определенного числа сообщений, логгер передает их на обработку целям логов. Максимальное количество сообщений определяется свойством flushInterval компонента `log`:

    return [
        'bootstrap' => ['log'],
        'components' => [
            'log' => [
                'flushInterval' => 100,   // по умолчанию 1000
                'targets' => [...],
            ],
        ],
    ];

> Информация: При завершении приложения, так же происходит передача сообщений на обработку.

После передачи сообщений объектом логгера в цели логов, сообщения не выгружаются немедленно. Вместо этого, выгрузка сообщений происходит когда цель логов накопит определенное количество фильтрованных сообщений. Максимальное количество сообщений определяется свойством exportInterval цели логов. Например,

    [
        'class' => 'yii\log\FileTarget',
        'exportInterval' => 100,  // по умолчанию 1000
    ]

Из-за того, что значения максимального количества сообщений для передачи и выгрузки по умолчанию достаточно велико, при вызове метода `Yii::debug()` или любого другого метода логирования, сообщение не появится сразу в файле или таблице базы данных. Такое поведение может стать проблемой, например, в консольных приложениях с большим временем исполнения. Для того, чтобы все сообщения логов сразу попадали в лог, необходимо установить значения свойств flushInterval и exportInterval равными 1, например так:

    return [
        'bootstrap' => ['log'],
        'components' => [
            'log' => [
                'flushInterval' => 1,
                'targets' => [
                    [
                        'class' => 'yii\log\FileTarget',
                        'exportInterval' => 1,
                    ],
                ],
            ],
        ],
    ];

> Примечание: Частая передача и выгрузка сообщений может сильно снизить производительность приложения.

### Переключение целей логов ¶

Свойство enabled отвечает за включение или отключение цели логов. Возможно управлением этим свойством как в конфигурации приложения, так и при помощи следующего PHP-кода:

    Yii::$app->log->targets['file']->enabled = false;

В данном примере используется цель логов `file`, которая может быть настроена в конфигурации приложения следующим образом:

    return [
        'bootstrap' => ['log'],
        'components' => [
            'log' => [
                'targets' => [
                    'file' => [
                        'class' => 'yii\log\FileTarget',
                    ],
                    'db' => [
                        'class' => 'yii\log\DbTarget',
                    ],
                ],
            ],
        ],
    ];

### Создание новых целей ¶

Создание новой цели логов не является сложной задачей. В общем случае нужно реализовать метод yii\log\Target::export(), выгружающий массив yii\log\Target::$messages в место хранения логов. Возможно использование метода yii\log\Target::formatMessage() для форматирования сообщения. Детали реализации можно подсмотреть в исходном коде любого из классов целей логов, включенных в состав Yii.

## Профилирование производительности ¶

Профилирование производительности - это специальный тип сообщений логов, используемый для измерения времени выполнения определенных участков кода и определения проблем производительности. Например, класс yii\db\Command использует профилирование производительности для определения времени исполнения каждого запроса базы данных.

Для использования профилирования производительности нужно определить участок кода для измерения и обернуть его вызовами методов Yii::beginProfile() и Yii::endProfile(). Например,

    \Yii::beginProfile('myBenchmark');

    // участок кода для профилирования...

    \Yii::endProfile('myBenchmark');

где `myBenchmark` является уникальным идентификатором данного измеряемого участка кода. В дальнейшем, при изучении результатов профилирования, уникальный идентификатор поможет определить время выполнения соответствующего участка кода.

Очень важно соблюдать уровни вложенности пар `beginProfile` и `endProfile`. Например,

    \Yii::beginProfile('block1');

        // код для профилирования

        \Yii::beginProfile('block2');
            // другой код для профилирования
        \Yii::endProfile('block2');

    \Yii::endProfile('block1');

Если пропустить `\Yii::endProfile('block1')` или поменять местами `\Yii::endProfile('block1')` и `\Yii::endProfile('block2')`, профилирование производительности не будет работать.

Для каждого участка кода, будет записано сообщение лога с уровнем важности `profile`. Для сбора таких сообщений можно настроить цель логов или воспользоваться Отладчиком Yii, который имеет встроенную панель профилирования производительности, отображающую результаты измерений.