1 // +------------------------------------------------------------------+
2 // | ____ _ _ __ __ _ __ |
3 // | / ___| |__ ___ ___| | __ | \/ | |/ / |
4 // | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
5 // | | |___| | | | __/ (__| < | | | | . \ |
6 // | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
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.
28 #include <unordered_map>
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
))
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
];
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
73 bool LogEntry::assign(Param par
, const std::string
&field
) {
76 this->_host_name
= field
;
78 case Param::ServiceDescription
:
79 this->_service_description
= field
;
81 case Param::HostState
:
82 this->_state
= static_cast<int>(parseHostState(field
));
84 case Param::ServiceState
:
85 this->_state
= static_cast<int>(parseServiceState(field
));
88 this->_state
= atoi(field
.c_str());
90 case Param::StateType
:
91 this->_state_type
= field
;
94 this->_attempt
= atoi(field
.c_str());
97 this->_comment
= field
;
99 case Param::CommandName
:
100 this->_command_name
= field
;
102 case Param::ContactName
:
103 this->_contact_name
= field
;
105 case Param::PluginOutput
:
106 this->_plugin_output
= field
;
113 std::vector
<LogEntry::LogDef
> LogEntry::log_definitions
{
114 LogDef
{"INITIAL HOST STATE",
116 LogEntryKind::state_host_initial
,
117 {Param::HostName
, Param::HostState
, Param::StateType
, Param::Attempt
,
118 Param::PluginOutput
}},
120 LogDef
{"CURRENT HOST STATE",
122 LogEntryKind::state_host
,
123 {Param::HostName
, Param::HostState
, Param::StateType
, Param::Attempt
,
124 Param::PluginOutput
}},
128 LogEntryKind::alert_host
,
129 {Param::HostName
, Param::HostState
, Param::StateType
, Param::Attempt
,
130 Param::PluginOutput
}},
132 LogDef
{"HOST DOWNTIME ALERT",
134 LogEntryKind::downtime_alert_host
,
135 {Param::HostName
, Param::StateType
, Param::Comment
}},
137 LogDef
{"HOST ACKNOWLEDGE ALERT",
139 LogEntryKind::acknowledge_alert_host
,
140 {Param::HostName
, Param::StateType
, Param::ContactName
,
143 LogDef
{"HOST FLAPPING ALERT",
145 LogEntryKind::flapping_host
,
146 {Param::HostName
, Param::StateType
, Param::Comment
}},
148 LogDef
{"INITIAL SERVICE STATE",
150 LogEntryKind::state_service_initial
,
151 {Param::HostName
, Param::ServiceDescription
, Param::ServiceState
,
152 Param::StateType
, Param::Attempt
, Param::PluginOutput
}},
154 LogDef
{"CURRENT SERVICE STATE",
156 LogEntryKind::state_service
,
157 {Param::HostName
, Param::ServiceDescription
, Param::ServiceState
,
158 Param::StateType
, Param::Attempt
, Param::PluginOutput
}},
160 LogDef
{"SERVICE ALERT",
162 LogEntryKind::alert_service
,
163 {Param::HostName
, Param::ServiceDescription
, Param::ServiceState
,
164 Param::StateType
, Param::Attempt
, Param::PluginOutput
}},
166 LogDef
{"SERVICE DOWNTIME ALERT",
168 LogEntryKind::downtime_alert_service
,
169 {Param::HostName
, Param::ServiceDescription
, Param::StateType
,
172 LogDef
{"SERVICE ACKNOWLEDGE ALERT",
174 LogEntryKind::acknowledge_alert_service
,
175 {Param::HostName
, Param::ServiceDescription
, Param::StateType
,
176 Param::ContactName
, Param::Comment
}},
178 LogDef
{"SERVICE FLAPPING ALERT",
180 LogEntryKind::flapping_service
,
181 {Param::HostName
, Param::ServiceDescription
, Param::StateType
,
184 LogDef
{"TIMEPERIOD TRANSITION",
186 LogEntryKind::timeperiod_transition
,
189 LogDef
{"HOST NOTIFICATION",
190 Class::hs_notification
,
192 {Param::ContactName
, Param::HostName
, Param::StateType
,
193 Param::CommandName
, Param::PluginOutput
}},
195 LogDef
{"SERVICE NOTIFICATION",
196 Class::hs_notification
,
198 {Param::ContactName
, Param::HostName
, Param::ServiceDescription
,
199 Param::StateType
, Param::CommandName
, Param::PluginOutput
}},
201 LogDef
{"HOST NOTIFICATION RESULT",
202 Class::hs_notification
,
204 {Param::ContactName
, Param::HostName
, Param::StateType
,
205 Param::CommandName
, Param::PluginOutput
, Param::Comment
}},
207 LogDef
{"SERVICE NOTIFICATION RESULT",
208 Class::hs_notification
,
210 {Param::ContactName
, Param::HostName
, Param::ServiceDescription
,
211 Param::StateType
, Param::CommandName
, Param::PluginOutput
,
214 LogDef
{"HOST NOTIFICATION PROGRESS",
215 Class::hs_notification
,
217 {Param::ContactName
, Param::HostName
, Param::StateType
,
218 Param::CommandName
, Param::PluginOutput
}},
220 LogDef
{"SERVICE NOTIFICATION PROGRESS",
221 Class::hs_notification
,
223 {Param::ContactName
, Param::HostName
, Param::ServiceDescription
,
224 Param::StateType
, Param::CommandName
, Param::PluginOutput
}},
226 LogDef
{"HOST ALERT HANDLER STARTED",
227 Class::alert_handlers
,
229 {Param::HostName
, Param::CommandName
}},
231 LogDef
{"SERVICE ALERT HANDLER STARTED",
232 Class::alert_handlers
,
234 {Param::HostName
, Param::ServiceDescription
, Param::CommandName
}},
236 LogDef
{"HOST ALERT HANDLER STOPPED",
237 Class::alert_handlers
,
239 {Param::HostName
, Param::CommandName
, Param::ServiceState
,
240 Param::PluginOutput
}},
242 LogDef
{"SERVICE ALERT HANDLER STOPPED",
243 Class::alert_handlers
,
245 {Param::HostName
, Param::ServiceDescription
, Param::CommandName
,
246 Param::ServiceState
, Param::PluginOutput
}},
248 LogDef
{"PASSIVE SERVICE CHECK",
251 {Param::HostName
, Param::ServiceDescription
, Param::State
,
252 Param::PluginOutput
}},
254 LogDef
{"PASSIVE HOST CHECK",
257 {Param::HostName
, Param::State
, Param::PluginOutput
}},
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,
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
);
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()
283 _type
= &_message
[timestamp_prefix_length
];
284 if (textStartsWith("LOG VERSION: 2.0")) {
285 _class
= Class::program
;
286 _kind
= LogEntryKind::log_version
;
289 if (textStartsWith("logging initial states") ||
290 textStartsWith("logging intitial states")) {
291 _class
= Class::program
;
292 _kind
= LogEntryKind::log_initial_states
;
295 if (textContains("starting...") || textContains("active mode...")) {
296 _class
= Class::program
;
297 _kind
= LogEntryKind::core_starting
;
300 if (textContains("shutting down...") || textContains("Bailing out") ||
301 textContains("standby mode...")) {
302 _class
= Class::program
;
303 _kind
= LogEntryKind::core_stopping
;
306 if (textContains("restarting...")) {
307 _class
= Class::program
;
308 _kind
= LogEntryKind::none
;
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"
330 void LogEntry::applyWorkarounds() {
331 if (_class
!= Class::hs_notification
|| // no need for any workaround
332 _state_type
.empty()) { // extremely broken line
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
));
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);
363 std::unordered_map
<std::string
, ServiceState
> serviceStateTypes
{
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
{
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
}};
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()));
404 if (!_service_description
.empty()) {
405 // Older Nagios headers are not const-correct... :-P
407 find_service(const_cast<char *>(_host_name
.c_str()),
408 const_cast<char *>(_service_description
.c_str()));
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()));
416 if (!_command_name
.empty()) {
417 _command
= mc
->find_command(_command_name
);