Is your Logger global?

Started by
18 comments, last by Paragon123 7 years, 2 months ago

Depends on the project like everyone sayd:

For all my games tech stuff i am creating, i just use a inline function in a separate header file for logging thats it, no global, no class - but i dont use this often.

Logging is great to store user environment states at initialization or write infos when a required asset are missing!

In a critical condition, like a font asset for measuring string sizes is missing or memory runs out, i log it and then let it crash forcefully.

Crashing is good, crashing makes you fix things. Logging only may not!

Advertisement

Personally I go crazy when I see teams who don't use logging. They'll typically tell me that they don't see a need for logs, while struggling on using the debugger to step slowly through problems, with occasional hand-rolled printfs when things are really crazy, and video capture of glitches that someone else has to work out how to reproduce, etc. Every game I've worked on that has logging from the start has been 100% easier to fix bugs on. I might take out new lines of logging after a bug is fixed if I don't think it helps much, but I always want that sort of system there, especially if it means designers and artists who see problems can send me a file that shows what happened on their machine before Things Went Wrong.

I like to use something like the Python system (although it is overly Java-like in some ways) - you get a global 'logger' object, which you can add named children to locally, so you get a tree of loggers. They all hang off the central logger object, but each one is local to a file or a subsystem, so you get the best of both worlds.

Ideally you want to configure the outputs at application level at startup time based on configuration values, which can be applied to the root logger. Past that point, the only 'global' access is to create individual sub-loggers.

Just be careful with it. The three are globally accessible objects, not singletons. As for the standard streams being near-universally useful, note that a good logging system can have an any number of logging listeners. That includes adding a listener that directs to stdout and stderr depending on severity or logging level.

You can also hang multiple outputs off the standard C++ streams, and completely disconnect them from stdout and stderr.. Oh, I understand few do it because it's more interesting to just reinvent something, but if everyone stuck to the standard, even libraries and third-party code would be logging the same way. Just sayin'.

Stephen M. Webb
Professional Free Software Developer

I dunno, I can't say when I'm using any other system - and I literally mean anything from Python to Java to Unity to UE4 - that I ever think, "oh, I wish I was using C++'s stream insertion syntax for all of this", and I certainly never think "creating a new streambuf-derived class looks like a clean way to handle this". C++ streams seem to me to be one of those things that nearly everybody has written off as a poor implementation.

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

To me the whole point of a logger is to a) be lightweight enough to be usable inside a loop, b) be easy to use and tansparent and c) be reserved for error messages and debugging (eg it is invoked only during development or when something goes wrong). This alone justifies the use of a global and a variety of macros (gasp!) that dramatically improve productivity.

My logger has close to zero state apart from a per-thread accumulation buffer and a list of simple message sinks, which are called each time it receives an endl token. It wouldn't even require a separate object where it not for the fact that it needs to manage these backends (console/ethernet out etc).

Personally I can't fully understand what the fuss is about with an iron stance on globals. Yes, abusing them is poor practice and error prone, but so is driving too slowly. There's rules and then there's practice and in practice Occam's razor tends to be a good measure to go by: if using a global makes your life infinitely easier, then use a global. In particular this applies to things like stable objects whose corrupt state would be terminal anyway, objects that would otherwise need to be constantly passed between threads, because you use them everywhere. Especially when you're still writing new code and want to make stuff work quickly. If you know you only have one application instance and you have a whole multithreaded component structure that requires access to it, then passing around the handle just because it's "right" is just extra work that you need to put in. Yes, try to avoid resorting to this if possible, but at the end of the day it's fine if you're simply mindful about your gloals (of which you need very few). You might want to gate them behind a getter, though, so you'd have harder time accidenally corrupting the handle (perhaps even requiring a const_cast to make yourself think twice whether you really want or need to access it).

In general, if the logger becomes corrupt, then there really isn't much you can do in the form saving things anyway, so I personally don't see a reason to start thinking all complex about it. The logger is a tool. And in order to be effective, a tool needs to be easy to use.

So what you want is do this:

#include "logger.h"

So you can do this:

log << "message" << endl;

log("message");

LOG(val1, val2, val3);

To be completely honest, I don't see the value in reinventing this particular wheel.

Apache, Google, and boost all have logging libraries, and there are many more with a variety of permissive licenses. It's a well-known problem that's been solved many times over.

Unless you're doing this as a learning exercise, or you have special needs or requirements that aren't fulfilled by any existing library, I would just choose one with an API you like and call it a day. Spend your valuable development time on the application's real problems.

To be completely honest, I don't see the value in reinventing this particular wheel.

Apache, Google, and boost all have logging libraries, and there are many more with a variety of permissive licenses. It's a well-known problem that's been solved many times over.

Unless you're doing this as a learning exercise, or you have special needs or requirements that aren't fulfilled by any existing library, I would just choose one with an API you like and call it a day. Spend your valuable development time on the application's real problems.

And yet all three of those links are massive libraries with snaking dependencies, a crapload of files, build complexities, and more. I'm sorry but if you're a game developer using one of those, your priorities are severely distorted and you're going to create more pain for yourself rather than less. Please don't inflict any of that garbage on your codebase. No, a logging solution should be a single file arrangement, much like the stb libraries.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.

To be completely honest, I don't see the value in reinventing this particular wheel.

Apache, Google, and boost all have logging libraries, and there are many more with a variety of permissive licenses. It's a well-known problem that's been solved many times over.

Unless you're doing this as a learning exercise, or you have special needs or requirements that aren't fulfilled by any existing library, I would just choose one with an API you like and call it a day. Spend your valuable development time on the application's real problems.

And yet all three of those links are massive libraries with snaking dependencies, a crapload of files, build complexities, and more. I'm sorry but if you're a game developer using one of those, your priorities are severely distorted and you're going to create more pain for yourself rather than less. Please don't inflict any of that garbage on your codebase. No, a logging solution should be a single file arrangement, much like the stb libraries.

Ok. Then don't use them. I chose those examples because of their name recognition, not because I was trying to prescribe them specifically. There are plenty of smaller libraries out there with a variety of featuresets but I didn't feel like regurgitating Google search results. If you want or need to write your own logging library then that's fine. You know your requirements best, and I'm not suggesting anyone pigeonhole themselves into a solution that doesn't fit. But when it comes to priorities, a game developer's should be writing games, not common software utilities you can find everywhere.

However I don't want to digress any further from the main topic. I was troubled by what I perceived in a lot of responses as an unspoken assumption that rolling your own logger is the rule rather than the exception, which a part of me couldn't ignore. If anyone wants to continue this discussion, I'd be happy to in another thread or PM.

I re-invented the wheel... a long long time ago. Now that is the only logger I use. Loggers them selves are instanced, but the log factory is global. I use instance loggers rather than global because I found it easier to mix and match loggers this way... my solution had a couple different logging types... log to file, log to console, log to window event viewer. Then It had a null logger for turning logging off, a multi logger for logging to multiple specific loggers with one log command and an ILog interface for application specific logging.

This way I can create a trace logger and an in game logger and an exception logger. Then when I release I can turn the trace logger into a null logger to "turn it off". The ILog interface allowed me to create a game specific log that logs to a GUI Element... so with one line log.Log("This"); it logs to both the GUI Control and the console and/or a file(That wouldn't be visible in non debug vers.)

Because the factory is global it is just as easy to access the loggers as it would be if the loggers where global... so... there that... i guess...

This topic is closed to new replies.

Advertisement