Finalization of a log unit called too early

252 Views Asked by At

I am running a ISAPI service which runs with IdHTTPWebBrokerBridge (for debugging as standalone EXE) as well as in Apache with mod_isapi (in productive enviroment).

While logging some stuff at the destruction of the web module, I found following problem:

unit LogFactory;

...

initialization
  GlobalLogFactory := TMyLogFactory.Create;
finalization
  FreeAndNil(GlobalLogFactory);
end.

-

unit MyWebModuleUnit;

...

uses LogFactory;

procedure TMyWebModule.WebModuleDestroy(Sender: TObject);
begin
  Assert(Assigned(GlobalLogFactory)); // <-- failure
  GlobalLogFactory.GetLogger('D:\test.txt').LogLine('test'); // <-- Nullpointer Exception
end;

LogFactory.pas creates the object GlobalLogFactory on its initialization and destroys it on its finalization.

But LogFactory.pas:finalization gets called BEFORE TMyWebModule.WebModuleDestroy , since it was included by this unit only, and so the finalization will be done in reverse order.

How can I ensure that my GlobalLogFactory will be correctly freed (i.e. FastMM will not warn about a memory leak), but at the same time, I want to give all destruction/finalization-procedures the chance to log something?

One Workaround would be to include LogFactory.pas explicitely in the DPR file as the first unit. But I don't like that very much, since this Log-Unit should be used in many projects and it should be useable by simply including it in the unit where you need to log something. Putting this log unit in every DPR which might want to log something in future, is a big effort and forgetting it might cause problems for the developers who are not knowing what I did/require.

2

There are 2 best solutions below

0
On BEST ANSWER

If you want

  1. Any unit in your program to be able to log, and
  2. Logging to be available during finalization, and
  3. The logging code to require tidy up,

then you don't have many options. One option is to include the log unit very early in the .dpr file. I don't know why you regard that to be a problem. It's the standard way to achieve your goals. It's the technique used by external memory managers for example. Delphi developers are familiar with the concept. You tell the developers what they need to do, and if they don't follow your instructions, the program does not work. That is their problem.

If you still cannot face doing it this way then I see one alternative. Arrange for the logger initialization to happen before any other initialization in your code. And the finalization after all other finalization. You can do that by putting the logger implementation into an external module that is linked with load time linking.

4
On

Instad of using a class instance in a global variable use an interface and a function to get that.

unit LogFactory;

interface

type
  ILogFactory = interface
    [{GUID}]
    function GetLogger(...) : TLogger;
    ...
  end;

  TLogFactory = class( TInterfacedObject, ILogFactory )
    function GetLogger(...) : TLogger;
  end;

function GlobalLogFactory : ILogFactory;

implementation

var
  _LogFactory : ILogFactory;

function GlobalLogFactory : ILogFactory;
begin
  if not Assigned( _LogFactory ) then
    _LogFactory := TLogFactory.Create;
  Result := _LogFactory;
end;

{ TLogFactory Implementation }

initialization

// nothing needed

finalization

// nothing needed

end.

There is no need for any initialization or finalization at all.