From c404a05dfce61bf6d99ed835246ed1618677ff41 Mon Sep 17 00:00:00 2001 From: Matthias Maier Date: Wed, 17 Apr 2013 17:37:03 +0000 Subject: [PATCH] Let LogStream respect std::flush for writing out content as well LogStream (and therefore deallog) now respect std::flush and std::endl to write out the buffered (thread local) content to console (and file). git-svn-id: https://svn.dealii.org/trunk@29318 0785d39b-7218-0410-832d-ea1e28bc413d --- deal.II/doc/news/changes.h | 6 + deal.II/include/deal.II/base/logstream.h | 546 ++++++++--------------- deal.II/source/base/logstream.cc | 105 ++--- 3 files changed, 246 insertions(+), 411 deletions(-) diff --git a/deal.II/doc/news/changes.h b/deal.II/doc/news/changes.h index 0b389615b6..772ca58f88 100644 --- a/deal.II/doc/news/changes.h +++ b/deal.II/doc/news/changes.h @@ -106,6 +106,12 @@ this function.

Specific improvements

    +
  1. Improved: Logstream (and deallog) now respect std::flush in addition to std::endl +to write out content to the console/file. +
    +(Matthias Maier, 2013/04/17) +
  2. +
  3. New: Functions::FEFieldFunction can now deal with parallel::distributed::Triangulation objects.
    diff --git a/deal.II/include/deal.II/base/logstream.h b/deal.II/include/deal.II/base/logstream.h index 2dfba16c7d..2ef7be3fda 100644 --- a/deal.II/include/deal.II/base/logstream.h +++ b/deal.II/include/deal.II/base/logstream.h @@ -44,17 +44,23 @@ DEAL_II_NAMESPACE_OPEN * * * The usual usage of this class is through the pregenerated object - * deallog. Typical steps are + * deallog. Typical setup steps are: *
      - *
    • deallog.attach(std::ostream): write logging information into a file. *
    • deallog.depth_console(n): restrict output on screen to outer loops. - *
    • Before entering a new phase of your program, e.g. a new loop, - * LogStream::Prefix p("loopname"). - *
    • deallog << anything << std::endl; to write logging information - * (Usage of std::endl is mandatory!). - *
    • Destructor of the prefix will pop the prefix text from the stack. + *
    • deallog.attach(std::ostream): write logging information into a file. + *
    • deallog.depth_file(n): restrict output to file to outer loops. *
    * + * Before entering a new phase of your program, e.g. a new loop, + * a new prefix can be set via LogStream::Prefix p("loopname");. + * The destructor of the prefix will pop the prefix text from the stack. + * + * Writes via the << operator, + * deallog << "This is a log notice"; will be buffered thread + * locally until a std::flush or std::endl is + * encountered, which will trigger a writeout to the console and, if set + * up, the log file. + * *

    LogStream and reproducible regression test output

    * * Generating reproducible floating point output for regression tests @@ -87,53 +93,40 @@ class LogStream : public Subscriptor { public: /** - * A subclass allowing for the - * safe generation and removal of - * prefices. + * A subclass allowing for the safe generation and removal of prefices. * - * Somewhere at the beginning of - * a block, create one of these - * objects, and it will appear as - * a prefix in LogStream output - * like @p deallog. At the end of - * the block, the prefix will - * automatically be removed, when + * Somewhere at the beginning of a block, create one of these objects, + * and it will appear as a prefix in LogStream output like @p deallog. At + * the end of the block, the prefix will automatically be removed, when * this object is destroyed. * - * In other words, the scope of the object so created - * determines the lifetime of the prefix. The advantage of - * using such an object is that the prefix is removed - * whichever way you exit the scope -- by continue, - * break, return, throw, - * or by simply reaching the closing brace. In all of these - * cases, it is not necessary to remember to pop the prefix - * manually using LogStream::pop. In this, it works just like - * the better known Threads::Mutex::ScopedLock class. + * In other words, the scope of the object so created determines the + * lifetime of the prefix. The advantage of using such an object is that + * the prefix is removed whichever way you exit the scope -- by + * continue, break, return, + * throw, or by simply reaching the closing brace. In all of + * these cases, it is not necessary to remember to pop the prefix + * manually using LogStream::pop. In this, it works just like the better + * known Threads::Mutex::ScopedLock class. */ class Prefix { public: /** - * Set a new prefix for - * @p deallog, which will be - * removed when the variable - * is destroyed . + * Set a new prefix for @p deallog, which will be removed when the + * variable is destroyed. */ Prefix(const std::string &text); /** - * Set a new prefix for the - * given stream, which will - * be removed when the - * variable is destroyed . + * Set a new prefix for the given stream, which will be removed when + * the variable is destroyed. */ Prefix(const std::string &text, LogStream &stream); /** - * Remove the prefix - * associated with this - * variable. + * Remove the prefix associated with this variable. */ ~Prefix (); @@ -141,305 +134,234 @@ public: SmartPointer stream; }; + /** - * Standard constructor, since we - * intend to provide an object - * deallog in the library. Set the - * standard output stream to std::cerr. + * Standard constructor, since we intend to provide an object + * deallog in the library. Set the standard output stream to + * std::cerr. */ LogStream (); + /** * Destructor. */ ~LogStream(); + /** - * Enable output to a second - * stream o. + * Enable output to a second stream o. */ void attach (std::ostream &o); + /** - * Disable output to the second - * stream. You may want to call - * close on the stream that was - * previously attached to this object. + * Disable output to the second stream. You may want to call + * close on the stream that was previously attached to this + * object. */ void detach (); + /** - * Setup the logstream for - * regression test mode. + * Setup the logstream for regression test mode. * - * This sets the parameters - * #double_threshold, - * #float_threshold, and #offset - * to nonzero values. The exact - * values being used have been - * determined experimentally and - * can be found in the source - * code. + * This sets the parameters #double_threshold, #float_threshold, and + * #offset to nonzero values. The exact values being used have been + * determined experimentally and can be found in the source code. * - * Called with an argument - * false, switches off - * test mode and sets all - * involved parameters to zero. + * Called with an argument false, switches off test mode and + * sets all involved parameters to zero. */ void test_mode (bool on=true); + /** * Gives the default stream (std_out). */ std::ostream &get_console (); + /** * Gives the file stream. */ std::ostream &get_file_stream (); + /** - * @return true, if file stream - * has already been attached. + * @return true, if file stream has already been attached. */ bool has_file () const; + /** - * Reroutes cerr to LogStream. - * Works as a switch, turning - * logging of cerr on - * and off alternatingly with - * every call. + * Reroutes cerr to LogStream. Works as a switch, turning logging of + * cerr on and off alternatingly with every call. */ void log_cerr (); + /** * Return the prefix string. */ const std::string &get_prefix () const; + /** - * Push another prefix on the - * stack. Prefixes are - * automatically separated by a - * colon and there is a double - * colon after the last prefix. + * Push another prefix on the stack. Prefixes are automatically separated + * by a colon and there is a double colon after the last prefix. * - * A simpler way to add a prefix (without the manual - * need to add the corresponding pop()) is to use the - * Prefix class. + * A simpler way to add a prefix (without the manual need to add the + * corresponding pop()) is to use the Prefix class. */ void push (const std::string &text); + /** * Remove the last prefix added with push(). */ void pop (); + /** - * Maximum number of levels to be - * printed on the console. This - * function allows to restrict - * console output to the upmost - * levels of iterations. Only - * output with less than n - * prefixes is printed. By calling - * this function with n=0, no - * console output will be written. + * Maximum number of levels to be printed on the console. This function + * allows to restrict console output to the upmost levels of iterations. + * Only output with less than n prefixes is printed. By calling + * this function with n=0, no console output will be written. * - * The previous value of this - * parameter is returned. + * The previous value of this parameter is returned. */ unsigned int depth_console (const unsigned int n); + /** - * Maximum number of levels to be - * written to the log file. The - * functionality is the same as - * depth_console, nevertheless, - * this function should be used - * with care, since it may spoile - * the value of a log file. + * Maximum number of levels to be written to the log file. The + * functionality is the same as depth_console, nevertheless, + * this function should be used with care, since it may spoile the value + * of a log file. * - * The previous value of this - * parameter is returned. + * The previous value of this parameter is returned. */ unsigned int depth_file (const unsigned int n); + /** - * Set time printing flag. If this flag - * is true, each output line will - * be prepended by the user time used - * by the running program so far. + * Set time printing flag. If this flag is true, each output line will be + * prepended by the user time used by the running program so far. * - * The previous value of this - * parameter is returned. + * The previous value of this parameter is returned. */ bool log_execution_time (const bool flag); + /** - * Output time differences - * between consecutive logs. If - * this function is invoked with - * true, the time difference - * between the previous log line - * and the recent one is - * printed. If it is invoked with - * false, the accumulated - * time since start of the - * program is printed (default - * behavior). + * Output time differences between consecutive logs. If this function is + * invoked with true, the time difference between the previous + * log line and the recent one is printed. If it is invoked with + * false, the accumulated time since start of the program is + * printed (default behavior). * - * The measurement of times is - * not changed by this function, - * just the output. + * The measurement of times is not changed by this function, just the + * output. * - * The previous value of this - * parameter is returned. + * The previous value of this parameter is returned. */ bool log_time_differences (const bool flag); + /** - * Write detailed timing - * information. - * - * + * Write detailed timing information. */ void timestamp(); + /** * Log the thread id. */ bool log_thread_id (const bool flag); + /** - * Set a threshold for the - * minimal absolute value of - * double values. All numbers - * with a smaller absolute value - * will be printed as zero. + * Set a threshold for the minimal absolute value of double values. All + * numbers with a smaller absolute value will be printed as zero. * - * The default value for this - * threshold is zero, - * i.e. numbers are printed + * The default value for this threshold is zero, i.e. numbers are printed * according to their real value. * - * This feature is mostly useful - * for automated tests: there, - * one would like to reproduce - * the exact same solution in - * each run of a - * testsuite. However, subtle - * difference in processor, - * operating system, or compiler - * version can lead to - * differences in the last few - * digits of numbers, due to - * different rounding. While one - * can avoid trouble for most - * numbers when comparing with - * stored results by simply - * limiting the accuracy of - * output, this does not hold for - * numbers very close to zero, - * i.e. zero plus accumulated - * round-off. For these numbers, - * already the first digit is - * tainted by round-off. Using - * the present function, it is - * possible to eliminate this - * source of problems, by simply - * writing zero to the output in - * this case. + * This feature is mostly useful for automated tests: there, one would + * like to reproduce the exact same solution in each run of a testsuite. + * However, subtle difference in processor, operating system, or compiler + * version can lead to differences in the last few digits of numbers, due + * to different rounding. While one can avoid trouble for most numbers + * when comparing with stored results by simply limiting the accuracy of + * output, this does not hold for numbers very close to zero, i.e. zero + * plus accumulated round-off. For these numbers, already the first digit + * is tainted by round-off. Using the present function, it is possible to + * eliminate this source of problems, by simply writing zero to the + * output in this case. */ void threshold_double(const double t); + + /** - * The same as - * threshold_double(), but for - * float values. + * The same as threshold_double(), but for float values. */ void threshold_float(const float t); + /** - * Output a constant something - * through this stream. + * Output a constant something through this stream. */ template LogStream &operator << (const T &t); + /** - * Output double precision - * numbers through this - * stream. + * Output double precision numbers through this stream. * - * If they are set, this function - * applies the methods for making - * floating point output - * reproducible as discussed in - * the introduction. + * If they are set, this function applies the methods for making floating + * point output reproducible as discussed in the introduction. */ LogStream &operator << (const double t); + /** - * Output single precision - * numbers through this - * stream. + * Output single precision numbers through this stream. * - * If they are set, this function - * applies the methods for making - * floating point output - * reproducible as discussed in - * the introduction. + * If they are set, this function applies the methods for making floating + * point output reproducible as discussed in the introduction. */ LogStream &operator << (const float t); + /** - * Treat ostream - * manipulators. This passes on - * the whole thing to the - * template function with the - * exception of the - * std::endl - * manipulator, for which special - * action is performed: write the - * temporary stream buffer - * including a header to the file - * and std::cout and - * empty the buffer. + * Treat ostream manipulators. This passes on the whole thing to the + * template function with the exception of the std::endl + * manipulator, for which special action is performed: write the + * temporary stream buffer including a header to the file and + * std::cout and empty the buffer. * - * An overload of this function is needed - * anyway, since the compiler can't bind - * manipulators like @p std::endl - * directly to template arguments @p T - * like in the previous general - * template. This is due to the fact that - * @p std::endl is actually an overloaded - * set of functions for @p std::ostream, - * @p std::wostream, and potentially more - * of this kind. This function is - * therefore necessary to pick one - * element from this overload set. + * An overload of this function is needed anyway, since the compiler + * can't bind manipulators like @p std::endl directly to template + * arguments @p T like in the previous general template. This is due to + * the fact that @p std::endl is actually an overloaded set of functions + * for @p std::ostream, @p std::wostream, and potentially more of this + * kind. This function is therefore necessary to pick one element from + * this overload set. */ LogStream &operator<< (std::ostream& (*p) (std::ostream &)); + /** - * Determine an estimate for - * the memory consumption (in - * bytes) of this - * object. Since sometimes - * the size of objects can - * not be determined exactly - * (for example: what is the - * memory consumption of an - * STL std::map type with a - * certain number of - * elements?), this is only - * an estimate. however often - * quite close to the true - * value. + * Determine an estimate for the memory consumption (in bytes) of this + * object. Since sometimes the size of objects can not be determined + * exactly (for example: what is the memory consumption of an STL + * std::map type with a certain number of elements?), this is + * only an estimate. however often quite close to the true value. */ std::size_t memory_consumption () const; + /** * Exception. */ @@ -448,46 +370,36 @@ public: private: /** - * Stack of strings which are printed - * at the beginning of each line to - * allow identification where the - * output was generated. + * Stack of strings which are printed at the beginning of each line to + * allow identification where the output was generated. */ std::stack prefixes; /** - * Default stream, where the output - * is to go to. This stream defaults - * to std::cerr, but can be set to another - * stream through the constructor. + * Default stream, where the output is to go to. This stream defaults to + * std::cerr, but can be set to another stream through the + * constructor. */ std::ostream *std_out; /** - * Pointer to a stream, where a copy of - * the output is to go to. Usually, this - * will be a file stream. + * Pointer to a stream, where a copy of the output is to go to. Usually, + * this will be a file stream. * - * You can set and reset this stream - * by the attach function. + * You can set and reset this stream by the attach function. */ std::ostream *file; /** - * Value denoting the number of - * prefixes to be printed to the - * standard output. If more than - * this number of prefixes is - * pushed to the stack, then no - * output will be generated until - * the number of prefixes shrinks + * Value denoting the number of prefixes to be printed to the standard + * output. If more than this number of prefixes is pushed to the stack, + * then no output will be generated until the number of prefixes shrinks * back below this number. */ unsigned int std_depth; /** - * Same for the maximum depth of - * prefixes for output to a file. + * Same for the maximum depth of prefixes for output to a file. */ unsigned int file_depth; @@ -507,53 +419,33 @@ private: double last_time; /** - * Threshold for printing double - * values. Every number with - * absolute value less than this - * is printed as zero. + * Threshold for printing double values. Every number with absolute value + * less than this is printed as zero. */ double double_threshold; /** - * Threshold for printing float - * values. Every number with - * absolute value less than this - * is printed as zero. + * Threshold for printing float values. Every number with absolute value + * less than this is printed as zero. */ float float_threshold; /** - * An offset added to every float - * or double number upon - * output. This is done after the - * number is compared to - * #double_threshold or #float_threshold, - * but before rounding. + * An offset added to every float or double number upon output. This is + * done after the number is compared to #double_threshold or + * #float_threshold, but before rounding. * - * This functionality was - * introduced to produce more - * reproducible floating point - * output for regression - * tests. The rationale is, that - * an exact output value is much - * more likely to be 1/8 than - * 0.124997. If we round to two - * digits though, 1/8 becomes - * unreliably either .12 or .13 - * due to machine accuracy. On - * the other hand, if we add a - * something above machine - * accuracy first, we will always - * get .13. + * This functionality was introduced to produce more reproducible + * floating point output for regression tests. The rationale is, that an + * exact output value is much more likely to be 1/8 than 0.124997. If we + * round to two digits though, 1/8 becomes unreliably either .12 or .13 + * due to machine accuracy. On the other hand, if we add a something + * above machine accuracy first, we will always get .13. * - * It is safe to leave this - * value equal to zero. For - * regression tests, the function - * test_mode() sets it to a - * reasonable value. + * It is safe to leave this value equal to zero. For regression tests, + * the function test_mode() sets it to a reasonable value. * - * The offset is relative to the - * magnitude of the number. + * The offset is relative to the magnitude of the number. */ double offset; @@ -563,49 +455,38 @@ private: bool print_thread_id; /** - * The value times() returned - * on initialization. + * The value times() returned on initialization. */ double reference_time_val; /** - * The tms structure times() - * filled on initialization. + * The tms structure times() filled on initialization. */ struct tms reference_tms; /** - * Original buffer of - * std::cerr. We store - * the address of that buffer - * when #log_cerr is called, and - * reset it to this value if - * #log_cerr is called a second - * time, or when the destructor - * of this class is run. + * Original buffer of std::cerr. We store the address of that + * buffer when #log_cerr is called, and reset it to this value if + * #log_cerr is called a second time, or when the destructor of this + * class is run. */ std::streambuf *old_cerr; /** - * Print head of line. This prints - * optional time information and - * the contents of the prefix stack. + * A flag indicating whether output is currently at a new line */ - void print_line_head (); + bool at_newline; /** - * Actually do the work of - * writing output. This function - * unifies the work that is - * common to the two - * operator<< functions. + * Print head of line. This prints optional time information and the + * contents of the prefix stack. */ - template - void print (const T &t); + void print_line_head (); + /** - * Check if we are on a new line - * and print the header before - * the data. + * Internal wrapper around "thread local" outstreams. This private + * function will return the correct internal ostringstream buffer for + * operater<<. */ std::ostringstream &get_stream(); @@ -615,9 +496,7 @@ private: typedef std::map > stream_map_type; /** - * We generate a stringstream for - * every process that sends log - * messages. + * We generate a stringstream for every process that sends log messages. */ stream_map_type outstreams; @@ -627,34 +506,13 @@ private: /* ----------------------------- Inline functions and templates ---------------- */ -template -inline -void -LogStream::print (const T &t) -{ - // if the previous command was an - // std::endl, print the topmost - // prefix and a colon - std::ostringstream &stream = get_stream(); - stream << t; - // print the rest of the message -// if (prefixes.size() <= std_depth) -// *std_out << t; - -// if (file && (prefixes.size() <= file_depth)) -// *file << t; -} - - - template inline LogStream & LogStream::operator<< (const T &t) { - // do the work that is common to - // the operator<< functions - print (t); + // print to the internal stringstream: + get_stream() << t; return *this; } @@ -664,20 +522,16 @@ inline LogStream & LogStream::operator<< (const double t) { - // we have to make sure that we - // don't catch NaN's and +-Inf's - // with the test, because for these - // denormals all comparisons are - // always false. thus, for a NaN, - // both t<=0 and t>=0 are false at - // the same time, which can't be - // said for any other number + // we have to make sure that we don't catch NaN's and +-Inf's with the + // test, because for these denormals all comparisons are always false. + // thus, for a NaN, both t<=0 and t>=0 are false at the same time, which + // can't be said for any other number if (! (t<=0) && !(t>=0)) - print (t); + get_stream() << t; else if (std::fabs(t) < double_threshold) - print ('0'); + get_stream() << '0'; else - print(t*(1.+offset)); + get_stream() << t*(1.+offset); return *this; } @@ -688,20 +542,16 @@ inline LogStream & LogStream::operator<< (const float t) { - // we have to make sure that we - // don't catch NaN's and +-Inf's - // with the test, because for these - // denormals all comparisons are - // always false. thus, for a NaN, - // both t<=0 and t>=0 are false at - // the same time, which can't be - // said for any other number + // we have to make sure that we don't catch NaN's and +-Inf's with the + // test, because for these denormals all comparisons are always false. + // thus, for a NaN, both t<=0 and t>=0 are false at the same time, which + // can't be said for any other number if (! (t<=0) && !(t>=0)) - print (t); + get_stream() << t; else if (std::fabs(t) < float_threshold) - print ('0'); + get_stream() << '0'; else - print(t*(1.+offset)); + get_stream() << t*(1.+offset); return *this; } diff --git a/deal.II/source/base/logstream.cc b/deal.II/source/base/logstream.cc index 475e30ffa5..0a1cf46faa 100644 --- a/deal.II/source/base/logstream.cc +++ b/deal.II/source/base/logstream.cc @@ -49,7 +49,7 @@ LogStream::LogStream() std_depth(10000), file_depth(10000), print_utime(false), diff_utime(false), last_time (0.), double_threshold(0.), float_threshold(0.), - offset(0), old_cerr(0) + offset(0), old_cerr(0), at_newline(true) { prefixes.push("DEAL:"); std_out->setf(std::ios::showpoint | std::ios::left); @@ -61,10 +61,8 @@ LogStream::LogStream() LogStream::~LogStream() { - // if there was anything left in - // the stream that is current to - // this thread, make sure we flush - // it before it gets lost + // if there was anything left in the stream that is current to this + // thread, make sure we flush it before it gets lost { const unsigned int id = Threads::this_thread_id(); if ((outstreams.find(id) != outstreams.end()) @@ -73,42 +71,20 @@ LogStream::~LogStream() && (outstreams[id]->str().length() > 0)) { - // except the situation is - // not quite that simple. if - // this object is the - // 'deallog' object, then it - // is destroyed upon exit of - // the program. since it's - // defined in a shared - // library that depends on - // libstdc++.so, destruction - // happens before destruction - // of std::cout/cerr, but - // after all file variables - // defined in user programs - // have been destroyed. in - // other words, if we get - // here and the object being - // destroyed is 'deallog' and - // if 'deallog' is associated - // with a file stream, then - // we're in trouble: we'll - // try to write to a file - // that doesn't exist any - // more, and we're likely - // going to crash (this is - // tested by - // base/log_crash_01). rather - // than letting it come to - // this, print a message to - // the screen (note that we - // can't issue an assertion - // here either since Assert - // may want to write to - // 'deallog' itself, and - // AssertThrow will throw an - // exception that can't be - // caught) + // except the situation is not quite that simple. if this object is + // the 'deallog' object, then it is destroyed upon exit of the + // program. since it's defined in a shared library that depends on + // libstdc++.so, destruction happens before destruction of + // std::cout/cerr, but after all file variables defined in user + // programs have been destroyed. in other words, if we get here and + // the object being destroyed is 'deallog' and if 'deallog' is + // associated with a file stream, then we're in trouble: we'll try + // to write to a file that doesn't exist any more, and we're likely + // going to crash (this is tested by base/log_crash_01). rather + // than letting it come to this, print a message to the screen + // (note that we can't issue an assertion here either since Assert + // may want to write to 'deallog' itself, and AssertThrow will + // throw an exception that can't be caught) if ((this == &deallog) && file) std::cerr << ("You still have content that was written to 'deallog' " "but not flushed to the screen or a file while the " @@ -125,18 +101,11 @@ LogStream::~LogStream() if (old_cerr) std::cerr.rdbuf(old_cerr); - // on some systems, destroying the - // outstreams objects of deallog - // triggers some sort of memory - // corruption, in particular when - // we also link with Trilinos; - // since this happens at the very - // end of the program, we take the - // liberty to simply not do it by - // putting that object into a - // deliberate memory leak and - // instead destroying an empty - // object + // on some systems, destroying the outstreams objects of deallog triggers + // some sort of memory corruption, in particular when we also link with + // Trilinos; since this happens at the very end of the program, we take + // the liberty to simply not do it by putting that object into a + // deliberate memory leak and instead destroying an empty object #ifdef DEAL_II_WITH_TRILINOS if (this == &deallog) { @@ -170,19 +139,29 @@ LogStream::test_mode(bool on) LogStream & LogStream::operator<< (std::ostream& (*p) (std::ostream &)) { - // do the work that is common to - // the operator<< functions - print (p); + std::ostringstream &stream = get_stream(); - // next check whether this is the - // endl manipulator, and if so - // set a flag + // Print to the internal stringstream: + stream << p; + + // Next check whether this is the flush or endl + // manipulator, and if so print out the message. + std::ostream & (* const p_flush) (std::ostream &) = &std::flush; std::ostream & (* const p_endl) (std::ostream &) = &std::endl; - if (p == p_endl) + if (p == p_flush || p == p_endl) { Threads::Mutex::ScopedLock lock(write_lock); - print_line_head(); - std::ostringstream &stream = get_stream(); + + // Print the line head in case of a newline: + if (at_newline) + print_line_head(); + + if(p == p_flush) + at_newline = false; + + if(p == p_endl) + at_newline = true; + if (prefixes.size() <= std_depth) *std_out << stream.str(); @@ -192,6 +171,7 @@ LogStream::operator<< (std::ostream& (*p) (std::ostream &)) // Start a new string stream.str(""); } + return *this; } @@ -213,7 +193,6 @@ LogStream::get_stream() } - void LogStream::attach(std::ostream &o) { -- 2.39.5