MessageLogger.cc
Go to the documentation of this file.
2 // vim: set sw=2 expandtab :
3 
6 #include "cetlib/bold_fontify.h"
17 
18 #include <atomic>
19 #include <cstdlib>
20 #include <iostream>
21 #include <map>
22 #include <memory>
23 #include <mutex>
24 #include <sstream>
25 #include <string>
26 #include <type_traits>
27 #include <utility>
28 #include <vector>
29 
30 #include <arpa/inet.h>
31 #include <ifaddrs.h>
32 #include <netdb.h>
33 #include <netinet/in.h>
34 
35 using namespace std;
36 using namespace std::string_literals;
37 
38 namespace {
39  enum class destination_kind { ordinary, statistics };
40 }
41 
42 namespace mf {
43 
44  namespace {
45 
47  getPluginPath()
48  {
49  if (getenv("MF_PLUGIN_PATH") != nullptr) {
50  return cet::search_path{"MF_PLUGIN_PATH"};
51  }
52  return cet::search_path{cet::plugin_libpath(), std::nothrow};
53  }
54 
55  cet::BasicPluginFactory pluginFactory_{getPluginPath(), "mfPlugin"};
56  cet::BasicPluginFactory pluginStatsFactory_{getPluginPath(),
57  "mfStatsPlugin"};
58  atomic<bool> isStarted{false};
59  map<string const, unique_ptr<service::ELdestination>> destinations_;
60  bool cleanSlateConfiguration_{true};
61  atomic<bool> purgeMode_{false};
62  atomic<int> count_{0};
63  std::recursive_mutex msgMutex_{};
64  string hostname_;
65  string hostaddr_;
66  string application_;
67  long pid_{};
68 
69  // Any text you want. It goes into the message header in the module
70  // position.
71  thread_local string module_ = "Early";
72 
73  // Kind of pointless, arbitrarily set to an alternate spelling of
74  // the phase, or the {run|subrun|event} number by art::MFStatusUpdater.
75  // FIXME: The statistics gatherer attempts to parse this!
76  thread_local string iteration_ = "pre-events";
77 
78  int
79  initGlobalVars(string const& applicationName = "")
80  {
81  char hostname[1024] = {0};
82  hostname_ =
83  (gethostname(hostname, 1023) == 0) ? hostname : "Unknown Host";
84  hostent* host = nullptr;
85  host = gethostbyname(hostname);
86  if (host != nullptr) {
87  // Host lookup succeeded.
88  char* ip = inet_ntoa(*(struct in_addr*)host->h_addr);
89  hostaddr_ = ip;
90  } else {
91  // Loop over all network interfaces and use the first non-loopback
92  // address found.
93  ifaddrs* ifAddrStruct = nullptr;
94  if (getifaddrs(&ifAddrStruct)) {
95  // Failed, use the loopback address.
96  hostaddr_ = "127.0.0.1";
97  } else {
98  // Loop over all interfaces.
99  for (ifaddrs* ifa = ifAddrStruct; ifa != nullptr;
100  ifa = ifa->ifa_next) {
101  if (ifa->ifa_addr->sa_family == AF_INET) {
102  // This interface has a valid IPv4 address.
103  void* tmpAddrPtr = &((sockaddr_in*)ifa->ifa_addr)->sin_addr;
104  char addressBuffer[INET_ADDRSTRLEN];
105  inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
106  hostaddr_ = addressBuffer;
107  } else if (ifa->ifa_addr->sa_family == AF_INET6) {
108  // This interface has a valid IPv6 address.
109  void* tmpAddrPtr = &((sockaddr_in6*)ifa->ifa_addr)->sin6_addr;
110  char addressBuffer[INET6_ADDRSTRLEN];
111  inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
112  hostaddr_ = addressBuffer;
113  }
114  // Use the address if it is not a loopback address.
115  if (!empty(hostaddr_) && hostaddr_.compare("127.0.0.1") &&
116  hostaddr_.compare("::1")) {
117  break;
118  }
119  }
120  if (empty(hostaddr_)) {
121  // Failed to find anything, use the loopback address.
122  hostaddr_ = "127.0.0.1";
123  }
124  }
125  }
126  if (!empty(applicationName)) {
127  application_ = applicationName;
128  } else {
129  // get process name from '/proc/pid/cmdline'
130  stringstream ss;
131  ss << "//proc//" << pid_ << "//cmdline";
132  ifstream procfile{ss.str().c_str()};
133  string procinfo;
134  if (procfile.is_open()) {
135  // FIXME: This can fail with ERETRY!
136  procfile >> procinfo;
137  procfile.close();
138  }
139  auto end = procinfo.find('\0');
140  auto start = procinfo.find_last_of('/', end);
141  application_ = procinfo.substr(start + 1, end - start - 1);
142  }
143  pid_ = static_cast<long>(getpid());
144  return 0;
145  }
146 
147  // FIXME: Do not want to read from /proc/pid/cmdline if we do not have to!
148  // static int globalsInitializer = initGlobalVars();
149 
150  unique_ptr<service::ELdestination>
151  makePlugin_(cet::BasicPluginFactory& plugin_factory,
152  string const& libspec,
153  string const& psetname,
154  fhicl::ParameterSet const& pset)
155  {
156  unique_ptr<service::ELdestination> result;
157  try {
158  auto const pluginType = plugin_factory.pluginType(libspec);
159  if (pluginType ==
161  result =
162  plugin_factory.makePlugin<unique_ptr<service::ELdestination>>(
163  libspec, psetname, pset);
164  } else {
165  throw Exception(errors::Configuration, "MessageLoggerScribe: ")
166  << "unrecognized plugin type " << pluginType << "for plugin "
167  << libspec << ".\n";
168  }
169  }
170  catch (cet::exception const& e) {
171  throw Exception(errors::Configuration, "MessageLoggerScribe: ", e)
172  << "Exception caught while processing plugin spec.\n";
173  }
174  return result;
175  }
176 
177  string const default_destination_config_string = " type: cerr"
178  " categories: {"
179  " default: {"
180  " limit: -1"
181  " }"
182  " }"s;
184  default_destination_config()
185  {
186  return fhicl::ParameterSet::make(default_destination_config_string);
187  }
188 
190  default_destination_set_config()
191  {
192  string const config{"cerr: { "s + default_destination_config_string +
193  " }"s};
195  }
196 
197  void
198  sendMsgToDests(
199  ErrorObj& msg,
200  map<string const, unique_ptr<service::ELdestination>>& destinations)
201  {
202  if (empty(msg.xid().hostname())) {
203  msg.setHostName(GetHostName());
204  }
205  if (empty(msg.xid().hostaddr())) {
206  msg.setHostAddr(GetHostAddr());
207  }
208  if (empty(msg.xid().application())) {
210  }
211  if (msg.xid().pid() == 0) {
212  msg.setPID(GetPid());
213  }
214  if (empty(destinations)) {
215  cerr << "\nERROR LOGGED WITHOUT DESTINATION!\nAttaching destination "
216  "\"cerr\" by default\n\n";
217  destinations.emplace(
218  "cerr",
219  make_unique<service::ELostreamOutput>(
220  default_destination_set_config(), cet::ostream_handle{cerr}));
221  }
222  for (auto& destid_and_destination : destinations) {
223  destid_and_destination.second->log(msg);
224  }
225  }
226 
227  void
228  makeDestinations(fhicl::ParameterSet const& dests,
229  destination_kind const configuration)
230  {
231  set<string> ids;
232  vector<string> config_errors;
233  for (auto const& psetname : dests.get_pset_names()) {
234  auto dest_pset = dests.get<fhicl::ParameterSet>(psetname);
235  if (dest_pset.is_empty()) {
236  dest_pset = default_destination_config();
237  }
238  // Grab the destination type and filename.
239  string dest_type{};
240  if (!dest_pset.get_if_present("type", dest_type)) {
242  << "No 'type' specified for destination '" << psetname << "'.\n";
243  }
244  if (configuration == destination_kind::statistics) {
245  if ((dest_type != "cout"s) && (dest_type != "cerr"s) &&
246  (dest_type != "file"s)) {
248  << "\n"
249  << "Unsupported type [ " << dest_type
250  << " ] chosen for statistics printout.\n"
251  << "Must choose ostream type: \"cout\", \"cerr\", or \"file\""
252  << "\n";
253  }
254  }
255  string outputId{dest_type};
256  if ((dest_type != "cout"s) && (dest_type != "cerr"s) &&
257  (dest_type != "syslog"s)) {
258  outputId += ":" + dest_pset.get<string>("filename", psetname);
259  }
260  if (!ids.emplace(outputId).second) {
261  // We have a duplicate.
262  if (configuration == destination_kind::statistics) {
264  << " Output identifier: \"" << outputId << "\""
265  << " already specified within ordinary/statistics block in FHiCL "
266  "file"
267  << "\n";
268  }
269  }
270  auto iter_id_dest = destinations_.find(outputId);
271  if (iter_id_dest != destinations_.end()) {
272  constexpr cet::HorizontalRule rule{76};
273  string const hrule{'\n' + rule('=') + '\n'};
274  ostringstream except_msg;
275  except_msg << hrule << "\n Duplicate name for a ";
276  if (configuration == destination_kind::ordinary) {
277  except_msg << "MessageLogger";
278  } else {
279  except_msg << "MessageLogger Statistics";
280  }
281  except_msg << " destination: \"" << outputId << '"';
282  ostringstream orig_config_msg;
283  orig_config_msg
284  << "\n Only original configuration instructions are used. \n"
285  << hrule;
286  if (cleanSlateConfiguration_) {
287  LogError("duplicateDestination")
288  << except_msg.str() << orig_config_msg.str();
289  } else {
290  LogWarning("duplicateDestination")
291  << except_msg.str() << orig_config_msg.str();
292  }
293  continue;
294  }
295  string const& libspec = dest_type;
296  auto& plugin_factory = (configuration == destination_kind::statistics) ?
297  pluginStatsFactory_ :
298  pluginFactory_;
299  try {
300  destinations_[outputId] =
301  makePlugin_(plugin_factory, libspec, psetname, dest_pset);
302  }
303  catch (fhicl::detail::validationException const& e) {
304  string msg{"Configuration error for destination: " +
305  cet::bold_fontify(psetname) + "\n\n"};
306  msg += e.what();
307  config_errors.push_back(move(msg));
308  }
309  }
310  if (!empty(config_errors)) {
311  string msg{"\nThe following messagefacility destinations have "
312  "configuration errors:\n\n"};
313  constexpr cet::HorizontalRule rule{60};
314  msg += rule('=');
315  msg += "\n\n";
316  auto start = cbegin(config_errors);
317  msg += *start;
318  ++start;
319  for (auto it = start, e = cend(config_errors); it != e; ++it) {
320  msg += rule('-');
321  msg += "\n\n";
322  msg += *it;
323  }
324  msg += rule('=');
325  msg += "\n\n";
327  }
328  }
329 
330  void
331  configure(MFDestinationConfig::Config const& config)
332  {
333  if (destinations_.size() > 1) {
334  LogWarning("multiLogConfig")
335  << "The message logger has been configured multiple times";
336  cleanSlateConfiguration_ = false;
337  }
338  fhicl::ParameterSet dest_psets;
339  fhicl::ParameterSet ordinaryDests;
340  if (!config.destinations.get_if_present(dest_psets)) {
341  dest_psets = default_destination_set_config();
342  }
343  ordinaryDests = dest_psets;
344  ordinaryDests.erase("statistics");
345  // Dial down the early destination once the ordinary destinations are
346  // filled.
347  destinations_["cerr_early"s]->setThreshold(ELhighestSeverity);
348  fhicl::ParameterSet default_statistics_config;
349  if (ordinaryDests.is_empty()) {
350  string const default_config{"file_stats: {\n"
351  " type: file\n"
352  " filename: \"err.log\"\n"
353  " threshold: WARNING\n"
354  "}\n"};
355  default_statistics_config = fhicl::ParameterSet::make(default_config);
356  }
357  makeDestinations(ordinaryDests, destination_kind::ordinary);
358  auto statDests = dest_psets.get<fhicl::ParameterSet>(
359  "statistics", default_statistics_config);
360  makeDestinations(statDests, destination_kind::statistics);
361  }
362 
363  void
364  logMessage(ErrorObj* msg)
365  {
366  if (purgeMode_) {
367  // We are dropping all messages due to an earlier exception.
368  delete msg;
369  return;
370  }
371  std::lock_guard sentry{msgMutex_};
372  // Ok, no other thread active, process the current message.
373  try {
374  unique_ptr<ErrorObj> msgHolder{msg};
375  msg->setReactedTo(false);
376  sendMsgToDests(*msg, destinations_);
377  }
378  catch (cet::exception const& e) {
379  ++count_;
380  cerr << "MessageLoggerScribe caught " << count_
381  << " cet::exceptions, text = \n"
382  << e.what() << "\n";
383  if (count_ > 25) {
384  cerr << "MessageLogger will no longer be processing messages due to "
385  "errors (entering purge mode).\n";
386  purgeMode_ = true;
387  }
388  }
389  catch (...) {
390  cerr << "MessageLoggerScribe caught an unknown exception and will no "
391  "longer be processing messages. (entering purge mode)\n";
392  purgeMode_ = true;
393  }
394  }
395 
396  void
397  summarize() try {
398  for (auto& destid_and_dest : destinations_) {
399  auto& dest = *destid_and_dest.second;
400  dest.summary();
401  }
402  }
403  catch (cet::exception const& e) {
404  cerr << "MessageLoggerScribe caught exception during summarize:\n"
405  << e.what() << "\n";
406  }
407  catch (...) {
408  cerr << "MessageLoggerScribe caught unknown exception type during "
409  "summarize. (Ignored)\n";
410  }
411 
412  } // unnamed namespace
413 
414  // Note: Safe to call from multiple threads.
415  bool
417  {
418  return isStarted.load();
419  }
420 
421  // Note: Safe to call from multiple threads.
422  void
424  {
425  if (isStarted.load()) {
426  logMessage(msg);
427  return;
428  }
429  if (msg->is_verbatim()) {
430  ostringstream buf;
431  buf << msg->fullText() << '\n';
432  cerr << buf.str();
433  } else {
434  ostringstream buf;
435  buf << "%MSG" << msg->xid().severity().getSymbol() << ' '
436  << msg->xid().id() << msg->idOverflow() << ": \n"
437  << msg->fullText() << "\n"
438  << "%MSG\n";
439  cerr << buf.str();
440  }
441  delete msg;
442  }
443 
444  // Note: Call this from single-threaded mode only!
445  void
447  {
448  if (isStarted.load()) {
449  summarize();
450  }
451  }
452 
453  // Note: Obsolete! Remove when user code migrated.
454  void
456  {}
457 
458  // Note: Call this from single-threaded mode only!
459  void
461  string const& applicationName)
462  {
463  if (isStarted.load()) {
464  return;
465  }
466  // FIXME: We should not have to call StartMessageFacility() to get these
467  // initialized!
468  initGlobalVars(applicationName);
469  try {
470  destinations_["cerr_early"s] = makePlugin_(
471  pluginFactory_, "cerr", "cerr_early", default_destination_config());
472  }
473  catch (fhicl::detail::validationException const& e) {
474  string msg{"\nConfiguration error for destination: " +
475  cet::bold_fontify("cerr_early") + "\n\n"};
476  msg += e.what();
478  }
479  try {
480  // Note: We make all the destinations here.
481  configure(MFDestinationConfig::Config{
482  MFConfig::Parameters{pset}().destinations()});
483  }
484  catch (Exception const& ex) {
485  // FIXME: Hardly seems necessary to rethrow just to change the message to
486  // say it was during configure!
488  "Exception from MessageLoggerScribe::configure",
489  ex);
490  }
491  isStarted.store(true);
492  }
493 
494  // Note: Call this from single-threaded mode only!
495  void
497  {
498  isStarted.store(false);
499  // FIXME: The finish() call in all known uses does nothing, the destination
500  // dtor can probably handle this, remove!
501  for (auto& category_and_destination : destinations_) {
502  category_and_destination.second->finish();
503  }
504  }
505 
506  // Note: Call this from single-threaded mode only!
507  void
508  SetApplicationName(string const& applicationName)
509  {
510  application_ = applicationName;
511  }
512 
513  // Note: Call this from single-threaded mode only!
514  void
515  SetHostName(string const& hostname)
516  {
517  hostname_ = hostname;
518  }
519 
520  // Note: Call this from single-threaded mode only!
521  void
522  SetHostAddr(string const& hostaddr)
523  {
524  hostaddr_ = hostaddr;
525  }
526 
527  // Note: Call this from single-threaded mode only!
528  void
529  SetPid(long const pid)
530  {
531  pid_ = pid;
532  }
533 
534  // Note: Call this from single-threaded mode only!
535  string const&
537  {
538  return application_;
539  }
540 
541  // Note: Call this from single-threaded mode only!
542  string const&
544  {
545  return hostname_;
546  }
547 
548  // Note: Call this from single-threaded mode only!
549  string const&
551  {
552  return hostaddr_;
553  }
554 
555  // Note: Call this from single-threaded mode only!
556  long
558  {
559  return pid_;
560  }
561 
562  // Phase or {run|subrun|event} number.
563  // Note: Obsolete! Remove when user code migrated.
564  // Note: This is thread specific and thread safe.
565  void
566  SetContextIteration(string const& val)
567  {
568  iteration_ = val;
569  }
570 
571  // Phase or {run|subrun|event} number.
572  // Note: This is thread specific and thread safe.
573  void
574  SetIteration(string const& val)
575  {
576  iteration_ = val;
577  }
578 
579  string const&
581  {
582  return iteration_;
583  }
584 
585  // Note: Obsolete! Remove when user code migrated.
586  // Note: This is thread specific and thread safe.
587  void
588  SetContextSinglet(string const& val)
589  {
590  module_ = val;
591  }
592 
593  // Note: This is thread specific and thread safe.
594  void
595  SetModuleName(string const& val)
596  {
597  module_ = val;
598  }
599 
600  string const&
602  {
603  return module_;
604  }
605 
606  // Note: Call this from single-threaded mode only!
607  // FIXME: This does not give the same answer as before because it is no longer
608  // per-module!
609  // Note: Obsolete! Remove when user code migrated.
610  bool
612  {
613  return false;
614  }
615 
616  // Note: Call this from single-threaded mode only!
617  // FIXME: This does not give the same answer as before because it is no longer
618  // per-module!
619  // Note: Obsolete! Remove when user code migrated.
620  bool
622  {
623  return true;
624  }
625 
626  // Note: Call this from single-threaded mode only!
627  // FIXME: This does not give the same answer as before because it is no longer
628  // per-module!
629  // Note: Obsolete! Remove when user code migrated.
630  bool
632  {
633  return true;
634  }
635 
636  // This static global will be destroyed before any other
637  // file-local or global variables when this dll is unloaded
638  // (because it is the last in the file), and will take care
639  // of cleanup if the user forgot to call EndMessageFacility().
640  static struct FinalShutdown {
642  {
643  if (isStarted.load()) {
645  }
646  }
647  } ensureShutdown;
648 
649 } // namespace mf
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
fhicl::OptionalDelegatedParameter destinations
Definition: MFConfig.h:18
ELseverityLevel severity() const
Definition: ELextendedID.cc:33
void SetIteration(string const &val)
std::optional< T > get_if_present() const
void SetHostAddr(string const &hostaddr)
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:87
static QCString result
void msg(const char *fmt,...)
Definition: message.cpp:107
void EndMessageFacility()
static ParameterSet make(intermediate_table const &tbl)
Definition: ParameterSet.cc:68
std::string const & application() const
Definition: ELextendedID.cc:63
virtual void setPID(long)
Definition: ErrorObj.cc:201
string const & GetModuleName()
std::enable_if_t<!std::is_function_v< RESULT_TYPE >, RESULT_TYPE > makePlugin(std::string const &libspec, ARGS &&...args) const
STL namespace.
void statistics()
Definition: doxygen.cpp:257
destination_kind
static struct mf::FinalShutdown ensureShutdown
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
ELslProxy< ELhighestSeverityGen > constexpr ELhighestSeverity
void FlushMessageLog()
virtual void setHostName(std::string const &)
Definition: ErrorObj.cc:183
void SetContextIteration(string const &val)
bool is_verbatim() const
Definition: ErrorObj.cc:91
constexpr char const * plugin_libpath()
cet::coded_exception< errors::error, detail::translate > Exception
Definition: exception.h:16
std::string const & idOverflow() const
Definition: ErrorObj.cc:67
bool isMessageProcessingSetUp()
std::vector< std::string > get_pset_names() const
void StartMessageFacility(fhicl::ParameterSet const &pset, string const &applicationName)
const double e
bool isWarningEnabled()
std::string const & hostaddr() const
Definition: ELextendedID.cc:57
bool isInfoEnabled()
static Config * config
Definition: config.cpp:1054
ELextendedID const & xid() const
Definition: ErrorObj.cc:61
std::string getenv(std::string const &name)
Definition: getenv.cc:15
def move(depos, offset)
Definition: depos.py:107
T get(std::string const &key) const
Definition: ParameterSet.h:271
std::string bold_fontify(std::string const &s)
Definition: bold_fontify.h:8
bool isDebugEnabled()
virtual void setHostAddr(std::string const &)
Definition: ErrorObj.cc:189
std::string const & id() const
Definition: ELextendedID.cc:27
std::string fullText() const
Definition: ErrorObj.cc:110
virtual void setApplication(std::string const &)
Definition: ErrorObj.cc:195
void LogStatistics()
void SetHostName(string const &hostname)
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
string const & GetHostName()
bool is_empty() const
char const * what() const noexcept override
string const & GetIteration()
std::string pluginType(std::string const &libspec) const
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
void SetContextSinglet(string const &val)
decltype(auto) constexpr cbegin(T &&obj)
ADL-aware version of std::cbegin.
Definition: StdUtils.h:82
virtual void setReactedTo(bool)
Definition: ErrorObj.cc:177
bool erase(std::string const &key)
void LogErrorObj(ErrorObj *msg)
long pid() const
Definition: ELextendedID.cc:69
void SetModuleName(string const &val)
void SetApplicationName(string const &applicationName)
std::string const & hostname() const
Definition: ELextendedID.cc:51
static QCString * s
Definition: config.cpp:1042
void SetPid(long const pid)
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:97
std::string getSymbol() const
string const & GetApplicationName()
string const & GetHostAddr()
long GetPid()