contrib/mul/mbl/mbl_log.cxx
Go to the documentation of this file.
00001 //:
00002 // \file
00003 // \author Ian Scott
00004 // \date 16-Jan-2005
00005 // \brief Flexible Status and Error Logging.
00006 // These classes are patterned after the log4j logging library,
00007 // although without all of the sophistication.
00008 
00009 // n.b. We have not copied any code from log4j (or other logging libraries)
00010 // - just the ideas.
00011 
00012 #include "mbl_log.h"
00013 #include <vcl_cstddef.h>
00014 #include <vcl_fstream.h>
00015 #include <vcl_memory.h>
00016 #include <vcl_algorithm.h>
00017 #include <vcl_cassert.h>
00018 #include <vcl_iostream.h>
00019 #include <vcl_sstream.h>
00020 #include <vcl_utility.h>
00021 #include <vul/vul_string.h>
00022 #include <mbl/mbl_read_props.h>
00023 #include <mbl/mbl_exception.h>
00024 
00025 vcl_ostream& operator<<(vcl_ostream&os, mbl_logger::levels level)
00026 {
00027   switch (level)
00028   {
00029    case mbl_logger::NONE:
00030     os << "NONE";
00031     break;
00032    case mbl_logger::EMERG:
00033     os << "EMERG";
00034     break;
00035    case mbl_logger::ALERT:
00036     os << "ALERT";
00037     break;
00038    case mbl_logger::CRIT:
00039     os << "CRIT";
00040     break;
00041    case mbl_logger::ERR:
00042     os << "ERR";
00043     break;
00044    case mbl_logger::WARN:
00045     os << "WARN";
00046     break;
00047    case mbl_logger::NOTICE:
00048     os << "NOTICE";
00049     break;
00050    case mbl_logger::INFO:
00051     os << "INFO";
00052     break;
00053    case mbl_logger::DEBUG:
00054     os << "DEBUG";
00055     break;
00056    case mbl_logger::ALL:
00057     os << "ALL";
00058     break;
00059    default:
00060     os << "LOG" << level;
00061     break;
00062   }
00063   return os;
00064 }
00065 
00066 
00067 vcl_ostream& operator<<(vcl_ostream&os, const mbl_log_categories::cat_spec& spec)
00068 {
00069   os << "{ level: " << static_cast<mbl_logger::levels>(spec.level);
00070   switch (spec.output)
00071   {
00072    case mbl_log_categories::cat_spec::FILE_OUT:
00073     os << " file_output: " << spec.name;
00074     break;
00075    case mbl_log_categories::cat_spec::NAMED_STREAM:
00076     os << " stream_output: " << spec.name;
00077     break;
00078    default:
00079     assert(!"This should not happen: invalid spec.output");
00080     break;
00081   }
00082   if (!spec.dump_prefix.empty())
00083     os << " dump_prefix: " << spec.dump_prefix;
00084 
00085   os << " }";
00086   return os;
00087 }
00088 
00089 //Notes - use two different stream bufs to hendle the mt_log() and the log().
00090 // one should response to flushes with a terminate - the other not.
00091 
00092 
00093 // Got the a streambuf example from vul_redirector and hacked around with it.
00094 // It passes on all stuff direct to the real_streambuf, whilst calling
00095 // extra functions on the output object to print message headers and footers.
00096 
00097 
00098 int mbl_log_streambuf::sync()
00099 {
00100 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00101 
00102   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00103 
00104   if (n)
00105     logger_->output_->append(pbase(), n);
00106   logger_->output_->terminate_flush();
00107 
00108   pbump(-n);  // Reset pptr().
00109 #endif
00110   return 0;
00111 }
00112 
00113 int mbl_log_streambuf::overflow(int ch)
00114 {
00115 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00116   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00117 
00118   if (n)
00119     logger_->output_->append(pbase(), n);
00120   pbump(-n);  // Reset pptr().
00121 
00122   if (ch == EOF)
00123     return 0;
00124 
00125   char cbuf = (char)ch;
00126   logger_->output_->append(&cbuf, 1);
00127   return ch;
00128 #else
00129   return EOF;
00130 #endif
00131 }
00132 
00133 vcl_streamsize mbl_log_streambuf::xsputn( const char *ptr, vcl_streamsize nchar)
00134 {
00135 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00136 
00137   // Output anything already in buffer
00138   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00139   if (n)
00140     logger_->output_->append(pbase(), n);
00141   pbump(-n);  // Reset pptr().
00142 
00143   logger_->output_->append(ptr, nchar);
00144   return nchar;
00145 #else
00146   return 0;
00147 #endif
00148 }
00149 
00150 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00151 
00152 //: Default constructor only available to root's default logger.
00153 mbl_logger::mbl_logger():
00154   level_(NOTICE),
00155   output_(new mbl_log_output_stream(vcl_cerr, "")),
00156   streambuf_(this),
00157   logstream_(&streambuf_),
00158   mt_logstream_(&logstream_)
00159 {
00160   // This will have to change to support proper hierarchical control over categories.
00161 //  logstream_.tie(output_.real_stream_);
00162   // Don't insert default root logger - this would cause infinite recursion.
00163   root().all_loggers_.insert(this);
00164 }
00165 
00166 mbl_logger::~mbl_logger()
00167 {
00168   root().all_loggers_.erase(this);
00169   delete output_;
00170 }
00171 
00172 mbl_log_output_stream::mbl_log_output_stream(vcl_ostream& real_stream, const char *id):
00173   real_stream_(&real_stream), id_(id), has_started_(false)
00174 {}
00175 
00176 //: Start a new log entry, with id info.
00177 void mbl_log_output_stream::start()
00178 {
00179   // Deal with unfinished log message
00180   if (has_started_)
00181     (*real_stream_) << "LOG_MESSAGE_TERMINATED_EARLY\n";
00182 
00183   // Avoid interspersed output.
00184   real_stream_->flush();
00185 
00186   has_started_=true;
00187 }
00188 
00189 //: Start a new log entry, with id info.
00190 // Future calls to terminate_flush will be ignored.
00191 void mbl_log_output_stream::start_with_manual_termination(int level, const char *srcfile, int srcline)
00192 {
00193   ignore_flush_=true;
00194   start();
00195   (*real_stream_) << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00196 }
00197 
00198 //: Start a new log entry, with id info.
00199 // Future calls to terminate_flush will be honoured.
00200 void mbl_log_output_stream::start_with_flush_termination(int level, const char *srcfile, int srcline)
00201 {
00202   ignore_flush_=false;
00203   start();
00204   (*real_stream_) << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00205 }
00206 //: add contents to the existing log entry.
00207 void mbl_log_output_stream::append(const char * contents, vcl_streamsize n_chars)
00208 {
00209   // Deal with unfinished log message
00210   if (!has_started_)
00211   {
00212     (*real_stream_) << "UNKNOWN_START_LOG: " << id_ << ' ';
00213     has_started_ = true;
00214     ignore_flush_ = true;
00215   }
00216 
00217   real_stream_->rdbuf()->sputn(contents, n_chars);
00218 }
00219 
00220 //: Finish the log entry, sent from a stream flush.
00221 void mbl_log_output_stream::terminate_manual()
00222 {
00223   real_stream_->flush();
00224   has_started_=false;
00225 }
00226 
00227 //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00228 void mbl_log_output_stream::terminate_flush()
00229 {
00230   if (!ignore_flush_)
00231   {
00232     real_stream_->flush();
00233     has_started_=false;
00234   }
00235 }
00236 
00237 mbl_log_output_file::mbl_log_output_file(const vcl_string &filename, const char *id):
00238   file_(filename.c_str(), vcl_ios_app), id_(id), has_started_(false)
00239 {}
00240 
00241 //: Start a new log entry, with id info.
00242 void mbl_log_output_file::start()
00243 {
00244   // Deal with unfinished log message
00245   if (has_started_)
00246     file_ << "LOG_MESSAGE_TERMINATED_EARLY\n";
00247 
00248   // Avoid interspersed output.
00249   file_.flush();
00250 
00251   has_started_=true;
00252 }
00253 
00254 //: Start a new log entry, with id info.
00255 // Future calls to terminate_flush will be ignored.
00256 void mbl_log_output_file::start_with_manual_termination(int level, const char *srcfile, int srcline)
00257 {
00258   ignore_flush_=true;
00259   start();
00260   file_ << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00261 }
00262 
00263 //: Start a new log entry, with id info.
00264 // Future calls to terminate_flush will be honoured.
00265 void mbl_log_output_file::start_with_flush_termination(int level, const char *srcfile, int srcline)
00266 {
00267   ignore_flush_=false;
00268   start();
00269   file_ << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00270 }
00271 
00272 //: add contents to the existing log entry.
00273 void mbl_log_output_file::append(const char * contents, vcl_streamsize n_chars)
00274 {
00275   // Deal with unstarted log message
00276   if (!has_started_)
00277   {
00278     file_ << "UNKNOWN_START_LOG: " << id_ << ' ';
00279     has_started_=true;
00280     ignore_flush_=false;
00281   }
00282 
00283   file_.rdbuf()->sputn(contents, n_chars);
00284 }
00285 
00286 //: Finish the log entry, sent from a stream flush.
00287 void mbl_log_output_file::terminate_manual()
00288 {
00289   file_.flush();
00290   has_started_=false;
00291 }
00292 
00293 //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00294 void mbl_log_output_file::terminate_flush()
00295 {
00296   if (!ignore_flush_)
00297   {
00298     file_.flush();
00299     has_started_=false;
00300   }
00301 }
00302 
00303 #if 0 // This logger causes gcc to throw a recursive_init
00304       // Not entirely surprising, by MSVC didn't complain.
00305       // FIXME This logger is useful - for finding every logger in use
00306       // in a program
00307 static mbl_logger& local_logger()
00308 {
00309   static mbl_logger l("mul.mbl.log");
00310   return l;
00311 }
00312 #endif
00313 
00314 mbl_logger::mbl_logger(const char *id):
00315   output_(0),
00316   streambuf_(this),
00317   logstream_(&streambuf_),
00318   mt_logstream_(&logstream_)
00319 {
00320 #if 0 // FIXME
00321   MBL_LOG(INFO, local_logger(), "Creating logger: " << id);
00322 #endif
00323   const mbl_log_categories::cat_spec &cat =
00324     mbl_logger::root().categories().get(id);
00325 #if 0 // FIXME
00326   MBL_LOG(DEBUG, local_logger(), "Using cat_spec: " << cat);
00327 #endif
00328 
00329   level_ = cat.level;
00330   dump_prefix_ = cat.dump_prefix;
00331 
00332   if (cat.output == mbl_log_categories::cat_spec::NAMED_STREAM)
00333   {
00334     output_ = new mbl_log_output_stream(*cat.stream, id);
00335 //    logstream_.tie(output_.real_stream_);
00336   }
00337   else if (cat.output == mbl_log_categories::cat_spec::FILE_OUT)
00338   {
00339     output_ = new mbl_log_output_file(cat.name, id);
00340 //    logstream_.tie(output_.real_stream_);
00341   }
00342 
00343   root().all_loggers_.insert(this);
00344 }
00345 
00346 void mbl_logger::reinitialise()
00347 {
00348   const char *id = output_->id();
00349   const mbl_log_categories::cat_spec &cat =
00350     mbl_logger::root().categories().get(id);
00351 
00352   level_ = cat.level;
00353   dump_prefix_ = cat.dump_prefix;
00354 
00355   if (cat.output == mbl_log_categories::cat_spec::NAMED_STREAM)
00356   {
00357     delete output_;
00358     output_ = new mbl_log_output_stream(*cat.stream, id);
00359 //    logstream_.tie(output_.real_stream_);
00360   }
00361   else if (cat.output == mbl_log_categories::cat_spec::FILE_OUT)
00362   {
00363     delete output_;
00364     output_ = new mbl_log_output_file(cat.name, id);
00365 //    logstream_.tie(output_.real_stream_);
00366   }
00367 }
00368 
00369 void mbl_logger::set(int level, mbl_log_output_base* output)
00370 {
00371   level_ = level;
00372   delete output_;
00373   output_ = output;
00374 //  logstream_.tie(output_.real_stream_);
00375 }
00376 
00377 vcl_ostream &mbl_logger::log(int level, const char * srcfile, int srcline)
00378 {
00379   if (level_ < level)
00380     return root().null_stream_;
00381   output_->start_with_flush_termination(level, srcfile, srcline);
00382   return logstream_;
00383 }
00384 
00385 void mbl_logger::mtstart(int level, const char * srcfile, int srcline)
00386 {
00387   if (level_ < level)
00388   {
00389     mt_logstream_ = &root().null_stream_;
00390     return;
00391   }
00392   mt_logstream_ = &logstream_;
00393   output_->start_with_manual_termination(level, srcfile, srcline);
00394 }
00395 
00396 void mbl_logger::mtstop()
00397 {
00398   logstream_.flush();
00399   output_->terminate_manual();
00400 }
00401 #endif
00402 
00403 
00404 mbl_logger_root &mbl_logger::root()
00405 {
00406   static vcl_auto_ptr<mbl_logger_root> root_;
00407 
00408   if (!root_.get())
00409     root_ = vcl_auto_ptr<mbl_logger_root>(new mbl_logger_root());
00410   return *root_;
00411 }
00412 
00413 
00414 //:Load a default configuration file
00415 // Current Format is
00416 // \verbatim
00417 //LEVEL
00418 // \endverbatim
00419 // where LEVEL is an integer - setting the logging level.
00420 // see mbl_logger:levels for useful values.
00421 void mbl_logger_root::load_log_config_file(
00422   const vcl_map<vcl_string, vcl_ostream *> &stream_names)
00423 {
00424 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00425   // Make sure this list of mbl_log.properties locations code
00426   // stays in sync with mul/contrib/tools/print_mbl_log_properties.cxx
00427   vcl_ifstream config_file("mbl_log.properties");
00428   if (!config_file.is_open())
00429     config_file.open("~/mbl_log.properties");
00430   if (!config_file.is_open())
00431     config_file.open("~/.mbl_log.properties");
00432   if (!config_file.is_open())
00433   {
00434     vcl_string home1("${HOME}/mbl_log.properties");
00435     vcl_string home2("${HOME}/.mbl_log.properties");
00436     vcl_string home3("${HOMESHARE}/mbl_log.properties");
00437     vcl_string home4("${HOMEDRIVE}${HOMEDIR}/mbl_log.properties");
00438     vcl_string home5("${HOMEDRIVE}${HOMEPATH}/mbl_log.properties");
00439     vcl_string home6("${USERPROFILE}/mbl_log.properties");
00440     if (vul_string_expand_var(home1))
00441       config_file.open(home1.c_str());
00442     if (!config_file.is_open() && vul_string_expand_var(home2))
00443       config_file.open(home2.c_str());
00444     if (!config_file.is_open() && vul_string_expand_var(home3))
00445       config_file.open(home3.c_str());
00446     if (!config_file.is_open() && vul_string_expand_var(home4))
00447       config_file.open(home4.c_str());
00448     if (!config_file.is_open() && vul_string_expand_var(home5))
00449       config_file.open(home5.c_str());
00450     if (!config_file.is_open() && vul_string_expand_var(home6))
00451       config_file.open(home6.c_str());
00452   }
00453   if (!config_file.is_open())
00454     config_file.open("C:\\mbl_log.properties");
00455 
00456   if (!config_file.is_open())
00457   {
00458     vcl_cerr << "WARNING: No mbl_log.properties file found.\n";
00459     return;
00460   }
00461 
00462   config_file.clear(); // May have been set to fail on failed open.
00463   load_log_config(config_file, stream_names);
00464 #endif
00465 }
00466 
00467 
00468 //:Load a default configuration file
00469 // Current Format is
00470 // \verbatim
00471 //LEVEL
00472 // \endverbatim
00473 // where LEVEL is an integer - setting the logging level.
00474 // see mbl_logger:levels for useful values.
00475 void mbl_logger_root::load_log_config(vcl_istream& is,
00476                                       const vcl_map<vcl_string, vcl_ostream *> &stream_names)
00477 {
00478 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00479   categories_.config(is, stream_names);
00480   update_all_loggers();
00481 #endif
00482 }
00483 
00484 // Make sure all known loggers reinitialise themselves.
00485 void mbl_logger_root::update_all_loggers()
00486 {
00487 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00488   for (vcl_set<mbl_logger *>::iterator it=all_loggers_.begin(),
00489        end=all_loggers_.end(); it!=end; ++it)
00490     (*it)->reinitialise();
00491 #endif
00492 }
00493 
00494 
00495 mbl_log_categories::mbl_log_categories()
00496 {
00497   cat_spec default_spec;
00498   default_spec.level = mbl_logger::NOTICE;
00499   default_spec.output = cat_spec::NAMED_STREAM;
00500   default_spec.name = "vcl_cerr";
00501   default_spec.stream = &vcl_cerr;
00502   default_spec.dump_prefix = "";
00503   cat_list_[""] = default_spec;
00504 }
00505 
00506 typedef vcl_map<vcl_string, vcl_ostream*> stream_names_t;
00507 
00508 
00509 inline mbl_log_categories::cat_spec parse_cat_spec(const vcl_string &str,
00510                                                    const stream_names_t& stream_names)
00511 {
00512   mbl_log_categories::cat_spec spec;
00513   vcl_istringstream ss(str);
00514   mbl_read_props_type props = mbl_read_props_ws(ss);
00515 
00516   vcl_string s = props.get_required_property("level");
00517   if (s == "NONE")
00518     spec.level = mbl_logger::NONE;
00519   else if (s == "EMERG")
00520     spec.level = mbl_logger::EMERG;
00521   else if (s == "ALERT")
00522     spec.level = mbl_logger::ALERT;
00523   else if (s == "CRIT")
00524     spec.level = mbl_logger::CRIT;
00525   else if (s == "ERR")
00526     spec.level = mbl_logger::ERR;
00527   else if (s == "WARN")
00528     spec.level = mbl_logger::WARN;
00529   else if (s == "NOTICE")
00530     spec.level = mbl_logger::NOTICE;
00531   else if (s == "INFO")
00532     spec.level = mbl_logger::INFO;
00533   else if (s == "DEBUG")
00534     spec.level = mbl_logger::DEBUG;
00535   else if (s == "ALL")
00536     spec.level = mbl_logger::ALL;
00537   else
00538   {
00539     mbl_exception_warning(
00540       mbl_exception_parse_error(
00541         vcl_string("mbl_log_categories.cxx:parse_cat_spec: unknown level: ") + s) );
00542     // Default to NOTICE if no exceptions.
00543     spec.level = mbl_logger::NOTICE;
00544   }
00545 
00546   spec.dump_prefix = props.get_optional_property("dump_prefix");
00547   spec.timestamp = props.get_optional_property("timestamp");
00548 
00549   if (props.find("file_output") != props.end())
00550   {
00551     spec.output = mbl_log_categories::cat_spec::FILE_OUT;
00552     spec.name = props["file_output"];
00553     props.erase("file_output");
00554   }
00555   else if (props.find("stream_output") != props.end())
00556   {
00557     spec.name = "";
00558     vcl_string s = props["stream_output"];
00559     spec.output = mbl_log_categories::cat_spec::NAMED_STREAM;
00560     spec.name = s;
00561     stream_names_t::const_iterator it = stream_names.find(s);
00562 
00563     if (s == "cout" || s == "vcl_cout" || s == "std::cout")
00564       spec.stream = &vcl_cout;
00565     else if (s == "cerr" || s == "vcl_cerr" || s == "std::cerr")
00566       spec.stream = &vcl_cerr;
00567     else if (it != stream_names.end())
00568       spec.stream = it->second;
00569     else
00570     {
00571       mbl_exception_warning(
00572         mbl_exception_parse_error(
00573           vcl_string("mbl_log.cxx:parse_cat_spec: unknown stream output name: ")
00574           + props["stream_output"]) );
00575       // Default to CERR if no exceptions.
00576       spec.stream = &vcl_cerr;
00577       spec.name = "vcl_cerr";
00578     }
00579     props.erase("stream_output");
00580   }
00581   else
00582   {
00583     spec.output = mbl_log_categories::cat_spec::NAMED_STREAM;
00584     spec.stream = &vcl_cerr;
00585     spec.name = "vcl_cerr";
00586   }
00587 
00588   mbl_read_props_look_for_unused_props("mbl_log.cxx::parse_cat_spec", props);
00589 
00590   return spec;
00591 }
00592 
00593 //: Configure whole category list from a file.
00594 // New entries are added to any existing category details.
00595 void mbl_log_categories::config(vcl_istream&s, const stream_names_t& stream_names)
00596 {
00597   mbl_read_props_type props = mbl_read_props_ws(s);
00598 
00599   //Deal with "root" special case.
00600   mbl_read_props_type::iterator it1=props.find("root");
00601   if (it1 == props.end())
00602     it1 = props.find("ROOT");
00603   if (it1 != props.end())
00604   {
00605     cat_spec spec = parse_cat_spec(it1->second, stream_names);
00606     cat_list_[""] = spec;
00607     props.erase(it1);
00608   }
00609 
00610   for (mbl_read_props_type::const_iterator it2=props.begin(), end = props.end();
00611        it2 != end; ++it2)
00612   {
00613     cat_spec spec = parse_cat_spec(it2->second, stream_names);
00614     cat_list_[it2->first] = spec;
00615   }
00616 }
00617 
00618 //: Make the category list empty;
00619 // An "empty" list still contains a root entry.
00620 void mbl_log_categories::clear()
00621 {
00622   cat_list_.clear();
00623   cat_spec default_spec;
00624   default_spec.level = mbl_logger::NOTICE;
00625   default_spec.name = "cerr";
00626   default_spec.stream = &vcl_cerr;
00627   default_spec.output = cat_spec::NAMED_STREAM;
00628   default_spec.dump_prefix = "";
00629   cat_list_[""] = default_spec;
00630 }
00631 
00632 struct mbl_log_prefix_comp
00633 {
00634   vcl_string s2;
00635   mbl_log_prefix_comp(const vcl_string& s): s2(s) {}
00636 
00637   bool operator() (const vcl_pair<vcl_string, mbl_log_categories::cat_spec>& s1)
00638   {
00639 // simple version:     return s1.first == s2.substr(0,s1.first.size());
00640 // However this would allow s1=AA.11 to match against AA.111
00641 
00642     if (s1.first.size() == s2.size())
00643       return s1.first == s2;
00644     else if (s1.first.size() > s2.size())
00645       return false;
00646     else if (s1.first.empty()) // always match against root.
00647       return true;
00648     else
00649       return s1.first == s2.substr(0,s1.first.size()) && s2[s1.first.size()] == '.';
00650   }
00651 };
00652 
00653 
00654 const mbl_log_categories::cat_spec&
00655   mbl_log_categories::get(const vcl_string& category) const
00656 {
00657   typedef vcl_map<vcl_string, cat_spec>::const_reverse_iterator iter;
00658 
00659   iter it = vcl_find_if(cat_list_.rbegin(), cat_list_.rend(),
00660                         mbl_log_prefix_comp(category));
00661   // The search shouldn't get past the first (root) entry.
00662   assert(it != cat_list_.rend());
00663 
00664   // vcl_cerr << "MBL_LOG: Using category \"" << it->first << '\"' << '\n';
00665   return it->second;
00666 }
00667 
00668 
00669 void mbl_log_categories::print(vcl_ostream& os) const
00670 {
00671   typedef vcl_map<vcl_string, cat_spec>::const_iterator iter;
00672   assert(!cat_list_.empty());
00673 
00674   iter it = cat_list_.begin(), end = cat_list_.end();
00675   assert(it->first.empty());
00676 
00677   os << "root:\n  " << it->second << '\n';
00678 
00679   ++it;
00680   for (; it!=end; ++it)
00681     os << it->first << ":\n  " << it->second << '\n';
00682 
00683   os.flush();
00684 };