• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
TTT_Dutch

How to Log and stay Modular

56 posts in this topic

[quote name='speciesUnknown' timestamp='1315498641' post='4859092']
Holy shit man, option 4 was supposed to be an obviously wrong answer, and return0's addition was a joke O_o please tell me that from all this you didn't learn that the best solution was the most complicated and intrusive one?
[/quote]

You've been re-bad-joked.
0

Share this post


Link to post
Share on other sites
How many objects really even need to log? I mean logically. What do they write to the log, error cases? Just do assert, throw exception, return error codes or something, and then log at the error handling code. Lets say for example a renderer class, why should it even have to know that a log exists, or even know about std::string. Now this limits a lot of classes that needs to know about logging, when there are special logging requirements, passing the log to a (high level) class is not so bad. Then use the global log for all general cases.

Also decide if there even will be a log in finished product, and what kind of log?
0

Share this post


Link to post
Share on other sites
[quote name='Ftn' timestamp='1315518208' post='4859233']
How many objects really even need to log? I mean logically. What do they write to the log, error cases?
...[/quote]

Absolutely any system under development might benefit from the ability to log. Some random examples might be:
[list]
[*]Memory manager: Allocations and deallocations, internal infrastructure state at these points, etc.
[/list][list]
[*]AI: State changes, stimulus events, etc.
[/list][list]
[*]Path Finder: Path requests, long searches, etc.
[/list]
The problem with logs in videogame development isn't that no one needs them, it's that they quickly become unusable as they get spammed by every system under development.
0

Share this post


Link to post
Share on other sites
[quote name='return0' timestamp='1315521008' post='4859254']
ILogServiceLocator to every constructor, enterprise!
[/quote]
The name alone makes me shudder in horror.
0

Share this post


Link to post
Share on other sites
[quote name='VReality' timestamp='1315510324' post='4859181']
[quote name='speciesUnknown' timestamp='1315498641' post='4859092']
Holy shit man, option 4 was supposed to be an obviously wrong answer, and return0's addition was a joke O_o please tell me that from all this you didn't learn that the best solution was the most complicated and intrusive one?
[/quote]

You've been re-bad-joked.
[/quote]

I hope so
0

Share this post


Link to post
Share on other sites
My solution is a mix of those proposed in some replies.

Base module providing logging capabilities.
Logger is a class but log messages are added via macros, different error levels.
Many logger classes each supporting different log formats (right now just txt, rtf and xml. xml logs have some extra features like error level filter search, etc.)

That way I still have global access but at the same time I can change the log type when I need it. Could easily write a network logger class and use it modifying 2 lines of code.
Also I can easily select verbosity based on error levels.
0

Share this post


Link to post
Share on other sites
[quote name='undead' timestamp='1315670169' post='4860016']
My solution is a mix of those proposed in some replies.

Base module providing logging capabilities.
Logger is a class but log messages are added via macros, different error levels.
Many logger classes each supporting different log formats (right now just txt, rtf and xml. xml logs have some extra features like error level filter search, etc.)

That way I still have global access but at the same time I can change the log type when I need it. Could easily write a network logger class and use it modifying 2 lines of code.
Also I can easily select verbosity based on error levels.
[/quote]

What do you mean by "log messages are added via macros" and "different error levels"? Could you please give some sample code too?

Thanks,
Brent
0

Share this post


Link to post
Share on other sites
[quote name='TTT_Dutch' timestamp='1315669392' post='4860010']
Okay wait so what is the best way then? I shouldn't have the log passed as a param?
[/quote]
You should create a web service that can receive your log. Everywhere in your code, you make an HTTP Post containing your log. The web service then translates the raw log to an excel spreadsheet with really nice formatting and color coded. This excel spreadsheet is then translated to HTML5/Javascript/CSS complete with charts and graphs.
0

Share this post


Link to post
Share on other sites
It's simple.

You have a globally accessible class, let's say an application class even if it might not be the case. Depends on your design.

Question: who is responsible of enabling/disabling/configuring log?
Answer: your application, because different applications might require different logs and also you might want to change one line to enable/disable log, compile and run without rebuilding everything.

So your application class at startup should configure the logger.

Question nr.2: who can access log?
Answer: potentially every class.

So what you do is to build globally accessible macros for emitting logs pointing at a class WRAPPING the logger.

Example:

[size="2"][color="#0000ff"][size="2"][color="#0000ff"]#define[/color][/size][/color][/size][size="2"] my_LOG(sStr,...) myLogWrapper->EngineLog(my_LOGT_UNKNOWN,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[/size][size="2"][color="#0000ff"][size="2"][color="#0000ff"]#define[/color][/size][/color][/size][size="2"] my_WRNLOG(sStr,...) myLogWrapper->EnginelLog(my_LOGT_WARNING,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[color="#0000ff"]#define[/color][size="2"] my_ERRLOG(sStr,...) myLogWrapper->EnginelLog(my_LOGT_ERROR,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[/size]
Which is nothing special, just a call to a global log wrapper class passing also a log type.
The log type identifies the error level. For example the base module always logs CPU/OS/System infos and my rendering module logs the device capabilities. Both are INFO logs.

The point in having a wrapper is simple. The base module always initializes the wrapper but not the log. This way when you want a logger actually you ADD a logger to the wrapper.
So the function EngineLog, in its simplest form, parses all available logs from an array of logs. If none is availble no message is emitted, if many you have multiple output logs.

And what I like is it's easy to change how log works, for example if you wanted to bind specific log types to different log files all you would need to do is to write a new enginelog method, change the macros and just recompile. That's it.

[/size]
0

Share this post


Link to post
Share on other sites
[quote name='undead' timestamp='1315671562' post='4860027']
It's simple.

You have a globally accessible class, let's say an application class even if it might not be the case. Depends on your design.

Question: who is responsible of enabling/disabling/configuring log?
Answer: your application, because different applications might require different logs and also you might want to change one line to enable/disable log, compile and run without rebuilding everything.

So your application class at startup should configure the logger.

Question nr.2: who can access log?
Answer: potentially every class.

So what you do is to build globally accessible macros for emitting logs pointing at a class WRAPPING the logger.

Example:

[size="2"][color="#0000ff"][size="2"][color="#0000ff"]#define[/color][/size][/color][/size][size="2"] my_LOG(sStr,...) myLogWrapper->EngineLog(my_LOGT_UNKNOWN,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[/size][size="2"][color="#0000ff"][size="2"][color="#0000ff"]#define[/color][/size][/color][/size][size="2"] my_WRNLOG(sStr,...) myLogWrapper->EnginelLog(my_LOGT_WARNING,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[color="#0000ff"]#define[/color][size="2"] my_ERRLOG(sStr,...) myLogWrapper->EnginelLog(my_LOGT_ERROR,__LINE__,__FUNCTION__,__FILE__,sStr,__VA_ARGS__);
[/size]
Which is nothing special, just a call to a global log wrapper class passing also a log type.
The log type identifies the error level. For example the base module always logs CPU/OS/System infos and my rendering module logs the device capabilities. Both are INFO logs.

The point in having a wrapper is simple. The base module always initializes the wrapper but not the log. This way when you want a logger actually you ADD a logger to the wrapper.
So the function EngineLog, in its simplest form, parses all available logs from an array of logs. If none is availble no message is emitted, if many you have multiple output logs.

And what I like is it's easy to change how log works, for example if you wanted to bind specific log types to different log files all you would need to do is to write a new enginelog method, change the macros and just recompile. That's it.

[/size]
[/quote]


Okay thankyou very much!

0

Share this post


Link to post
Share on other sites
OK, seriously.


Someone [i]please[/i] explain to me [i]how the freaking hell[/i] it makes sense to use an [b]object[/b] for logging when a free/static function can do exactly the same things at less overhead.


Please.
1

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1315693234' post='4860139']
OK, seriously.


Someone [i]please[/i] explain to me [i]how the freaking hell[/i] it makes sense to use an [b]object[/b] for logging when a free/static function can do exactly the same things at less overhead.


Please.
[/quote]

How exactly are you going to provide a logging hook to a library by simply a free/static function?

The OP specifically asked how to do logging in a modular fashion. Discounting the utility of that requirement, at the very least you'll need some static variable that the user of the library can re-assign/populate.
0

Share this post


Link to post
Share on other sites
[quote name='VReality' timestamp='1315510324' post='4859181']
[quote name='speciesUnknown' timestamp='1315498641' post='4859092']
Holy shit man, option 4 was supposed to be an obviously wrong answer, and return0's addition was a joke O_o please tell me that from all this you didn't learn that the best solution was the most complicated and intrusive one?
[/quote]

You've been re-bad-joked.
[/quote]
No, no he wasn't:
[quote name='TTT_Dutch' timestamp='1315669392' post='4860010']
Okay wait so what is the best way then? I shouldn't have the log passed as a param?
[/quote]
And actually, that just makes it even funnier.



@ApochPiQ The difference is generally minimal, as is the overhead. I can understand someone having a personal preference (and depending on one's circumstances/needs, those preferences can be legit), but worrying about the overhead of calling a member function vs a free function for logging is a lame argument, IMHO. Compared to the file access/write times that logging takes, really, that kind of overhead is nothing to worry about.

But if you're talking about programming time/LOC overhead, well, both can be done relatively simply and in similar amounts of LOC/programming time. I agree that this logging systems often done in some kind of overkill way, but there's no reason one method can't be as simple as the other.
0

Share this post


Link to post
Share on other sites
Okay so it seems like just creating a function that logs stuff either to a file or to the console should be fine as it is the simplest and there is really no need for anything more that that. If I need something more, like a logger that sends stuff through the network then I could just take the log data and send it with a different thing. After all, I shouldn't make something that does everything.
1

Share this post


Link to post
Share on other sites
[quote name='Telastyn' timestamp='1315702542' post='4860181']
How exactly are you going to provide a logging hook to a library by simply a free/static function?

The OP specifically asked how to do logging in a modular fashion. Discounting the utility of that requirement, at the very least you'll need some static variable that the user of the library can re-assign/populate.
[/quote]



Function pointers. Delegates. Whatever your language of choice supports. Doesn't matter.

I guess maybe Java is at a disadvantage here, but I did get the impression we're talking about C++, so function pointer it is. If you're fixated on passing an object, it can still be a static member function if absolutely necessary, [i]but I still honestly don't see any benefit whatsoever to passing an object for this.[/i]


Or you have your logging itself be provided by a module (statically linked lib) by itself, which exposes a free function/set of free functions; this seems like the obvious choice to me, and indeed has long been my preference (and that of many programmers smarter than me with whom I have worked over the years).


And for the record, I didn't intend to refer to "overhead" in the sense of runtime cost; although there [i]is[/i] some cost, it's probably negligible next to the cost of actually doing logging in the first place. My point isn't that free functions are preferable because they're "faster" or "more efficient" or some vacuous nonsense like that; it's about [i]mental[/i] overhead.

Most of the time, you get something like this:

[code]Logger log;
log.Write("Something happened!");[/code]

Oh boy! Now it takes two lines of code to do one thing.

Worse, we get suggestions like earlier in the thread. This is a pattern I have seen [i]in actual code[/i] and it always makes me want to vomit:

[code]#define LOG(x) do { Logger log; log.Write(x); } while(false)[/code]

Now you have a macro to generate two lines of code to do one thing.


I repeat my former plea: someone please explain to me [i]why this is considered a reasonable thing to do, ever.[/i]
0

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
[quote name='Telastyn' timestamp='1315702542' post='4860181']
How exactly are you going to provide a logging hook to a library by simply a free/static function?

The OP specifically asked how to do logging in a modular fashion. Discounting the utility of that requirement, at the very least you'll need some static variable that the user of the library can re-assign/populate.
[/quote]

Function pointers. Delegates. Whatever your language of choice supports. Doesn't matter.
[/quote]

Cool, just making sure that's what you meant.
0

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
Most of the time, you get something like this:
[code]Logger log;
log.Write("Something happened!");[/code][/quote]Sorry, I've just jumped in at the end of the thread... but what is this?

[font="Courier New"]log.Write[/font] is saying "[i]write this line to this log output[/i]", right? Which means the constructor of [font="Courier New"]Logger[/font] should take an argument that says what that output channel is, like L[font="Courier New"]ogger log("c:/foobar.txt");[/font] ?

Without that constructor argument, then [font="Courier New"]Logger[/font] is a monostate, which puts it in the same bucket as singletons and globals, but in the more-obscure-than-singleton corner!

So this particular example seems to be a complete straw-man, as it's conceptually identical to a free-function implementation, except with stupid monostate-syntax...
Do people really use monostate log implementations?
IMHO the point of using a log object would be in configuring it's output/destination via the constructor, like above, instead of having a global log output/destination.

...and in C++, the [i]delegate-to-free-function[/i] being recommended could likely be implemented as an object... [img]http://public.gamedev.net/public/style_emoticons/default/wink.gif[/img]
0

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
And for the record, I didn't intend to refer to "overhead" in the sense of runtime cost; although there [i]is[/i] some cost, it's probably negligible next to the cost of actually doing logging in the first place. My point isn't that free functions are preferable because they're "faster" or "more efficient" or some vacuous nonsense like that; it's about [i]mental[/i] overhead.
[/quote]
Ok, good. I was worried for a second. Although if you're getting mental overhead from creating a global logger object and using it, You're Doing It Wrong. It's not supposed to be tricky, complex, ugly, etc.

[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
Most of the time, you get something like this:

[code]Logger log;
log.Write("Something happened!");[/code]
[/quote]
I hope not. I'm not talking about creating a new Logger object whenever you want to log something. I'm talking about having a global Logger object that the one can access from anywhere and use to log.

[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
This is a pattern I have seen [i]in actual code[/i] and it always makes me want to vomit:

[code]#define LOG(x) do { Logger log; log.Write(x); } while(false)[/code]
[/quote]
I agree, whoever does that should be fired.

[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
I repeat my former plea: someone please explain to me [i]why this is considered a reasonable thing to do, ever.[/i]
[/quote]
Ever? Really? I'm in agreement with you 95% of the time. Most of the time you won't need anything more than some free function to do the logging. And in the OP's case, I think a free function will provide him with all the functionality he needs and it will a clean and great solution. But there are a few times where it's convenient to make a logger object that can store its state and encapsulate its information. For example, let's say you want to provide the user with the ability to choose where the log gets outputted to. It's easy and clean with an object, because you can simply do globalLogger = Logger("new/output/file.txt");, and none of the other code has to know that there was any kind of change at all. You can also tell the user where it's currently logging by simply doing globalLogger.getLogFilePath(), or something like that. The idea is that the data is encapsulated. You can provide this same kind of functionality with free functions, but IMHO using an object as a logger is a cleaner solution in this case because it keeps all the data encapsulated. And no, I wouldn't add any atrocious boiler plate code, like in your example. That'd just be ridiculous.

[quote name='Antheus' timestamp='1315750356' post='4860337']
So, what's the final color of bike shed?
[/quote]
Neon pink witha neon green roof, why? :)
0

Share this post


Link to post
Share on other sites
[quote name='Cornstalks' timestamp='1315752689' post='4860345']
[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
This is a pattern I have seen [i]in actual code[/i] and it always makes me want to vomit:

[code]#define LOG(x) do { Logger log; log.Write(x); } while(false)[/code]
[/quote]
I agree, whoever does that should be fired.[/quote]
Ever used std::clog or std::cout?
0

Share this post


Link to post
Share on other sites
[quote name='Antheus' timestamp='1315753332' post='4860347']
[quote name='Cornstalks' timestamp='1315752689' post='4860345']
[quote name='ApochPiQ' timestamp='1315725950' post='4860254']
This is a pattern I have seen [i]in actual code[/i] and it always makes me want to vomit:

[code]#define LOG(x) do { Logger log; log.Write(x); } while(false)[/code]
[/quote]
I agree, whoever does that should be fired.[/quote]
Ever used std::clog or std::cout?
[/quote]
Yeah, and I wish the industry would use and support them more. I'm not saying we should reinvent the wheel unnecessarily. Just discussing some options that can be used when a different wheel/tool is needed.
0

Share this post


Link to post
Share on other sites
Sure, if your logger needs to store state, then by all means use a lightweight object to do so; that's fine by me. (I'm skeptical that 99% of the loggers written would ever need to store any state, and that last 1% better be darn convincing, but I wouldn't rule it out entirely.)

What I'm arguing against is people who think they need state who simply don't. My monostate logger example is really just an extreme stripped-down version of what I see most people doing; they [i]think [/i]there is state but there really isn't, and so their code boils down to a verbose version of what I flippantly dismissed as evil in my earlier post.



My preferred solution is to invert the relationship between the [i]code doing the logging[/i] (call it the "client") and the [i]logging system itself [/i](call it the "provider"). Most solutions involve the client telling the provider where to log, etc. at the point of invocation (or nearby if you're using constructors and logging objects). To me this seems brittle; if you suddenly want all of your graphics-related logging to go to foo.txt instead of bar.txt, have fun updating your 500 constructor calls across the project!

Instead, I like to abstract the notion of a "channel" into a simple enum. Everything related to Foo goes into the Foo channel. There is also often a useful case where a severity can be attached; every Foo-related Error gets a channel of Foo and a severity of Error. Then, all call sites to the logging provider look the same: they provide a channel, a severity, and some content. Period. There is no need for state or other cruft; hence my tendency towards a simple free function.

The real power comes when you look at the provider itself. The obvious approach is to have your Log() function simply wrap std ostreams or whatever else. What gets interesting is when you make the Log() function just a dispatcher; all it does is forward the log request, possibly based on its channel and severity, to a "back end" implementation. The back end then takes care of doing the heavy lifting: writing to a network log, serializing to disk, printing something in the debugger if applicable, etc.

Last but not least, you write a couple of free functions for pushing and popping back ends onto a simple stack (only the topmost "active" back end is invoked by the provider dispatch layer). Add some RAII objects (which is totally legit!) to handle scope-controlled back end manipulation, and poof, you're done.

Now you have a system which minimizes mental overhead. If all you want to do is write to the logs, you write a single function call. If you want to change the log temporarily to reroute it, you create a single RAII wrapper on the stack. If you want to change the way all of your logging is handled across the program, which might be millions of lines of code, all you do is hook in a new provider back end at the appropriate point.

This gives you complete flexibility and control over all aspects of your logging, while pretty much ensuring that anything you want to do requires minimal effort.
1

Share this post


Link to post
Share on other sites
[quote name='TTT_Dutch' timestamp='1315708973' post='4860206']
Okay so it seems like just creating a function that logs stuff either to a file or to the console should be fine as it is the simplest and there is really no need for anything more that that. If I need something more, like a logger that sends stuff through the network then I could just take the log data and send it with a different thing. After all, I shouldn't make something that does everything.
[/quote]
Yes. Is the purpose of your log to keep track of your code path? Then the standard output should do just fine. std::cout, printf, or System.out.println, whatever fancies you the most.

If you want more complex behavior, such as sending it over to a network for further processing, then yes, you'd need more functionalities. Even in such cases, I advise you that you still do not need to make such complex objects that you pass around wherever you need logging.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0