Question

I'm working on TLogger class that is logging my application logs to file...

I have to way of getting Logs from File to TMemo: 1. assign TMemo to TLogger class then, assign True to DisplayInMemo property, then just call GetLogFromFile(); 2. call GetLogsFromFile(); then Self.Memo1.Text := TLogger.LogsResult;

Below... Commented solution works fine... Uncommented solution works only every 2 click on button 4

procedure TForm1.Button4Click(Sender: TObject);   // get log.file to memo
begin
  // automatic forwarding logs from File to TMemo - it works!
  //logger.DisplayMemo := Self.Memo1;
  //logger.DisplayInMemo := True;
  //logger.GetLogsFromFile();

  // tested - half-automatic method of formwarding logs to TMemo - works every 2 clicks :(
  logger.DisplayInMemo := False;
  logger.GetLogsFromFile();
  Self.Memo1.Text := logger.LogsResult;
end;

Whole TLogger implementation:

unit Logger;

interface

uses
  System.IOUtils, System.TypInfo, System.SysUtils, FMX.Forms, FMX.Dialogs, System.Classes, FMX.Graphics, FMX.ExtCtrls, LoggerThread, FMX.Memo;

type

  TLogger = class
  private
    FileName : String;                // name of file to log
    FilePath : String;                // path to app / log-file

    LStringResult : String;           // result of thread log.file reading
    LLoggerMemo : TMemo;              // copy of memo - place where GetLogsFromFile put results

    LDisplayInMemo : Boolean;         // bool - if True  GetLogsFromFile puts results to DisplayMemo, otherwise waiting in LogsResult
    NewLoggerThread : TLoggerThread;  // thread object - created in Create()

    procedure GetLogsFromFileThreadTerminateHandler(sender: TObject);

  public
    constructor Create(); overload;                     // open or create 'development.log'
    constructor Create(LogFileName : String); overload; // open or create LogFileName for logging
    destructor Destroy(); overload;                     // cleaner of TLogger object
    // main procedures
    procedure Log(LogString : String);                  // add line to log file
    procedure GetLogsFromFile();                        // get all logs from log file to string
    // settings, reading results,
    property DisplayInMemo : Boolean read LDisplayInMemo write LDisplayInMemo; //bool - if True  GetLogsFromFile puts results to DisplayMemo, otherwise waiting in LogsResult
    property LogsResult : String read LStringResult write LStringResult;       //string results after Getters from TLogger usage
    property DisplayMemo : TMemo read LLoggerMemo write LLoggerMemo;           // sets TMemo where results will be put if DisplayInMemo set to True
  end;

implementation

  constructor TLogger.Create();
  begin
    {$IFDEF Android}
      FilePath := TPath.GetDownloadsPath + System.SysUtils.PathDelim;
    {$ELSE}
      FilePath := ExtractFilePath(ParamStr(0));
    {$ENDIF}
    FileName := 'development.log';
    LDisplayInMemo := False;
    // inherited
    inherited Create;
  end;

  constructor TLogger.Create(LogFileName : String);
  begin
    {$IFDEF Android}
      FilePath := TPath.GetDownloadsPath + System.SysUtils.PathDelim;
      //TPath.Combine(TPath.GetDocumentsPath,'test.txt');  // to have / \ auto-change
    {$ELSE}
      FilePath := ExtractFilePath(ParamStr(0));
    {$ENDIF}
    FileName := LogFileName;
    LDisplayInMemo := False;
    // inherited
    inherited Create;
  end;

  destructor TLogger.Destroy();
  begin
    inherited Destroy;
  end;

  // adds a sigle line to log file with date time
  procedure TLogger.Log(LogString : String);
  begin
    NewLoggerThread :=  TLoggerThread.Create(True);
    NewLoggerThread.FreeOnTerminate := True;
    NewLoggerThread.Log := LogString;                                    //log to write - date time then added in execute
    NewLoggerThread.LoggerInstruction := TLoggerInstruction.liLogToFile; //set instuction for thread - LogToFile
    NewLoggerThread.FileName := FileName;  //file to write
    NewLoggerThread.FilePath := FilePath;  //path to file

    try
      NewLoggerThread.Start;
    except
      NewLoggerThread.Free();
    end;

  end;

  // results String with LogFile content
  procedure TLogger.GetLogsFromFile();
  begin
    NewLoggerThread :=  TLoggerThread.Create(True);
    NewLoggerThread.FreeOnTerminate := True;
    NewLoggerThread.OnTerminate := GetLogsFromFileThreadTerminateHandler;
    NewLoggerThread.FileName := FileName;  //file to write
    NewLoggerThread.FilePath := FilePath;  //path to file
    NewLoggerThread.LoggerInstruction := TLoggerInstruction.liGetLogsFromFile; //set instuction for thread - GetLogFromFile

    try
      NewLoggerThread.Start;
    except
      NewLoggerThread.Free();
    end;

  end;

  procedure TLogger.GetLogsFromFileThreadTerminateHandler(sender: TObject);
  begin
    LStringResult := (Sender as TLoggerThread).StringResult;
    if LDisplayInMemo then
        LLoggerMemo.Text := (Sender as TLoggerThread).StringResult;
  end;

end.

As you can see only difference is in LDisplayInMemo: if is True TMemo fills with logs... when is False I need 2 clicks on Button 4 to get results in TMemo...

  procedure TLogger.GetLogsFromFileThreadTerminateHandler(sender: TObject);
  begin
    LStringResult := (Sender as TLoggerThread).StringResult;
    if LDisplayInMemo then
        LLoggerMemo.Text := (Sender as TLoggerThread).StringResult;
  end;

Any ideas? To be honest I have no idea what's the reason of diffenerce in working of both solutions :( I also tried ProcessMessages after Self.Memo1.Text := logger.LogsResult;

Was it helpful?

Solution

The reason the following code only works the second time you click the button is that your code to actually get the log information runs in another thread... it's asynchronous!

logger.DisplayInMemo := False;
logger.GetLogsFromFile();
Self.Memo1.Text := logger.LogsResult; //This line runs AT THE SAME TIME you're getting logs!

NOTE: You're reading the value of logger.LogsResult before it gets a value from your LoggerThread.

When you click the button a second time, the thread has finished running (the first time), and you're now able to read a value.

The reason your commented section works, is that you are only assigning the memo text when the thread terminates - i.e. finished doing it's work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top