← к содержанию

Урок 7. Ведение журнала событий

При разработке программы в подавляющем большинстве случаев её требуется «отлаживать». Т.е. если программа ведёт себя «не так, как задумывалось», то нужно выяснять причины и устранять их. Нам нужно узнавать значения переменных в разные моменты времени, выяснять, когда какие функции выполняются и т. д. Для этого можно задействовать, например:

Для отладки игр и других им подобных 3D-программ, когда некие «события» происходят с частотой обновления кадров экрана (т. е., например, 60 раз в секунду), вариант с ведением журнала мне кажется более подходящим. Грубо говоря, подход примерно такой: мы запускаем программу, добираемся до места, где происходит «что-то не то», останавливаем (или программа сама вылетает с ошибкой), а потом спокойно читаем файл журнала и разбираемся.

В языке D в стандартной библиотеке присутствует модуль std.experimental.logger, предназначенный как раз для ведения такого журнала. Вы вполне можете использовать его для своего приложения, но я, исходя из некоторых соображений, посчитал, что для библиотеки DDD он не подходит, и сделал свой велосипед вариант.

Итак, включим журналирование в нашем маленьком 3D-приложении. Всего у записей в журнале может быть один из пяти уровней:

КритическаяОшибка, Ошибка, Предупреждение, Информация, Отладка.

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

  1. Сначала добавим несколько параметров в настройки программы. При желании можно не использовать настройки для параметров журнала, а сразу вписать их в тексте программы, но тогда пропадёт всё удобство выборочного журналирования. Откройте модуль nastroyki.d и добавьте внутрь объявления кортежа Кортеж_Настроек три дополнительные строки, соответствующие трём вышеупомянутым параметрам:

    string,         "общий_уровень",  "\"Информация\"",
    string,         "файл_журнала",    "\"журнал.log\"",
    string[string], "уровни_модулей", "[\"nastroyki\":\"Информация\"]",

К сожалению, я так и не понял, каким образом можно было бы задавать пустые ассоциативные массивы для параметров по-умолчанию, поэтому хотя бы одно значение должно присутствовать.

  1. Теперь откроем главный модуль программы urok.d и добавим импорт модуля ведения журнала:

import my_utils.journal;
  1. Теперь инициализируем журнал в функции main. Добавьте строку после чтения настроек и до инициализации DDD:

строковая_инициализация_журнала(настройки.файл_журнала, настройки.общий_уровень, настройки.уровни_модулей);
  1. Собственно, по-минимому, этого достаточно, чтобы программа начала вести журнал. Скомпилируйте и запустите её, а после закрытия окна в каталоге с двоичным файлом программы появится текстовый файл журнал.log. У меня он был вот с таким содержимым, ваш журнал наверняка будет отличаться хотя бы в значениях времени:

   0.00063  Начало работы журнала
---------------------------------------------------------------------------
   Время  |  Модуль       |  Уровень   |           Сообщение 
---------------------------------------------------------------------------
   0.08118|окно_SDL2      |Информация  | Шаг 1.1 Загружен SDL
   0.26676|окно_SDL2      |Информация  | Шаг 2. Инициализирован SDL
   0.26707|окно_SDL2      |Информация  | Шаг 3. Загружен DerelictGL
   0.27524|окно_SDL2      |Информация  | Шаг 4. Создано окно размером 800x600, с именем "Урок по DDD"
   0.29415|окно_SDL2      |Информация  | Шаг 5. Создан SDL_GL-контекст
   0.29609|окно_SDL2      |Информация  | Шаг 6. Перезагружен GL3. Версия GL = GL21 
   0.29619|окно_SDL2      |Информация  | Текущий видеорежим:  1920x1080 60Hz 24 bpp
   0.31451|ресурсы        |Информация  | Загруженные имена ресурсов: 
...
   0.34228|меш            |Информация  | Начинаем загрузку меша ресурсы/меши/Сюзанна.me
   0.34408|меш            |Информация  | вершин=2249,  граней=3968
   0.34431|меш            |Информация  | Меш, типа, загружен
   0.34720|окно_SDL2      |Информация  | Восстанавливаем окно
   1.53955|события        |Информация  | Сработала клавиша Esc
   1.53962|визуализатор   |Информация  | Закончили рендерить 1587 кадров

Я тут пропустил перечисление всех найденных программой ресурсов.

  1. Взглянем на смену кадров визуализации. Добавьте в файл с настройками настройки.cfg такую строку:

уровни_модулей = ["визуализатор":"Отладка"]

Запустите и остановите программу. Откройте файл журнала и увидите огромный и скучный список одинаковых событий «Отрисован кадр N». Закомментируйте пока в файле настроек добавленную нами строку (строки, начинающиеся с символа «#» в файле настроек считаются комментариями и игнорируются).

  1. Давайте теперь посмотрим на модель нашей обезьянки, так сказать, с её цифровой изнанки. Откройте модуль scena.d, добавьте в его начало импорт модуля журналирования my_utils.journal, а затем перейдите почти в самый конец функции сцена, туда где создаётся переменная 3D-объекта объект1 и присоединяется к своему Месту. После присоединения, но всё ещё внутри блока условия if добавьте такую строку:

в_журнал(имя_модуля, УровниЖурнала.Информация, 
            журнал_многострочное_форматирование(меш1.toString()));

В программе при записи в журнал первым аргументом задаётся имя текущего модуля, затем задаётся уровень одним из вариантов перечисления УровниЖурнала, последним аргументом идёт само сообщение, которое должно иметь тип string. Если предполагается, что сообщение состоит из нескольких строк, то можно перед его передачей в журнал обработать функцией журнал_многострочное_форматирование, чтобы вторая и последующие строки начинались ровно под первой (чисто из эстетических соображений).

Откройте сформированный журнал, и увидите огромный массив из 6747 чисел. Это координаты 2249 вершин Сюзанны. Каждая тройка чисел является координатами одной вершины. Кроме массива координат вершин меш содержит координаты нормалей, координаты текстур и массив индексов, но эти массивы метод toString() класса Меш не показывает (иначе вывод был бы в несколько раз объёмнее).

Особой пользы от вывода больших массивов координат обычно немного, а журнал от них становится менее читаемым, так что надо этот вывод отключить. Очевидным подходом было бы удалить только что добавленную в функцию сцена строку, но у нас же тут урок по ведению журнала, так что сделаем иначе. Поменяйте второй аргумент этой функции на УровниЖурнала.Отладка, и, несмотря на то, что сама функция никуда не делалась, пока для модуля сцена действует уровень журналирования Информация, сообщение уровня Отладка в журнал попадать не будет.