Open code review: g3log

by Max Galkin

This is one of the “open code review” posts, where I publish my notes after looking through and playing with one of the open source C++ projects. My main goal here is to become a better coder by learning from the experience of other developers, and my secondary goal is to build a mental map of the tools and frameworks available “out there” to not reinvent the proverbial wheel, should I ever need one. The blog post expresses my personal opinion, not affiliated, endorsed, sponsored, etc. I am not arguing for or against the usage of any specific open source library. I will be grateful if you take time to point out any misunderstanding I might have.

Today I looked into KjellKod/g3log, an asynchronous logger “with dynamic sinks”.

g3log was inspired by Google’s glog library, but, while it feels similar API-wise, it fixes a significant flaw in glog: namely, g3log is asynchronous. Log requests in g3log are put into a queue and flushed to disk by a background thread. This alone makes the library much more attractive than glog, in my opinion. Typically, people write in C++ because they want performance, and blocking disk IO calls in the middle of some intensive computation just do not feel right.

You can find more details about the logger and the sample usage on the project page or in Kjell’s blog. Here is a quick example, the syntax is quite straightforward:

LOG(INFO) << "streaming API is as easy as ABC or " << 123;
LOGF(WARNING, "Printf-style syntax is also %s", "available");

int less = 1;
int more = 2;
LOG_IF(INFO, (less < more)) << "If [true], then this text will be logged";
// or with printf-like syntax
LOGF_IF(INFO, (less < more), "if %d<%d then this text will be logged", less, more);


I cloned the source code from GitHub, and built it without any trouble: Windows with VS 2015 CTP6, CMake 3.1.3, they’ve only recently added support for VS2015 in CMake. The cmake command line I used to get the VS solution was:

cmake -G "Visual Studio 14 2015 Win64" -DUSE_G3LOG_UNIT_TEST=ON 
-DCMAKE_BUILD_TYPE=Release ..\..\g3log

I had to unpack gtest in g3log/3rdparty/gtest/ folder.

All the unit tests passed too (even though the comment in make files say that they were only tested on linux). It seems that the last time this project was built on Windows before VS 2013 was released, and so some code is disabled conditionally on Windows, I tried to enable some pieces and they worked too. The library relies on C++11 futures, atomics, mutexes, smart pointers, and timers, many of which were not available before VS 2013. Also, you will notice that many entities (namespaces, classes, files, …) in the library are still named “g2” or “g2log”, because the project was only recently renamed to “g3log”, unfortunately, renaming is not an easy task for many codebases.

The code is quite modern, and has enough comments to read it without much trouble, the only concerning thing that caught my attention is the MoveOnCopy class. It turns a non-copyable movable thing into a thing that does move-on-copy. This immediately reminded me of the deprecated auto_ptr and all the problems associated with it. Yes, you can put it into a standard container technically, in the sense that it will pass the compiler type check, but it is still quite dangerous semantically, because standard containers are not constrained in the number of copies they make, and they consider all copies to be equivalent and so, for example, some implementation of sort can be broken for a container of such objects, and potentially other operations too.


   // A straightforward technique to move around packaged_tasks.
   //  Instances of std::packaged_task are MoveConstructible and MoveAssignable, but
   //  not CopyConstructible or CopyAssignable. To put them in a std container they need
   //  to be wrapped and their internals "moved" when tried to be copied.

   template<typename Moveable>
   struct MoveOnCopy {
      mutable Moveable _move_only;

      explicit MoveOnCopy(Moveable&& m) : _move_only(std::move(m)) {}
      MoveOnCopy(MoveOnCopy const& t) : _move_only(std::move(t._move_only)) {}
      MoveOnCopy(MoveOnCopy&& t) : _move_only(std::move(t._move_only)) {}

      MoveOnCopy& operator=(MoveOnCopy const& other) {
         _move_only = std::move(other._move_only);
         return *this;
      }

      MoveOnCopy& operator=(MoveOnCopy&& other) {
         _move_only = std::move(other._move_only);
         return *this;
      }

      void operator()() { _move_only(); }
      Moveable& get() { return _move_only; }
      Moveable release() { return std::move(_move_only); }
   };

   // ...
   // Later this class is used for example as follows:
   typedef MoveOnCopy<std::unique_ptr<FatalMessage>> FatalMessagePtr;
   typedef MoveOnCopy<std::unique_ptr<LogMessage>> LogMessagePtr;