Put simply, the ACE Logging Service is a configurable two-tier replacement for UNIX syslog. Both syslog and the Windows Event Logger are pretty good at what they do and can even be used to capture messages from remote hosts. But if you have a mixed environment, they simply aren't sufficient.
Table 3.7. ACE_Log_Record Attributes
Attribute |
Description |
---|---|
type |
The log record type from Table 3.1 |
priority |
Synonym for type |
priority_name |
The log record's priority name |
length |
The length of the log record, set by the creator of the log record |
time_stamp |
The timestamp—generally, creation time—of the log record; set by the creator of the log record |
pid |
ID of the process that created the log record instance |
msg_data |
The textual message of the log record |
msg_data_len |
Length of the msg_data attribute |
The ACE netsvcs logging framework has a client/server design. On one host in the network, you run the logging server that will accept logging requests from any other host. On that and every host in the network where you want to use the distributed logger, you invoke the logging client. The client acts somewhat like a proxy by accepting logging requests from clients on the local system and forwarding them to the server. This may seem to be a bit of an odd design, but it helps prevent pounding the server with a huge number of client connections, many of which may be transient. By using the proxy approach, the proxy on each host absorbs a little bit of the pounding, and everyone is better off.
To configure our server and client proxy, we will use the ACE Service Configurator framework. The Service Configurator is an advanced topic that is covered in Chapter 19. We will show you just enough here to get things off the ground. Feel free to jump ahead and read a bit more about the Service Configurator now, or wait and read it later.
To start the server, you need to first create a file server.conf with the following content:
dynamic Logger Service_Object * ACE:_make_ACE_Logging_Strategy() "-s foobar -f STDERR|OSTREAM|VERBOSE" dynamic Server_Logging_Service Service_Object * netsvcs:_make_ACE_Server_Logging_Acceptor () active "-p 20009"
Note these lines are wrapped for readability. Your server.conf should contain only two lines, each beginning with the word dynamic. The first line defines the logging strategy to write the log output to standard error and the output stream attached to a file named foobar. This line also requests verbose log messages instead of a more terse format. (Section 3.8 discusses more ways to use this service.) The second line of server.conf causes the server to listen for client connections at TCP (Transmission Control Protocol) port 200091 on all network interfaces available on your computer. You can now start the server with:
$ACE_ROOT/netsvcs/servers/main -f server.conf
The next step is to create the configuration file for the client proxy and start the proxy. The file could be named client.conf and should look something like this:
dynamic Client_Logging_Service Service_Object * netsvcs:_make_ACE_Client_Logging_Acceptor () active "-p 20009 -h localhost"
Again, that's all on one line. The important parts are -p 20009, which tells the proxy which TCP port the server will be listening to—this should match the -p value in your server.conf—and -h localhost, which sets the host name where the logging server is executing. For our simple test, we are executing both client and server on the same system. In the real world, you will most likely have to change localhost to the name of your real logging server.
Although we provide the port on which the server is listening, we did not provide a port value for clients of the proxy. This value is known as the logger key, and its form and value change, depending on the capabilities of the platform the client logger is built on. On some platforms, it's a pipe; where that's not possible, it's a loopback TCP socket at address localhost:20012. If you want your client proxy to listen at a different address, you can specify that with the -k parameter in client.conf.
You can now start the client logger with:
$ACE_ROOT/netsvcs/servers/main -f client.conf
Using the logging service in one of our previous examples is trivial:
#include "ace/Log_Msg.h" int ACE_TMAIN (int, ACE_TCHAR *argv[]) { ACE_LOG_MSG->open (argv[0], ACE_Log_Msg::LOGGER, ACE_DEFAULT_LOGGER_KEY); ACE_TRACE (ACE_TEXT ("main")); ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("%IHi Mom\n"))); ACE_DEBUG ((LM_INFO, ACE_TEXT ("%IGoodnight\n"))); return 0; }
As with the syslog example, we must use the open() method when we want to use the logging service; set_flags() isn't sufficient. Note also the open() parameter ACE_DEFAULT_LOGGER_KEY. This has to be the same logger key that the client logger is listening at; if you changed it with the -k option in client.conf, you must specify the new value to open().
To summarize: On every machine on which you want to use the logging service, you must execute an instance of the client logger. Each instance is configured to connect to a single instance of the logging server somewhere on your network. Then, of course, you execute that server instance on the appropriate system.
For the truly adventurous, your application can communicate directly with the logging server instance. This approach has two problems:
-
Your program is now more complicated because of the connection and logging logic.
-
You run the risk of overloading the server instance because you've removed the scaling afforded by the client proxies.
However, if you still want your application to talk directly to the logging server, here's a way to do so:
#include "ace/Log_Msg.h" #include "Callback-3.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; }
This looks very much like our previous callback example. We use the callback hook to capture the ACE_Log_Record instance that contains our message. Our new Callback object then sends that to the logging server:
#include "ace/streams.h" #include "ace/Log_Msg.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" #include "ace/SOCK_Stream.h" #include "ace/SOCK_Connector.h" #include "ace/INET_Addr.h" #define LOGGER_PORT 20009 class Callback : public ACE_Log_Msg_Callback { public: Callback () { this->logger_ = new ACE_SOCK_Stream; ACE_SOCK_Connector connector; ACE_INET_Addr addr (LOGGER_PORT, ACE_DEFAULT_SERVER_HOST); if (connector.connect (*(this->logger_), addr) == -1) { delete this->logger_; this->logger_ = 0; } } virtual ~Callback () { if (this->logger_) { this->logger_->close (); } delete this->logger_; } void log (ACE_Log_Record &log_record) { if (!this->logger_) { log_record.print (ACE_TEXT (""), ACE_Log_Msg::VERBOSE, cerr); return; } size_t len = log_record.length(); log_record.encode (); if (this->logger_->send_n ((char *) &log_record, len) == -1) { delete this->logger_; this->logger_ = 0; } } private: ACE_SOCK_Stream *logger_; };
We've introduced some things here that you won't read about for a bit. The gist of what we're doing is that the callback object's constructor opens a socket to the logging service. The log() method then sends the ACE_Log_Record instance to the server via the socket. Because several of the ACE_Log_Record attributes are numeric, we must use the encode() method to ensure that they are in a network-neutral format before sending them. Doing so will prevent much confusion if the byte ordering of the host executing your application is different from that of the host executing your logging server.