Hi. I am wondering if a log file can be created in a header which is associated with a try-catch block. Below is my stripped down code for the log_header.h and log_test.cpp

Here is log_header.h

#include<fstream>
#include<iostream>
using namespace std;

class thrower{
    //fstream myfile2;
public:
    void errormessage();
};

void thrower::errormessage(){
    cout<<"Thrower works\n";
    ///////These are log commnands////////
    //myfile2.open("C:\\error2.txt",ios::out);
    //myfile2<<"Message 2\n"<<endl;
    //myfile2.close();
    ////////////////////////////////////////
};

Here is log_test.cpp

#include"log_header.h"
#include<fstream>

#include<iostream>
using namespace std;

thrower t;
int main(){

    fstream myfile;

    try{
        int y=4;
        if (y<5){
            throw t;
        }
    }
    catch(thrower tt){
        tt.errormessage();
        myfile.open("C:\\error.txt",ios::out);
        myfile<<"Message \n"<<endl;
        myfile.close();
    }
    return 0;
}   

If I comment out all of the log_header.h lines that contain "myfile2", then the "myfile" object in the log_test.cpp gets updated and the error message from the errormessage method is printed. It would be more elegant if the log file creation was contained in the header. Please assist.

Will the header be used by more than one .cpp module?
If so, will that cause problems with the log file presence?

Will more than one programmer need to use your header?

The header file would be used for all future .cpp programs. Ideally, if the log file is maintained in the header then I would only have to call the errormessage() method in future catch blocks instead of calling the myfile object in each catch.

I think that your design is somewhat flawed. First of all, you require the catcher to know and require that the exception is one that is being logged, which you may not always want to do and may not be able to do in certain cases, leaving your catch-clauses very heterogeneous. Second, exceptions are common-place during the execution of a program, they aren't used only for completely erroneous occurrences, they are a mechanism to report out-of-the-ordinary conditions (and roll-back changes), which are sometimes quite common. Third, in your current implementation the thrower of the exception just throws a normal exception, and the catcher decides if the exception will be logged. I'm not sure that this is the best thing, but you can argue either way (that the thrower or the catcher should make that decision). Finally, as a general rule, you should keep the operations that occur during the propagation of an exception as simple as possible, to reduce the risk of a second exception being thrown, which would lead to an abort of the program (immediate termination). Also, opening and closing a file each time you want to log a message is too much stuff to do.

In light of these things, here is a somewhat more "professional" implementation of an exception-logger (while keeping it simple):

#ifndef EXCEPT_LOGGER_HPP
#define EXCEPT_LOGGER_HPP

#include <iostream>
#include <type_traits>

// Create a singleton class to access the logger's ostream.
class except_logger {
  private:
    std::ostream* destination;
    static except_logger& get_instance();
    except_logger(std::ostream& dest = std::cerr);
  public:
    static std::ostream& get();
    static void set(std::ostream& str = std::cerr);
};


// this is a class template to allow the thrower to specify that this exception
// can be logged if the catcher wants to:
template <typename Exception>
class allow_logging {
  private:
    Exception e;
  public:
    template <typename... Args>
    allow_logging(Args&&... args) : e(std::forward(args)...) { };

    // Create an implicit, logging-free, conversions to the exception type:
    operator const Exception&() const noexcept { return e; };
    operator Exception&() noexcept { return e; };
};

// this function template can be overloaded for specific types of exceptions.
template <typename Exception>
std::ostream& operator<<(std::ostream& out, const allow_logging<Exception>& e) {
  out << "Exception caught! Message reads: '" << e.what() << "'" << std::endl;
  return out;
};

// this is a class template to allow the catcher to demand that the exception 
// be logged upon catching it.
template <typename Exception1>
class demand_logging {
  private:
    Exception1& e;
  public:
    template <typename Exception2>
    demand_logging(
      typename std::enable_if< 
        std::is_convertible< Exception2&, Exception1& >::value,
        allow_logging<Exception2>& >::type al_e) : e(al_e) { 
      except_logger::get() << al_e;
    };

    operator const Exception1&() const noexcept { return e; };
};

#endif


// in the cpp file, implement the singleton:

#include "except_logger.hpp"

except_logger& except_logger::get_instance() {
  static except_logger inst;
  return inst;
};

except_logger::except_logger(std::ostream& dest) : destination(&dest) { };

// in actuality, you should protect those get/set functions with a mutex!

std::ostream& except_logger::get() {
  return *(except_logger::get_instance().destination);
};

void except_logger::set(std::ostream& str) {
  except_logger::get_instance().destination = &str;
};

The above is a very simple and crude example, but the main points are these:

  • The destination of the log messages should be a singleton, such that all catchers (from anywhere) can access a common destination for their log messages. Additionally, using the base-class ostream to refer to the output stream, you allow more flexibility in choosing any destination you want (cerr, a file, a string-stream, or anything else). Having one common and always opened logger makes the operations upon catching an exception much simpler and faster.
  • You must allow the thrower and the catcher to participate in the decision of allowing for logging the exception or not. In the scheme above, the thrower decides whether it is allowed to log, and the catcher decides if he wants the exception to be logged. But you could choose another scheme.
  • You can automate the logging of the exception through the implicit conversion that occurs between the thrown exception object (allow_logging<E>) and the caught object (demand_logging<E>). This leads to seemless syntax at the catch-site and at the throw site. As shown below.
  • You should allow for some mechanism, in the same fashion as the overloading of the iostream operators (>> and <<), for the implementation of specialized code to log specific types of exceptions, as I have included above.

Here are some simple uses of the above code:

int main() {

  try {
    throw allow_logging<std::out_of_range>("Out of range!");
  } catch(demand_logging<std::exception> e) {
    // exception has already been logged at this point.
  };

  try {
    throw allow_logging<std::out_of_range>("Out of range!");
  } catch(std::exception& e) {
    // no logging has been done at this point.
  };

  try {
    throw std::out_of_range("Out of range!");
  } catch(demand_logging<std::exception> e) {
    // will not reach this point!
  } catch(...) {
    except_logger::get() << "Unloggable exception encountered!" << std::endl;
    throw;
  };

  return 0;
};

Edited 4 Years Ago by mike_2000_17: added a bit of text to fix a format problem

Thanks. It does make sense to always keep the logger. The singleton approach makes perfect sense whereby allowing one common destination. It will take me some time to absorb all of this but I will get back if necessary. Thanks for your post.

This question has already been answered. Start a new discussion instead.