5

I've created a simple logger which, well, logs everything of importance into a text file. I'm using std::ofstream but there's a problem - when the program doesn't close the file (call std::ofstream::close()) for whatever reason (like crash), the created log is actually empty (0 size). And since the log is most useful in exceptional situations (when something goes wrong) - you see the problem.

Is there any way to protect my log from that? I could try closing the file after writing every couple of lines and using append - but that still doesn't protect me from the situation when the program crashes in the middle of logging/before closing the file. Is there any solution or am I just doomed?

NPS
  • 6,003
  • 11
  • 53
  • 90
  • If it is mission critical information I would just open and close the log on each write. If the log would grow over time then you could employ a rolling log type solution with a "capped" size. – Ross Bush Apr 28 '13 at 21:20
  • @KendrickLamar But when I open (append) already created file to add something and never close it (e.g. crash) the file still gets corrupted. – NPS Apr 28 '13 at 21:25
  • An error log should never be extremely Huge. That's why I suggested opening the file prior to writes and then closing the file afterwards. Is threading an issue her? – Ross Bush Apr 28 '13 at 21:28
  • @KendrickLamar What are you talking about? I'm saying that if I open the file (call `std::ofstream::open()`) and fail to close it (for whatever reason) then the result (physical) file has the size of 0 (and I lose all the log information). – NPS Apr 28 '13 at 21:32
  • Thanks for clarification. Then your logging needs exception handling. File writes should be pretty plain/jane and never jack up your application code. If you are worried about crashing during a log write then I would suggest using shadow files. What os are you using? – Ross Bush Apr 28 '13 at 21:36
  • @KendrickLamar Windows (7 64-bit) What are shadow files? – NPS Apr 28 '13 at 21:40

2 Answers2

4

You should employ flush method, it is there to precisely solve problems like the one you're facing.

There is another approach which can be considered more safe, but requires substantially more effort to implement and test. The approach boils down to what is known as inter-process communication (IPC). In brief, you could implement your logger as a separate logger application which would communicate with your target application by means of specific protocol (interface). You can develop such protocol by yourself or employ one of the existing ones (which are usually very generic, i.e. general purpose). As a result, if your target application crashes, it doesn't drag logger application with it, and therefore logger can safely finish its job.

That approach is usually used by some huge, complex, and safety-critical systems. However, I guess that in your case this is definitely an overkill and bare flush() after each append is more than enough.

Alexander Shukaev
  • 16,674
  • 8
  • 70
  • 85
  • Always knew it but never thought it actually does **this**. :P But what happens if I `flush()` but don't `close()`? – NPS Apr 28 '13 at 21:49
  • Nothing, the contents which are _flushed_ are already there in the file (physically). In other words, if you forget (or can't) to `close()`, only the information **after** the last `flush()` can be lost. – Alexander Shukaev Apr 28 '13 at 21:53
  • IPC not only is an overkill but it actually doesn't solve the problem at all - as the logging process might crash just as well (and the same unsaved data problem occurs). – NPS Apr 28 '13 at 21:59
  • 1
    No, it cannot crash as long as it's well-tested lightweight application which provides bare logging facilities, and no exotic/hardcore business logic, algorithms, etc. (the ones which are usually the subject to crash). By the way, it didn't imply that logger application doesn't use `flush()` facility itself, of course it does. – Alexander Shukaev Apr 28 '13 at 22:01
  • You can only keep the risk to minimum - theoretically even the simplest application can always have bugs and in extreme situations even crash. Or let's say I have some other evil process in the system that kills my logging process just for fun. :P – NPS Apr 28 '13 at 22:05
  • Yes, sure if we go to such extreme cases, then my mom could also come to PC and cut the power cable with scissors, yelling at me to go to bed. Not only the logger would crash, but if I'm incredibly lucky I could end-up reinstalling OS and/or buying new hardware. So it's good that I live alone now. `;)` – Alexander Shukaev Apr 28 '13 at 22:08
  • Yes but to be serious - the more I can get my code fireproof the better, I think. – NPS Apr 28 '13 at 22:12
  • I've also posted a related question (for anyone interested): http://stackoverflow.com/questions/16268433/what-does-stdofstreamclose-actually-do – NPS Apr 28 '13 at 22:12
2

In our commercial application, we had a very robust solution for that. The price was non-portability.

We installed a Vectored Exception Handler. This handler is called in case of an unhandled OS exception, before the program exits (which is by far the most common crash). When it's called, you can not call C++ Standard Library function anymore (or even C functions). Even fflush is unreliable.

Yet it is OK to call basic OS functions. Be careful, though. Setting up the arguments for the OS call shouldn't use malloc either. Have the name of the crashfile already set up before the crash actually happens, etc. Do close the file immediately, again using OS functions.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • >Do close the file immediately, again using OS functions. *Reply*: How to achieve this goal by non-OS functions? – John Aug 28 '21 at 06:39
  • >installed a Vectored Exception Handler. *Reply*: Could you please show me a simple demo snippet to fully understand how it works? – John Aug 28 '21 at 06:41
  • @John: MSDN has [an example](https://learn.microsoft.com/en-us/windows/win32/debug/using-a-vectored-exception-handler). And I'll repeat my warning: non-OS functions must not be called after the exception happens. `CreateFile` and `WriteFile` are generally OK. No need for `flush`, call `CloseFile`. – MSalters Aug 30 '21 at 07:07
  • Thanks for your reply. Could you please explain in more detail about why **non-OS** functions **must not** be called after the exception happens? – John Aug 30 '21 at 07:18
  • @John: It's unspecified how those functions rely on their runtime, and in general your process state is wildly unknown. The runtime can be entirely broken - it's not uncommon for the Vectored Exception to happen in response to heap corruption, or buffer overflows, or a host of other state-corrupting errors. – MSalters Aug 30 '21 at 07:33
  • So, **OS** functions do not have the said problems? – John Aug 30 '21 at 07:45
  • @John: Much less so. However, do use common sense. Call `CreateFileW`, not `CreateFileA` (if you weren't already using Unicode). But that's part of what I said earlier - make sure that you have the filename ready before the crash happens. – MSalters Aug 30 '21 at 07:59
  • I see, thank you. Do you think `flush()` should be called every time when writing every single message to prevent data loss which is caused by the program suddenly crashes. – John Aug 30 '21 at 08:02