Solved

Log like stringstream (stdlib)

Posted on 2006-10-27
11
397 Views
Last Modified: 2013-12-14
I have a class for Logging called "Log".  It has a method for writing to the log.... write(string).

Log mylog("c:\log.log");
mylog.write("This message will self destuct in 5 seconds...");

But when I want to pass a message which contains mixed vars, I do this

Log mylog("c:\log.log");
stringstream ss;
ss << "This message will self desctuct in " << countdown << " seconds");
mylog.write(ss.str());

but what I want to do is this:

Log mylog("c:\log.log");
mylog << "This message will self destrict in " << coundown << "seconds");

It would really tidy up my code.

I don't know enough about the << operator to be sure about what I'm doing.

Any pointers or a quick example?

Thanks!


Here is my Log.h

class Log
{
public:
      Log();   // Log to console
      Log(string filename);   // log to file
      ~Log();
      void write(string);
      void warn(string);


private:
      void waitForLock();
      void unLock();
      fstream *fp_stream;
      CRITICAL_SECTION cs;

};
 

0
Comment
Question by:peeldog
  • 4
  • 3
  • 2
  • +1
11 Comments
 
LVL 86

Accepted Solution

by:
jkr earned 150 total points
ID: 17821532
You could just provide the necessary overloads, e.g.

class Log
{
public:
     Log();   // Log to console
     Log(string filename);   // log to file
     ~Log();
     void write(string);
     void warn(string);

     Log& operator<<(char* p) { *fp_stream << p; return *this;}
     Log& operator<<(string s) { *fp_stream << s; return *this;}
     Log& operator<<(int n) { *fp_stream << n; return *this;}
     // provide others if necessary


private:
     void waitForLock();
     void unLock();
     fstream *fp_stream;
     CRITICAL_SECTION cs;

};
0
 
LVL 2

Author Comment

by:peeldog
ID: 17822974
The write method prefixes the line with the time/date and may or may not write to fp_stream (it may write to the console).

I tried this:

      Log& operator <<(char *p) { write(p); return *this; }
      Log& operator <<(string s)  { write(s); return *this; }
      Log& operator <<(int i)  { stringstream ss; ss << i; write(ss.str()); return *this; }
      Log& operator <<(float f)  { stringstream ss; ss << f; write(ss.str()); return *this; }

but I get something like this:

Oct-27  2:41pm   "This message will self destrict in "
Oct-27  2:41pm   2
Oct-27  2:41pm   Seconds.

Help me EE... you're my only hope!
0
 
LVL 86

Expert Comment

by:jkr
ID: 17824331
I was assuming that you were stiing the stream accordingly in your constructor code anyway, just add a 'std::string get_time(); method that writes the time in he respecive overloads of 'operator<<()'.
0
Courses: Start Training Online With Pros, Today

Brush up on the basics or master the advanced techniques required to earn essential industry certifications, with Courses. Enroll in a course and start learning today. Training topics range from Android App Dev to the Xen Virtualization Platform.

 
LVL 15

Expert Comment

by:efn
ID: 17824458
You could define a manipulator for the class that marks the end of a line and require the user to use it.  The Log object then would have to buffer the string until it saw this manipulator and then call the write function.  The calling code then might look like this, for example:

mylog << "This message will self destruct in " << countdown << "seconds" << logEnd;

This imposes an extra burden on the Log class user, but without it, the Log object can't tell where to end a line.

For information on how to define a manipulator, see:

http://www.awprofessional.com/articles/article.asp?p=171014&seqNum=2&rl=1
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 17828115
An alternative approach is to use a global object of a class that is ostream-insertable to create the timestamp and simply use ostreams for logging.

e.g.

class Timestamp {
    static string toString();  // Creates something like "Oct-27  2:41pm: "
} stamp;

ostream& operator<<(ostream operator& os,const Timestamp& ts) {
    return os << ts.toString();
}

cout << stamp << "This message will self destruct in " << countdown << "seconds\n";

This puts the onus onto the beginning of the insertion rather than then end and it means you don't have to re-invent ostream.
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 17828131
With this approach you can do an evil macro like...

#define LOG_WITH_PREFIX(verbosity,prefix) \
    if (verbosity > level) clog << stamp <<
#define EP LOG_WITH_PREFIX(1,"Error: ")
#define WP LOG_WITH_PREFIX(2,"Warning: ")
#define MP LOG_WITH_PREFIX(3,"Info: ")
#define DP LOG_WITH_PREFIX(4,"Debug: ")

However, you need to warn users to avoid doing things like:

   DP << "Important counter is now " << some_counter++ << '\n';

It isn't obvious that some_counter is only incremented when the verbosity is at the right level. So this isn't really suitable for a library for use by others.
0
 
LVL 2

Author Comment

by:peeldog
ID: 17830669
I tried the custom manipulator thing, as I don't want to inserting the timestamp on the stream as I want to be able to turn off the timestamp within the log class -e.g. when writing to the console.  I'd like to avoid macros if I can help it.

I tried this:

public:
      Log& operator <<(char *p)  { m_buffer << p; return *this; }
      Log& operator <<(string s) { m_buffer << s; return *this; }
      Log& operator <<(int i)    { m_buffer<< i; return *this; }
      Log& operator <<(float f)  { m_buffer<< f; return *this; }

private:
        ostringstream m_buffer;


But I couldn't figure out how to define the custom manipulator and get it to write the cache.  I hacked it to work like this:

public:
   Log& operator <<(char c)   { if(c==0) { write(m_buffer.str()); m_buffer.str(""); } else m_buffer << c; return *this; }

main() {
   log << "This message will self destruct in " << countdown << " seconds" << '\0';

which is cheezy.  Is there a neater way to do this by defining a custom entity.  I tried but couldn't get anything to compile.  

I'm really close and it's working, but I just need to figure out how to make it nice and tidy.

Thanks!






0
 
LVL 15

Expert Comment

by:efn
ID: 17831023
As the first step, you can define a (public) Log member function that terminates a line, such as:

void Log::terminateLine()
{
    write(m_buffer.str());
    m_buffer.str("");
}

This is just the same code you had in your char inserter.  I just repackaged it.

Then you can define the manipulator to call the function.

Log& logEnd(Log& stream)
{
    stream.terminateLine();
    return stream;
}

I think this should work, but I haven't tested it.
0
 
LVL 2

Author Comment

by:peeldog
ID: 17831102
It mostly worked, but I was getting this compile error:

3>cell.cpp(179) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Log &(__cdecl *)(Log &)' (or there is no acceptable conversion)
3>        log.h(42): could be 'Log &Log::operator <<(char *)'
3>        log.h(43): or 'Log &Log::operator <<(std::string)'
3>        log.h(44): or 'Log &Log::operator <<(int)'
3>        log.h(45): or 'Log &Log::operator <<(float)'
3>        while trying to match the argument list '(Log, Log &(__cdecl *)(Log &))'

So I added this:

public:
    Log& operator <<(Log &(__cdecl *f)(Log &))  { return f(*this);}

I figured the Log class would need an operator that can handle a pointer to that sort of function, and when it sees it, it should run it.

It seems to be working...  did I do right?

Thanks!

P.S. f(*this)  ... how true.
0
 
LVL 15

Assisted Solution

by:efn
efn earned 350 total points
ID: 17831442
> It seems to be working...  did I do right?

Yes.  You could make it more portable by taking out the "__cdecl", if it still works when you do that.

The sources I looked at did not specify this function, so I guess I assumed that the language provides it, but it actually makes more sense for it not to be built into the language.  It is built into standard library streams, so if you are implementing a manipulator for one of those, you don't need to provide this function.

For example, here's the function from the ostream header file from Microsoft Visual C++ 2005 Express Edition:

      _Myt& __CLR_OR_THIS_CALL operator<<(_Myt& (__cdecl *_Pfn)(_Myt&))
            {      // call basic_ostream manipulator
            _DEBUG_POINTER(_Pfn);
            return ((*_Pfn)(*this));
            }

It's quite similar to yours.
0
 
LVL 2

Author Comment

by:peeldog
ID: 17831467
Great, thanks for your help everyone.  This now works just like I want it to.  I've also found I can implement a number of terminators such as "logWarn", "logErr" and "logMsg" which can do different things to the message in the buffer.
0

Featured Post

Live: Real-Time Solutions, Start Here

Receive instant 1:1 support from technology experts, using our real-time conversation and whiteboard interface. Your first 5 minutes are always free.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction This article is the first in a series of articles about the C/C++ Visual Studio Express debugger.  It provides a quick start guide in using the debugger. Part 2 focuses on additional topics in breakpoints.  Lastly, Part 3 focuses on th…
Update (December 2011): Since this article was published, the things have changed for good for Android native developers. The Sequoyah Project (http://www.eclipse.org/sequoyah/) automates most of the tasks discussed in this article. You can even fin…
The viewer will learn how to use and create new code templates in NetBeans IDE 8.0 for Windows.
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…

785 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question