Asynchronous thread-safe logging in C++
Asked Answered
L

6

20

I'm looking for a way to do asynchronous and thread-safe logging in my C++ project, if possible to one file. I'm currently using cerr and clog for the task, but since they are synchronous, execution shortly pauses every time something is logged. It's a relatively graphics-heavy app, so this kind of thing is quite annoying.

The new logger should use asynchronous I/O to get rid of these pauses. Thread-safety would also be desirable as I intend to add some basic multithreading soon.

I considered a one-file-per-thread approach, but that seemed like it would make managing the logs a nightmare. Any suggestions?

Lattimore answered 28/5, 2010 at 15:24 Comment(0)
J
20

I noticed this 1 year+ old thread. Maybe the asynchronous logger I wrote could be of interest.

http://www.codeproject.com/KB/library/g2log.aspx

G2log uses a protected message queue to forward log entries to a background worker that the slow disk accesses.

I have tried it with a lock-free queue which increased the average time for a LOG call but decreased the worst case time, however I am using the protected queue now as it is cross-platform. It's tested on Windows/Visual Studio 2010 and Ubuntu 11.10/gcc4.6.

It's released as public domain so you can do with it what you want with no strings attached.

Jenelljenelle answered 26/11, 2011 at 12:34 Comment(5)
The usual thing I do if logging mustn't lead to any blocking (e.g. debugging race conditions with prints.. hey it works and is simple!) is to use a thread local message queue that stores tokens with timestamps (well in this case probably better output it directly with async IO). If you need the data, you just have to sort the output later on and it's rather cumbersome for general logging, but it has the lowest possible synchronization amount: nil.Jeri
What is important is of course not only to be thread safe., and minimum blocking but also to make sure that the log entries are on file if the software would crash (segmentation fault, floating point error etc). G2log provides that by using a signal handler, in case of a "fatal signal" it flushes the queued entries to file before the signal continues (and aborts the program)Ressler
@KjellHedström - follow the steps in this link stackoverflow.com/help/user-merge to get your accounts merged.Masters
Thanks Chris. I deleted my other "1 point" account which was obsolete.Ressler
By now there are several asynchronous, thread safe, logging libraries. Several are faster than g2log. The successor of g2log is called g3log github.com/KjellKod/g3log with additional community sinks located at github.com/KjellKod/g3sinksRessler
W
4

This is VERY possible and practical. How do I know? I wrote exactly that at my last job. Unfortunately (for us), they now own the code. :-) Sadly, they don't even use it.

I intend on writing an open source version in the near future. Meanwhile, I can give you some hints.

  1. I/O manipulators are really just function names. You can implement them for your own logging class so that your logger is cout/cin compatible.
  2. Your manipulator functions can tokenize the operations and store them into a queue.
  3. A thread can be blocked on that queue waiting for chunks of log to come flying through. It then processes the string operations and generates the actual log.

This is intrinsically thread compatible since you are using a queue. However, you still would want to put some mutex-like protection around writing to the queue so that a given log << "stuff" << "more stuff"; type operation remains line-atomic.

Have fun!

Wellknown answered 28/5, 2010 at 15:33 Comment(7)
Wouldn't a mutex mean that if one thread is already trying to write, it'd have to spin until it was unlocked? How does this help?Lattimore
I would certainly not delay the conversion of the object into the stream. Putting it in another thread means having to synchronize it... and that's likely to hurt as hell.Speos
@Lattimore - not spin, but block. But if you're not doing string processing it is a VERY short interval to load the queue. The string processing is done by the logging thread. @Matthieu, doing the string processing in real time hurts a lot more than any mutex blocking while loading a queue.Wellknown
I need the log message to be passed immediately for an async write, and any kind of waiting by my threads would probably cause the annoying pauses, again.Lattimore
doing the string processing asynchronously causes issues of thread safety on all the logged objects, correctness comes before performance, and the issue here is not blocking on the queue but IO slowliness.Speos
In order to avoid blocking, you could use a lock-free queue, like this one.Superfluity
In my small asynchronous logger I have a vector of messages, add method, which does lock mutex->add message->unlock. Separate thread accesses vector every second. It locks mutex, then if vector is not empty it does local_vector.swap(vector) then unlocks mutex and does message formatting and file output. So no blocking, vector.swap() is very fast for any element count.Cream
F
1

I think the proper approach is not one-file-per-thread, but one-thread-per-file. If any one file (or resource in general) in your system is only ever accessed by one thread, thread-safe programming becomes so much easier.

So why not make Logger a dedicated thread (or several threads, one per file, if you're logging different things in different files), and in all other threads, writing to log would place the message on the input queue in the appropriate Logger thread, which would get to it after it's done writing the previous message. All it takes is a mutex to protect the queue from adding an event while Logger is reading an event, and a condvar for Logger to wait on when its queue is empty.

Framboise answered 28/5, 2010 at 15:52 Comment(2)
You can get away without using a mutex with a lock-free queue. The conditional variable is much more different though. Depending on the charge, you might prefer spinning rather than go to sleep and be awaken later. It also depends on whether or not you have idle cores :)Speos
If something busy waits in our system, watchdog will kill the whole application :)Framboise
S
1

Have you considered using a log library.

There are several available, I discovered Pantheios recently and it really seems to be quite incredible.

It's more a front-end logger, you can customize which system is used. It can interact with ACE or log4cxx for example and it seems really easy to use and configure. The main advantage is that it use typesafe operators, which is always great.

If you just want a barebone logging library:

  • ACE
  • log4c*
  • Boost.Log

Pick any :)

I should note that it's possible to implement lock-free queues in C++ and that they are great for logging.

Speos answered 28/5, 2010 at 15:53 Comment(1)
Hm, I don't think I want to bring in any of those. The former two seem like overkill, and the latter had a lot of performance issues in its review, which was one of the reasons they didn't add it to the Boost collection proper.Lattimore
S
0

I had the same issue and I believe I have found the perfect solution. I present to you, a single-header library called loguru: https://github.com/emilk/loguru

It's simple to use, portable, configurable, macro-based and by default doesn't #include anything (for that sweet, sweet compilation times).

Spartacus answered 29/9, 2017 at 3:59 Comment(0)
K
0

There's an MIT-licensed C++14-based library to do this:

https://github.com/odygrd/quill

Kawasaki answered 8/11, 2023 at 0:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.