?
Solved

Log file

Posted on 2006-05-04
10
Medium Priority
?
235 Views
Last Modified: 2013-03-13
What is the best method to create a function to write into a text file for multithreaded application ?

I have a text file (log file) called "LOG.TXT" and there are 10 threads writing into it.

So, it can be accessed in the same time.

Anyone have a good code for this kind of task ?
0
Comment
Question by:dudup
8 Comments
 
LVL 27

Expert Comment

by:kretzschmar
ID: 16607701
>So, it can be accessed in the same time.
it can't physically, one thread do have already to wait until the file is free for write,
guess you have to try to open the file within a loop until you can open it for write

meikl ;-)

0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 300 total points
ID: 16610311
1 safe way to have multiple threads writing to the same log file would be to use a TCriticalSectionin the logging code
However, if you do a lot of logging, e.g. constantly writing all the time, this can greatly impact your performance.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  syncobjs; // needed for TCriticalSection

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    procedure AddToLog(S: string);
  public
    { Public declarations }
    LogCrit: TCriticalSection;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
    begin
        LogCrit := TCriticalSection.Create;
    end;

procedure TForm1.FormDestroy(Sender: TObject);
    begin
        FreeAndNil(LogCrit);
    end;

procedure TForm1.AddToLog(S: string);
    var
        TextF:TextFile;
        logfilename: string;
    begin
        logfilename := ChangeFileExt(Paramstr(0), '.log');
        LogCrit.Enter;
        try
            assignFile(TextF, logfilename);
            try
                if FileExists(logfilename) then
                  append(TextF)
                else
                  Rewrite(TextF);
                writeln(TextF, S);
            finally
                closefile(TextF);
            end;
        finally
            LogCrit.Leave;
        end;
    end;
end.
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16610350
Another slightly less intensive method is to have your threads do a synchronize for logging, e.g.

type TMythread := class(TThread)
public
    mainform: TForm1;
    logmessage: string;
    procedure SyncLog
.....
...

in the threads execute method
try
 ... do stuff
except
    on e: exception do
    begin
        logmessage := 'Error: ' + E.Message;
        synchronize(SyncLog);
    end;
end;

....
...

procedure TMyThread.SyncLog;
begin
    MainForm.AddToLog(logmessage);
end;



....

procedure TForm1.AddToLog(S: string);
    var
        TextF:TextFile;
        logfilename: string;
    begin
        logfilename := ChangeFileExt(Paramstr(0), '.log');
        assignFile(TextF, logfilename);
        try
            if FileExists(logfilename) then
              append(TextF)
            else
              Rewrite(TextF);
            writeln(TextF, S);
        finally
            closefile(TextF);
        end;
    end;

0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
LVL 3

Assisted Solution

by:piba
piba earned 300 total points
ID: 16610443
And another way of doing it.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

Const
  WM_logit = WM_user + 1;

type
  TTestThread = class(TThread)
    Form1Handle:THandle;
    procedure Execute; Override;
  End;

  TTestThread2 = class(TThread)
    S:String;
    Procedure WriteLog;
    procedure Execute; Override;
  End;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    FS:TFileStream;
    FThreads:array[1..500] of TTestThread;
    FThreads2:array[1..500] of TTestThread2;
    procedure WriteLog2(S: String);
    Procedure WriteLog(var Msg:TMessage);Message WM_logit;
  end;

var
  Form1: TForm1;


implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.WriteLog2(S:String);
Begin        
  FS.WriteBuffer(S[1],Length(S));
End;

procedure TForm1.WriteLog(var Msg:TMessage);//Message WM_logit;
Var
  S:PString;
begin
  S:=Pointer(Msg.WParam);
  FS.WriteBuffer(S^[1],Length(S^));
  Dispose(S);
end;

procedure TForm1.Button1Click(Sender: TObject);
Var
  T:Integer;
begin
  For T:= 1 To Length(FThreads) Do
  Begin
    FThreads[T]:=TTestThread.create(True);
    FThreads[T].Form1Handle:=Self.Handle;
    FThreads[T].Priority:=tpLower;
  End;
  For T:= 1 To Length(FThreads) Do
  Begin
    FThreads[T].Resume;
  End;

  For T:= 1 To Length(FThreads) Do
  Begin
    FThreads2[T]:=TTestThread2.create(True);
    FThreads2[T].Priority:=tpLower;
  End;
  For T:= 1 To Length(FThreads2) Do
  Begin
    FThreads2[T].Resume;
  End;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FS:=TFileStream.Create('logfile.log',fmCreate      );
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FS);
end;

{ TTestThread }

procedure TTestThread.Execute;
Var
  T:Integer;
  S:PString;
begin
  inherited;
  For T:= 1 To 1000 Do
  Begin
    New(S);
    S^:='LogIt   '#13#10;
    While Not PostMessage(Form1Handle,WM_logit,Integer(S),0) Do
      Sleep(10);
  End;
end;
             
procedure TTestThread2.Execute;
Var
  T:Integer;
begin
  inherited;
  For T:= 1 To 1000 Do
  Begin
    S:='LogThis '#13#10;;
    Synchronize(WriteLog);
  End;
end;

procedure TTestThread2.WriteLog;
begin
  Form1.WriteLog2(S);
end;

end.
0
 

Author Comment

by:dudup
ID: 16612807
Which one is less CPU usage ? TCriticalSection or Synchronize ?
0
 
LVL 17

Expert Comment

by:TheRealLoki
ID: 16626802
cpu usage, i would say TCriticalSection, since it would suspend everything else while in it, but synchronize or postmessage would be the more "correct" way
0
 

Expert Comment

by:gutyka
ID: 16739192
Isn't it easier if you introduce a global variable to check if the LOG file is in use? If it is in use, then you do an application.processmessages until it is free.

var
 LogInUse  :Boolean=false;

procedure WriteLog;
begin
 while LogInUse do application.processmessages;
 LogInUse:=true;
 // write to the log file
 LogInUse:=false;
end;
0
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17120651
See http://www.workshop-alex.org/Sources/Delphi/untExtraInterfaces.html or download http://www.workshop-alex.org/Sources/Delphi/untExtraInterfaces.pas

Look for the functions AssignInterface, combined with GetDebugFile and GetLogFile. Basically, it works a bit like this:

var MyText:TextFile;
begin
  AssignInterface(MyText, GetDebugFile('Logfile.txt'));
  // or: AssignInterface(MyText, GetLogFile('Logfile.txt', 'YYYY-MM-DD, HH:NN:SS.ZZZ'));
  Rewrite(MyText);
  // Write what you like.
  CloseFile(MyText);
end;

I wrote these years ago and both are (supposed to be) thread-safe. The DebugFile just generates normal textfiles which I used for debugging. The LogFile is a special textfile format with a timestamp at the beginning of every line, which can be very useful. I've made many improvements on this code recently for Delphi 2006 but am still testing them before I want to share them.
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Please read the paragraph below before following the instructions in the video — there are important caveats in the paragraph that I did not mention in the video. If your PaperPort 12 or PaperPort 14 is failing to start, or crashing, or hanging, …
This lesson discusses how to use a Mainform + Subforms in Microsoft Access to find and enter data for payments on orders. The sample data comes from a custom shop that builds and sells movable storage structures that are delivered to your property. …
Suggested Courses

864 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question