Monitoring text logfile while an applications writes to it

Stefaan
Stefaan used Ask the Experts™
on
Hi Folks,

I would like to know if there is some way to monitor the contents of a log file ( a simple text file ) while the application that generates the text file is still writing messages into it.

What I would like to achieve is detect if a message was appended to the text file, and fetch that message.  So in some way I will need read access to that file while the application that generates the file is writing messages in it.

Does anyone know if this is possible, and if it is, How could I do that ?

Best regards,


Stefaan
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®

Commented:
AFAIK a Textfiel that is opened is not locked an can be opened again. Just try it with a text file, open it (NOTEPAD for ex.)and open it a second time. It works without problems.

You could open it, read it in a Stringlist and put the Number of Lines in a variable. In a timerevent open it every minute and count the lines again. If the new count is bigger tha the Count before you can get the new lines with reading the lines from Lines[OldCount] to Lines[NewCount].

There might be more elegant solutions, but this should work.

Author

Commented:
Hm, gonna check this out.  Only problem might be that the files can get several megabytes in size, so opening and reopening it time after time might be a but intensive.

Best regards,
Hi Stefaan,

Long time ago isn't it ?
Everything OK ?


About your question : Maybe you can approach your logfile with the TTable-component, using a SCHEMA-File. Be sure to set the 'TableType'-property to ttASCII, and fill the Textfile-name into property TableName. Also set Filterd to True.

At runtime you can lookup if a certain value exists :
   TTable1.Filter := 'Something*';
   TTable1.Open;

or something like

  Table1.Open;
  if Table1.Locate('fieldname','value', [loCaseInsensitive]) then
    showmessage('found')
  else
    showmessage('not found');
  Table1.Close;



Best regards,
The Mayor.

PS : My compliments to wife and son.

Exploring SQL Server 2016: Fundamentals

Learn the fundamentals of Microsoft SQL Server, a relational database management system that stores and retrieves data when requested by other software applications.

Author

Commented:
Hi Wim,

Acutally it isn't that easy.  Doing it using a plain TTable would require me to install the BDE, which I definately DONT want.

And the text files are just Simple log files, not formatted in any way, the just contains some various texts which I should read, and depending on the message do something.

Anyway, tnx for the Input.

Best regards,


Stefaan
Hi,

It depends on how the app has opened the file: using fmShareExclusive or fmShareDenyWrite only. See 'File open mode constants' in your Delphi help.

If the file was opened with fmShareExclusive then you can't open it for reading.

You could find out if the file can be opened for reading this way:

var
  FileHandle : Integer;
  Buffer: PChar;
begin
  FileHandle := FileOpen('c:\test.txt', fmOpenRead or fmShareDenyNone); // open
  if FileHandle > 0 then begin
   try
    ShowMessage('The file was opened');
    Buffer := PChar(AllocMem(11));
    try
      FileRead(FileHandle, Buffer, 10); // read first 10 bytes
      ShowMessage(string(Buffer));
    finally
      FreeMem(Buffer);
    end;
   finally
    FileClose(FileHandle);
   end;
  end else ShowMessage('ERROR opening the file');
end;

Regards, Geo
Is this log-file frequently being updated, or just occasionally ?

Another possibility coudl be to check the datetime-stamp of the logfile itself, and only open it in a TStringList if its date has changed since the previous check.

Author

Commented:
Hi,

While the application that generates the Log Messages is running, the log file is updated very frequently.  Something happens in the application and it is written into the log file.

My task is now to detect the new messages that have been added to the end of this textfile, as soon as possible, and maybe react on them.

I'll try to check if I can use the time stamp thing to check if a file has been modified ( or something else for that matter ) and read it in again using a TStringList.  Only problem I can see here is that the file might get several megabytes in size, and reopening the file each time it gets modified might be a very intensive process.

Best regards,


Stefaan
Here is a way to read last few lines from that file. I think that you have to open it every time in order to read the newly added lines.

procedure FileReadLastLines(Filename: string; Lines: integer; sl: TStrings);
var
  i: integer;
  ch: char;
  s: String;
begin
  with TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone) do
    try
      Seek(-1, soFromEnd);
      for i := 1 to Lines do
        while (Read(ch, 1) = 1) and (Seek(-2, soFromCurrent) > 0) and (ch <> #10) do;
      Seek(2, soFromCurrent);
      s := '';
      while (Read(ch, 1) = 1) do
        s := s + ch;
      sl.Text := s;
    finally
      Free;
    end;
end;

// usage
procedure TForm1.Button1Click(Sender: TObject);
begin
  FileReadLastLines('c:\test.txt',10,Memo1.Lines);
end;

Regards, Geo

Author

Commented:
Haha, we are getting where I should be.  I'll try this out immediately.

Now the only thing I need is some kind of monitor which would look if the file has been changed.  This way I could only read the last few lines if there has effectivly been some changes on that file.

Best regards,


Stefaan
This is to know the 'Last Access' of a file :

function LastAccess (const filename : string) : string;
var
  FileHandle : THandle;
  LocalFileTime : TFileTime;
  DosFileTime : DWORD;
  LastAccessedTime : TDateTime;
  FindData : TWin32FindData;
begin
  Result := ''; { never :-) }
  FileHandle := FindFirstFile(filename, FindData);
  if FileHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    begin
      FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
      FileTimeToDosDateTime(LocalFileTime,
      LongRec(DosFileTime).Hi,LongRec(DosFileTime).Lo);
      LastAccessedTime := FileDateToDateTime(DosFileTime);
      Result := DateTimeToStr(LastAccessedTime);
    end;
  end;
end;



OR

if you still use RX-library then you can use :

GetFileSize() and FileDateTime() functions (in unit FileUtil).



function FileGetDateModified(const Filename: string; var Modified: TDateTime): boolean;
var sr:TSearchRec;
   SysTimeStruct: SYSTEMTIME;
   lft:_FILETIME;
begin
 Result:=False;
 if FindFirst(FileName,faAnyFile,sr) = 0 then
  if FileTimeToLocalFileTime(sr.FindData.ftLastWriteTime, lft) and
      FileTimeToSystemTime(lft, SysTimeStruct) then begin
      Modified := SystemTimeToDateTime(SysTimeStruct);
      result := true;
  end;
  FindClose(sr);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Date1: TDateTime;
begin
  if FileGetDateModified('c:\test.txt',Date1) then begin
    ShowMessage('Last Modified: '+DateTimeToStr(Date1));
  end;
end;

Regards, Geo

Author

Commented:
Well the two of you seemed to be pointing me in the right direction.  Geobul's ReadLastLines is doing exactly what I want it to do, and Wim pointed me to RxLib where I found the TRxFolderMonitor component ( allows me to monitor for any change in a specific folder ).

So what I propose is a split up of the points, well actually I'll post another question for Wim so he gets 100 points too.

Hope this sounds fair to you guys.

Best regards,


Stefaan
Fine by me. Always happy to get some points ;-)
Of course. Glad to help (and be rewarded ;-)

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial