We help IT Professionals succeed at work.

We've partnered with Certified Experts, Carl Webster and Richard Faulkner, to bring you two Citrix podcasts. Learn about 2020 trends and get answers to your biggest Citrix questions!Listen Now

x

Log like stringstream (stdlib)

peeldog
peeldog asked
on
Medium Priority
419 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;

};
 

Comment
Watch Question

CERTIFIED EXPERT
Top Expert 2012
Commented:
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;

};

Not the solution you were looking for? Getting a personalized solution is easy.

Ask the Experts

Author

Commented:
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!
jkr
CERTIFIED EXPERT
Top Expert 2012

Commented:
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<<()'.
efn

Commented:
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
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.
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.

Author

Commented:
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!






efn

Commented:
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.

Author

Commented:
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.
efn
Commented:
> 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.

Author

Commented:
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.
Access more of Experts Exchange with a free account
Thanks for using Experts Exchange.

Create a free account to continue.

Limited access with a free account allows you to:

  • View three pieces of content (articles, solutions, posts, and videos)
  • Ask the experts questions (counted toward content limit)
  • Customize your dashboard and profile

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.