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.
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( ); }