Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement thread-safe logging

My task is to create a class which will gather user activity around several applications.

Let's say I have a class TLogging and a global object called Logging.

User activity (screen open, etc...) should be collected in memory (maybe put into a (string)list of TLogging) and saved into log file after some time interval (each 10 minutes), or when application is closed.

The most important is that logging must be in "silent mode", it mustn't affect user workflow in any way: no screen hanging, no exceptions.

Please give me the direction for this task.

like image 766
Yuriy Avatar asked Nov 15 '11 10:11

Yuriy


2 Answers

This is a very broad question that touches several areas. A few hints:

  • At least consider an established logging framework for this. Newer Delphi versions come with CodeSite. SmartInspect is another alternative.

  • Use synchronization primitives to make your class thread-safe: TCriticalSection, TMREWSync

  • Make sure you understand the issues involved in multithreading and synchronization before attempting to write a thread-safe logging framework. A good start is Martin Harvey's guide Multithreading - The Delphi Way.

  • Use a background thread that flushes the log content to disk in regular intervals or if enough data has been buffered.

  • Ask more specific questions here on SO if you encounter specific problems.

like image 182
jpfollenius Avatar answered Oct 27 '22 05:10

jpfollenius


Using OmniThreadLibrary and assuming that the Logging object is a singleton, this is really simple. I would also limit maximum number of messages waiting to be written so that internal queue cannot use too much memory.

const
  CMaxMsgCount = 1000;
  CMaxLogTimeout_ms = 10{min}*60{sec/min}*1000{ms/sec};

type
  TLogging = class
  strict private
    FLogMsgCount: IOmniResourceCount;
    FLogQueue: IOmniBlockingCollection;
    FWriter: IOmniTaskControl;
  strict protected
    procedure Logger(const task: IOmniTask);
  public
    constructor Create;
    destructor  Destroy;
    procedure Log(const msg: string);
  end;

var
  Logging: TLogging;

constructor TLogging.Create;
begin
  FLogMsgCount := CreateResourceCount(CMaxMsgCount);
  FLogQueue := TOmniBlockingCollection.Create;
  FWriter := CreateTask(Logger, 'Logger').Run;
end;

destructor TLogging.Destroy;
begin
  FWriter.Terminate;
end;

procedure TLogging.Log(const msg: string);
begin
  FLogQueue.Add(msg);
  FLogMsgCount.Allocate;
end;

procedure TLogging.Logger(const task: IOmniTask);

  procedure Flush;
  var
    logData: TOmniValue;
  begin
    // open file, possibly with retry
    while FLogQueue.TryTake(logData) do begin
      FLogMsgCount.Release;
      // write logData.AsString
    end;
    // close file
  end;

begin
  while DSiWaitForTwoObjects(task.TerminateEvent, FLogMsgCount.Handle, false, CMaxLogTimeout_ms) <> WAIT_OBJECT_0 do
    Flush;
  Flush;
end;

(Disclaimer: "It compiles on my machine", otherwise untested.)

like image 22
gabr Avatar answered Oct 27 '22 06:10

gabr