Dimitri C.
Dimitri C.

Reputation: 22526

How to log exceptional situations in C++?

When writing a function, my implementation very frequently looks like this:

A crucial part is the logging. Every function that fails should add a short description to the log. This way, at the level where the exception is handled, the user can be shown a detailed error message.

For example, consider an application where a new user account can be created, and there is a problem with the database connection. The following inverse stack trace results:

Using the exceptions feature, I would implement this as follows:

void CreateUser()
{
    try {
        OpenDatabaseConnection();
    }
    catch(std::exception& e) {
        e.AddLog("Failed to create the new user");
        throw;
    }
    //...
}

Using a simple return value, I'd write the following:

bool CreateUser(Log& log)
{
    if (!OpenDatabaseConnection(log))
    {
        log.Add("Failed to create the new user");
        return false;
    }
    //...
    return true;
}

I find both implementations equally good. Therefore, I don't see much advantage in using exceptions. I am well aware that exception handling is generally considered a useful feature, but I don't really understand why. A long time ago, I used exception handling extensively, but I didn't see the big advantage, so now I never use them anymore. Hence my questions:

Note: I use the term logging as "collecting an explanation of what went wrong, so it can be presented to the user later on". I'd rather not store that explanation in a global collection of log messages (in memory, in a file or in a database), as it directly describes the specific exception.

Update: Thanks for you responses so far. I understand that exceptions are only useful if the user doesn't need detailed feedback on what went wrong. (Please correct me if I misinterpreted this.)

Upvotes: 3

Views: 2360

Answers (7)

Staseg
Staseg

Reputation: 101

Exceptions are using in only extreme situations. Execution of exception is too slow. For log not great errors try use return value.

Example:

int someMethod{
    if(erorr_file_not_found){
        logger.add("File not found");
        return 1;
    }

    if(error_permission){
        logger.add("You have not permissons to write this file");
        return 2;
    }

    return 0;
}

In this case you can print error and process this error on higher level.

Or (more complex):

int someMethod{
    int retval=0;
    if(someshit){
        retval=1;
        goto _return;
    }
    //...
    _return:
    switch(retval){
        case 1:logger.add("Situation 1");break;
        case 2:logger.add("Situation 2");break;
        //...
    }
    /*
    close files, sockets, etc.
    */
    return retval;
}

This way is more hard but most fast.

Upvotes: 1

Tobias Langner
Tobias Langner

Reputation: 10828

Exception handling removes error handling from the normal control flow. This way, the code structured more clean. Exception handling also unwinds the stack automatically. This way, you need not to include error handling code in each method called on the way to the error. If you need one of those features, go with exceptions. If you don't, use error-codes or any other method because exceptions have costs (computing time) even if they are not thrown.


Additional answers to your comment. Imagine a code, that calls several functions that may fail.

procedure exceptionTest(...)
{
  try
  {
    call1();
    call2();
    call3();
    call4();
  } 
  catch (...)
  {
    //errorhandling outside the normal control flow
  }
}

without exception:

procedure normalTest(...)
{
   if (!call1())
   {
     //errorHandling1
   } 
   else if (!call2())
   {
     //errorHandling2
   }
   else if ....
   ...
}

As you can easily see, the normal control flow is disrupted with error handling. Compared to this code, the code using exceptions is easier to read.

If you need to add error handling in each method you call, exceptions may not provide benefits. But if you have nested calls that each may generate errors, it may be easier to catch the exception at top level. That's what I meant. It is not the case in your example, still it's good to know where to benefit from exceptions.

Upvotes: 1

Sebastian
Sebastian

Reputation: 4950

I would propose to separate error handling from logging and from the user interaction.

  1. Every method can write to log files for itself. With a small log message framework, methods can output debug, informational and error message. Depending on the context your applications runs in defined by a config file, e.g., only critical error messages are actually written.

  2. Especially in networking applications, connection failures can always occur and are not exceptional. Use exceptions for unexpected errors that should not occur or that occur only rarely. It can also make sense to use exceptions internally if you need e.g. the stack unrolling feature:

    void CreateUser() {
        try {
           CDatabaseConnection db = ConnectToDatabase(); 
           InsertIntoDB(db, "INSERT INTO ... "); 
           SetPermission(...);
        } catch(...) {}
    }
    

    If InsertIntoDB throws an exception because the network connection is lost again, object CDatabaseConnection will be destroyed and SetPermission is never run. Using this can lead to better code.

  3. The third thing you want to do is give the user feedback in an interactive application. That's a whole different thing. Let your internal methods return enumerations of possible error codes eerrorCONNECTIONLOST, eerrorUSERNAMEINVALID, etc Don't return the error strings from core methods. The user interface layer should bother which strings to display (possibly internationalizing them). Internally, the error codes will be much more useful. You could e.g. retry five times if your login method returned eerrorCONNECTIONLOST.

Upvotes: 0

Nemanja Trifunovic
Nemanja Trifunovic

Reputation: 24561

Depending on your circumstances, you may be able to log from a constructor of your exception (maybe asynchronously) That way your code would look like:

void CreateUser()
{
      OpenDatabaseConnection();
}

Of course, you would need to throw your custom exception from OpenDatabaseConnection().

I worked on two projects when this strategy was used with success.

Upvotes: 0

John Feminella
John Feminella

Reputation: 311735

I think a big case for using exceptions here is that you've now made logging part of your method signatures. In general, I don't think that should be the case, because it's a cross-cutting concern. Imagine trying to do an analogous thing with user permissions, for example. Are you going to write this everywhere?

bool CreateUser(Log& log, UserSecurityContext& u) {
    if (!HasPermissionsFor(u, SecurityRoles::AddUser)) {
        log.Add("Insufficient permissions");
        return false;
    }
    //...
    return true;
}

There are other reasons to want to use exceptions (see Elemental's answer), but anytime the non-use of a language feature impacts the design of your software, it's worth thinking about whether that was the right way to do it.

Upvotes: 2

T.E.D.
T.E.D.

Reputation: 44814

If you always want to handle your exceptional conditions immediately after the call, then there is no real advantage.

The advantage comes when you want to handle the condition several layers up the call chain. To do that with your success flag, you'd have to bubble the flag up several layers of subroutine calls. Every layer would have to be written with the knowldege that it has to keep track of the special flag from way down in the bowels of the code. This is just a major primo PITA.

For instance, for realtime work we typically build our applications around an iteration loop. Any error during the loop generally just aborts that iteration of the loop (excepting "fatal" errors, which abort the entire app). The easiest way to handle this is to just throw exceptions from wherever they occur, and handle them all in their own catch blocks at the very outermost of the application.

Upvotes: 2

Elemental
Elemental

Reputation: 7521

Your strategy seems to avoid the most useful aspect of exceptions, you can throw an exception class which already has the text of the log information in it - that is generate the text for the log at time the exception is thrown not at the time the exception is caught. Then you don't have to catch at every level going up the stack, but only at the top level.

Thus only one try block, and one log.add - much less code in general. Something like this seems to remove all your replication.

void OpenDatabaseConnection()
{
   if (Error) throw MyException("Failed opening database");
}

void CreateUser()
{
    try {
        OpenDatabaseConnection();
        //...... do everything here
    }
    catch(MyException& E) { //only one copy of this code
        E.AddLog(E.getMessage());
        throw;
    }
}

Upvotes: 5

Related Questions