Error Logging System

From Second Life Wiki
Jump to navigation Jump to search
KBwarning.png This article is out of date!
The facilities here are still supported for backwards compatibility, but are not preferred: there are newer and richer macros described in Logging System Overview


This design deals with the error logging facilities in C++. These are the facilities supplied with llerror.h and llerrorstream.h. (llerrorbuffer.h is essentially private).

Goals

The error logging system aims at making logging much more flexible at run time. The decision to log or not is highly configurable, and controllable while running. One should be able to enable or disable messages based on source file, class, function, and level. This enables easier debugging as developers can turn messages on as needed to see what is going on without recompiling or restarting. It will also help in that messages which are no longer useful can be disabled and so not flood the logs.

A secondary goal is the regularization of all messages so that the class and function are printed out automatically at the start of all messages. This greatly aids in both find the source of messages, and in filtering log files.

Of course, if all message logging code is to be compiled in, another goal must be to make skipping suppressed log messages as efficient as possible.

Log Site

Efficiency is handled by having two small static bools generated at each log call site. These keep track of if we should log this call, and if we have made the expensive determination to do so yet. Thus, roughly:

   llinfos << "Hi there" << llendl;

will expand via the new #define definitions of llinfos and llendl to something like:

   {
       static bool _cached = false;
       static bool _should_log = false;
       
       if (!_cached)
       {
           _cached = true;
           _should_log = LLErrorSystem::shouldLog(LLErrorSystem::INFO, 
                   typeid(LOG_CLASS), __FUNCTION__, __FILE__, __LINE__, &_cached);
       }
       if (_should_log)
       {
           LLErrorSystem::output()
               << "Hi there" <<
               std::endl;
       }
   }

The function LLErrorSystem::shouldLog does the work of finding out if this function, file, line and/or level indicates that this message should or should not be logged. It remembers the pointer to the _cached variable so that LLErrorSystem can invalidate it (set it to false) if the logging configuration is ever changed (via a live file, or via HTTP).

Note: The actual expansion of those defines is smaller than the code shown, and both smaller and more efficient than the old expansion for those same defines.

Caveat: As a consequence, each message must immediately be terminated with llendl. For example, the following code would work with normal iostream but not with llinfos:

   for( i=... )
   {
       llinfos << i << " ";    // ERROR! llendl missing
   }
   llinfos << llendl;

Class Modification

In order to capture class information, the following line needs to be added to a class declaration:

   CLASS_LOG_TYPE(LLFoo);

which will expand to

   typedef LLFoo LOG_CLASS;

When added, logging from that class' functions will automatically get the class and function name prepended, and can be enabled or disabled by class or function. For existing classes, that don't have this, the logging functionality will stay the same, though they still can be enabled and disabled by source file.

Log Control

The logging setup is controlled by a live file. It is an LLSD/XML file that contains the list of what should be logged at which level. An example:

   <llsd>
   <map>
       <key>default-level</key>    <string>INFO</string>
       <key>print-location</key>   <boolean>false</boolean>
       <key>settings</key
           <array>
               <map>
                   <key>level</key><string>INFO</string>
                   <key>classes</key>
                       <array>
                           <string>LLPumpIO</string>
                           <string>LLIOPipe</string>
                           <string>LLBufferArray</string>
                           <string>LLChannelDescriptors</string>
                       </array>
               </map>
               <map>
                   <key>level</key><string>DEBUG</string>
                   <key>files</key>
                       <array>
                           <string>llbutton.cpp</string>
                           <string>llcheckboxctrl.cpp</string>
                           <string>llclipboard.cpp</string>
                           <string>llcombobox.cpp</string>
                           <string>llalertdialog.cpp</string>
                       </array>
               </map>
               <map>
                   <key>level</key><string>INFO</string>
                   <key>classes</key>
                       <array>
                           <string>LLHTTPNode</string>
                           <string>LLHTTPPipe</string>
                           <string>LLHTTPNodeForFactory</string>
                       </array>
               </map>
           </array>
   </map>
   </llsd>

Notice that one can have multiple sections with the same level. This facilitates having logical groupings in the control file and enabling them all at once. This convenience introduces ambiguity into the file, since a class or a file can appear more than once, at the same or different levels. The rule shall be last matching entry wins, but class is always considered before file.

This file is live, and so can be edited while code is running, and the logging will change within a few seconds. For the viewer, this file will be in app_settings.