How Does the Logbook Work?

The logbook uses COM+ events to pass the information collected from the application to the logbook components. The components (the HTML and XML versions) implement the ILogbook interface (see Figure A-3), a custom interface with methods corresponding to what is being logged—method call, event, or error. The ILogbook interface is defined as:

interface ILogbook : IUnknown
{
  typedef struct tagLOG_ENTRY
  {   
    HRESULT  hres;
    DWORD    dwErrorCode;
    DWORD    dwProcessID;
    DWORD    dwThreadID;
    GUID     guidActivityID;
    GUID     guidTransactionID;
    GUID     guidContextID;
    BSTR     bstrMachineName;
    BSTR     bstrSourceFileName;
    BSTR     bstrModuleName;
    BSTR     bstrMethodName;
    DWORD    dwLineNumber;
    BSTR     bstrDescription;
    IID      iidError;
    FILETIME eventTime;
  }LOG_ENTRY; 

  HRESULT LogError ([in]LOG_ENTRY* pErrorEntry);
  HRESULT LogMethod([in]LOG_ENTRY* pMethodEntry);
  HRESULT LogEvent ([in]LOG_ENTRY* pEventEntry);
};

The helper macros collect the information on the application side, pack it into a LOG_ENTRY struct, create a COM+ event class that implements ILogbook, and fire the appropriate event. The logbook receives the event, formats it appropriately (to HTML or XML), and writes it to the log file.

Deciding to use COM+ events was the easy part of the design. Deciding how to channel all the events to the same logbook component and how to collect all the tracing information you are interested in is more challenging.

To solve the first challenge, you can use COM+ instance managements services. The components in the logbook application are configured to use Object Pooling and Just-in-Time Activation (JITA) to create the COM+ equivalent of a singleton (as discussed in Chapter 3). Each component (HTML and XML) implements the IObjectControl interface and returns TRUE from IObjectControl::CanBePooled( ). The object pool is configured to have a minimum and a maximum pool size of 1, ensuring that there is always exactly one instance of a component of that type (see Figure A-4).

When a logging client application publishes an event, COM+ delivers the event to the persistent subscriptions of the logbook component. But because the logbook component is pooled, with a pool size of exactly 1, COM+ does not create a new instance of the persistent subscriber. Instead, it retrieves the logbook component from the pool, hands the event to the component, and releases it back to the pool once the method returns. However, what would happen if a greedy application created the logbook component directly and held on to it? The maximum pool size is 1, so COM+ wouldn’t create another instance of the logbook component to publish the event to it, but would instead wait for the existing object to return to the pool. The object wouldn’t return, though, since the greedy application would be holding a reference to it. As a result, all attempts from other applications to publish to the logbook would fail after the timeout specified in the Creation Timeout (see Figure A-4).

As you saw in Chapter 3, JITA is designed to handle such greedy clients. If the logbook component indicates to COM+ that it is willing to be deactivated and is configured to use JITA, COM+ deactivates the component. In this case, it returns back to the pool, as opposed to a real release. The greedy application does not know the difference because it still has a reference to a valid proxy. The next time the greedy client application tries to access the logbook, COM+ detects it, retrieves the object from the pool, and hooks it up with the interceptor so the greedy application’s logging call goes through.

The logbook components are therefore configured to use JITA (see Figure A-4). However, a logbook component still has to let COM+ know when it is okay to deactivate it. The logical place would be at method boundaries when it is done logging to the file. Therefore, the logbook components use COM+ method auto-deactivation (see Figure A-5). Every logging method is configured to automatically deactivate the object on return.

COM+ deactivates the object after each method call

Figure A-5.  COM+ deactivates the object after each method call

Because the logbook application is a server application, there is little impact for the component’s threading model, since all calls are marshaled across process boundaries anyway. For the remote possibility of ever being deployed in a library application, the logbook components use the Both threading model. Synchronization is provided by having the components’ synchronization configured as Required. Note that, as explained in Chapter 5, JITA requires synchronization, so the only available synchronization settings are Required and Requires New.

One other configuration setting used is to have COM+ leave the logbook application running when idle (on the Logbook application properties page, Advanced tab). This is required to keep the pool alive, even if there are no external clients using logging. As a result, all logging is written to the same file. Because you only have to create a new application and component instance once, performance improves.

You already saw that the filename is passed as a constructor string. As explained in Chapter 3, the logbook components implement IObjectConstruct to access that string. COM+ queries for that interface after creating the object. Then it passes to the only method, Construct( ) , a pointer to an IObjectConstructString object. You can use that pointer to get the constructor string, which is a filename in this case. Look at Example 3-2 in Chapter 3 to learn how to gain access to the constructor string.

The other major challenge in developing the logbook is collecting the information on the client side. Some of it, like line number, file, and module name, has nothing to do with COM+ and are just neat programming tricks that use predefined compiler macros; look at the source files if you are curious. Obtaining the execution and context IDs is another thing. Fortunately, COM+ has an excellent infrastructure for this purpose: the IObjectContextInfo interface. As demonstrated in Chapter 2 and and other chapters in the book, you can use the IObjectContextInfo interface to retrieve the context, transaction, and activity ID. This is exactly what the helper macros (Table A-1) do on the client side. The macros actually use a helper class, CEventLogger , to collect the information and publish it to the logbook. Example A-2 shows how the LOGEVENT macro is implemented.

Example A-2. The LOGEVENT helper macro

#define LOGEVENT(x)  DoLogEvent(x)

inline void DoLogEvent(const CString& sEvent)
{
   CEventLogger eventLogger;
   eventLogger.DoLogEvent(sEvent);
}

inline void CEventLogger::DoLogEvent(const CString& sEvent) const
{
   LOG_ENTRY logEntry; 
   HRESULT    hres = S_OK;
   ILogbook* pLogbook  = NULL;

   FillLogEntry(&logEntry,sEvent);//using IObjectContextInfo to get the IDs

   //Create the event class and publish
   hres = ::CoCreateInstance(CLSID_LogbookEventClass,..,
                             IID_ILogbook,&pLogbook);

   //Publish to the logbook
   hres = pLogbook->LogEvent(&logEntry);
   pLogbook->Release(  );
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset