tdaq-develop-2025-02-12
ConsoleSupervisor.h
1 #ifndef _ots_ConsoleSupervisor_h_
2 #define _ots_ConsoleSupervisor_h_
3 
4 #include <boost/regex.hpp>
5 #include <boost/tokenizer.hpp>
6 #include "otsdaq/CoreSupervisors/CoreSupervisorBase.h"
7 
8 #include <mutex> //for std::mutex
9 #include <queue> //for std::queue
10 
11 namespace ots
12 {
13 
14 // clang-format off
15 
20 class ConsoleSupervisor : public CoreSupervisorBase
21 {
22  public:
23  XDAQ_INSTANTIATOR();
24 
26  {
27  std::vector<std::string> needleSubstrings; /* custom trigger needle substrings */
28  std::string action; /* action */
29  size_t triggeredMessageCountIndex = -1; /* message arrival count that fired the trigger */
30  size_t occurrences = 0;
31  }; //end CustomTriggeredAction_t struct
32 
33 
34  ConsoleSupervisor (xdaq::ApplicationStub* s);
35  virtual ~ConsoleSupervisor (void);
36 
37  void init (void);
38  void destroy (void);
39 
40  xoap::MessageReference resetConsoleCounts (xoap::MessageReference message);
41 
42  virtual void defaultPage (xgi::Input* in, xgi::Output* out) override;
43  virtual void request (const std::string& requestType,
44  cgicc::Cgicc& cgiIn,
45  HttpXmlDocument& xmlOut,
46  const WebUsers::RequestUserInfo& userInfo) override;
47  virtual std::string getStatusProgressDetail (void) override;
48 
49  virtual void forceSupervisorPropertyValues (void) override;
53 
54  void doTriggeredAction (const CustomTriggeredAction_t& triggeredAction);
55 
56  std::atomic<bool> customTriggerActionThreadExists_ = false;
57  static const std::set<std::string> CUSTOM_TRIGGER_ACTIONS;
58 
59  private:
60  static void messageFacilityReceiverWorkLoop (ConsoleSupervisor* cs);
61  static void customTriggerActionThread (ConsoleSupervisor* cs);
62  void insertMessageRefresh (HttpXmlDocument* xmldoc, const size_t lastUpdateCount);
63  void prependHistoricMessages (HttpXmlDocument* xmlOut, const size_t earliestOnhandMessageCount);
64 
65  void addCustomTriggeredAction (const std::string& triggerNeedle, const std::string& triggerAction, uint32_t priority = -1);
66  uint32_t modifyCustomTriggeredAction (const std::string& currentNeedle, const std::string& modifyType, const std::string& setNeedle, const std::string& setAction, uint32_t setPriority);
67 
68  void loadCustomCountList (void);
69  void saveCustomCountList (void);
70 
71  public:
76  {
77  ConsoleMessageStruct(const std::string& msg, const size_t count,
78  std::vector<CustomTriggeredAction_t>& priorityCustomTriggerList)
79  : countStamp(count)
80  {
81  boost::regex timestamp_regex_("(\\d{2}-[^-]*-\\d{4}\\s\\d{2}:\\d{2}:\\d{2})");
82  boost::regex file_line_regex_("^\\s*([^:]*\\.[^:]{1,3}):(\\d+)(.*)");
83 
84  boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
85  typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
86  tokenizer tokens(msg, sep);
87  tokenizer::iterator it = tokens.begin();
88 
89  // There may be syslog garbage in the first token before the timestamp...
90  boost::smatch res;
91  while(it != tokens.end() && !boost::regex_search(*it, res, timestamp_regex_))
92  {
93  ++it;
94  }
95 
96  struct tm tm;
97  std::string value(res[1].first, res[1].second);
98  strptime(value.c_str(), "%d-%b-%Y %H:%M:%S", &tm);
99  tm.tm_isdst = -1;
100  fields[FieldType::TIMESTAMP] = std::to_string(mktime(&tm));
101 
102  auto prevIt = it;
103  try
104  {
105  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
106  {
107  // seqNum = std::stoi(*it);
108  fields[FieldType::SEQID] = *it;
109  }
110  }
111  catch(const std::invalid_argument& e)
112  {
113  it = prevIt;
114  }
115  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
116  {
117  // hostname = *it;
118  fields[FieldType::HOSTNAME] = *it;
119  }
120  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
121  {
122  ; //not needed (yet): hostaddr = *it;
123  }
124  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
125  {
126  // sev = mf::ELseverityLevel(*it);
127  fields[FieldType::LEVEL] = mf::ELseverityLevel(*it).getName();
128  }
129  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
130  {
131  // category = *it;
132  fields[FieldType::LABEL] = *it;
133  }
134  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
135  {
136  // application = *it;
137  fields[FieldType::SOURCE] = *it;
138  }
139  prevIt = it;
140  try
141  {
142  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
143  {
144  // pid = std::stol(*it);
145  fields[FieldType::SOURCEID] = *it;
146  }
147  }
148  catch(const std::invalid_argument& e)
149  {
150  it = prevIt;
151  }
152  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
153  {
154  ; //not needed (yet): eventID = *it;
155  }
156  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
157  {
158  ; //not needed (yet): module = *it;
159  }
160  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
161  {
162  // file = *it;
163  fields[FieldType::FILE] = *it;
164  }
165  if(it != tokens.end() && ++it != tokens.end() /* Advances it */)
166  {
167  // line = *it;
168  fields[FieldType::LINE] = *it;
169  }
170  std::ostringstream oss;
171  bool first = true;
172  while(it != tokens.end() && ++it != tokens.end() /* Advances it */)
173  {
174  if(!first)
175  {
176  oss << "|";
177  }
178  else
179  {
180  first = false;
181  }
182  oss << *it;
183  }
184  fields[FieldType::MSG] = oss.str();
185 
186 #if 0
187  for (auto& field : fields) {
188  std::cout << "Field " << field.second.fieldName << ": " << field.second.fieldValue
189  << std::endl;
190  }
191 #endif
192 
193  //check custom triggers
194  //Note: to avoid recursive triggers, Label=Console can not trigger
195  for(auto& triggeredAction : priorityCustomTriggerList)
196  {
197  if(getLabel() == "Console") break;
198 
199  size_t pos = 0;
200  bool foundAll = false;
201  for(const auto& needleSubstring : triggeredAction.needleSubstrings)
202  if((pos = getMsg().find(needleSubstring)) == std::string::npos)
203  {
204  foundAll = false;
205  //FOR DEBUGGING std::cout << "Not a full match on '" << needleSubstring << ".' Message: " <<
206  // getSourceIDAsNumber() << ":" << getMsg().substr(0,100) << __E__;
207  break;
208  }
209  else
210  foundAll = true;
211 
212  if(foundAll)
213  {
214  //FOR DEBUGGING std::cout << "Full match of custom trigger! Message: " <<
215  // getSourceIDAsNumber() << ":" << getMsg().substr(0,100) << __E__;
216  triggeredAction.occurrences++;
217  customTriggerMatch = triggeredAction;
218  customTriggerMatch.triggeredMessageCountIndex = getCount();
219  break;
220  }
221  } //end custom trigger search
222 
223  } //end ConsoleMessageStruct constructor
224 
225  void setCustomTriggerMatch(const CustomTriggeredAction_t& forcedCustomTriggerMatch) { customTriggerMatch = forcedCustomTriggerMatch; }
226  const CustomTriggeredAction_t& getCustomTriggerMatch() const { return customTriggerMatch; }
227  bool hasCustomTriggerMatchAction() const { return customTriggerMatch.action.size(); }
228  const std::string& getTime() const { return fields.at(FieldType::TIMESTAMP); }
229  void setTime(time_t t) { fields[FieldType::TIMESTAMP] = std::to_string(t); }
230  const std::string& getMsg() const { return fields.at(FieldType::MSG); }
231  const std::string& getLabel() const { return fields.at(FieldType::LABEL); }
232  const std::string& getLevel() const {
233  //identify 9+ levels as TRACE (mf calls them DEBUG)
234  if(getMsg().size() > 4)
235  {
236  if(getMsg()[0] == '9' && getMsg()[1] == ':') return ConsoleMessageStruct::LABEL_TRACE;
237  if(getMsg()[0] >= '1' && getMsg()[0] <= '3' &&
238  getMsg()[1] >= '0' && getMsg()[1] <= '9' &&
239  getMsg()[2] == ':') return ConsoleMessageStruct::LABEL_TRACE_PLUS;
240  }
241  return fields.at(FieldType::LEVEL);
242  }
243  const std::string& getFile() const { return fields.at(FieldType::FILE); }
244  const std::string& getLine() const { return fields.at(FieldType::LINE); }
245 
246  const std::string& getSourceID() const { return fields.at(FieldType::SOURCEID); }
247  uint32_t getSourceIDAsNumber() const
248  {
249  auto val = fields.at(FieldType::SOURCEID);
250  if(val != "")
251  {
252  return std::stoul(val);
253  }
254  return 0;
255  }
256  const std::string& getSource() const { return fields.at(FieldType::SOURCE); }
257  const std::string& getSequenceID() const { return fields.at(FieldType::SEQID); }
258  size_t getSequenceIDAsNumber() const
259  {
260  auto val = fields.at(FieldType::SEQID);
261  if(val != "")
262  {
263  return std::stoul(val);
264  }
265  return 0;
266  }
267 
268  //count is incrementing number across all sources created at ConsoleSupervisor
269  size_t getCount() const { return countStamp; }
270 
271  // define field index enum alias
272  enum class FieldType
273  { // must be in order of appearance in buffer
274  TIMESTAMP,
275  //count is incrementing number across all sources created at ConsoleSupervisor
276  SEQID,
277  LEVEL,
278  LABEL,
279  SOURCEID,
280  HOSTNAME,
281  SOURCE,
282  FILE,
283  LINE,
284  MSG,
285  };
286 
287  mutable std::unordered_map<FieldType, std::string /* fieldValue */> fields;
288  static const std::map<FieldType, std::string /* fieldNames */> fieldNames;
289 
290  private:
291  size_t countStamp;
292  CustomTriggeredAction_t customTriggerMatch;
293 
294  static const std::string LABEL_TRACE, LABEL_TRACE_PLUS;
295  }; //end ConsoleMessageStruct
296 
297  private:
298  void addMessageToResponse (HttpXmlDocument* xmlOut, ConsoleSupervisor::ConsoleMessageStruct& msg);
299 
300  std::deque<ConsoleMessageStruct> messages_;
301  std::mutex messageMutex_;
302  size_t messageCount_;
303  size_t maxMessageCount_, maxClientMessageRequest_;
304 
305  std::map<std::string /*TRACE hostname*/, std::string /*xdaq context hostname*/>
306  traceMapToXDAQHostname_;
307 
309  xercesc::DOMElement* refreshParent_;
310 
311  std::vector<CustomTriggeredAction_t> priorityCustomTriggerList_;
312  std::queue<CustomTriggeredAction_t> customTriggerActionQueue_;
313 
315  size_t errorCount_ = 0, warnCount_ = 0, infoCount_ = 0;
316  std::string lastErrorMessage_, lastWarnMessage_, lastInfoMessage_, firstErrorMessage_, firstWarnMessage_, firstInfoMessage_;
317  time_t lastErrorMessageTime_ = 0, lastWarnMessageTime_ = 0, lastInfoMessageTime_ = 0, firstErrorMessageTime_ = 0, firstWarnMessageTime_ = 0, firstInfoMessageTime_ = 0;
318 };
319 
320 // clang-format on
321 } // namespace ots
322 
323 #endif
virtual void forceSupervisorPropertyValues(void) override
virtual void request(const std::string &requestType, cgicc::Cgicc &cgiIn, HttpXmlDocument &xmlOut, const WebUsers::RequestUserInfo &userInfo) override
static const std::set< std::string > CUSTOM_TRIGGER_ACTIONS
Count always happens, and System Message always happens for FSM commands.
virtual std::string getStatusProgressDetail(void) override
@ SEQID
sequence ID is incrementing number independent from each source
ConsoleMessageStruct(const std::string &msg, const size_t count, std::vector< CustomTriggeredAction_t > &priorityCustomTriggerList)