Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Искусство управления ошибками

Оформил: DeeCo

Автор: Даутов Ильдар

Часть II

Продолжая тему "Управление ошибками в Delphi", поставим следующие задачи :
  • программа-монитор ошибок должна работать как системный сервис Windows NT
  • журнал ошибок должен сохраняться на диске и постоянно пополняться
  • список текущих ошибок и полный журнал ошибок должны быть доступны для просмотра на любом компьютере локальной сети предприятия
Реализуем следующую схему взаимодействия программ при возникновении ошибки :
  • ошибка, возникшая в клиентской программе, передается по сети монитору-сервису Windows NT. Для передачи используем механизм каналов Mailslot
  • монитор сохраняет текст ошибки на диске. Для хранения используем текстовый файл
  • монитор пересылает по сети текст ошибки программе просмотра ошибок. Для передачи используем механизм каналов Mailslot
  • программа просмотра принимает текст ошибки и отображает его на экране
  • программа просмотра может запросить полный журнал ошибок. Для получения полного журнала используем механизм разделяемых сетевых файловых ресурсов
В статье представлены 2 проекта : монитор ошибок и окно просмотра ошибок. Клиентская программа, имитирующая ошибку, была представлена в предыдущей статье, и здесь не рассматривается.

Монитор ошибок

Оформить программу как сервис Windows NT (Win32 service) не составляет большого труда :
  • создаем новое приложение File | New... | New | Service Application. Создается приложение с глобальной переменной Application типа TServiceApplication и объектом типа TService, который и реализует всю функциональность сервиса
  • устанавливаем требуемые свойства объекта TService
    • имя сервиса
    • параметры запуска сервиса
    • имя и пароль пользователя, от имени которого стартует сервис
  • переписываем событие OnExecute объекта TService, в котором реализуем требуемую функциональность сервиса
  • компилируем проект
  • регистрируем созданный сервис на сервере Windows NT и запускаем
Регистрация сервиса выполняется из командной строки следующим образом :
ErrorMonitorService.exe /install
Удаление сервиса :
ErrorMonitorService.exe /uninstall
Запуск сервиса выполняется из командной строки следующим образом :
net start ErrorMonitor
Останов сервиса :
net stop ErrorMonitor

Оформив эту последовательность команд как BAT-файл, можно значительно облегчить себе жизнь при отладке сервиса.

Достаточно подробную информацию о сервисах Windows NT можно найти в книге : А.В.Фролов, Г.В.Фролов 'Программирование для Windows NT (часть вторая)', Москва, ДИАЛОГ-МИФИ, 1997

Для сохранения протокола (журнала) пользовательских ошибок используем следующую схему :
  • журнал ведется в текстовом файле в определенном каталоге Windows NT
  • журнал имеет имя yyyy-mm-dd.log, соответствующее календарной дате запуска сервера
  • при каждом запуске монитор проверяет наличие файла, имя которого соответствует текущей дате. При отсутствии - файл создается, иначе происходит дозапись в конец файла
  • сохраняются только последние 7 файлов журнала
Текст программы монитора ошибок приведен ниже :
unit uErrorMonitorService;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, ScktComp;

type
  TErrorMonitor = class(TService)
    procedure Service1Execute(Sender: TService);
    procedure ServiceEMCreate(Sender: TObject);
  private
  public
    function GetServiceController: PServiceController; override;
    procedure SendError;
    function InitLog: boolean;
  end;

var
  ErrorMonitor: TErrorMonitor;

implementation
uses Dialogs;

{$R *.DFM}

const
  LogDir = 'C:\Log\'; // каталог, где сохраняются журналы
var
  LogFile: TextFile; // файл текущего журнала
  LogName: string; // имя файла текущего журнала
  h: THandle; // handle канала Mailslot
  str: string[250]; // буфер для передачи информации
  MsgNumber, MsgNext, Read: DWORD;

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  ErrorMonitor.Controller(CtrlCode);
end;

function TErrorMonitor.GetServiceController: PServiceController;
begin
  Result := @ServiceController;
end;

// Передача текста ошибки от сервиса программе просмотра

procedure TErrorMonitor.SendError;
var
  h: THandle;
  i: integer;
begin
  // открытие MailSlot-канала, по которому будет передаваться протокол
  // используется широковещательная передача в домене
  h := CreateFile(PChar('\\*\mailslot\EMonMess'), GENERIC_WRITE,
    FILE_SHARE_READ, nil,
    OPEN_EXISTING, 0, 0);
  if h <> INVALID_HANDLE_VALUE then
  begin
    // запись в канал и закрытие канала
    WriteFile(h, str, Length(str) + 1, DWORD(i), nil);
    CloseHandle(h);
  end;
end;

// инициализация файла журнала
// журналы ведутся в отдельных файлах по каждой дате

function TErrorMonitor.InitLog: boolean;
var
  sr: TSearchRec;
  i: integer;
begin
  Result := True;
  // удаление старых файлов журнала
  //(сохраняются только последние 7 журналов)
  with TStringList.Create do
  begin
    Sorted := True;
    i := FindFirst(LogDir + '*.log', faAnyFile, sr);
    while i = 0 do
    begin
      Add(sr.Name);
      i := FindNext(sr);
    end;
    FindClose(sr);
    if Count > 7 then
      for i := 0 to Count - 8 do
        DeleteFile(LogDir + Strings[i]);
    Free;
  end;
  // текущий файл журнала
  LogName := LogDir + FormatDateTime('yyyy-mm-dd', Date) + '.log';
  AssignFile(LogFile, LogName);
  try
    if FileExists(LogName) then
      Append(LogFile)
    else
      Rewrite(LogFile);
  except
    str := 'Ошибка создания файла журнала : ' + LogName;
    Status := csStopped;
    LogMessage(str);
    ShowMessage(str);
    Result := False;
  end;
end;

// основная логика сервиса

procedure TErrorMonitor.Service1Execute(Sender: TService);
begin
  // создание MailSlot-канала с именем EMon - по этому имени к нему
  // будут обращаться клиенты, у которых возникли ошибки
  h := CreateMailSlot('\\.\mailslot\EMon', 0, MAILSLOT_WAIT_FOREVER, nil);
  if h = INVALID_HANDLE_VALUE then
  begin
    Status := csStopped;
    // запись в журнал событий NT
    str := 'Ошибка создания канала EMon !';
    LogMessage(str);
    ShowMessage(str);
    Exit;
  end;
  // создание файла журнала
  if not InitLog then
    Exit;
  try
    while not Terminated do
    begin
      // определение наличия сообщения в канале
      if not GetMailSlotInfo(h, nil, DWORD(MsgNext), @MsgNumber, nil) then
      begin
        Status := csStopped;
        str := 'Ошибка сбора информации канала EMon !';
        LogMessage(str);
        ShowMessage(str);
        Break;
      end;
      if MsgNext <> MAILSLOT_NO_MESSAGE then
      begin
        beep;
        // чтение сообщения из канала и добавление в текст протокола
        if ReadFile(h, str, 200, DWORD(Read), nil) then
        begin
          // запись в журнал
          Writeln(LogFile, str);
          // посылка сообщения для показа
          SendError;
        end
        else
        begin
          str := 'Ошибка чтения сообщения !';
          Writeln(LogFile, str);
          SendError;
        end;
        Flush(LogFile);
      end;
      sleep(500);
      ServiceThread.ProcessRequests(False);
    end;
  finally
    CloseHandle(h);
    CloseFile(LogFile);
  end;
end;

procedure TErrorMonitor.ServiceEMCreate(Sender: TObject);
begin
  // под таким именем наш сервис будет виден в Service Control Manager
  DisplayName := 'ErrorMonitor';
  // необходимо при использовании ShowMessage
  InterActive := True;
end;

end.

Окно просмотра ошибок
Окно просмотра ошибок

Текст программы окна просмотра ошибок приведен ниже :
unit fErrorMonitorMessage;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ScktComp;

type
  TfmErrorMonitorMessage = class(TForm)
    // протокол текущих ошибок
    meErrorTextNow: TMemo;
    meJournals: TMemo;
    // таймер для опроса канала
    Timer: TTimer;
    paJournals: TPanel;
    buJournals: TButton;
    lbJournals: TListBox;
    laJournals: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure buJournalsClick(Sender: TObject);
  private
  public
  end;

  // сетевой разделяемый ресурс, где сохраняются журналы
  // (укажите здесь имя своего ресурса и обеспечьте права для доступа)
const
  LogDir = '\\MyServer\C$\Log\';

var
  fmErrorMonitorMessage: TfmErrorMonitorMessage;
  h: THandle; // handle Mailslot-канала
  str: string[250]; // буфер обмена
  MsgNumber, MsgNext, Read: DWORD;

implementation
{$R *.DFM}

procedure TfmErrorMonitorMessage.FormCreate(Sender: TObject);
var
  sr: TSearchRec;
  i: integer;
begin
  // создание Mailslot-канала с именем EMonMess
  // по этому каналу будем получать сообщения об ошибках от сервиса NT
  h := CreateMailSlot('\\.\mailslot\EMonMess', 0, MAILSLOT_WAIT_FOREVER, nil);
  if h = INVALID_HANDLE_VALUE then
  begin
    ShowMessage('Ошибка создания канала !');
    Halt;
  end;
  // интервал опроса канала Mailslot - 3 секунды
  Timer.Interval := 3000;
  // таймер первоначально был выключен
  Timer.Enabled := True;
  // заполнение списка доступных журналов
  i := FindFirst(LogDir + '*.log', faAnyFile, sr);
  while i = 0 do
  begin
    lbJournals.Items.Add(sr.Name);
    i := FindNext(sr);
  end;
  lbJournals.ItemIndex := lbJournals.Items.Count - 1;
  FindClose(sr);
end;

procedure TfmErrorMonitorMessage.TimerTimer(Sender: TObject);
var
  str: string[250];
begin
  Timer.Enabled := False;
  // определение наличия сообщения в канале
  if not GetMailSlotInfo(h, nil, DWORD(MsgNext), @MsgNumber, nil) then
  begin
    ShowMessage('Ошибка сбора информации !');
    Close;
  end;
  if MsgNext <> MAILSLOT_NO_MESSAGE then
  begin
    beep;
    // чтение сообщения из канала и добавление в текст протокола
    if ReadFile(h, str, 200, DWORD(Read), nil) then
      meErrorTextNow.Lines.Add(str)
    else
      ShowMessage('Ошибка чтения сообщения !');
  end;
  Timer.Enabled := True;
end;

procedure TfmErrorMonitorMessage.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  CloseHandle(h);
end;

procedure TfmErrorMonitorMessage.buJournalsClick(Sender: TObject);
var
  Journal: TFileStream;
  s: string;
begin
  // получение журнала ошибок за дату
  meJournals.Lines.Clear;
  meJournals.Lines.Add('Файл журнала ' +
    lbJournals.Items[lbJournals.ItemIndex]);
  Journal := TFileStream.Create(LogDir + lbJournals.Items[lbJournals.ItemIndex],
    fmOpenRead or fmShareDenyNone);
  SetLength(s, Journal.Size);
  Journal.Read(PChar(s)^, Journal.Size);
  meJournals.Lines.Add(s);
  Journal.Free;
end;

end.
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay