Refactoring: Changed all check parameters starting with a 'p' to the new rulespec...
[check_mk.git] / livestatus / src / LogEntry.cc
blob204882e15b9708f272771b3fc71925e927bfa92f
1 // +------------------------------------------------------------------+
2 // | ____ _ _ __ __ _ __ |
3 // | / ___| |__ ___ ___| | __ | \/ | |/ / |
4 // | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
5 // | | |___| | | | __/ (__| < | | | | . \ |
6 // | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
7 // | |
8 // | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
14 // check_mk is free software; you can redistribute it and/or modify it
15 // under the terms of the GNU General Public License as published by
16 // the Free Software Foundation in version 2. check_mk is distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
18 // out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19 // PARTICULAR PURPOSE. See the GNU General Public License for more de-
20 // tails. You should have received a copy of the GNU General Public
21 // License along with GNU Make; see the file COPYING. If not, write
22 // to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 // Boston, MA 02110-1301 USA.
25 #include "LogEntry.h"
26 #include <cstdlib>
27 #include <stdexcept>
28 #include <unordered_map>
29 #include <utility>
30 #include "MonitoringCore.h"
32 // 0123456789012345678901234567890
33 // [1234567890] FOO BAR: blah blah
34 static constexpr size_t timestamp_prefix_length = 13;
36 // TODO(sp) Fix classifyLogMessage() below to always set all fields and remove
37 // this set-me-to-zero-to-be-sure-block.
38 LogEntry::LogEntry(MonitoringCore *mc, size_t lineno, std::string line)
39 : _lineno(static_cast<int32_t>(lineno))
40 , _message(std::move(line))
41 , _state(0)
42 , _attempt(0)
43 , _host(nullptr)
44 , _service(nullptr)
45 , _contact(nullptr) {
46 // pointer to options (everything after ':')
47 size_t pos = _message.find(':');
48 if (pos != std::string::npos) {
49 pos = _message.find_first_not_of(' ', pos + 1);
51 if (pos == std::string::npos) {
52 pos = _message.size();
54 _options = &_message[pos];
56 try {
57 if (_message.size() < timestamp_prefix_length || _message[0] != '[' ||
58 _message[11] != ']' || _message[12] != ' ') {
59 throw std::invalid_argument("timestamp delimiter");
61 _time = std::stoi(_message.substr(1, 10));
62 } catch (const std::logic_error &e) {
63 _class = Class::invalid;
64 _kind = LogEntryKind::none;
65 return; // ignore invalid lines silently
68 classifyLogMessage();
69 applyWorkarounds();
70 updateReferences(mc);
73 bool LogEntry::assign(Param par, const std::string &field) {
74 switch (par) {
75 case Param::HostName:
76 this->_host_name = field;
77 break;
78 case Param::ServiceDescription:
79 this->_service_description = field;
80 break;
81 case Param::HostState:
82 this->_state = static_cast<int>(parseHostState(field));
83 break;
84 case Param::ServiceState:
85 this->_state = static_cast<int>(parseServiceState(field));
86 break;
87 case Param::State:
88 this->_state = atoi(field.c_str());
89 break;
90 case Param::StateType:
91 this->_state_type = field;
92 break;
93 case Param::Attempt:
94 this->_attempt = atoi(field.c_str());
95 break;
96 case Param::Comment:
97 this->_comment = field;
98 break;
99 case Param::CommandName:
100 this->_command_name = field;
101 break;
102 case Param::ContactName:
103 this->_contact_name = field;
104 break;
105 case Param::PluginOutput:
106 this->_plugin_output = field;
107 break;
110 return true;
113 std::vector<LogEntry::LogDef> LogEntry::log_definitions{
114 LogDef{"INITIAL HOST STATE",
115 Class::state,
116 LogEntryKind::state_host_initial,
117 {Param::HostName, Param::HostState, Param::StateType, Param::Attempt,
118 Param::PluginOutput}},
119 ////////////////
120 LogDef{"CURRENT HOST STATE",
121 Class::state,
122 LogEntryKind::state_host,
123 {Param::HostName, Param::HostState, Param::StateType, Param::Attempt,
124 Param::PluginOutput}},
125 ////////////////
126 LogDef{"HOST ALERT",
127 Class::alert,
128 LogEntryKind::alert_host,
129 {Param::HostName, Param::HostState, Param::StateType, Param::Attempt,
130 Param::PluginOutput}},
131 ////////////////
132 LogDef{"HOST DOWNTIME ALERT",
133 Class::alert,
134 LogEntryKind::downtime_alert_host,
135 {Param::HostName, Param::StateType, Param::Comment}},
136 ////////////////
137 LogDef{"HOST ACKNOWLEDGE ALERT",
138 Class::alert,
139 LogEntryKind::acknowledge_alert_host,
140 {Param::HostName, Param::StateType, Param::ContactName,
141 Param::Comment}},
142 ////////////////
143 LogDef{"HOST FLAPPING ALERT",
144 Class::alert,
145 LogEntryKind::flapping_host,
146 {Param::HostName, Param::StateType, Param::Comment}},
147 ////////////////
148 LogDef{"INITIAL SERVICE STATE",
149 Class::state,
150 LogEntryKind::state_service_initial,
151 {Param::HostName, Param::ServiceDescription, Param::ServiceState,
152 Param::StateType, Param::Attempt, Param::PluginOutput}},
153 ////////////////
154 LogDef{"CURRENT SERVICE STATE",
155 Class::state,
156 LogEntryKind::state_service,
157 {Param::HostName, Param::ServiceDescription, Param::ServiceState,
158 Param::StateType, Param::Attempt, Param::PluginOutput}},
159 ////////////////
160 LogDef{"SERVICE ALERT",
161 Class::alert,
162 LogEntryKind::alert_service,
163 {Param::HostName, Param::ServiceDescription, Param::ServiceState,
164 Param::StateType, Param::Attempt, Param::PluginOutput}},
165 ////////////////
166 LogDef{"SERVICE DOWNTIME ALERT",
167 Class::alert,
168 LogEntryKind::downtime_alert_service,
169 {Param::HostName, Param::ServiceDescription, Param::StateType,
170 Param::Comment}},
171 ////////////////
172 LogDef{"SERVICE ACKNOWLEDGE ALERT",
173 Class::alert,
174 LogEntryKind::acknowledge_alert_service,
175 {Param::HostName, Param::ServiceDescription, Param::StateType,
176 Param::ContactName, Param::Comment}},
177 ////////////////
178 LogDef{"SERVICE FLAPPING ALERT",
179 Class::alert,
180 LogEntryKind::flapping_service,
181 {Param::HostName, Param::ServiceDescription, Param::StateType,
182 Param::Comment}},
183 ////////////////
184 LogDef{"TIMEPERIOD TRANSITION",
185 Class::state,
186 LogEntryKind::timeperiod_transition,
187 {}},
188 ////////////////
189 LogDef{"HOST NOTIFICATION",
190 Class::hs_notification,
191 LogEntryKind::none,
192 {Param::ContactName, Param::HostName, Param::StateType,
193 Param::CommandName, Param::PluginOutput}},
194 ////////////////
195 LogDef{"SERVICE NOTIFICATION",
196 Class::hs_notification,
197 LogEntryKind::none,
198 {Param::ContactName, Param::HostName, Param::ServiceDescription,
199 Param::StateType, Param::CommandName, Param::PluginOutput}},
200 ////////////////
201 LogDef{"HOST NOTIFICATION RESULT",
202 Class::hs_notification,
203 LogEntryKind::none,
204 {Param::ContactName, Param::HostName, Param::StateType,
205 Param::CommandName, Param::PluginOutput, Param::Comment}},
206 ////////////////
207 LogDef{"SERVICE NOTIFICATION RESULT",
208 Class::hs_notification,
209 LogEntryKind::none,
210 {Param::ContactName, Param::HostName, Param::ServiceDescription,
211 Param::StateType, Param::CommandName, Param::PluginOutput,
212 Param::Comment}},
213 ////////////////
214 LogDef{"HOST NOTIFICATION PROGRESS",
215 Class::hs_notification,
216 LogEntryKind::none,
217 {Param::ContactName, Param::HostName, Param::StateType,
218 Param::CommandName, Param::PluginOutput}},
219 ////////////////
220 LogDef{"SERVICE NOTIFICATION PROGRESS",
221 Class::hs_notification,
222 LogEntryKind::none,
223 {Param::ContactName, Param::HostName, Param::ServiceDescription,
224 Param::StateType, Param::CommandName, Param::PluginOutput}},
225 ////////////////
226 LogDef{"HOST ALERT HANDLER STARTED",
227 Class::alert_handlers,
228 LogEntryKind::none,
229 {Param::HostName, Param::CommandName}},
230 ////////////////
231 LogDef{"SERVICE ALERT HANDLER STARTED",
232 Class::alert_handlers,
233 LogEntryKind::none,
234 {Param::HostName, Param::ServiceDescription, Param::CommandName}},
235 ////////////////
236 LogDef{"HOST ALERT HANDLER STOPPED",
237 Class::alert_handlers,
238 LogEntryKind::none,
239 {Param::HostName, Param::CommandName, Param::ServiceState,
240 Param::PluginOutput}},
241 ////////////////
242 LogDef{"SERVICE ALERT HANDLER STOPPED",
243 Class::alert_handlers,
244 LogEntryKind::none,
245 {Param::HostName, Param::ServiceDescription, Param::CommandName,
246 Param::ServiceState, Param::PluginOutput}},
247 ////////////////
248 LogDef{"PASSIVE SERVICE CHECK",
249 Class::passivecheck,
250 LogEntryKind::none,
251 {Param::HostName, Param::ServiceDescription, Param::State,
252 Param::PluginOutput}},
253 ////////////////
254 LogDef{"PASSIVE HOST CHECK",
255 Class::passivecheck,
256 LogEntryKind::none,
257 {Param::HostName, Param::State, Param::PluginOutput}},
258 ////////////////
259 LogDef{"EXTERNAL COMMAND", Class::ext_command, LogEntryKind::none, {}}};
261 // A bit verbose, but we avoid unnecessary string copies below.
262 void LogEntry::classifyLogMessage() {
263 for (const auto &def : log_definitions) {
264 if (textStartsWith(def.prefix) &&
265 _message.compare(timestamp_prefix_length + def.prefix.size(), 2,
266 ": ") == 0) {
267 _type = &def.prefix[0];
268 _class = def.log_class;
269 _kind = def.log_type;
270 // TODO(sp) Use boost::tokenizer instead of this index fiddling
271 size_t pos = timestamp_prefix_length + def.prefix.size() + 2;
272 for (Param par : def.params) {
273 size_t sep_pos = _message.find(';', pos);
274 size_t end_pos =
275 sep_pos == std::string::npos ? _message.size() : sep_pos;
276 assign(par, _message.substr(pos, end_pos - pos));
277 pos = sep_pos == std::string::npos ? _message.size()
278 : (sep_pos + 1);
280 return;
283 _type = &_message[timestamp_prefix_length];
284 if (textStartsWith("LOG VERSION: 2.0")) {
285 _class = Class::program;
286 _kind = LogEntryKind::log_version;
287 return;
289 if (textStartsWith("logging initial states") ||
290 textStartsWith("logging intitial states")) {
291 _class = Class::program;
292 _kind = LogEntryKind::log_initial_states;
293 return;
295 if (textContains("starting...") || textContains("active mode...")) {
296 _class = Class::program;
297 _kind = LogEntryKind::core_starting;
298 return;
300 if (textContains("shutting down...") || textContains("Bailing out") ||
301 textContains("standby mode...")) {
302 _class = Class::program;
303 _kind = LogEntryKind::core_stopping;
304 return;
306 if (textContains("restarting...")) {
307 _class = Class::program;
308 _kind = LogEntryKind::none;
309 return;
311 _class = Class::info;
312 _kind = LogEntryKind::none;
315 bool LogEntry::textStartsWith(const std::string &what) {
316 return _message.compare(timestamp_prefix_length, what.size(), what) == 0;
319 bool LogEntry::textContains(const std::string &what) {
320 return _message.find(what, timestamp_prefix_length) != std::string::npos;
323 // The NotifyHelper class has a long, tragic history: Through a long series of
324 // commits, it suffered from spelling mistakes like "HOST_NOTIFICATION" or "HOST
325 // NOTIFICATION" (without a colon), parameter lists not matching the
326 // corresponding format strings, and last but not least wrong ordering of
327 // fields. The net result of this tragedy is that due to legacy reasons, we have
328 // to support parsing an incorrect ordering of "state type" and "command name"
329 // fields. :-P
330 void LogEntry::applyWorkarounds() {
331 if (_class != Class::hs_notification || // no need for any workaround
332 _state_type.empty()) { // extremely broken line
333 return;
336 if (_state_type == "check-mk-notify") {
337 // Ooops, we encounter one of our own buggy lines...
338 std::swap(_state_type, _command_name);
341 if (_state_type.empty()) {
342 return; // extremely broken line, even after a potential swap
345 _state = _service_description.empty()
346 ? static_cast<int>(parseHostState(_state_type))
347 : static_cast<int>(parseServiceState(_state_type));
350 namespace {
351 // Ugly: Depending on where we're called, the actual state type can be in
352 // parentheses at the end, e.g. "ALERTHANDLER (OK)".
353 std::string extractStateType(const std::string &str) {
354 if (!str.empty() && str[str.size() - 1] == ')') {
355 size_t lparen = str.rfind('(');
356 if (lparen != std::string::npos) {
357 return str.substr(lparen + 1, str.size() - lparen - 2);
360 return str;
363 std::unordered_map<std::string, ServiceState> serviceStateTypes{
364 // normal states
365 {"OK", ServiceState::ok},
366 {"WARNING", ServiceState::warning},
367 {"CRITICAL", ServiceState::critical},
368 {"UNKNOWN", ServiceState::unknown},
369 // states from "... ALERT"/"... NOTIFICATION"
370 {"RECOVERY", ServiceState::ok}};
372 std::unordered_map<std::string, HostState> hostStateTypes{
373 // normal states
374 {"UP", HostState::up},
375 {"DOWN", HostState::down},
376 {"UNREACHABLE", HostState::unreachable},
377 // states from "... ALERT"/"... NOTIFICATION"
378 {"RECOVERY", HostState::up},
379 // states from "... ALERT HANDLER STOPPED" and "(HOST|SERVICE) NOTIFICATION
380 // (RESULT|PROGRESS)"
381 {"OK", HostState::up},
382 {"WARNING", HostState::down},
383 {"CRITICAL", HostState::unreachable},
384 {"UNKNOWN", HostState::up}};
385 } // namespace
387 ServiceState LogEntry::parseServiceState(const std::string &str) {
388 auto it = serviceStateTypes.find(extractStateType(str));
389 return it == serviceStateTypes.end() ? ServiceState::ok : it->second;
392 HostState LogEntry::parseHostState(const std::string &str) {
393 auto it = hostStateTypes.find(extractStateType(str));
394 return it == hostStateTypes.end() ? HostState::up : it->second;
397 unsigned LogEntry::updateReferences(MonitoringCore *mc) {
398 unsigned updated = 0;
399 if (!_host_name.empty()) {
400 // Older Nagios headers are not const-correct... :-P
401 _host = find_host(const_cast<char *>(_host_name.c_str()));
402 updated++;
404 if (!_service_description.empty()) {
405 // Older Nagios headers are not const-correct... :-P
406 _service =
407 find_service(const_cast<char *>(_host_name.c_str()),
408 const_cast<char *>(_service_description.c_str()));
409 updated++;
411 if (!_contact_name.empty()) {
412 // Older Nagios headers are not const-correct... :-P
413 _contact = find_contact(const_cast<char *>(_contact_name.c_str()));
414 updated++;
416 if (!_command_name.empty()) {
417 _command = mc->find_command(_command_name);
418 updated++;
420 return updated;