I'm using a global list of Appender instances, the Appender is a implementation of a logging system like VS debug output, console output, file output, ... .
The logsystem is a small set of static functions which create jobs and enqueue them into a singleton thread pool.
I use this design because I have a time critical main loop and some logging systems are using system calls and additional string processing.
String formatting and system calls(console, file logging) take some time and with more intensive logging the peaks and overall load got relevant.
The string formatting is still an issue in debug build because it runs on the main loop but the lockfree queue outperforms every logging by far.
This design will be slower on a single core system(because of the thread pool job overhead and thread switch) but faster on a two or more core system.
The game server are running on 4 core systems with up to 5k clients logging and running the racing physic and validation of each client.
With the straight forward logging we only achieved around 2k clients.
The numbers I gathered in debug build and I stop adding bots if the main loop dropped below 60 FPS.
I use 4 channels Info, Error, FatalError and Debug but 5 logging functions.
The additional logging function is RF_IO::LogDebugBuild which will only do anything in Debug build.
If you use LogDebug in a release build it will do it's job but you can switch the channels on/off at runtime.
I did this because you have sometimes threading or logic bugs which occure in really rare cases in a release build.
You can enable the debugging log in a productive environment and if you found it you can simply turn it off without rebuild/deploy or restart the service.
https://github.com/tak2004/RadonFramework/blob/master/include/RadonFramework/IO/Log.hpp
https://github.com/tak2004/RadonFramework/blob/master/src/IO/Log.cpp
https://github.com/tak2004/RadonFrameworkExample/blob/master/code/console/console.cpp <- example