Debugging functions

From Mailutils
Jump to navigationJump to search

Mailutils API provides programmers with three ways of outputting additional debugging information:

  1. Via the mu_debug macro.
  2. Via the mu_debug_log functions.
  3. By writing directly to the mu_strerr stream.

The two former are the most often used, and the latter is a rather low-level method suitable when the desired output cannot be achieved by other means.

Categories and Levels

Although debugging output is often regarded as being useful for developers only, it is quite often helpful for regular users. Therefore it is important to give user a possibility to choose what debugging information he needs and how verbose he wants it to be.

Mailutils achieves this using a concept of debugging categories and levels. A category comprises all debugging information related to a certain part of the functionality of a program. For example, the category acl means everything related to access control lists. A level controls what information is desired in the given category.

An easy to use symbolic notation is provided which allows users to select the desired categories and associated levels from the command line and configuration files. In this article we will look at the internal aspects of these notions, from the point of view of a programmer.

Categories are represented by integer numbers. There is a set of built-in categories, described in file libmailutils/diag/debcat[1] and the include/mailutils/sys/debcat.h header, which is generated from it. These categories can be referred to in program text by their macro names. For example, the category auth is available under the name MU_DEBCAT_AUTH, and so on. To access these names, include the file mailutils/debug.h.

Applications can also register their own category names. It is useful, for example, for loadable libraries. A new category is registered by calling the mu_debug_register_category function:

  size_t mu_debug_register_category (char *name);

The name argument supplies the symbolic name for this category, under which it will be available for users. The function returns a category index which can subsequently be used in mu_debug* calls (see below).

Levels are implemented as integer numbers, which can be combined to form level bitmasks. There are 12 levels available:

MU_DEBUG_ERROR
Mild error conditions which are normally not reported, but passed back to the caller layers for handling.
MU_DEBUG_TRACE0 through MU_DEBUG_TRACE9
Ten levels of verbosity, from minimal to the maximum amount of output.
MU_DEBUG_PROT
Displays network protocol interaction, where applicable.

Two macros are available for creating level masks:

MU_DEBUG_LEVEL_MASK(n) creates a level mask for the level n. Such masks can be combined using binary 'OR', e.g.

 MU_DEBUG_LEVEL_MASK(MU_DEBUG_TRACE6) | MU_DEBUG_LEVEL_MASK(MU_DEBUG_PROT)

MU_DEBUG_LEVEL_UPTO(n) creates a mask containing all levels from MU_DEBUG_ERROR up to and including n.

The function mu_debug_level_p is provided to check whether a given level is set for a given category:

  int mu_debug_level_p (int category, int level);

The mu_debug macro

The mu_debug macro is the basic mechanism for implementing debugging output in programs:

 mu_debug(category, level, (args))

The first two arguments specify the category and level, and the third one must be an argument list suitable for the mu_debug_log invocation.

Note: args must always be enclosed in an extra pair of parentheses.

The macro tests if the level is set for the given category, and if so, it invokes mu_debug_log with the argument list (args). Additionally. if the mu_debug_line_info global variable is set, it arranges for the current source location to be shown in the output. For example, the following invocation:

 mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE1, 
           ("mbox_scan (%s) returned %d", mud->name, result));

will format the argument list given as its third argument if the mailbox.=trace1 level is set.

The mu_debug_log function family

The mu_debug_log functions are the mechanism used by the mu_debug macro to do the actual output:

  void mu_debug_log (const char *fmt, ...);
  void mu_debug_log_begin (const char *fmt, ...);
  void mu_debug_log_cont (const char *fmt, ...);
  void mu_debug_log_end (const char *fmt, ...);

All functions are variadic, with their argument fmt argument being a usual printf format specification[2], and the rest of arguments supplying the actual data to output.

The mu_debug_log function formats its arguments, and then sends the formatted data terminated with a newline character to the mu_strerr stream. Therefore, the fmt argument to this function must not contain terminating \n.

If you need to output debugging information in several chunks but want these chunks to form a single line on output, do as follows:

  1. Call mu_debug_log_begin. This function prepares the log stream for subsequent continuous output, formats its data and sends them down the stream as the first chunk.
  2. Call mu_debug_log_cont as much times as needed (perhaps within a loop) to format more parts of the output sequence. Neither mu_debug_log_begin nor mu_debug_log_cont add newlines to their output, so until now the output is not flushed.
  3. Send the lest portion of data using the mu_debug_log_end call. After formatting its data, the function sends a terminating newline, thereby flushing the formatted output to the log stream.

You can also terminate the line with a call to mu_debug_log_nl() function, if there is no more data left for output:

  void mu_debug_log_nl (void);

The following code fragment illustrates this approach.

  if (mu_debug_level_p (MU_DEBCAT_FILTER, MU_DEBUG_TRACE1))
    {
      int i;

      mu_debug_log_begin ("Command line was:");
      for (i = 0; argv[i]; i++)
        mu_debug_log_cont (" %s", argv[i]);
      mu_debug_log_nl ();
    }

This code checks if the level filter.=trace1 is set, and if so formats the array char **argv into the log output stream.

The mu_strerr stream

The stream mu_strerr is a Mailutils counterpart of the traditional stderr stream, except that it provides much more possibilities. All writes to this stream are directed to whatever log transport stream was specified in the Mailutils configuration file.

The stream is line-buffered, so the output gets flushed after each \n character written to it. Upon writing to the transport stream, a diagnostic prefix is output before each lineful of data. The prefix consists of:

  1. The value of mu_log_tag variable, followed by a colon and space character;
  2. Input stream location, if it is set;
  3. Optionally, if mu_log_print_severity is set, the severity designator, such as debug for debugging information, warning for warnings, etc.

Mu_strerr is an instance of log stream, and supports all ioctls and format directives supported by it. For example, to set location you can either use the MU_IOCTL_LOGSTREAM ioctl, or (to set it temporarily, for this line of output only), the f and l log escapes, as shown in the example code below:

  mu_stream_printf (mu_strerr, "\033f<%d>%s\033l<%d>%s", strlen (file), file, line, 
                    "bad input")


Notes