Sign in to follow this  
riruilo

How can I write a function like prinft to buid a log class?

Recommended Posts

Hi mates! I would like to create a log class with a method like this: m_log->Write_log("example number %d", 1); What I don't know is how to create/program arguments like printf. Thank you for your help. BTW, using Code::Blocks and C++

Share this post


Link to post
Share on other sites
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

A better solution is to overload some other operator -- such as <<, as the iostreams do. Other common options are operator% and operator(). Or simply using Boost.Format.

You'd end up with something like

LogScope log;
log.Write() << foo << bar << baz;

Share this post


Link to post
Share on other sites
printf is a variadic function. Writing them requires using the va_args facility of C, and it's important to be familiar with it, however don't do this.

If you're writing C++, support operating chaining instead, like C++'s I/O streams. Instead of
lobObject.write("example number %d", 1);
do
logObject << "example number " << 1;

Implementing this requires that your insertion operator (here we use operator <<, just as with C++ streams) return a reference to the log object. You will need to provide overloads for each insertable type:
Log & operator << (Log & log, int i){ ... }
Log & operator << (Log & log, float f){ ... }
Log & operator << (Log & log, char c){ ... }
Log & operator << (Log & log, const std::string & s){ ... }

Share this post


Link to post
Share on other sites
Here's what I use:


ReturnCode CLog::Log(LogType Type, const char *Format, ...)
{
va_list Args;
char String[256];

if (FALSE == Logging && Error == Type)
{
Logging = TRUE;
}

va_start(Args, Format);

#if _MSC_VER >= 1400
vsprintf_s(String, Format, Args);
#else
vsprintf(String, Format, Args);
#endif

va_end(Args);

return LogString(Type, String);
}

ReturnCode CLog::LogString(LogType Type, string NewString)
{
#ifdef _DEBUG
OutputDebugString(NewString.c_str());
OutputDebugString("\n");
#endif

LogDateTime();

switch (Type)
{
case Error:
LogStrings.push_back("<FONT COLOR=#FF0000>Error: ");

break;

case Warning:
LogStrings.push_back("<FONT COLOR=#FFFF00>Warning: ");

break;

case Message:
default:
LogStrings.push_back("<FONT COLOR=#00FF00>");

break;
}

LogStrings.push_back(NewString);
LogStrings.push_back("</FONT>");

return NoError;
}

ReturnCode CLog::LogDateTime()
{
char String[256];

LogStrings.push_back("<FONT COLOR=#FFFFFF>[");

#ifdef LOGDATE
GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL, "ddd',' MMM dd yy", String, sizeof(String));
LogStrings.push_back(String);
LogStrings.push_back(" ");
#endif

GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, "hh':'mm':'ss tt", String, sizeof(String));

LogStrings.push_back(String);
LogStrings.push_back("]</FONT> ");

return NoError;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.


That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions. If you want to use it with your own procedures then you need to apply an additional attribute

Personally, I find C++ style printing functions unreadable and encourage the use of printf-like. (Since as I said, it's type unsafety is generally untrue, and it's more convenient; KISS)

[edit]
Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.
[/edit]

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu
Quote:
Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions.

Warnings on mismatched type are not the same thing as strong type validation.

Quote:
Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.

It's less secure, though. Boost.Format is a compromise:
cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto, x=40.230 : 50-th try"

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
Quote:
Original post by desudesu
Quote:
Original post by jpetrie
You'd use the va_args API -- va_start, va_end, and v*printf() methods. However, this is generally considered poor practice in C++ because of the extreme lack of type safety and otherwise brittle nature of the varargs methods.

That is generally not true, at least with GCC, which gives you warnings when you pass an argument with mismatched type to printf-like functions.

Warnings on mismatched type are not the same thing as strong type validation.

Quote:
Compare the readability:

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.

It's less secure, though. Boost.Format is a compromise:
cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto, x=40.230 : 50-th try"


If you want then you can tick GCC's flag to treat these warnings as errors. Unless I'm missing something obvious, I don't see any security defect with printf-like functions, provided you will treat these warnings as errors. Why go the hard way when the easy one is as good? (More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)

Share this post


Link to post
Share on other sites
Quote:

The second one is definitely more readable; besides, it's less to type.

Wrong. Readability is fairly subjective (you said it yourself -- 'in [your] opinion'), so the assertion is off-base (I find them equally readable, for example).

Quote:

(Since as I said, it's type unsafety is generally untrue,

Wrong, see above. Warnings are not type safety.

Quote:

Why go the hard way when the easy one is as good?

Because it isn't as good. Warnings on a specific compiler about a potential mismatch to not constitute type safety. Type safety is a language-level feature, and here the language abandons you.

The operator chaining concept is the standard, idiomatic way of doing this in C++ and is vastly more useful and beneficial than the vararg idiom of C. If nothing else, it lets you put (non-POD) objects directly into the format stream... something that is flat out impossible with vararg functions.

The implementation effort is relatively similar -- you don't have to manually overload the operator for each type, for example, you can use a template method.

Quote:

More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)

Implementing varags requires you depend on at least one other header for va_arg and the like. Implementing operator chaining yourself requires no additional headers. I don't know what kind of machine you're using, but on my machine single templated operator<< doesn't even make a dent in my compile time. And I fail to see how you think actual type-safety and the ability to use (non-POD) objects as first-class parameters (like std::string) is not 'obviously useful.'

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Quote:

(Since as I said, it's type unsafety is generally untrue,

Wrong, see above. Warnings are not type safety.

You can turn them into errors with appropriate flag. I don't see how this type-safety is different from that type-safety.

Quote:
Quote:

More dependencies, more complexity and longer compile-times are generally a bad thing if they don't contribute something obviously useful.)

Implementing varags requires you depend on at least one other header for va_arg and the like. Implementing operator chaining yourself requires no additional headers. I don't know what kind of machine you're using, but on my machine single templated operator<< doesn't even make a dent in my compile time. And I fail to see how you think actual type-safety and the ability to use objects as first-class parameters (like std::string) is not 'obviously useful.'


I was talking about Boost.format, which is an overkill for me personally, but then, YMMV.

Share this post


Link to post
Share on other sites
Quote:

You can turn them into errors with appropriate flag. I don't see how this type-safety is different from that type-safety.

Because your relying on external non-language features of the implementation that can be circumvented, perhaps unwittingly, since you're using a globalized 'shotgun' technique (turn on warnings-as-errors for everything and pray).

Besides, this doesn't fix the non-POD type issue.

Share this post


Link to post
Share on other sites
Wow! Thanks everybody!

In my opinion printf style is more readable, and I like cdoty code, also if I use:

Log & operator << (Log & log, int i){ ... }

What happens if I want something like
Write_log("The user %s with id=%d haas made an invalid operation: code=%d\n", user, id, code);
?

I think it is not possible using last method.

By the way, I'm using c++.

Any suggestions before I use cdoty code? ;)

Edit: And I use gcc, I want my program cross-platform.

[Edited by - riruilo on May 8, 2008 12:53:44 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Quote:

You can turn them into errors with appropriate flag. I don't see how this type-safety is different from that type-safety.

Because your relying on external non-language features of the implementation that can be circumvented, perhaps unwittingly, since you're using a globalized 'shotgun' technique (turn on warnings-as-errors for everything and pray).

Besides, this doesn't fix the non-POD type issue.


Obviously, you didn't use GCC much. (: You can specify which warnings you can treat as errors, ie. in this case "-Werror=format".

True, it doesn't fix non-POD type issue. It still gives a warning, (or error if you tell him to) but, IMO, the contributed readability of printf-like style outweighs a simple call to .c_str( ) (if it's a std::string) or any other to-char function for printing the object.

Share this post


Link to post
Share on other sites
Quote:

In my opinion printf style is more readable

But it's still poor C++.

Quote:

What happens if I want something like
Write_log("The user %s with id=%d haas made an invalid operation: code=%d\n", user, id, code);
?

I think it is not possible using last method.

Of course it is:

log.Write() << "The user" << user << " with id " << id << " has made an invalid operation: code " << code << "\n";

or

log.Write() % "The user" % user % " with id " % id % " has made an invalid operation: code " % code % "\n";

or

log.Write()("The user")(user)(" with id ")(id)(" has made an invalid operation: code " )(code)("\n");


Quote:

Any suggestions before I use cdoty code? ;)

Yes. Don't.

Share this post


Link to post
Share on other sites
Quote:

Obviously, you didn't use GCC much. (: You can specify which warnings you can treat as errors, ie. in this case "-Werror=format".

I use GCC plenty. Enough to have developed a healthy aversion to any solution that is tool-specific, such as yours, when there is a tool-agnostic solution. Especially when that solution has other objective advantages and the only subjective disadvantages are that 'it takes more effort to implement.'

Quote:

True, it doesn't fix non-POD type issue. It still gives a warning, (or error if you tell him to) but, IMO, the contributed readability of printf-like style outweighs a simple call to .c_str( ) (if it's a std::string) or any other to-char function for printing the object.

Until the object doesn't have c_str, of course. But fine, if you'd like to continue writing like you're using C, I'm not going to waste my time.

Share this post


Link to post
Share on other sites
Quote:
Original post by riruilo
Any suggestions before I use cdoty code? ;)

(I assume you are using GCC under the hood.)

Yes. Apply the format attribute to the logging function so you get varargs to be type-checked. Also, be sure to wrap the attribute in a #define, so you can switch it off for other compilers. You can also add "-Werror=format" to the compiler's command line to treat type mismatches as errors.

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Until the object doesn't have c_str, of course. But fine, if you'd like to continue writing like you're using C, I'm not going to waste my time.

To me, it is not writing like I'm using C, it's keeping things simple, readable and easily maintainable. I use other useful C++ features, hell, I love them. The thing is that I won't write code using the X way just because everyone else does; if a feature doesn't make sense, and I can do it in a more simple way, then I'll do it that way. From my experience I find that overcomplicating is generally not good. But then, that's only my personal opinion and you have right to disagree.

[Edited by - desudesu on May 8, 2008 5:13:15 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu
Quote:
Original post by jpetrie
Until the object doesn't have c_str, of course. But fine, if you'd like to continue writing like you're using C, I'm not going to waste my time.

To me, it is not writing like I'm using C, it's keeping things simple, readable and easily maintainable. I use other useful C++ features, hell, I love them. The thing is that I won't write code using the X way just because everyone else does; if a feature doesn't make sense, and I can do it in a more simple way, then I'll do it that way. From my experience I find that overcomplicating is generally not good. But then, that's only my personal opinion and you have right to disagree.

[edit]Great. Thanks for rating me down for my opinion. Next time I won't help anyone here, since I get only rated down for that.[/edit]


Hey
All your opinions are useful for me, moreover this is a forum, where you write them...

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu
You can turn them into errors with appropriate flag. I don't see how this type-safety is different from that type-safety.

"That" type safety is also in effect at runtime.
"That" type safety affords you an opportunity to gracefully resolve a value-based mismatch.

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu
[edit]Great. Thanks for rating me down for my opinion. Next time I won't help anyone here, since I get only rated down for that.[/edit]

Assumption is the mother of all fuck-ups. You're likely not being rated down for your opinion but for your manner of presenting it. Trust me, it happens to me all the time! [smile]

It's not a big deal. Don't call attention to it; just continue contributing positively and your net rating will climb until it plateaus.

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.
[/edit]


Perhaps, if that's how you use it.

I use it like this:
LOG_WARNING << "Account: " << account << " accessed unauthorized content.";


which results in:
08-05-08 12:48:33.5231 authentication.cpp:255 [NetworkLogin:0002] WARN Account TEST_ACCOUNT accessed unauthorized content.

This gets stored into a file, or into a DB, it may be filtered, output to any of std::c___ streams, it may be sent to remote machine. Logging preferences are configured on per-module basis (NetworkLogin is logical module), warning levels can be set remotely during run-time.

As always, YMMV.

Also, if you're using logging for such purposes, your project likely isn't big enough to need logging, it just needs debug output, for which, printf or std::cout are adequate.

Logging usually implies some consistent trace of program execution, potentially with thousands of entries per second, with multiple sources, multiple destinations and arbitrary filters.

Share this post


Link to post
Share on other sites
Quote:
Original post by desudesu
To me, it is not writing like I'm using C, it's keeping things simple, readable and easily maintainable.
It's great isn't it. Using your method if the type of a variable changes everything will automatically work without any further maintenance work. You can print to any kind of sink with a minimum of work. You can use it in templates with no problems. The corresponding input functions are easy to use and hard to abuse.

Oh, wait. None of that's actually true.

Σnigma

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Quote:
Original post by desudesu

std :: cout << "Hello " << first_name << " " << last_name << ".";
printf( "Hello %s %s.", first_name, last_name );

The second one is definitely more readable; besides, it's less to type.
[/edit]


Perhaps, if that's how you use it.

I use it like this:
LOG_WARNING << "Account: " << account << " accessed unauthorized content.";


which results in:
08-05-08 12:48:33.5231 authentication.cpp:255 [NetworkLogin:0002] WARN Account TEST_ACCOUNT accessed unauthorized content.

This gets stored into a file, or into a DB, it may be filtered, output to any of std::c___ streams, it may be sent to remote machine. Logging preferences are configured on per-module basis (NetworkLogin is logical module), warning levels can be set remotely during run-time.

As always, YMMV.

Also, if you're using logging for such purposes, your project likely isn't big enough to need logging, it just needs debug output, for which, printf or std::cout are adequate.

Logging usually implies some consistent trace of program execution, potentially with thousands of entries per second, with multiple sources, multiple destinations and arbitrary filters.


It's a game. In release mode (fullscreen) I want a file, but in debug mode (windowed), I want consela and a file.
Because of that, I'm building a log class. Perhaps yours it's too much for me.

Share this post


Link to post
Share on other sites
Quote:
Original post by Enigma
Quote:
Original post by desudesu
To me, it is not writing like I'm using C, it's keeping things simple, readable and easily maintainable.
It's great isn't it. Using your method if the type of a variable changes everything will automatically work without any further maintenance work. You can print to any kind of sink with a minimum of work. You can use it in templates with no problems. The corresponding input functions are easy to use and hard to abuse.

Oh, wait. None of that's actually true.

Σnigma


It's very rare for me to change types of variables; I have a set interface for such print-like handywork, so yes, I can point it to any sink I want, changing the object; and with a consistent interface I can use it along with templates with no problem.

Oh, wait. In real world applications you change your variable's types all the time, string -> int -> float (remember, non-POD types need a helper function anyway), and you use inconsistent interfaces.

Generally, YMMV. My applications' design is usually stable, so the problems you've described don't really apply.

Share this post


Link to post
Share on other sites
Quote:
Original post by jpetrie
Quote:

In my opinion printf style is more readable

But it's still poor C++.

Quote:

What happens if I want something like
Write_log("The user %s with id=%d haas made an invalid operation: code=%d\n", user, id, code);
?

I think it is not possible using last method.

Of course it is:

log.Write() << "The user" << user << " with id " << id << " has made an invalid operation: code " << code << "\n";

or

log.Write() % "The user" % user % " with id " % id % " has made an invalid operation: code " % code % "\n";

or

log.Write()("The user")(user)(" with id ")(id)(" has made an invalid operation: code " )(code)("\n");


Quote:

Any suggestions before I use cdoty code? ;)

Yes. Don't.


By the way, if I use this method, should I always add \n at the end? or is it any possibily to add \n in the insert method? (I dont guess so)

Thanks a lot.

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