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