To this point, we've been content to give our logging output to ACE_Log_Msg, which formatted the messages and directed them to the configured logging sinks. For most cases, that will be fine. What if, though, we want to do something with that output ourselves? Can we inspect or even modify the logging output before it reaches its final destination? Of course. That's where ACE_Log_Msg_Callback comes in.
Using a callback object is quite easy. Follow these steps:
-
Derive a callback class from ACE_Log_Msg_Callback, and reimplement the following method:
virtual void log (ACE_Log_Record &log_record);
-
Create an object of your new callback type.
-
To register the callback object with an ACE_Log_Msg instance, pass a pointer to your callback object to the ACE_Log_Msg::msg_callback() method.
-
Call ACE_Log_Msg::set_flags() to enable output to your callback object.
Once registered and enabled, your callback object's log() method will be invoked with an ACE_Log_Record object any time ACE_Log_Msg::log() is invoked. As it turns out, that is exactly what happens when an output-producing ACE logging macro is used.
Some important caveats to remember when using the callback approach are documented on the ACE_Log_Msg_Callback reference page. They bear repeating here.
-
Callback registration and enabling are specific to each ACE_Log_Msg instance. Therefore, a callback set up in one thread won't be used by any other thread in your application.
-
Callback objects are not inherited by the ACE_Log_Msg instances created for any threads you create. So if you're going to be using callback objects with multithreaded applications, you need to take special care that each thread is given an appropriate callback instance. It is possible to use a single object safely: see the description of ACE_Singleton in Section 1.6.3.
-
As with the OSTREAM caveat, be sure that you don't delete a callback instance that might still be used by the ACE_Log_Msg instance it's registered with.
A simple callback implementation follows:
#include "ace/streams.h" #include "ace/Log_Msg.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" class Callback : public ACE_Log_Msg_Callback { public: void log (ACE_Log_Record &log_record) { log_record.print (ACE_TEXT (""), 0, cerr); log_record.print (ACE_TEXT (""), ACE_Log_Msg::VERBOSE, cerr); } };
The program that uses it follows:
#include "ace/Log_Msg.h" #include "Callback.h" int ACE_TMAIN (int, ACE_TCHAR *[]) { Callback *callback = new Callback; ACE_LOG_MSG->set_flags (ACE_Log_Msg::MSG_CALLBACK); ACE_LOG_MSG->clr_flags (ACE_Log_Msg::STDERR); ACE_LOG_MSG->msg_callback (callback); ACE_TRACE (ACE_TEXT ("main")); ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("%IHi Mom\n"))); ACE_DEBUG ((LM_INFO, ACE_TEXT ("%IGoodnight\n"))); return 0; }
The program creates this output:
(1024) calling main in file `Use_Callback.cpp' on line 12 Sep 24 12:35:02.829 2003@@22396@LM_TRACE@(1024) calling main in file `Use_Callback.cpp' on line 12 Hi Mom Sep 24 12:35:02.830 2003@@22396@LM_DEBUG@ Hi Mom Goodnight Sep 24 12:35:02.830 2003@@22396@LM_INFO@ Goodnight (1024) leaving main Sep 24 12:35:02.830 2003@@22396@LM_TRACE@(1024) leaving main
The first log_record.print() simply prints the message we've always seen. The second, however, uses the VERBOSE flag to provide much more information. Both direct their output to the standard error stream.
Once you have access to the ACE_Log_Record instance, you have control to do anything you want. Let's take a look at a bit more of the information contained in ACE_Log_Record:
#include "ace/streams.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" #include "ace/SString.h" class Callback : public ACE_Log_Msg_Callback { public: void log (ACE_Log_Record &log_record) { cerr << "Log Message Received:" << endl; unsigned long msg_severity = log_record.type (); ACE_Log_Priority prio = ACE_static_cast (ACE_Log_Priority, msg_severity); const ACE_TCHAR *prio_name = ACE_Log_Record::priority_name (prio); cerr << "\tType: " << ACE_TEXT_ALWAYS_CHAR (prio_name) << endl; cerr << "\tLength: " << log_record.length () << endl; const time_t epoch = log_record.time_stamp ().sec (); cerr << "\tTime_Stamp: " << ACE_TEXT_ALWAYS_CHAR (ACE_OS::ctime (&epoch)) << flush; cerr << "\tPid: " << log_record.pid () << endl; ACE_CString data (">> "); data += ACE_TEXT_ALWAYS_CHAR (log_record.msg_data ()); cerr << "\tMsgData: " << data.c_str () << endl; } };
The following output is created:
Log Message Received: Type: LM_TRACE Length: 88 Time_Stamp: Wed Sep 24 12:35:09 2003 Pid: 22411 MsgData: >> (1024) calling main in file `Use_Callback2.cpp' on line 12 Log Message Received: Type: LM_DEBUG Length: 40 Time_Stamp: Wed Sep 24 12:35:09 2003 Pid: 22411 MsgData: >> Hi Mom Log Message Received: Type: LM_INFO Length: 40 Time_Stamp: Wed Sep 24 12:35:09 2003 Pid: 22411 MsgData: >> Goodnight Log Message Received: Type: LM_TRACE Length: 48 Time_Stamp: Wed Sep 24 12:35:09 2003 Pid: 22411 MsgData: >> (1024) leaving main
As you can see, we have quite a bit of access to the ACE_Log_Record internals. We're not limited to changing only the message text. We can, in fact, change any of the values we want. Whether that makes any sense is up to your application. Table 3.7 lists the attributes of ACE_Log_Record and what they mean.