Is your Logger global?

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

Hello forum : )

Using C++ but topic probably affects similar languages as well.

I will talk about a class/object called "logger", my definition of a logger: A class that can be used by every class to print significant code-happenings to a file. E.g. new state of a state-machine has been reached.

I know globals can be very unhealthy to use and should be avoided. This is what I do, I have not a single global within my own code (what libraries provide does not count).

Nonetheless, I want to implement a logger - easy way to track bugs happening while my software is running without some debugger-console.

Users will have an easy way to help me fixing bugs, too.

So I was wondering whether global loggers are common practice or if the senior programmers would hunt and burn me if they find out :' )
Literally every component within my software could and will use the logger, so why bother passing around a logger?

Especially because nobody relies on a logger, it is a simple "write-only"-class.

Thanks a lot for your time, have a nice day : )

Advertisement

I tend not to bother logging things; I mostly think it's a waste of time for most applications.

When I do, it's typically transient: that is, I'll log a bunch of stuff related to a some problem I am working on now, but remove all of that one I fix the problem.

I just call a logging function, which itself is just wrapping whatever the most-natural logging mechanism is for the platform I am on. I don't really see the need to turn the concept of a log into an object with it's inherent complexities about lifetime and management and all that.

"Don't use globals" I think is one of those general guidelines that makes sense to break IF you know why you're doing it (and it's a good reason). Like whenever you hear "don't use pointers!" - it's not because they should never be used, but more that you should know exactly *why* you're using them and be comfortable with the implications.

Maybe your particular logger makes sense to be accessed as a global.

Maybe an object in your game should be taking ownership of the logger anyway.

Maybe make your logger a singleton, Logger::Get()->Log("My debug message...")

Maybe put your log functions in a namespace instead of a class, Logging::Log("My debug message...")

It really depends on your logging needs.

On one end of the spectrum you have a set of simple logging functions that write to standard output/error and maybe a few files. This is pretty straightforward to write yourself and doesn't require any complex object modeling. On the other end you have libraries like log4cxx which make heavy use of objects and statics/globals and allow you to create all sorts of complex logging behaviors and configurations.

So I would start by asking yourself the extent to which your code needs logging, and what kind of functionality you need. Then decide whether that's best handled with a simple functional interface, or if you need to start using objects (and whether or not you want to write this yourself or use an existing library).

Depends on the project as well.

We generally use a well-known instance. That is a global pointer to an instance that has established semantics of when it is created and destroyed.

Subsystems can (and sometimes do) create their own sub-loggers. Also the primary logger is an event listener, so code can either call the logger directly or pass messages indirectly.

Free function all the way. It should be the single authoritative point of entry for anything that wants to log, and all intricacies of getting logs formatted, on disk, over the network, etc. should hide behind it.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Parts of the development environment itself make sense to be global - anything that has a 1:1 relationship with the program. e.g. a function to see if the developer is in a debug session, or to log some debug info.

Non-global logs also make sense if they're a feature of the app instead of a developer feature. E.g. A server may want to keep a log of all login attempts, a journal of changes to settings, and a transient log of chat room activity -- these are normal application features and should follow normal software engineering practice.

Loggers are one of the few justifiable use of globals (whether hidden behind a global function or otherwise).

The standard C++ library even comes with one right out of the box, Well, three actually, spelled std::cout, std::cerr, and std::clog. The folks who designed it, and the folks who developed implementations of it, and the many people who use it to get stuff done are mostly pretty smart people, and that makes the standard library a good example to follow.

Note that it's fairly straightforward to add fancy streambufs and manipulators to the standard loggers so you can do everything with them you can do with third-party libraries (except proper localization, which is a nasty design flaw in the standard library with good third-party solutions). Using the standard library has the added advantages of (a) begin well-documented, (2) being widely known by most programmers, and (d) being available everywhere right from line zero of your project coding effort.

Stephen M. Webb
Professional Free Software Developer

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.

Mine's a global object. I could've made it global free functions with global state, same difference. I see it as stylistic bordering on academic in that case. I thought once that maybe applications or certain subsets of them would want to spool up private loggers so I'd just leave the use case available since it didn't seem to hurt the design as such. It hasn't come in useful yet. Hell, I even wrote a "log_printf" function proxy. It's really handy when quickly integrating external sample code or other things that have a bunch of printf logging. I have to integrate a lot of external code for testing purposes.

I tend not to bother logging things; I mostly think it's a waste of time for most applications.

I guess I'm gonna have to disagree with you there. I prefer to keep quite detailed logging functionality and control the output with verbosity levels. Every resource loaded or unloaded gets logged, along with details (eg pixel format and dimensions for textures). I have a neat little hierarchical scheme that allows me to generate a prettified HTML log for the record. 90% of the time, I'm just looking for lines in red (errors) and keeping aware of lines in yellow (warnings). But once in a while the extended verbosity gives me more useful information than expected and I'm glad to have it. I also settled on a convention that ALL abnormal loads use the same word "failed". Oddly enough that's perhaps the single most useful choice I've made, as I can simply search the output in any console. Detailed logger traces are also especially useful in certain types of code that tend to corrupt your call stack when they die. Normal debugging is a challenge in those cases, but the log gives you milestones to mark boundaries of where you're looking for your crash.

Oh and if you're going to do logging, implement colors for the messages. I can't read an error message flying by during load, but I sure can catch a split second flash of red.

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

This topic is closed to new replies.

Advertisement