Sign in to follow this  
Smit

A better way of logging text and variables?

Recommended Posts

Currently my log has the function:
void LogMessage( std::string sMessage, std::string sReporter, int nType, int nLevel );
However every time I want to log a variable such as the value of an int etc. I have to do this:
std::stringstream sTemp;
sTemp << "Task '" << pTask.GetName( ) << "' has been added with ID: " << nID;
Log::Get()->LogMessage( sTemp.str( ), "Kernel", LOG_MSGTYPE_INFO, LOG_LEVEL_NORMAL );
Is there a better way of allowing any text/ints/floats/etc as a parameter to the LogMessage() function? Thanks!

Share this post


Link to post
Share on other sites
overload the operator<< and have something like: beginLog and endLog where beginLog and operator<< only adds to the internal stringstream and endLog does the actual file-printing. That's how I've constructed my log, might be what you want. All in all it results in code looking something like
LOG_MESSAGE("Kernel", LOG_MSGTYPE_INFO, LOG_LEVEL_NORMAL, "Task '" << 
pTask.GetName( ) << "' has been added with ID: " << nID);
// or in my system:
LMSG("Kernel", "Task '" << pTask.GetName( ) << "' has been added with ID: " << nID);
// where L means log something and MSG means message. The level is bult into the message(if you want that).

If not you can probably use the C functions va_start/vsprintf/va_end but they result in ugly printf looking code.

// the dispatcher - similar to your Log

#ifndef LOGDISPATCHER_HPP_
#define LOGDISPATCHER_HPP_

#include <sstream>
#include <string>
#include <cassert>

namespace logging {
class Logger;

/** A util class responsible for dispatching messages to a Logger.
*/

class Dispatcher {
public:

/** The constructor.
* The Logger is grabbed from the AliasContainer.
* @param p_name the alias name for the Logger.
* @see AliasContainer
*/

Dispatcher(const std::string& p_name);

/** The destructor.
*/

~Dispatcher();

/** Begins logging.
* Assert if the logging is already begun.
*/

void beginLog();

/** End logging.
* Assert if the logging hasn't begun.
* @param p_type the type of the entry
* @param p_file the file were the entry originated from
* @param p_function the function were the entry originated from
* @param p_line the line where the entry originated from
*/

void endLog(int p_type, const char* p_file, const char* p_function, const long p_line);

/** Operator for building the message parameter.
* @param p_data the data to write to the message stream
* @return a dispatcher reference for further calling.
*/

template<class _Type>
Dispatcher& operator<<(const _Type& p_data) {
#ifdef DEBUG
assert(m_started && "The logging mus be started" );
#endif
m_message << p_data;
return *this;
}
private:
Logger* m_logger;
#ifdef DEBUG
bool m_started;
#endif
std::stringstream m_message;
};
};

#endif /*LOGDISPATCHER_HPP_*/



// logging macro helpers

#ifndef LOG_HPP_
#define LOG_HPP_

#include "logging/Types.hpp"
#include "logging/Dispatcher.hpp"
#include "logging/DebugLevel.hpp"

// define a standard logging dispatchet if it was not previously defined
// users of the logging system are encouraged to store a reference to logging named
// logging when logging
#ifndef LOG_DISPATCHER
#define LOG_DISPATCHER disp
#undef LOG_BY_POINTER
#undef LOG_BY_SMART_POINTER
#endif

// root define, all other logging macros should use BASIC_LOG
#ifdef LOG_BY_POINTER
#define BASIC_LOG(type, message) if(LOG_DISPATCHER) { LOG_DISPATCHER->beginLog(); (*(LOG_DISPATCHER)) << message; LOG_DISPATCHER->endLog(type, __FILE__, __FUNCTION__, __LINE__); }
#elif defined(LOG_BY_SMART_POINTER)
#define BASIC_LOG(type, message) if(LOG_DISPATCHER.get()) { LOG_DISPATCHER->beginLog(); (*(LOG_DISPATCHER)) << message; LOG_DISPATCHER->endLog(type, __FILE__, __FUNCTION__, __LINE__); }
#else
#define BASIC_LOG(type, message) { LOG_DISPATCHER.beginLog(); LOG_DISPATCHER << message; LOG_DISPATCHER.endLog(type, __FILE__, __FUNCTION__, __LINE__); }
#endif

/////////////////////////
// System level macros
#if SYSTEM_DEBUG_LEVEL == 3
// 100% debugging info
#define LOG1(type, message) BASIC_LOG(type, message)
#define LOG2(type, message) BASIC_LOG(type, message)
#define LOG3(type, message) BASIC_LOG(type, message)
#elif SYSTEM_DEBUG_LEVEL == 2
// enable level 1 and 2 macros
#define LOG1(type, message) BASIC_LOG(type, message)
#define LOG2(type, message) BASIC_LOG(type, message)
#define LOG3(type, message)
#elif SYSTEM_DEBUG_LEVEL == 1
// enable level 1 macros
#define LOG1(type, message) BASIC_LOG(type, message)
#define LOG2(type, message)
#define LOG3(type, message)
#else // SYSTEM_DEBUG_LEVEL == 0
// no debugging info at all
#define LOG1(type, message)
#define LOG2(type, message)
#define LOG3(type, message)
#endif

//////////////////////////////////////
// Standard logging macros
// This is the prefered way of logging
// level 1
#define LMINFO(message) LOG1( logging::LT_MAJOR_INFO , message )
#define LERROR(message) LOG1( logging::LT_ERROR, message )
#define LSUCCESS(message) LOG1( logging::LT_SUCCESS, message )
#define LCONFIG(message) LOG1( logging::LT_INFO, message )

// level 2
#define LWARNING(message) LOG2( logging::LT_WARNING, message )
#define LINFO(message) LOG2( logging::LT_INFO, message )
// message=window event or info as a result of an message(resizing)
#define LMESSAGE(message) LOG2( logging::LT_INFO, message )
// net message, specialisation of a message, since we may want to
// look closely at net messages but not at others...
// in that case we might increase this one to #1 or decrease the others to #3
#define LNMESSAGE(message) LOG2( logging::LT_INFO, message )

// level 3
#define LPARAMETER(message) LOG3( logging::LT_PARAMETER, message )
#define LRETURN(message) LOG3( logging::LT_RETURN, message )
#define LDEBUG(message) LOG3( logging::LT_DEBUG_INFO, message )

// LOG OBJECT macros
#define LO_PARAMETER(p) LPARAMETER("(" << #p << ") = " << p)
#define LO_RETURN(r) { LRETURN("Returning: (" << #r << ") == "<< r); return r; }
#define LO_OBJECT(object) LMINFO( "Debugging: (" << #object << ") == " << (object) )
#define LO_CONFIG(prop) LCONFIG( #prop << ": " << prop )

#endif /*LOG_HPP_*/



hth

Share this post


Link to post
Share on other sites
If you want to just pass the values in to the log funtion, then you could use C-style format strings and varargs. Something like this:

#include <stdlib.h>
#include <stdarg.h>

void LogMessage(char *format, ...)
{
va_list args;
char buffer[1024];

va_start(args, format);
vsprintf(pBuffer, format, args);
va_end(args);

// Write buffer to log here
}


Which you'd then use like this:

LogMessage("An int: %d, A float: %f, A string: %s", 12, 12.21f, "Hello, world!");


This is a pretty minimal example, though, and not terribly safe. You'd really want to add a bit of additional safety code to ensure it doesn't go off the end of the temporary buffer (if you're using VC, there's a _vscprintf function that'll tell you how big the buffer needs to be, so you can check before writing into it, or dynamically allocate one of the correct size as needed).

Share this post


Link to post
Share on other sites
Personally, I like keeping loggers simple like that. Two things that help for usability:

1. boost::lexical_cast. No need for the stringstream temporary, just lexical_cast anything that's non-string. You could even template the LogMessage function to auto-magically do it for you.

2. Function chaining (not sure if this is the proper term). Instead of LogMessage returning void, have it return 'this'. This will allow:


Log::Get()->LogMessage( ... ) -> LogMessage ( ... ) -> LogMessage ...


Less useful for log styles that require all the extra params, and less readable in most cases, but useful for some where the continuation is important.

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