3

I've a singleton logger class which will be used to write data into a single file and I'm just wondering how to handle the ofstream object in case of application crash.

#ifndef LOG_ERROR_H_
#define LOG_ERROR_H_

#include <iostream>
#include <thread>
#include <mutex>
#include <string>
#include <fstream>
#include <memory>

namespace batch
{

  class ErrorLogger
  {

  public:
    ErrorLogger(const ErrorLogger &) = delete; 

    static ErrorLogger& Instance(std::string filename_)
    {
      static ErrorLogger error_instance(filename_);
      return error_instance;
    }
    

    void WriteLine(std::string content)
    {
      try
      {
        std::lock_guard<std::mutex> lk(error_stream_mutex);
        error_stream << content << std::endl;
      }
      catch(std::runtime_error const& e)
      {
        //cout
      }
    }

    ~ErrorLogger()
    {
      error_stream.close(); //Destructor is not getting called, should I explicitly call it?
    }

  private:
    std::ofstream error_stream;
    std::mutex error_stream_mutex;

    ErrorLogger(std::string filename) 
    {
        error_stream.open(filename);

        if (error_stream.fail())
        {
          throw std::iostream::failure("Cannot open file: " + filename);
        }
    }
  };
}

#endif

What I tried is instead of keeping the ofstream object open always, instantiate once and then open, close during writing but let's assume a scenario where I get the instance and the ofstream object is initialized and before calling WriteLine(), the application crashed then how should I handle the ofstream object?  

ErrorLogger(std::string filename) 
{
    error_stream(filename);
    if (error_stream.fail())
    {
        throw std::iostream::failure("Cannot open file: " + filename);
    }
}
void WriteLine(std::string content)
{
    try
    {
    error_stream.open(filename);
    if (error_stream.fail())
    {
        throw std::iostream::failure("Cannot open file: " + filename);
    }
    std::lock_guard<std::mutex> lk(error_stream_mutex);
    error_stream << content << std::endl;
    error_stream.close();
    }
    catch(std::runtime_error const& e)
    {
    //cout
    }
}    

So the question is how to properly handle error_stream (in a positive scenario as well as application crash scenario) i.e when should I close it, right now it is not getting closed.

Aamir
  • 1,974
  • 1
  • 14
  • 18
m_alpha
  • 134
  • 13
  • 2
    It is unclear what problem you are trying to solve. – YSC Apr 12 '23 at 08:45
  • 2
    OT: `error_stream.open(filename);` will truncate the current file, not start appending to it. – Some programmer dude Apr 12 '23 at 08:46
  • @YSC In my 1st example the ofstream object is always open and never gets closed so I'm just asking when should I close it?. (In case of application crash and also incase of normal scenario) – m_alpha Apr 12 '23 at 08:48
  • 4
    As for the problem of a crashing application, there's really nothing you can do in your own program. An actual crash (as opposed to a thrown and unhandled exception) is almost impossible to catch, and if it is then the state of the program is indeterminate and you can't trust *any* data in the program, not even the file states. Just let it crash, and figure out a way to solve the actual crash instead. – Some programmer dude Apr 12 '23 at 08:48
  • As for when the program doesn't crash, then open the log file as early as possible and close it just before `main` returns. – Some programmer dude Apr 12 '23 at 08:49
  • 2
    Why do you think the file isn't getting closed? All open files are closed when a program exits. – molbdnilo Apr 12 '23 at 09:01
  • 1
    Just open the log file once and maybe close it explicitly at the end of main. After each write to the log file call `std::ostream::flush` which will ensure the log data will actually be written into the file. That's all. – Jabberwocky Apr 12 '23 at 09:39
  • 1
    The only reason (that I can see) to close and re-open the log file would be to allow log rotation, but in that case it should be done explicitly in response to some signal sent to your application by the log rotation software. In the case of a crash there's not much you can do and the OS will close the file (just as it'll release all memory and free other resources in use by your application (with a few exceptions, that are not relevant here, like SysV shared memory)). Just make sure to regularly `flush` the stream and you'll be good (or as good as possible). – Jesper Juhl Apr 12 '23 at 10:19
  • The ultimate crash is the user pulling the power from the computer and there's nothing you can do about that. – Jesper Juhl Apr 12 '23 at 10:47

1 Answers1

4

If the application crashes, the operating system will close all files that were held open by your process, so you do not need to do anything about that scenario. (Not to mention that you can't really do anything.)

While the application has not crashed (yet) you can keep flushing the file using std::flush (see https://en.cppreference.com/w/cpp/io/manip/flush) instead of closing it and then re-opening it; it is bound to perform better.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • 1
    Suggestion: Link to https://en.cppreference.com/w/cpp/io/manip/flush and/or https://en.cppreference.com/w/cpp/io/basic_ostream/flush - don't link to cplusplus.com, it's pretty much garbage compared to cppreference.com – Jesper Juhl Apr 12 '23 at 11:24
  • 1
    @JesperJuhl thanks for the suggestion, I followed it. – Mike Nakis Apr 12 '23 at 11:27
  • @MikeNakis so I should flush in between and not close and re-open is fine when do I release the ofstream object i.e error_stream.close() in destructor and calling destructor explicity before main return is fine or do u suggest something else? – m_alpha Apr 12 '23 at 13:13
  • 1
    yes, flush after each write, do not close and re-open, and finally closing the stream is entirely optional, but if you really want to do it, then yes, at the end of the scope, which would be before `Main()` returns. However, if something somehow manages to execute after `Main()` returns, (I don't know, some other thread, some hook, some timer, whatever) and if that something tries to log, it will fail. So, we usually do not close log files. – Mike Nakis Apr 12 '23 at 13:33