1

This is the brute-force inelegant mechanism that illustrates what I could do (likely done with MACROS in the end):

void myFunction(std::ostream& _log) { int threshold = 100; ... if (threshold >= 200) { _log << "Message with level 200 verbosity"; // Not emitted } if (threshold >= 0) { _log << "Message with level 0 verbosity"; // EMITTED } ... } 

Note above that when messages are "filtered out" because they do not meet the verbosity threshold, that it costs very little. The whole operation of constructing and handling those messages is skipped.

More elegant would be something like this:

class MessageFilterteringOStream : std::ostream { ... void setMessageThreshold(int unsigned threshold) { _threshold = threshold; } ... } ... void myFunction(MessageFilteringOStream& _log) { _log.setMessageThreshold(100); ... _log << _log.verbosity(200) << "Message with level 200 verbosity"; // NOT emitted _log << _log.verbosity( 0) << "Message with level 0 verbosity"; // emitted ... } 

To make this work, I'd need to do these things. The first two are easy. The third...?

  • Subclass std::ostream and add threshold and message_verbosity variables.
  • Add the verbosity() method that stores the verbosity of the current message into message_verbosity. (Copy the approach of std::setw() or something).
  • Somehow "intercept" the characters coming in and filter out everything while threshold < message_verbosity. Probably just by subclassing operator<<?

HOWEVER, this approach wouldn't be worth the trouble if the code didn't "short-circuit" the creation of the messages that are going to be filtered out anyway. I don't have any idea how to accomplish that... What do you say...?

EDIT: Thank you all for your advice/input. This codebase passes around std::ostream references to many different places. I was hoping to just, at the top-level, modify the verbosity of the thing before sending it down, so I didn't have to visit everywhere that messages are created. It sounds like that won't be easy. I could just interecept at the puts() level, but by then the messages have already been created, etc. (There can be MANY.) I"m still thinking about what you have said.

8
  • 2
    use spdlog Commented Mar 28 at 2:39
  • 1
    I concur with Ahmed. If you really want to roll this yourself you need to do it at the std::streambuf::putc level. Commented Mar 28 at 3:13
  • If you take references arguments in operator<<, then nothing executes. I don't understand what you're looking for. Commented Mar 28 at 4:43
  • 1
    There is no need to derive from streams classes just to add a property. Take a look at the std::ios_base class and its xalloc(), iword() and pword() methods. Commented Mar 28 at 6:21
  • 1
    my main issue with this is that logs are not streams, they are lines, each line has text (which IMO you can compress to a number in a table of strings, which is done in embedded devices) and parameters, treating them as streams just invites many problems like concurrency, compression and runtime cost. just use spdlog, its interface is correct and satisfies all requirements. Commented Mar 28 at 13:27

2 Answers 2

2

Way to short circuit << is to use MACRO:

#define LOG(level) (_log._verbosity >= level) ? _log : _log 

then

LOG(200) << "text"; 

becomes

(_log._verbosity >= 200) ? _log : _log << "text"; 

parsed as

(_log._verbosity >= 200) ? _log : (_log << "text"); 

thank to priority of operators.

Up to you to adjust MACRO to your needs (don't hardcode logger name, ...).

Demo

Sign up to request clarification or add additional context in comments.

1 Comment

That's simple and clever! Thanks! This code base I am looking at passes around std::ostream objects (cout/ofstream). I was hoping I didn't have to visit all the sites where messages are emitted. Instead, before calling printFoo(os&), I would set verbosity for all of the "foo" messages. I think there may be no way around it. I'm still thinking...
1

In this case it's simpler to not derive from std::ostream unless your use case really requires you to. This allows you to completely bypass generating the output strings, if you create your own std::ostream then you can only override std::putc which will only be called just before the string is output.

You only need to implement operator<< which then optionally forwards to the actual std::ostream. From the user's point of view your logger class will behave exactly the same as std::ostream:

#include <iostream> struct verbosity { explicit verbosity(int unsigned verbosity) :_verbosity(verbosity) { } int unsigned _verbosity; }; class MessageFilterteringOStream { public: MessageFilterteringOStream(std::ostream& output) :_output(output) { } void setMessageThreshold(int unsigned threshold) { _threshold = threshold; } void setVerbosity(int unsigned verbosity) { _verbosity = verbosity; } template <typename T> MessageFilterteringOStream& operator<<(T&& value) { if (_threshold >= _verbosity) { _output << std::forward<T>(value); } return *this; } MessageFilterteringOStream& operator<<(verbosity value) { _verbosity = value._verbosity; return *this; } private: std::ostream& _output; int unsigned _threshold = 0; int unsigned _verbosity = 0; }; int main() { MessageFilterteringOStream _log(std::cout); _log.setMessageThreshold(100); _log << verbosity(200) << "Message with level 200 verbosity"; // NOT emitted _log << verbosity( 0) << "Message with level 0 verbosity"; // emitted } 

1 Comment

That does exactly what I want, however, the codebase that I am working on passes std::ostream (cout or ofstream) references everywhere. I was hoping to create something I could pass around in the existing environment... Still thinking...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.