Рейтинг@Mail.ru

Журналирование или логирование в Delphi XE3

Автор: Alex. Опубликовано в Программирование . просмотров: 49642

Рейтинг:  5 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда активна
 

Передо мной возник вопрос, как логировать то, что происходит в программе в Delphi XE3. Причём мне нужно отписывать происходящие события не только в файл, но и в базу данных. Кроме того я хочу отписывать сообщения из двух приложений в один файл параллельно, т.е. из разных процессов. Поскольку за Delphi я не садился с далёкого 2000-го года, мне пришлось закатать рукава и заняться поисками, что появилось с тех давних времён.

Журналирование с помощью CodeSite

Изобретать велосипед и писать, что-то с нуля можно, но не очень хочется. Поэтому первое, что я посмотрел, это, а что есть в Delphi XE3 из коробки. Оказалось, что в комплект входит инструмент под названием CodeSite Express, т.е. урезанная версия CodeSite. Покупать я ничего не собирался и начал разбираться с экспресс-версией.

Справка для CodeSite не встраивается в справку Delphi XE3, но её можно открыть из меню Пуск. Написана она сразу для вариантов Delphi и .Net.

Для начала нужно – это подключить юнит CodeSiteLogging, и можно пользоваться методами глобального объекта CodeSite. Например, записать сообщение в лог:

CodeSite.Send('Hello, World');//Запись сообщений в лог.

При выполнении вышеприведённого кода автоматически всплывает окошко программы CodeSite Live Viewer с нашим сообщением.

Автоматически всплывающее окно логгера

Кроме того есть ещё много перегруженных вариантов функции Send. Например, можно записать сразу весь объект в журнал:

CodeSite.Send('TForm1', Self);//Запись объекта в лог.

После этого вы сможете просмотреть все свойства объекта в момент записи в окне CodeSite Live Viewer на панели Inspector Pane, чтобы её открыть, нажмите на F11.

Окно логгера в режиме просмотра свойств объекта

Режим работы CodeSite в которой вы сразу видите результат, это так называемый Live Logging. Теперь разберёмся с тем, как записывать журнал в файл, чтобы позже можно было анализировать его. Это делается так:

CodeSite.Destination := TCodeSiteDestination.Create(CodeSite);
CodeSite.Destination.LogFile.FilePath:=ExtractFilePath(Application.ExeName);
CodeSite.Destination.LogFile.FileName:='Log.csl';
CodeSite.Destination.LogFile.Active:=true;
CodeSite.Send('TForm1', Self);//Запись формы в лог.

После выполнения этого кода окно CodeSite Live Viewer появляться не будет, зато в папке с исполняемым файлом создастся файл Log.csl, в котором будут содержаться все наши сообщения. Чтобы посмотреть этот файл просто откройте его в программе CodeSite Live Viewer.

Посмотрим, куда ещё можно записывать сообщения? Открываем справку по объекту TCodeSiteDestination и видим, что все другие варианты кроме Live Logging и File Logging – доступны только в полной версии инструмента. Кроме того, упоминания о записи сообщений в базу данных нет вообще (ни в платной ни в бесплатной версиях). Также есть ещё один минус в этом инструменте - вы не можете посмотреть файл журнала прямо на компьютере пользователя, если у вас нет программы для просмотра журнала CodeSite Live Viewer. Что ж буду искать дальше.

Журналирование с помощью log4delphi или log4d

Закончив с CodeSite я посмотрел в сторону компонентов от сторонних разработчиков. Я посмотрел несколько компонентов для логирования на сайте Torry’s Delphi, но мне ничего не приглянулось. И тогда я решил поискать наудачу. Программируя на Java и C# я пользовался для логирования библиотеками jog4j и log4net, соответственно. Оба решения активно разрабатываются и поддерживаются сообществом разработчиков под эгидой Apache Software Foundation. Кроме того у них есть решение для PHP - log4php и для C++ - log4cxx. Почему же они забыли про Delphi?

Здесь нужно пару слов сказать про систему логирования от Apache. Библиотеки log4… обладают широчайшими возможностями, они бесплатны, документированы и имеют открытые исходные коды. Журналы можно записывать в файл, в базу данных, в консоль (если у вас консольное приложение), отправлять сообщения командой netsend (для Windows) или на SMTP-сервер. Кроме того можно использовать фильтры и шаблоны сообщений.

Немного поискав в Интернете выяснилось, что есть две параллельные разработки log4delphi и log4d. Когда я начал скачивать исходники и бинарники библиотеки log4delphi (http://sourceforge.net/projects/log4delphi/files/log4delphi/), выяснилось, что последнее обновление библиотеки было 16.11.2010г. Для библиотеки log4d ещё более худшая ситуация: последние изменения файлов в архиве датируются от 11.06.2007, хотя последнее обновление архива стоит 29.04.2013. Ну, если они старые, это не значит, что плохие. Будем проверять.

Журналирование с помощью log4delphi

Первым делом посмотрим библиотеку log4delphi, т.к. она новее. Здесь есть файлы проектов для Delphi 6 и Delphi 7. Я же использую Delphi XE3, поэтому сначала я попытался открыть и откомпилировать проекты log4delphi_D7_STD.dpk и log4delphi_D7_PROF.dpk. При открытии этих файлов Delphi XE3 поломала их, поэтому проекты не скомпилировались из-за ошибки. Тогда я сделал новую bpl-библиотеку (меню New Project -> Package), добавил в проект все pas файлы (в том числе и в папке util) и после этого всё прекрасно скомпилировалось.

Теперь давайте разберёмся, что может и чего не может эта библиотека. Сразу начну с ложки дёгтя: библиотека log4delphi не поддерживает загрузку конфигурации из xml файла. Конфигурацию можно загружать из ini-файла или настраивать программно. Запись в базу данных происходит сразу, без буфферизации, плюс в SQL-запросе должны быть определённые параметры, а это неудобно. Кроме того не поддерживается запись сообщений в файл журнала из разных процессов (только монопольное владение файлом) и мало регулярных выражений для шаблонов. Всё это выяснилось при анализе исходного кода.

Но может быть кому-нибудь эта библиотека понравится. Здесь поддерживается запись сообщений в базу данных (посредством BDE, SqlExpr, InterBase Express) и в файл. Есть возможности формирования лога в виде обычного текстового файла, HTML-документа, XML-файла.

Журналирование с помощью log4d

Теперь посмотрим библиотеку log4d. Собственно это даже и не библиотека, это просто несколько файлов, которые вы можете выборочно добавить в свой проект, что довольно удобно, или добавить их в bpl-библиотеку. Я создал bpl-библиотеку и добавил в неё все файлы с расширением .pas и .inc. После этого я откомпилировал её и получил 54 ошибки. Это и не удивительно. Ведь эта библиотека рассчитана на определённые версии Delphi, в число которых моя Delphi не входит: Delphi 4, 5, 6, 7, 2005, 2006. Пришлось поправить файл Defines.inc. Я прописал туда две версии XE2 и XE3 (аналогично можно добавить вашу версию Delphi):

{$IFDEF VER230} { Delphi XE2 / C++Builder XE2 (Win32/Win64) }
{$DEFINE DELPHI4_UP}
{$DEFINE DELPHI5_UP}
{$DEFINE DELPHI6_UP}
{$DEFINE DELPHI7_UP}
{$DEFINE DELPHI10_UP}
{$ENDIF}
 
{$IFDEF VER240} { Delphi XE3 / C++Builder XE3 (Win32/Win64) }
{$DEFINE DELPHI4_UP}
{$DEFINE DELPHI5_UP}
{$DEFINE DELPHI6_UP}
{$DEFINE DELPHI7_UP}
{$DEFINE DELPHI10_UP}
{$ENDIF}

Кроме того пришлось дополнить функцию TLogCustomLayout.Init:

{ Initialisation - date format is a standard option. }
procedure TLogCustomLayout.Init;
begin
   inherited Init;
{$IFDEF DELPHI10_UP}
   SetOption(DateFormatOpt, 'yyyy-mm-dd"T"hh":"nn":"ss"."zzz');
{$ELSE}
   SetOption(DateFormatOpt, ShortDateFormat);
{$ENDIF}
end;

И пришлось выкинуть из проекта файл Log4DNM.pas, т.к. компонентов NM… (а именно компонента TNMSMTP) теперь нет в Delphi. Это не страшно, т.к. есть юнит Log4DIndy.pas, который выполняет те же функции (отправляет сообщения на SMTP-сервер), только с помощью компонента TIdSMTP, который сразу поставляется вместе с Delphi XE3.

Также в юните Log4DXML.pas пришлось по-другому подключить библиотеку MSXML:

{$IFDEF DELPHI10_UP}
   MSXML, Winapi.MSXMLIntf;
{$ELSE}
   MSXML2_tlb;
{$ENDIF}

В этом же юните пришлось убрать два варианта функции _Set_documentLocator. После этого проект скомпилировался.

Теперь о возможностях этой библиотеки. Здесь нет возможности записи сообщений в базу данных и также не возможна запись сообщений в файл из разных процессов, зато есть вывод сообщений в дебаггер и есть отправка сообщений SMTP-серверу. Поддерживается загрузка конфигурации из XML-документа и из ini-файла. Есть возможности формирования обычного текстового файла и HTML-документа.

Не смотря на то, что в этой библиотеки нет записи в базу данных, эта библиотека мне понравилась больше. По своей внутренней структуре она ближе к log4j и log4net. Поэтому я решил остановиться на этой библиотеке и дописать те куски, которых мне не хватает.

В результате, после моих правок, библиотека log4d умеет записывать сообщений в базу данных посредством ADO-компонентов (юнит Log4DDB.pas) с возможностью буферизации (если кому то не нравится ADO, можете по аналогии дописать свой обработчик сообщений, используя желаемые компоненты), позволяет записывать сообщения в файл из разных процессов, причём допускаются разные привилегии. Также я добавил возможность подставлять в сообщения название приложения, имя текущего пользователя и имя компьютера, с помощью шаблона (класс TLogPatternLayout).

Версию библиотеки log4d с моими правками вы можете скачать здесь: 

Файлы:
log4d Версия:от 13.03.2022

Дополненная версия библиотеки log4d. Оригинал библиотеки можно найти здесь: http://log4d.sourceforge.net/.

В версии от 25.01.2014 добавлена возможность записи сообщений в базу данных с помощью ADO-компонентов и добавлена возможность записи сообщений в один файл журнала из разных процессов.

В версии от 20.02.2014 добавлена модель блокировки файла журнала MinimalLock (минимальная блокировка, файл блокируется при каждой записи) и исправлена ошибка в функции RollOver.

В версии от 26.12.2014 сделана поддержка Delphi XE2.

В версии от 28.05.2015 добавлена поддержка Delphi XE4 - XE7. На версиях XE4 - XE6 работа кода не проверялась.

В версии от 26.08.2015 добавлена поддержка Delphi XE8.

В версии от 26.10.2015 добавлена поддержка Delphi 10 Seattle.

В версии от 03.03.2016 добавлены таймауты соединения и команды в юнит Log4DDB.pas. Там же добавлена обработка ошибок при работе с БД если не используются транзакции.

В версии от 26.08.2016 сделано сохранение трассировки стека в объект TLogEvent и убрано использование юнита System.IOUtils.

В версии от 29.08.2016 сделана поддержка Delphi 2007. Исправлена ошибка при считывании MinimalLock из файла конфигурации.

В версии от 05.07.2017 добавлена поддержка Delphi 10.1 Berlin/Delphi 10.2 Tokyo и добавлено свойство ConnectivityTestQuery в TLogADOAppender для проверки соединения.

В версии от 30.06.2019 исправлена работа в режиме MinimalLock, добавлена поддержка Delphi 10.3 Rio.

В версии от 13.03.2022 исправлена ошибка AV, возникающая в деструкторе при наследовании от TLogFileAppender, и добавлен шаблон %P для записи в лог идентификатора процесса.

Дата 25.01.2014 Размер файла 42.82 KB Закачек 2712

Теперь пару слов о том, как работать с библиотекой. Для начала библиотеку нужно сконфигурировать. Например, загрузить конфигурацию их xml-файла:

//Пример загрузки конфигурации log4d из файла конфигурации.
TLogXMLConfigurator.Configure(l4dConfFileName);

А вот и пример файла конфигурации (здесь указано, что сообщения будут переданы в хранимую процедуру SaveLogMessage и будут сохранены в текстовый файл App.log):

<?xml version="1.0" encoding="UTF-8" ?>
<log4d>
   <appender name="dblog" class="TLogADOAppender">
      <param name="bufferSize" value="100" /> <!-- Буферизация 100 сообщений -->
      <param name="connectionString" value="Provider=SQLOLEDB.1;Password=123;Persist Security Info=True;User ID=sa;Initial Catalog=tst_db;Data Source=TEST;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Use Encryption for Data=False;Tag with column collation when possible=False" />
      <param name="commandText" value="SaveLogMessage" /> <!-- Имя процедуры или SQL-запрос -->
      <param name="commandType" value="3" /> <!-- cmdStoredProc -->
      <!-- @Date -->
      <param name="parameterName" value="@Date" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="0" /> 
      <param name="parameterPattern" value="%d" />
      <!-- @Level -->
      <param name="parameterName" value="@Level" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="10" />
      <param name="parameterPattern" value="%p" />
      <!-- @AppDomain -->
      <param name="parameterName" value="@AppDomain" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="255" />
      <param name="parameterPattern" value="%a" />
      <!-- @Thread -->
      <param name="parameterName" value="@Thread" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="255" />
      <param name="parameterPattern" value="%t" />
      <!-- @Logger -->
      <param name="parameterName" value="@Logger" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="255" />
      <param name="parameterPattern" value="%c" />
      <!-- @Message -->
      <param name="parameterName" value="@Message" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="4000" />
      <param name="parameterPattern" value="%m" />
      <!-- @Exception -->
      <param name="parameterName" value="@Exception" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="2000" />
      <param name="parameterPattern" value="%e" />
      <!-- @HostName -->
      <param name="parameterName" value="@HostName" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="255" />
      <param name="parameterPattern" value="%h" />
      <!-- @UserName -->
      <param name="parameterName" value="@UserName" />
      <param name="parameterType" value="1" /> <!-- ftString -->
      <param name="parameterDirection" value="1" /> <!-- pdInput -->
      <param name="parameterSize" value="255" />
      <param name="parameterPattern" value="%w" />
   </appender>
   <appender name="logfile" class="TLogRollingFileAppender">
      <param name="maxFileSize" value="10KB" /> <!-- Максимальный размер файла -->
      <param name="maxBackupIndex" value="3" /> <!-- Максимальное количество файлов журнала -->
      <param name="append" value="true" /> <!-- Сообщения всегда добавляются к существующим в конец файла -->
      <param name="lockingModel" value="InterProcessLock" /> <!-- Сообщения будут записываться из нескольких процессов параллельно -->
      <param name="fileName" value="C:\Logs\App.log" />
      <layout class="TLogPatternLayout">
         <param name="pattern" value="%d %p [%c] (%h:%w:%a:%t) - %m%n" /> <!-- Шаблон сообщения -->
      </layout>
   </appender>
   <root>
      <level value="DEBUG" />
      <appender-ref ref="logfile" />
      <appender-ref ref="dblog" />
   </root>
</log4d>

Можно сконфигурировать библиотеку log4d программно:

procedure Configure(logFileName, connectionString: string);
var
   parameters: TObjectList<TLogADOParameter>;
begin
   TLogBasicConfigurator.Configure(TLogRollingFileAppender.Create(
      'filelog',
      logFileName,
      TLogPatternLayout.Create('%d %p [%c] (%a:%t) - %m%n'),
      true,
      'InterProcessLock',
      $10000000,
      3));
   DefaultHierarchy.Root.Level := TLogLevel.GetLevel('DEBUG');
   parameters := TObjectList<TLogADOParameter>.Create;
   parameters.Add(TLogADOParameter.Create('@Date', ftString, pdInput, 0, '%d'));
   parameters.Add(TLogADOParameter.Create('@Level', ftString, pdInput, 10, '%p'));
   parameters.Add(TLogADOParameter.Create('@AppDomain', ftString, pdInput, 255, '%a'));
   parameters.Add(TLogADOParameter.Create('@Thread', ftString, pdInput, 255, '%t'));
   parameters.Add(TLogADOParameter.Create('@Logger', ftString, pdInput, 255, '%c'));
   parameters.Add(TLogADOParameter.Create('@Message', ftString, pdInput, 4000, '%m'));
   parameters.Add(TLogADOParameter.Create('@Exception', ftString, pdInput, 2000, '%e'));
   parameters.Add(TLogADOParameter.Create('@HostName', ftString, pdInput, 255, '%h'));
   parameters.Add(TLogADOParameter.Create('@UserName', ftString, pdInput, 255, '%w'));
   TLogBasicConfigurator.Configure(TLogADOAppender.Create(
      'dblog',
      '',
      '',
      connectionString,
      'SaveLogMessage',
      cmdStoredProc,
      parameters,
      false,
      100));
end;

После того как библиотека сконфигурирована, можно писать сообщения:

procedure TForm1.TestLog4D;
begin
   logger := TLogLogger.GetLogger(Self.ClassType); //Запрашиваем логгер (здесь логгеру будет дано имя такое же как имя класса)
   logger.Debug('Тестовое сообщение');//Отладочное сообщение
   logger.Error('Ошибка 1');//Сообщение об ошибке
   logger.Info('Информационное сообщение');//Информационное сообщение
   logger.Fatal('Фатальная ошибка, продолжение невозможно');//Сообщение о фатальной ошибке
   try
      raise Exception.Create('Ошибка');
   except
      on e: Exception do
         logger.Error('Ошибка 2', e);
   end;
end;

Файл лога получится в данном примере примерно такой:

2014-01-25T20:43:08.130 debug [TForm1] (MyTestApp.exe:13140) - Тестовое сообщение
2014-01-25T20:43:08.130 error [TForm1] (MyTestApp.exe:13140) - Ошибка 1
2014-01-25T20:43:08.130 info [TForm1] (MyTestApp.exe:13140) - Информационное сообщение
2014-01-25T20:43:08.130 fatal [TForm1] (MyTestApp.exe:13140) - Фатальная ошибка, продолжение невозможно
2014-01-25T20:43:08.130 error [TForm1] (MyTestApp.exe:13140) - Ошибка 2

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

Tags: Обзоры инструментов для программирования Учебники по программированию Журналирование Логирование log4j log4net log4delphi log4d CodeSite Delphi Android

Комментарии   

Alex
0 #51 Alex 11.06.2020 10:29
Цитирую Andrey_:
Добрый день. Подскажите пожалуйста. Как организовать запись, например, ошибок в один файл, прочих в другой?

Можете сделать свой Appender, например, унаследовавшись от TLogRollingFileAppender.
Цитировать
igor47
0 #52 igor47 19.07.2020 18:42
Можно пример, как програмно настроить вывод лога кроме текстового файла еще в Memo. Нужно писать свой TMemoAppender или есть готовое решение?
Цитировать
Alex
0 #53 Alex 23.07.2020 08:27
Цитирую igor47:
Можно пример, как програмно настроить вывод лога кроме текстового файла еще в Memo. Нужно писать свой TMemoAppender или есть готовое решение?

Вполне возможно, что где-то есть готовое решение. Я не в курсе. А так, да, нужно писать свой аппендер.
Цитировать
Ruslan18646
0 #54 Ruslan18646 17.03.2021 10:20
Коллеги,
а есть ли какой-либо готовый логгер с визуальным средством просмотра такого лога? Ну чтобы обновлялся в реальном времени, были фильтры, поиск, цветами разными типы ошибок выделяла...
Цитировать
Alex
0 #55 Alex 13.03.2022 12:31
Добавил версию от 13.03.2022. В версии исправлена ошибка AV, возникающая в деструкторе при наследовании от TLogFileAppender, и добавлен шаблон %P для записи в лог идентификатора процесса.
Цитировать

Добавить комментарий