D.2 Remote LoggingThe local logging facility discussed in Section D.1 is useful when the message to be logged is created in pieces that need to be logged together. However, the local logging facility can be used only by collections of single-threaded processes on the same host. The remote logging facility is meant to be used in a multithreaded environment or one in which processes on multiple machines are cooperating or communicating.Programs that depend on concurrency (primarily those that fork children, create multiple threads, or that depend on communicating processes) are often difficult to understand and debug. Debuggers for multithreaded programs are not generally available, let alone ones that can unify the debugging of communicating processes running on different machines, possibly on incompatible hardware.The logging facility described here allows for instrumenting code in a simple way to log events. The logged events are sent to a possibly remote machine and gathered for analysis. Events are timestamped according to when they arrive at the receiving machine. If the variance of network delays are small compared with the granularity of the logging, these times acceptably indicate the sequence of events that occur in logged programs. Optionally, messages can be timestamped with the time they were generated. This is useful if all messages are logged from the same host or from hosts with synchronized clocks.The underlying philosophy of the logging facility is to provide a simple, familiar C-language-based interface that can be mastered in a few minutes. Most of the complication is moved to the receiving end, which has a GUI for ease of use.The facility is thus broken into two independent parts, the C language interface which runs in a UNIX environment, and a Java-based GUI receiving module that can be run on any system having a Java runtime environment.The C language interface is modeled on the C language FILE pointer I/O interface and has functions corresponding to fopen, fclose and fprintf. These are called lopen, lclose and lprintf, respectively. Three other functions, lprintfg, lgenerator and lsendtime, allow more control over how the logged data is labeled.The logging functions return NULL (lopen) or 1 (all others) on error. Do not use errno with any of the functions in the library. By default, these functions do not print error messages. To simplify debugging, they send error messages to standard error if in debugging mode. You can enter debugging mode by calling ldebug(1) and exit debugging mode by calling ldebug(0). Alternatively, you can turn on debugging by compiling with LDEBUGGING defined.To use the logging facility, include rlogging.h, shown in Appendix B, and with uici.c and uiciname.c, described in Appendix C. If the program is used in a multithreaded environment, the constant LUSETHREAD should be defined. The simplest way to do this is with a compiler option. Many compilers support the use of -DLUSETHREAD option to define LUSETHREAD at compile time. Program D.3 rlogging.hThe header file for the remote logging module.
Program D.4 rlogging.cC source for the logging module.
D.2.1 Use of the remote logging facilityThis section briefly describes how to use the remote logging facility. For a more detailed discussion, see [98]. A complete user's guide and all the programs are available online [99].The logging GUI must be started first. It can be run on any host with a Java runtime environment. The GUI listens for connections using TCP. If no port number is specified on the command line, the GUI takes the port number from the environment variable LOGGINGPORT or uses a default port number if this environment variable is not defined.The program that is being logged must be linked with the restart library, the UICI library, the UICI name resolution library and the logging library. The only functions that need to be directly accessed are given in Program D.3.First, make a connection to the GUI by using lopen. The parameters are a host name and a port number. If the host name is NULL or the port number is less than or equal to zero, lopen uses the values of the environment variables (LOGGINGPORT and LOGGINGHOST). If these environment variables are undefined, lopen uses default values. The lopen function returns a pointer of type LFILE that is used as a parameter to the other logging functions. You can then set optional behavior with the lsendtime and lgenerator functions. Logging is done with the lprintf and lprintfg functions, which have syntax similar to that of fprintf.The implementation assumes that the thread ID can be cast to a long in a meaningful way. If this is not the case, the function get_threadid might have to be changed. Alternatively, when using the remote logger with threads, compile with NOTHREADID defined, and the thread ID will not be used as part of the generator.Details of these functions are given below.
open a connection to the logging GUI. The host parameter is the name of the host on which the GUI is running, and port is the port number that the GUI is using. If host is NULL, lopen takes the host name from the environment variable LOGGINGHOST. If LOGGINGHOST is not set, lopen uses the default host name localhost. If port is less than or equal to 0, lopen takes the port number from the environment variable LOGGINGPORT. If LOGGINGPORT is not set, lopen uses a default port number of 20100. The GUI uses the same default port number. If successful, lopen returns a pointer of type LFILE that is used by other logging functions. If unsuccessful, lopen returns NULL.
close the connection to the GUI. If successful, lclose returns 0. If unsuccessful, lclose returns 1. The lclose function is not thread-safe. Do not close the connection while other threads can send messages to the GUI. Making this function thread-safe would add considerable overhead to the logging functions and it was decided that thread safety was not necessary.
automatically send the local time with each message. The time is sent as two integer values giving the number of seconds since the Epoch and an additional number of microseconds. If successful, lsendtime returns 0. If unsuccessful, lsendtime returns 1. The design of lsendtime allows the GUI to optionally display the time that the message was sent rather than the time it was received. Call lsendtime before sending any messages to the GUI. When the GUI is set to display send times rather than receive times, messages sent before this call are displayed without a time. Displaying send times is useful when all messages are sent from the same host or from hosts with synchronized clocks. Otherwise, the receive times are more useful. The lsendtime function returns 0 if successful and 1 if unsuccessful. The lsendtime function is not thread-safe. Do not call lsendtime while other threads of the same process are concurrently logging.
set the generator string to be gen. The generator string appears in the gen column of the GUI to identify the output. If successful, lgenerator returns 0. If unsuccessful, lgenerator returns 1. Failure can occur only if the gen string is longer than LFILE_GENLENGTH or if mutex locking fails in a threaded environment. The generator string follows a format specification. The gen parameter is a string that will be the new generator. The generator string specifies a format for the generator sent to the remote GUI. The first occurrence of %p in the generator string is replaced with the process ID of the process sending the message. In a threaded environment, the first occurrence of %t is also replaced by the thread ID. If LUSETHREAD is defined, compiling with NOTHREADID defined causes %t to be replaced by 0. The specified generator overrides the default generator that is equivalent to %p in a nonthreaded environment and to %p.%t in a threaded environment (LUSETHREAD defined). The default generator can be restored by a call to lgenerator with a NULL value of the gen parameter.
output a string to the logger. The lprintf and lprintfg functions are identical with one exception: the latter uses gen for the generator of this message only and the former uses the default generator. If successful, these functions return 0. If unsuccessful, these functions return 1. The syntax and parameters are similar to fprintf. The fmt parameter specifies a format string, and the remaining parameters are values to be included in the message. These functions allow one additional format specification, %t, which is replaced by the current time with a precision of milliseconds. If the message automatically includes the time (because of a previous call to lsendtime), the same time is used for both. D.2.2 Implementation detailsThe logging facility can be used in a threaded or nonthreaded environment. The additional code for threaded operation is included if the constant LUSETHREAD is defined. The program uses mutex locks for synchronization. When LUSETHREAD is defined, all the functions are thread-safe except for lclose and lsendtime. Making these thread-safe would require additional synchronization every time the LFILE structure is accessed, adding considerable overhead and serializing much of the program being logged. The intention is that lopen and lsendtime be called before the threads are created and that lclose be called only when all logging has been completed. Optionally, you can avoid lclose completely by allowing the process exit to close the connection. Compiling with LSENDTIME defined causes the sending of the time to be the default.To allow for maximum concurrency, separate mutexes are used to protect calls to the ctime function, calls to the lgenerator function, and access to the nextID variable.Each connection to the GUI has an associated pipe. A call to lopen reserves three file descriptors: one for the connection to the GUI and two for the pipe. A new process is created to transfer anything written to the pipe to the GUI. This is done with a forked process rather than a thread so that the facility can be used in a nonthreaded environment. Also, some thread-scheduling mechanisms might not give sufficient priority to this thread when it is used with other CPU-bound threads.The maximum-size message (including the message header) that can be sent is given by PIPE_BUF. This choice allows all messages sent through one connection to be passed atomically to the GUI by having them go through a single pipe shared by processes or threads. Messages sent through different connections are sorted by the GUI. POSIX specifies that PIPE_BUF must be at least _POSIX_PIPE_BUF, which has the value of 512. Typical values of PIPE_BUF may be 10 times this value, but even the minimum is suitable for logging simple error or status information. |