Threads Module User's Guide : PART III Foundation Packages : Chapter 6 The Execution Tracing Package : Using Trace Clients
Using Trace Clients
Trace clients process trace event information, pass it on to another client, or both. Only one client can receive event messages directly from the trace manager. If you need multiple clients with different behavior, see the discussion of filters in “Using Trace Filters”.
Using the Predefined Client
The Execution Tracing package provides one predefined client RWTraceOstreamClient. To instantiate the client, use code similar to this:
 
#include <rw/trace/trace.h>
. . .
RWTraceOstreamClient myTraceClient(cerr);
 
The RWTraceOstreamClient sends output to any ostream specified to its constructor, such as cerr in the above example. This code comes from Example 49.
After instantiation, connect the client to the trace manager using the client’s connectToManager() member function:
 
myTraceClient.connectToManager();
 
To connect the client to another filter, use the client’s connect() member function and pass it the filter as an argument:
 
RWTraceMultiClientFilter myMultiFilter;
myTraceClient.connect( myMultiFilter );
 
See “Using Trace Filters” for the discussion of filters.
Creating User-defined Clients
If you need a client to do something more exotic than send trace output to an ostream, you need to define your own trace client class. For example, you could send the trace data out over a socket or perhaps over a serial line for remote debugging. The example in this section simply adds a timestamp to each trace message before sending it to an ostream.
NOTE >> Do not embed trace macros in the client’s trace event processing code. To prevent infinite recursion, the trace manager ignores any trace messages generated by a client.
Figure 32 shows the trace client classes. Like other Threads Module packages, the Execution Tracing package uses the handle-body idiom. For example, an RWTraceOstreamClient object is a handle to an RWTraceOstreamClientImp instance, which actually processes the trace messages. RWTraceOstreamClient and RWTraceOstreamClientImp together form a complete client.
Figure 32 – Architecture of RWTraceOstreamClient and its related classes
To create your own trace client, you must supply a new implementation derived from the body class RWTraceEventClientImp. If your client’s body class has any new public member functions (in addition to those inherited from WTraceEventClientImp), you also need to create a new handle class that forwards those calls to your body. The handle must be derived from RWTraceEventClient.
The RWTraceEventClientImp::doTrace() function, which actually processes the trace events, is declared pure-virtual, so a derived body class must provide an implementation of this function. If you do not create your own derived handle class, the body class must also supply a static make() function. The function constructs a body and returns a handle to it, similar to Example 51.
Example 51 – Providing a static make function for a derived body class
static RWTraceOstreamClient make(std::ostream& ostr = std::cerr) {
return new TimeStampClientImp(ostr);
}
NOTE >> The returned handle's type is actually the parent class of your derived handle.
The above code is from buildspace\examples\threads\trace\example4.cpp, which shows how to create a client both ways, with or without a derived handle.
Including a Timestamp in Trace Output
Example 52 provides an implementation for the pure-virtual function doTrace() in a class derived from RWTraceEventClientImp. The code is taken from buildspace\examples\trace\example4.cpp. In this example, the class derives from RWTraceOstreamClientImp, which already has most of the necessary behavior. Only the doTrace() function is modified to provide timestamping.
Example 52 – Producing trace output with a timestamp
#include <rw/tools/datetime.h>
. . .
// Override our parent's doTrace member.
void
TimeStampClientImp::doTrace(RWTraceEvent& ev)
{
RWTimeTime now;
RWCString time( now.asString("%H:%M:%S"));
{
// Acquire a lock so we can output safely.
RWGUARD(getMutex());
 
getOstream() << time << "|" << ev.getFileName() << "|"
<< ev.getLineNumber() << "|"
<< ev.getResourceId() << "|";
 
// If an object generated this event, output its address.
if (ev.getThisPtr()) getOstream() << ev.getThisPtr() << "|";
 
getOstream() << ev.getSeverity() << "> " << ev.getMessage()
<< endl;
}
}
};
 
Including Thread IDs in Trace Output
In Example 53 the derived client shown in Example 52 is given the ability to record which thread produced the trace events. The code is taken from one of the Threads package examples, -buildspace\examples\thread\trace.cpp.
Example 53 – Producing trace output with thread IDs
#include <rw/trace/trace.h>
#include <rw/tools/datetime.h> // for RWDateTime
#include <rw/sync/RWThreadId.h> // for rwThreadId()
 
...
 
class TimeStampClientImp : public RWTraceOstreamClientImp {
public:
// The static make function constructs a body, and returns a
// handle to it. Note: the returned handle's type is actually
// the parent class of our handle.
static RWTraceOstreamClient make(std::ostream& ostr = std::cerr) {
return new TimeStampClientImp(ostr);
}
 
protected:
// Default constructor.
TimeStampClientImp(void) { }
// A constructor that takes an ostream.
TimeStampClientImp(std::ostream& ostr)
: RWTraceOstreamClientImp(ostr) { }
 
// Override our parent's doTrace member.
virtual void doTrace(const RWTraceEvent& ev) {
RWTimeDate now;
RWCString time( now.asString("%H:%M:%S"));
unsigned threadID = rwThreadHash( rwThreadId() );
{
// Acquire a lock so we can output safely.
RWGUARD(getMutex());
 
getOstream() << time << "|" << "TID:" << threadID << "|"
<< ev.getFileName() << ":"
<< ev.getLineNumber() << "|"
<< ev.getResourceId() << "|";
 
// If an object generated this event, output its address.
if (ev.getThisPtr()) getOstream() << ev.getThisPtr() << "|";
 
getOstream() << ev.getSeverity() << "> "
<< ev.getMessage() << std::endl;
}
}
};
Using Your Client
To use your client, assuming you created a derived handle, instantiate the handle and connect it to the trace manager:
 
MyDerivedClient myTraceClient;
myTraceClient.connectToManager();
or connect it to another filter:
 
myTraceClient.connect(myMultiFilter);
 
If you did not create your own derived handle class, you must supply a static make() function (see “Creating User-defined Clients”). To use your client, instantiate a body with your client’s make() function and pass it to a handle of type RWTraceEventClient. Then connect the client handle to the manager or a filter:
 
RWTraceEventClient myTraceClient( TimeStampClientImp::make() );
myTraceClient.connectToManager();