Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are
spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the
password reset link.
Due to spam on this forum, all posts now need moderator approval.
Entire forum
➜ Programming
➜ General
➜ Logfile Stream ?
It is now over 60 days since the last post. This thread is closed.
Refresh page
| Posted by
| Samson
USA (683 posts) Bio
|
| Date
| Sun 24 Oct 2004 01:55 AM (UTC) |
| Message
| A question for anyone who might know. Would it be possible to use some kind of C++ stream to handle logfile routines such as the following?
log_printf( "%s: Unknown PC flag: %s\n\r", __FUNCTION__, flag );
void log_printf( const char *fmt, ... )
{
char buf[MSL*2];
char *strtime;
va_list args;
va_start( args, fmt );
vsnprintf( buf, MSL*2, fmt, args );
va_end( args );
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
fprintf( stderr, "%s :: %s\n", strtime, buf );
}
So that rather than using stdarg in this manner, you would end up doing something more like this:
log_printf << __FUNCTION__ << ": Unknown PC flag: " << flag;
log_printf( whatever_should_go_here_to_indicate_it_uses_a_stream )
{
char *strtime;
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
logfile << strtime << " :: " << the_argument_you_passed_here << endl;
}
logfile of course being a global file stream which was opened to write to the log, or perhaps cout instad if you've got output being directed by the startup script. | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #1 on Sun 24 Oct 2004 10:09 PM (UTC) Amended on Mon 25 Oct 2004 12:54 AM (UTC) by Nick Gammon
|
| Message
| That's an interesting question. :)
I don't think you can do it directly as you describe, because to pass a stream to a function is a bit fiddly. Here is how I have been doing it...
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <stdexcept>
using namespace std;
// make a string on-the-fly
#define MAKE_STRING(msg) \
(((ostringstream&) (ostringstream() << boolalpha << msg)).str())
const string logFileName = "logfile.txt";
ofstream logfile;
const string stringShortDate (const time_t & t)
{
struct tm *thetime;
char sTimeBuff [80];
thetime = localtime (&t);
strftime (sTimeBuff, sizeof sTimeBuff, "%Y-%m-%d %H:%M:%S", thetime);
return sTimeBuff;
} // end of stringShortDate
void log_printf (const string & msg)
{
logfile << stringShortDate (time (NULL)) << ": " << msg << endl;
if (logfile.fail ())
throw runtime_error ("Could not write to log file: " + logFileName);
}
void test ()
{
string flag = "blah";
log_printf (MAKE_STRING ( __FUNCTION__ << ": Unknown PC flag: " << flag));
}
int main ()
{
try
{
// open log file
logfile.open (logFileName.c_str (), ios::out | ios::app);
if (!logfile.is_open ())
throw runtime_error ("Could not open log file: " + logFileName + " for writing.");
test ();
} // end of try
catch (exception & e)
{
cerr << "Exception raised: " << e.what () << endl;
return 1;
}
}
Save as test.cpp, then g++ test.cpp, then run as ./a.exe under Cygwin.
The MAKE_STRING macro makes a quick stream on-the-fly and then converts it back to a string to pass to a function where required.
However after looking at that, I came pretty close to what you want. If you add a define, then the "test" function can now look like this:
#define LOG(msg) log_printf (MAKE_STRING ( __FUNCTION__ << ": " << msg ))
void test ()
{
string flag = "blah";
LOG ("Unknown PC flag: " << flag);
}
The only difference to the syntax you wanted is this is a function call (thus you need the brackets), you are not just streaming to a stream.
The advantage of the define above is that you can automatically pass down the function name without having to build it into every message.
However I think this is required, if you simply did this:
log_printf << __FUNCTION__ << ": Unknown PC flag: " << flag;
A stream doesn't know that you have "just started" to send to it, and thus wouldn't know to prepend the date to it. However in my solution the function call is what triggers the date to be added. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #2 on Mon 25 Oct 2004 08:40 PM (UTC) |
| Message
| You can create a 'wrapper' for a stream like so:
class StreamWrapper
{
protected:
std::ostream * stream_;
public:
StreamWrapper()
: stream_(NULL)
{
}
void setStream(std::ostream & stream)
{
stream_ = &stream;
}
std::ostream & getStream()
{
return *stream_;
}
template<class ParamType>
DebugTracer & operator << (ParamType param)
{
char *strtime;
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
(*stream_) << strtime << " :: " << param << endl;
return *this;
}
};
This class simply forwards everything to the stream, but tacking on that first bit.
So, you'd do (more or less):
ofstream logFile("mylog.txt");
StreamWrapper logStream;
logStream.setStream(logFile);
logStream << "hello there";
The stream could have been set as a constructor parameter, but I chose not to because this class is meant to be initialized statically (as a global variable, rather) - so there's no guarantee that the file stream actually exists yet.
Note that since the << operator is overloaded template-style, it will accept any argument type you try to give it. It will resolve as long as there is an operator defined for ostream, otherwise it will give you a template error.
Nick's MAKE_STRING macro is also very useful for creating a string on the fly, and the solution also works nicely. But if you want to do it purely "stream-wise", I think you need to use a wrapper class of sorts. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Samson
USA (683 posts) Bio
|
| Date
| Reply #3 on Tue 26 Oct 2004 01:26 AM (UTC) |
| Message
| So does something like this work if I have lines like so:
logStream << __FUNCTION__ << ": Unknown flag: " << flag << endl;
Where clearly now more than one thing is passing on that line. Does the strtime tack get called for each, or only once at the very front? | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #4 on Tue 26 Oct 2004 01:40 AM (UTC) Amended on Tue 26 Oct 2004 01:43 AM (UTC) by Nick Gammon
|
| Message
| You anticipate my reply, Samson. :)
The problem with Ksilyan's solution, as far as I can see, is that it doesn't address the problem that Samson wants to output multiple things. If he only wanted to do one thing it could be done with a templated function.
eg. He wants this:
logStream << __FUNCTION__ << ": Unknown flag: " << flag << endl;
However the stream (as shown) prepends the date to each thing it is called with. I tried the suggested code, and with some changes needed to make it compile (eg. what is DebugTracer?) like this:
#include <fstream>
#include <stdexcept>
using namespace std;
class StreamWrapper
{
protected:
std::ostream * stream_;
public:
StreamWrapper()
: stream_(NULL)
{
}
void setStream(std::ostream & stream)
{
stream_ = &stream;
}
std::ostream & getStream()
{
return *stream_;
}
template<class ParamType>
StreamWrapper & operator << (ParamType param)
{
time_t current_time;
time( ¤t_time );
char *strtime;
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
(*stream_) << strtime << " :: " << param << endl;
return *this;
}
};
int main ()
{
ofstream logFile("mylog.txt");
StreamWrapper logStream;
logStream.setStream(logFile);
logStream << "hello there " << 22 << " here is trouble" ;
}
This is what it output:
Tue Oct 26 11:30:27 2004 :: hello there
Tue Oct 26 11:30:27 2004 :: 22
Tue Oct 26 11:30:27 2004 :: here is trouble
So, it put the date in front of each item, and a newline after each one. This is the problem I was trying to solve with a function call. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #5 on Tue 26 Oct 2004 05:16 AM (UTC) Amended on Tue 26 Oct 2004 05:17 AM (UTC) by David Haley
|
| Message
| DebugTracer was the name of the class - I've been using this for something else. :)
My apologies for messing up the last function - I had changed it in my head but forgot to change it here. You need to do:
template<class ParamType>
StreamWrapper & operator << (ParamType param)
{
char *strtime;
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
(*stream_) << strtime << " :: " << param << endl;
return *stream;
}
Note the last line changed from *this to *stream - that way after the first stream operator you return the stream, not the wrapper, so that any subsequent calls are sent directly to the stream.
(EDIT) I again forgot to change DebugTracer to StreamWrapper. Oops. That's what I get for doing this while wrapping my brain around completely unrelated topics for my midterms. :P |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
| Posted by
| Nick Gammon
Australia (23,173 posts) Bio
Forum Administrator |
| Date
| Reply #6 on Tue 26 Oct 2004 06:51 AM (UTC) Amended on Tue 26 Oct 2004 06:53 AM (UTC) by Nick Gammon
|
| Message
| Er, yes, that looks better once the syntax problems are fixed. I had to make 3 changes:
- Change the return type from StreamWrapper & to std::ostream & since that was what it is now returning.
- Not append the endl, as this still put the line break after the first word
- It returns *stream_ not *stream
Now the caller has to insert the endl.
#include <fstream>
#include <stdexcept>
using namespace std;
class StreamWrapper
{
protected:
std::ostream * stream_;
public:
StreamWrapper()
: stream_(NULL)
{
}
void setStream(std::ostream & stream)
{
stream_ = &stream;
}
std::ostream & getStream()
{
return *stream_;
}
template<class ParamType>
std::ostream & operator << (ParamType param)
{
time_t current_time;
time( ¤t_time );
char *strtime;
strtime = ctime( ¤t_time );
strtime[strlen(strtime)-1] = '\0';
(*stream_) << strtime << " :: " << param;
return *stream_;
}
};
int main ()
{
// open log file
ofstream logFile("mylog.txt");
StreamWrapper logStream;
logStream.setStream(logFile);
logStream << "hello there " << 22 << " here is trouble" << endl ;
logStream << __FUNCTION__ << " a new day dawns " << 42 << " penguins " << endl ;
}
This is a good demonstration of doing it the C++ way and not using defines, however the define solutions saves you having to put __FUNCTION__ and endl into every logging call, so perhaps there is still a place for that. |
- Nick Gammon
www.gammon.com.au, www.mushclient.com | | Top |
|
| Posted by
| David Haley
USA (3,881 posts) Bio
|
| Date
| Reply #7 on Tue 26 Oct 2004 04:50 PM (UTC) |
| Message
| Sorry about all the syntax problems. I should probably try compiling before firing it off - that's what I get for being too hasty. But, I'm glad that in the end it worked out. :-)
Personally I think having manual control over the final 'endl' might be a good thing, but it's true that it would be nicer to have the __FUNCTION__ be automated. Is that a standard preprocessor command? I'm not sure if I've seen it before. Probably is, given its similarity to __LINE__, __FILE__, etc. |
David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone
http://david.the-haleys.org | | Top |
|
The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).
To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.
20,475 views.
It is now over 60 days since the last post. This thread is closed.
Refresh page
top