Поиск по блогу

четверг, 8 июля 2010 г.

Delphi: отладка, запись в лог

Давненько не писала про Delphi, а ведь именно в Delphi провожу большую часть дня :) Итак, сегодня расскажу о том, как я пишу логи.

Запись в лог я использую во всех более-менее серьезных проектах. Логирование помогает и на этапе отладки, и на этапе внедрения (иногда проще попросить прислать лог, чем со слов понять, в чем проблема). Давно уже использую для этих целей маленькую и удобную библиотечку uLog. Все, что от вас потребуется, это добавить ее в uses. Ну и по желанию некоторые настройки. Но даже уже без всяких настроек вы можете писать в лог с помощью процедуры sLog. Пример:
sLog ('MyProgram.log','Значение переменной ='+str);

Первый входной параметр — куда писать, второй — что писать. Если путь прописан не полностью — идет обращение к текущей директории проекта. Если файл не существует - он будет создан автоматически. В логе строчки появляются снабженные временем записи в лог. Пример части лога:
07.07.2010 16:34:11 [243] Starting...
07.07.2010 16:34:11 [243] Signature:A951D217D6B5E340 03040002 940000000500000001000000280A00000200000053657276696365205061636B2032000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
07.07.2010 16:34:11 [243] LoadConf...
(в квадратных скобках после времени - миллисекунды)

Немного о настройках. Первое, что можно настраивать, - это файл по умолчанию, в который будет писаться лог. Для этого обращаемся к uLog.LogFileName.
Пример:
uLog.LogFileName := ExtractFilePath(GetModuleName(HInstance))+'TaskManager.log';

После установки LogFileName лог будет писаться в указанный файл, если первый входной параметр у sLog не будет задан:
sLog('','Инициализация прошла успешно');

Еще один параметр - EnableMessages. Он отвечает за то, будут ли появляться сообщения об ошибке в этой библиотеке (например, когда файл лога не указан и не задан по умолчанию).
uLog.EnableMessages := false;

И, наконец, самый замечательный параметр. Он передается в sLog третьим. Это "уровень логирования", LogLevel.
slog('','Starting library',2);
slog('',tmpS,3);

Вы сами проставляете этот уровень в зависимости от того, что за информацию пишете в лог. Градация, например, может быть такой:
  1. мегаважный

  2. информационный (запустился, ...)

  3. отладочный

Уровней может быть больше, сколько угодно. При приведенном в предыдущем абзаце варианте вы на этапе разработки используете LogLevel := 3, а когда устанавливаете продукт клиенту, ставите LogLevel 1 или 2. LogLevel удобно задавать в ini-файле и считывать при запуске приложения. Если вдруг у клиента внезапно начнутся какие-то сбои - под вашим руководством он сможет поменять уровень логирования на 3 и прислать вам файл с отладочной информацией, которая поможет вам выяснить причину сбоев.

Листинг этой чудесной библиотечки:

unit uLog;

interface
procedure sLog (aLogFileName: String; aMessage : String;
aLevel : integer = 1 ;
aRewrite : boolean = false ;
aIncludeDateTime : boolean=true ) ;
var
LogLevel : integer = 3 ;
EnableMessages : boolean = true ;
LogFileName : String ;
// 1 - мегаважный
// 2 - информационный (запустился, ...)
// 3 - отладочный

implementation

uses SysUtils, Dialogs, DateUtils;

procedure sLog (aLogFileName: String; aMessage : String;
aLevel : integer = 1 ;
aRewrite : boolean = false ;
aIncludeDateTime : boolean=true ) ;
var
Y,M,D,H,Min,Sec,MSec : word ;
LF : Text ;
S : String ;
S1 : String ;
N : TDateTime ;
begin
{$I-}
if aLogFileName='' then aLogFileName:=LogFileName ;
if aLevel<= LogLevel then
begin
S:='' ;
AssignFile(LF,aLogFileName) ;
if FileExists(aLogFileName) then
begin
if aRewrite then Rewrite(LF) else append(LF) ;
if IOResult<>0 then
if EnableMessages then
ShowMessage('error appending message ' + aMessage) ;
end
else
begin
rewrite(LF) ;
if IOResult<>0 then
if EnableMessages then
ShowMessage('error rewriting message ' + aMessage) ;
end ;
if aIncludeDateTime then
begin
N:=Now ;
DecodeDateTime(N,Y,M,D,H,Min,Sec,MSec);
S1:=' [' + IntToStr(MSec)+ '] ' ;
while Length(S1) <7 do S1:=S1+' ' ;
S:=DateTimeToStr(N) + S1 ;
end;
S:=S + aMessage ;
writeln(LF,S) ;
if IOResult<>0 then
if EnableMessages then
ShowMessage('error writing message ' + aMessage) ;
closeFile(LF) ;
if IOResult<>0 then
if EnableMessages then ShowMessage('error closing message ' + aMessage) ;
end ;
{$I+}
end ;

end.

___

Добавлю ссылку на класс, который написал Егор. С помощью класса можно посмотреть дамп любой области памяти и актуальный стекфлоу. Спасибо ему большое!
___

Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

Статьи схожей тематики:



14 комментариев:

  1. Я считая, что если уже оформлять в качестве библиотеки — то надо по серьёзному всё:
    - лог класс в качестве синглтона + "защита" от многопоточности;
    - реализация сессий, конечно можно разделять лог по файлом, но было бы не менее удобнее создавать различные сессии в одном файле;
    - логирование прочих объектов через сериализация;
    - запаковка логов в архив и отправка POST запросом... иногда клиенты и лог файл найти не могут, не то, чтобы его куда то отправить =)

    p.s. это первое что пришло на ум... а так: работать, работать и ещё раз работать...

    ОтветитьУдалить
  2. + Нужен крешдамп
    + Стекфлоу

    для сопровождения маленьких проектов, может быть и пойдёт, но всё равно, я бы назвал эту библиотеку простой процедурой записи в файл. Без обид.

    ОтветитьУдалить
  3. Спасибо за комментарии :) Честно сказать, немного удивлена тем, что так серьезно отнеслись к этой статье. Наверное, надо было указать, что для начинающих. Эта простенькая либа (Егор, без обид, но все же буду называть ее так), в небольших проектах самое то - она очень легкая и (для меня) привычная. Думаю, у каждого программиста есть свои наработки, в больших распределенных системах вообще правильнее лог вести не только на клиентской стороне, но часть вообще писать в базу. Но тут, опять же, разговор об этом не идет.
    И да, для многопоточных приложений я, понятно, использую другой вариант.

    ОтветитьУдалить
  4. Маша, да всё нормально, ты - молодечик! Я давно тебя читаю, просто не комментил ) Ты всегда так чётко пишешь, а тут простите отписка получилась :) Ну а тут, уж сам Бог велел вставить 10коп. Ты уж на меня не обижайся, ну такой я, прямой. Вот )

    ОтветитьУдалить
  5. Да я не обижаюсь, там же написано "без обид" :)
    Вообще, блог рассчитан на определенную аудиторию, статьи не уровня "для профессионалов", об этом я не раз писала. И когда появляются комментарии типа "работать, работать и еще раз работать", хочется просто спросить комментатора, что ж он сам-то ничего полезного не напишет и не выложит для общественности? А обижаться - не, это не мое, на это у меня нет времени :))

    ОтветитьУдалить
  6. Здравствуйте!
    Про логирование как таковое согласен, но реализация библиотеки оставляет желать лучшего:
    1. Например "хитрое" преобразование времени в строку (с циклами, переаллокацией строк) меняется на 1 строчку FormatDateTime('yyyy-mm-dd hh:nn [ss]', Value)
    2. Writeln в текстовый файл на стандартном буфере - очень медленно. TFileStream с fmOpenWrite or fmShareDenyWrite будет в разы быстрее, или увеличить буфер.
    Это не критика, а рекомендации :)
    А.Трухин

    ОтветитьУдалить
  7. Большое спасибо за дельное замечание, обязательно учту!

    ОтветитьУдалить
  8. Есть эксперт для Делфи? GExpert называется. С ним поставляется замечательный модуль DbugIntf.pas, позволяющий в реальном времени отображать информацию в отладочном окошке - очень удобно для программиста во время написания программ. Плюс для всевозможных дампов (стек вызовов и др.) использую модули из JCL (JclDebug.pas, JclHookExcept.pas), которые помогают следить за ошибками на этапе beta-тестирования.

    ОтветитьУдалить
  9. Мария, я постарался написать некий класс, который думаю пригодится вам. В нём реализован и крешдамп и стекфлоу. Можете использовать его для своих целей. С уважением, Егор.

    ОтветитьУдалить
  10. Забыл ссылку походу, вот она : http://delphiday.blogspot.com/2010/07/tforgirlsdebug.html

    ОтветитьУдалить
  11. Егор, спасибо большое!)))) Посмеялась от души - первый раз в жизни в честь меня назвали класс ))))))) Сейчас отредактирую запись и поставлю активную ссылку.

    P.S. Прям настроение улучшилось в этот душный день с запахом Гарри.

    ОтветитьУдалить
  12. TMashaDebug теперь можеть получать значения регистров. И ещё TMashaDebug приняли в Torry.net :)
    http://www.torry.net/quicksearchd.php?String=TMashaDebug&Title=Yes

    ОтветитьУдалить
  13. Неужели под Windows нет ни какой штатной системы ведения логов, писать свой велосипед многопоточный плюс производительный (хотя бы кеширующий перед записью). Подозреваю что есть (точнее нашел), но нагулить как им грамотно пользоваться не смог. (в других операционках интерфейс логов един, а систем куда и как писать выше крыши, у javы своя система логов). Сам вышел из положения криво, система логов в каждом потоке своя.

    ОтветитьУдалить
  14. >Неужели под Windows нет ни какой штатной системы ведения логов

    EventLog

    API: Windows.pas
    Обвязка: C:\_dev.tools\RADStudio5\source\Win32\vcl\SvcMgr.pas

    ОтветитьУдалить

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

К сожалению, у меня нет столько свободного времени, чтобы отвечать на все частные вопросы, так что, может, свой вопрос лучше задать на каком-нибудь форуме?

Поделиться