Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Сокровищница
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  16:38[Войти] | [Зарегистрироваться]

Инструментарий для ведения логов в помощь разработчику программ

Денис Барановский
дата публикации 18-09-2011 14:02

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

Сначала сформулирую требования к этим средствам. Требования эти сформировались на основе личного опыта и отражают те моменты, которые показались мне важными при разработке различных программ, а также, не в последнюю очередь — компонентов для среды Delphi или Builder:

  1. Поскольку речь идет не о разработке драйверов или сервисов, мне показалось удобным оформить инструментарий в виде набора невизуальных компонентов. Лучше всего — бросаем компонент на форму, настраиваем его несложными действиями, и получаем возможность протоколирования. При этом не исключая создания тех же компонентов в рантайм в необходимом количестве.
  2. Как в любой уважающей себя программе потребуется вывод лога в файл. Или в несколько файлов.
  3. Очень важный момент — намучавшись с ловлей багов при разработке компонент (и по понятным причинам не имея возможности пользоваться отладчиком, когда разрабатываемые компоненты шлифуются в режиме дизайна), выносим требования: инструментарий должен позволять оперативно получать и просматривать отладочные сообщения прямо по ходу выполнения программы.
  4. Поскольку многопоточность приложения — дело обыденное, вывод отладочной информации должен быть потокобезопасным.
  5. Выводимая информация должна быть достаточно подробной, чтобы получатель мог разобраться в типах сообщений.
  6. Наконец, хорошо бы слегка упростить себе жизнь при просмотре свойств VCL-классов. Ведь так не хочется писать кучу отладочного кода для просмотра массива объектов!

Засим приступим.

Итак, решив, что в итоге мы хотим получить набор компонентов, разработаем два основных из них — компонент для ведения логов (назовем его TstDebugLog) и компонент, позволяющий мониторить отладочные сообщения (TstDebugHandler). Задача первого из них — осуществлять вывод отладочной информации в файл или, сообразно пункту 3 вышеизложенных пожеланий — куда-то вовне, каждому, кто хочет его услышать. Слышать же будет второй из перечисленных компонентов — слышать и сообщать о поступивших данных. При этом заранее примем, что будет позволен обмен сообщениями между процессами, что позволит разработать, например, отдельное приложение-монитор, отслеживающее отладочные данные из других программ.

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

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

Также нужно определить механизм, как ведущий лог компонент уведомляет потребителей о новых данных. Выберем обмен оконными сообщениями. Принцип уведомления будет следующий — компонент ведения лога при отправке сообщения ищет в системе окна с предопределенным именем (например, такое: 'stDebugHandlerWindow') и посылает им сообщения "готова строка". Каждое такое окно или немедленно обработает сообщение или проигнорирует его. Окна предполагается искать на самом верхнем уровне (то есть принадлежащие десктопу).

Также выше сказано, что вывод в лог должен быть потокозащищенным. Значит, все операции по записи в лог или изменении настроек, вызванные из разных потоков, не должны мешать друг другу или перекрываться. Для блокировки узких мест будем использовать стандартные критические секции Windows. Компонент-отправитель, по идее, может создаваться в неосновном потоке, но при этом не следует указывать ему владельца, во избежание нарушений работы VCL-структуры. Хотя этого, в общем-то, не нужно — достаточно обращаться к его методам из других нитей.

Наконец, компонент-отправитель будет иметь возможность выводить в лог (построчно) значения published-свойств произвольных наследников TPersistent, для чего предусмотрим соответствующий метод.

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

Теперь разберем структуру компонентов.

TstDebugLog

1. Методы блокировки компонента. Доступны извне, но вызываются автоматически самим компонентом в процедурах записи в лог и при изменении состояния компонента. Внутри используют критическую секцию

    procedure Lock;
    procedure Unlock;

2. Пара методов для структурирования лога — управление отступами. Слегка увеличивают читаемость лога

    procedure IndentInc;
    procedure IndentDec;

3. Методы записи на разные случаи жизни. По сути, все они формируют строку (или строки) с информацией и отправляют ее в лог. Помимо выходных значений принимают в качестве аргумента тип сообщения — константу TstDebugMessageType, которая является неотъемлемым атрибутом сообщения

    procedure WriteIndex(Name: String; Index: Integer; AType: TstDebugMessageType = mtDebug);
    procedure WriteInteger(Name: String; Value: Integer; AType: TstDebugMessageType = mtDebug);
    procedure WriteBoolean(Name: String; Value: Boolean; AType: TstDebugMessageType = mtDebug);
    procedure WriteString(Name: String; Value: String; AType: TstDebugMessageType = mtDebug);
    procedure WriteFloat(Name: String; Value: Extended; AType: TstDebugMessageType = mtDebug);
    procedure Write(Value: String; AType: TstDebugMessageType = mtDebug);
    procedure WriteFormatted(const Msg: string; Params: array of const; AType: TstDebugMessageType = mtDebug);

4. Пара методов для вывода информации об объекте и его свойствах

    procedure WritePropertiesOf(Msg: String; Instance: TPersistent; AType: TstDebugMessageType = mtDebug);
    procedure WriteObject(Msg: String; Instance: TObject; AType: TstDebugMessageType = mtDebug);

Также данный компонент имеет набор свойств:

1. Имя файла лога, куда будет выводиться лог в простом текстовом виде. Можно не указывать — тогда вывода в файл не будет

    property FileName: String;

2. Опции компонента. Позволяют задать, какие способы вывода в лог будут использованы — вывод в файл, вывод потребителям вовне

    property Options: TstDebugLogOptions;

3. Активность компонента, чтобы иметь возможность в любой момент его включить-выключить

    property Active: Boolean;

При создании TstDebugLog инициализирует внутри себя критическую секцию для осуществления блокировок.

Большинство методов просты, их содержимое можно посмотреть в прилагаемых исходниках. Но на некоторых методах остановимся.

Так, пара слов о методе WritePropertiesOf, предназначенном для вывода значений свойств объекта, в том числе вложенных. Он пользуется возможностями RTTI для перечисления имен, типов и значений свойств. Основа для него беззастенчиво содрана из класса TWriter, его методов WriteProperty и WriteProperties. Внутри себя он, используя инструментарий RTTI (описанный в стандартном модуле TypInfo), перебирает свойства предложенного объекта, получает их значения и формирует соответствующие строки. Если свойство приводится к типу TPersistent — метод рекурсивно перебирает свойства объекта, хранящегося в этом свойстве; если тип TComponent — просто выводит информацию о классе и адресе объекта, т.к. свойства этого типа традиционно считаются ссылками на внешние компоненты.

Набор процедур для получения значений свойств объектов собран в исходниках в модуле stInspectUnit.pas.

Самый главный метод класса TstDebugLog — DoWrite, осуществляющий непосредственно вывод строки в лог. Он состоит из двух частей — вывод строки в открытый файл лога и вывод строки сторонним потребителям. Для второго варианта он формирует данные, которые представляют собой служебную структуру с описанием сообщения с прицепленной к ней строкой:

TstDebugMessage = packed record
    dmProcessId: Integer; // отправляющий процесс
    dmThreadId: Integer; // отправляющий поток
    dmTime: TDateTime; // время отправки
    dmType: TstDebugMessageType; // тип сообщения
    dmString: array[0..0] of AnsiChar; // начало строки
end;

Код метода:

procedure TstDebugLog.DoWrite(Value: AnsiString; AType: TstDebugMessageType = mtDebug);
var
  I: Integer;
  P: Pointer;
  Win: THandle;
  TmpValue: String;
begin
  if (not Assigned(Self)) or (csDesigning in ComponentState) or (not FActive) then Exit;
  Lock;
  for I := 0 to FIndent-1 do Value := '    ' + Value;
  // запись в файл
  if Assigned(FFile) and (loWriteToFile in FOptions) then begin
    TmpValue := DateToStr(Date) + ' ' + TimeToStr(Time) + #09 + stTypeNames[AType] + #09 + Value + #13#10;
    FFile.Write(TmpValue[1], Length(TmpValue));
  end;
  // запись во внешнюю ловушку
  if loWriteToHandler in FOptions then begin
    P := stSharedBuffer.OpenBuffer;
    if Assigned(P) then begin
      // пишем строку
      with PstDebugMessage(P)^ do begin
        dmProcessId := GetCurrentProcessId;
        dmThreadId := GetCurrentThreadId;
        dmType := AType;
        dmTime := Date + Time; // на практике Now ведет себя странно
        if Length(Value) > stMaxMessageLen then begin
          SetLength(Value, stMaxMessageLen - 3);
          Value := Value + '...';
        end;
        CopyMemory(@dmString, Pointer(Value), Length(Value) + 1);
      end;
      // рассылаем сообщения
      Win := 0;
      repeat
        Win := FindWindowEx(0, Win, 0, stDebugHandlerWindow);
        if Win <> 0 then
          SendMessage(Win, WM_DEBUGMESSAGE, 0, Integer(GetCurrentProcessId));
      until Win = 0;
      // закрываем
      stSharedBuffer.CloseBuffer;
    end;
  end;
  Unlock;
end;

Данные помещаются в единый буфер, в начале которого находится структура, после которой расположена строка с сообщением. Чтобы не громоздить на каждый компонент TstDebugLog свой спроецированный в память файловый буфер, разработаем класс — хранилище буферизированных данных постоянной длины TstSharedBuffer, описанный в модуле stSharedBufferUnit.pas. Данный класс создается в единственном экземпляре, и реализует создание спроецированного в память файлового буфера указанной длины. Для доступа к буферу в классе предусмотрена пара методов:

    function OpenBuffer: Pointer;
    procedure CloseBuffer;

При этом буфер между вызовами этих методов блокируется критической секцией для предотвращения повторного обращения. То есть, пока буфер используется каким-либо компонентом TstDebugLog — он недоступен для других. Я сознательно пошел на такое ограничение в своих проектах. На самом деле, ничто не мешает в каждом компоненте-отправителе создавать собственный спроецированный в память буфер.

Следует сказать, что, т.к. спроецированный в память файл должен иметь имя — это имя составляется из стандартной строки 'stDebugLog' и идентификатора процесса, в котором работает компонент-отправитель.

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

TstDebugHandler

Специальных методов этот компонент не имеет, зато имеет свойство Active, позволяющее отключать получение сообщений, и обработчик OnReceiveString, уведомляющий о получении очередной отладочной строки.

Внутри компонент, как уже упоминалось выше, создает невидимое окно со стандартным классом TPUtilWindow и именем 'stDebugHandlerWindow', по которому TstDebugLog ищет окна для передачи им сообщений. В оконной процедуре этого окна осуществляется прием сообщений и выдача из компоненту TstDebugHandler.

Для создания окна используется процедура stAllocateHWnd — полный аналог стандартной AllocateHWnd, но позволяющей задать имя создаваемого окна (т.к. в стандартной это не предусмотрено). Удаление окна, как обычно — DeallocateHWND.

Итак, использование получившихся компонентов просто — помещаем на форму TstDebugLog, указываем ему, куда выводить лог (и имя файла, если нужно выводить в файл), и через его методы осуществляет вывод.

Положив на форму TstDebugHandler и назначив ему обработчик OnReceiveString, получим возможность ловить все отладочные сообщения от других процессов.

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

Все исходники классов, а также исходники и скомпилированный исполняемый файл приложения-монитора прилагаются.



Специально для Королевства Delphi


К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Тестирование проекта. Отладка.]

 Обсуждение материала [ 05-12-2011 15:32 ] 13 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования