Home > Articles > Software Development & Management

  • Print
  • + Share This
This chapter is from the book

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:

  1. Derive a callback class from ACE_Log_Msg_Callback, and reimplement the following method:

    virtual void log (ACE_Log_Record &log_record);
    
  2. Create an object of your new callback type.

  3. 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.

  4. 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
graphics/ccc.gif 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.

  • + Share This
  • 🔖 Save To Your Account