Localized Error Reporting, Logging and Exception Handling

 

Alexander Liss

 

06/28/04

 

 

There are many styles of Error Reporting, Logging and Exception Handling and there are various facilities supporting them.

One type of facilities is centralized; it is well known and it has many supporters. Its benefits in a case of small RAM (when it is desirable to store error messages separately in a large durable memory) or in a case of operating an application consisting of a set of processes written in different languages are obvious. However, here we advocate for localized Error Reporting, Logging and Exception Handling.

Centralized approach to Error Reporting, Logging and Exception Handling often simplifies initial development, because it provides ready facilities. However, it makes incremental development and especially software update increasingly more difficult, because updates of Error Reporting, Logging and Exception Handling require switches to different views of the system in development and requires coordination with other specialists. This interferes with developers' focus and introduces mistakes and delays.

Systematic localized approach simplifies incremental modification of software.

 

 

Wrappers

Localized approach starts with software wrappers.

Often, a wrapper is a class or a group of classes providing a logically organized, easy to understand and memorize interface to some functionality related to hardware, operating system, network etc. This functionality is provided by some low level libraries, and a wrapper translates their interface.

Any wrapper adapts functionality provided by low level libraries to development situations, which it serves. Such adaptation includes selection of a subset of services recommended for use in these development situations, reorganization of a set of possible error states and their descriptions.

Usually, wrappers do not log errors; they provide Error Reporting. In wrappers, there is a local to wrapper enumerator of error states and a wrapper returns a value from this enumerator in case of an error. In addition, it provides a string-description of the error.

Note that sometimes, maintainers of libraries used by the wrapper add new error states, which are described only in an error string, which could be extracted in the case of error. This forces a wrapper developer to define a generic error state, for example "see_string".

Errors, which happens during initialization (in constructor), should be reflected in a wrapper's "status".

A Wrapper class looks as:

 

class Wrapper

{

enum Status

{ok, };

enum ErrorState

{none, see_string, };

 

int status;

 

int error_string(string&, ErrorState );

 

 

};

 

 

Logging

 

Higher layers of functionality have to provide logging. At this point, developers run into a problem - different modules of software have to log into the same log facility.

There is no universal logging facility (beyond syslog), and developers tend to choose some logging facility or develop one for a project and use it throughout a project. This leads to not reusable and not portable code.

It is better to provide own logging interface for each software module, which could be implemented using a project's logging facility.

This could be implemented as follows.

For a class Aaa, there is a class AaaLogger, which provides such interface:

 

class AaaLogger

{

public:

 

virtual int log_event(const char*)const;

 

};

 

class Aaa

{

public:

 

int set_logger(AaaLogger&);

 

private:

AaaLogger *logger;

};

 

In an application, there is a logger class derived from AaaLogger, where its virtual function log_event() is implemented using project's logging facility. An instance of this class is passed to an instance of the class Aaa.

It could be a dedicated instance of logger class for an instance of the Aaa class. In this case, it is reasonable to create it on heap and let destructor of Aaa class delete it.

It could be one instance of a logger class for a few instances of the Aaa class. In this case, this instance should be deleted independently after all instances of the Aaa class, which are using it, are deleted.

Different instances of Aaa class could use different logger classes.

An additional benefit of such logging interface is flexibility of modification of logging related to a particular software module. Often, this modification could be localized to the implementation of logging interface used by this module.

 

 

Exception Handling

 

Exception Handling is a convenient tool, which is often overused. A problem arises when the logic of exceptions throwing and catching breaks modular and layered structure of code.

This problem is not visible in small applications used to demonstrate Exception Handling, however one becomes painfully aware of it, when one has to debug a large event driven or multithreaded application. It is such a big problem that developers of multithreaded applications tend to avoid using Exception Handling all together. This is not always possible though, because STL uses Exception Handling.

The solution is in cautious use of Exception Handling: any exception thrown in a software module should be caught in the same module. If a class is viewed as a software module, then its public member functions should not throw exceptions. If a group of classes is viewed as a software module, then public member functions of classes representing interface of this group should not throw exceptions.

Exception Handling is a convenient tool for dealing with problems occurring in constructors. However, constructors of classes serving as interface to a software module should not throw exceptions. Each such class, should have a special attribute "status", which is checked by an application immediately after an instance construction.

With this recommendation, each STL object should be used inside some software module, which catches all exceptions STL object could throw.