Логирование (logging) — это ведение записей(как правило сохранение в файл) в хронологическом порядке.
Ведение логов зачастую очень удобная, облегчающая жизнь разработчику, штука, а для больших серьёзных проектов это вообще неотъемлемая вещь.
Логирование помогает как на этапе отладки, так и на этапе внедрения, например я прицеплял утилиту к проектам, которая отправляла файл лога куда мне удобно(сайт, мыло, сервер), таким образом избегал процесса вымогательства у пользователя лог-файла.
Я использую логирование почти во всех своих проектах от средне до велика.
В этой статье я рассмотрю свою реализацию модуля ведения логов, которым пользуюсь и время от времени грейжу его.
Данный модуль можно так же применять в многопоточных приложениях.
Былина
За свою практику я переписал не мало разновидностей процессов логирования и каждый раз меня чутка тревожило ощущение, что «что-то тут осталось не таким удобным как мне хотелось бы» и от этого постоянно что-то дописывал/переписывал.
Так я пришёл от модуля, содержащего глобальные переменные — параметры лога и глобальную функцию типа WriteToLog(aMsg: string): boolean, к модулю с классом — менеджером логирования и методами записи на WinApi. Время дало мне возможность, так сказать, выбрать самый удобный вариант.
Во время улучшения менеджера логирования я встречал одну интересную реализацию в плане процесса записи:
- единое открытие файла на всё время работы приложения;
- запись по таймеру накопившейся очереди сообщений.
При этом для работы с файлом использовался какой-нить стрим, зачастую TFileStream. Но при таких раскладах у нас есть довольно большой риск «просеять» в логе сообщение именно про ту ошибку, которая и привела к краху проги из-за которой не дописался лог…
Парадоксальная неприятность)
Последнее важное изменение я внёс когда обнаружил, что мой менеджер пишет строку поверх другой строки при могопоточном использовании, хоть и допускал возможность такого использования.
Реализация
Я реализовал следующие элементы удобства:
- единый класс логирования для всего приложения посредством шаблона Singleton;
- типы сообщений (информация, предупреждение, ошибка);
- уровень приоритета сообщений (максимальный, информативный, отладочный);
- событийные типы как для объектных методов так и для обычных процедур;
- настройка менеджера через свойства;
- формирование заголовка из текущей даты и типа сообщения.
Код всего модуля uLogManager отображён в следующем листинге (Delphi 2007):
unit uLogManager; { Автор: Monax (x-monax[at]mail[dot]ru) } interface uses Windows, SysUtils, SyncObjs, uSingletonTemplate; type TLogMsgType = (lmtInfo, lmtWarning, lmtError); TLogMsgLevel = (lmlMax, lmlInfo, lmlDebug); TLogManagerEvent = procedure(const aLogMsgText: string; aLogMsgType: TLogMsgType); TLogManagerEventObj = procedure(const aLogMsgText: string; aLogMsgType: TLogMsgType) of object; TLogManager = class(TSingleton) private fcs : TCriticalSection; fFileName : string; // файл лога fMaxLogLevel : TLogMsgLevel; // уровень, который опредеяет записывать сообщение или нет fLogEvent : boolean; // вызывать свойства? fOnLogEvent : TLogManagerEvent; fOnLogEventObj : TLogManagerEventObj; function FormatLogTime(aDT: TDateTime): string; function FormatLogMsgType(aLogMsgType: TLogMsgType): string; function FormatLogMsgText(aText: string; aLogMsgType: TLogMsgType): string; procedure DoLogEvent(aLogMsgText: string; aLogMsgType: TLogMsgType; aWriteToEvent: boolean); protected constructor Create; override; public procedure WriteToLog(aText: string; aLogMsgType: TLogMsgType = lmtInfo; aLvl: TLogMsgLevel = Low(TLogMsgLevel); aWriteToEvent: boolean = True); public destructor Destroy; override; property FileName: string read fFileName write fFileName; // куда писать property MaxLogLevel: TLogMsgLevel read fMaxLogLevel write fMaxLogLevel; // максимальный доступный уровень property LogEvent: boolean read fLogEvent write fLogEvent; // вызывать события лога? property OnLogEvent: TLogManagerEvent read fOnLogEvent write fOnLogEvent; // событие лога property OnLogEventObj: TLogManagerEventObj read fOnLogEventObj write fOnLogEventObj; // событие лога для объектов end; // funcs function LogMng: TLogManager; implementation function LogMng: TLogManager; begin Result := TLogManager.GetInstance; end; { TLogManager } constructor TLogManager.Create; begin inherited; fcs := TCriticalSection.Create; fFileName := ''; fMaxLogLevel := High(TLogMsgLevel); fLogEvent := False; end; destructor TLogManager.Destroy; begin fcs.Enter; try finally fcs.Leave; end; fcs.Free; inherited; end; function TLogManager.FormatLogTime(aDT: TDateTime): string; begin DateTimeToString(Result, '[dd.mm.yyyy] [hh:mm:ss]', aDT); end; function TLogManager.FormatLogMsgType(aLogMsgType: TLogMsgType): string; begin case aLogMsgType of lmtInfo: Result := '[Info]'; lmtWarning: Result := '[Warning]'; lmtError: Result := '[Error]'; end; end; function TLogManager.FormatLogMsgText(aText: string; aLogMsgType: TLogMsgType): string; begin Result := format('%s %s: %s', [FormatLogTime(Now), FormatLogMsgType(aLogMsgType), aText]); end; procedure TLogManager.DoLogEvent(aLogMsgText: string; aLogMsgType: TLogMsgType; aWriteToEvent: boolean); begin if not fLogEvent or not aWriteToEvent then Exit; if Assigned(fOnLogEvent) then fOnLogEvent(aLogMsgText, aLogMsgType); if Assigned(fOnLogEventObj) then fOnLogEventObj(aLogMsgText, aLogMsgType); end; procedure TLogManager.WriteToLog(aText: string; aLogMsgType: TLogMsgType; aLvl: TLogMsgLevel; aWriteToEvent: boolean); var fh : THandle; lmsg : string; card : cardinal; begin if fFileName = '' then begin lmsg := FormatLogMsgText(aText, aLogMsgType); lmsg := Format('%s: %s', [FormatLogMsgType(lmtError), 'Не определён файл логирования! LogMsg: ' + lmsg]); DoLogEvent(lmsg, lmtError, True); Exit; end; if aLvl > fMaxLogLevel then Exit; fh := CreateFile(PChar(fFileName), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if fh = INVALID_HANDLE_VALUE then begin lmsg := FormatLogMsgText(aText, aLogMsgType); lmsg := Format('%s: %s', [FormatLogMsgType(lmtError), 'Ошибка лог-файла: INVALID_HANDLE_VALUE. LogMsg: ' + lmsg]); DoLogEvent(lmsg, lmtError, True); Exit; end; fcs.Enter; try // формируем сообщение lmsg := FormatLogMsgText(aText, aLogMsgType); DoLogEvent(lmsg, aLogMsgType, aWriteToEvent); lmsg := lmsg + #13#10; try SetFilePointer(fh, 0, nil, FILE_END); WriteFile(fh, lmsg[1], sizeof(lmsg[1]) * length(lmsg), card, nil); except on E: Exception do begin lmsg := Format('%s: %s', [FormatLogMsgType(lmtError), 'Ошибка записи в файл лога: ' + E.ClassName + ': ' + E.Message]); DoLogEvent(lmsg, lmtError, True); end; Else begin lmsg := Format('%s: %s', [FormatLogMsgType(lmtError), 'Неизвестная ошибка в TLogManager.WriteToLog!']); DoLogEvent(lmsg, lmtError, True); end; end; finally CloseHandle(fh); fcs.Leave; end; end; end.
Как видно уровней важности сообщений и типов сообщений может быть сколько угодно. Потом можно взять какой-нить хороший текстовый редактор(например я юзаю PSPad) и настроить подсветку строк относительно типов сообщений или их уровней. Красота да и только)
Пример использования
// организация лог-менеджера procedure TFormMain.FormActivate(Sender: TObject); begin // ... some code ... {$IFDEF Release} LogMng.MaxLogLevel := lmlInfo; {$ENDIF} LogMng.FileName := ExtractFilePath(Application.ExeName) + 'general.log'; LogMng.OnLogEventObj := LogMngInfoEvent; LogMng.LogEvent := True; LogMng.WriteToLog('Программа запустилась!'); end; // отображение логирования в реальном времени procedure TFormMain.LogMngInfoEvent(const aLogMsg: string; aLogMsgType: TLogMsgType); begin // выводим текст разного цвета в зависимости от типа лог-сообщения case aLogMsgType of lmtInfo: AddLogStr(aLogMsg, clWhite, False); lmtWarning: AddLogStr(aLogMsg, clYellow, False); lmtError: AddLogStr(aLogMsg, clRed, False); end; end; procedure TFormMain.AddLogStr(aLogMsg: string; aTextColor: TColor; aAddTime: boolean); var lm : string; begin if aAddTime then DateTimeToString(lm, '[dd.mm.yyyy] [hh:mm:ss]: ', Now) else lm := ''; reLog.SelAttributes.Color := aTextColor; // вывод в TRichEdit reLog.Lines.Add(lm + aLogMsg); end;
Заключение
Реализация проходила в Delphi 2007.
Напомню, что шаблон Singleton я рассматривал вот в этой статье, где так же приведён исходный код модулей его реализации.