Solved

Log like stringstream (stdlib)

Posted on 2006-10-27
11
393 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
Comment Utility
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
Comment Utility
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
Comment Utility
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
 
LVL 15

Expert Comment

by:efn
Comment Utility
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
Comment Utility
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
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 17

Expert Comment

by:rstaveley
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
> 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
Comment Utility
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

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
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…

771 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

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now