1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
.. _tut3:
.. currentmodule:: nagiosplugin
Tutorial #3: check_users
========================
In the third tutorial, we will learn how to process multiple metrics.
Additionally, we will see how to use logging and verbosity levels.
Multiple metrics
----------------
A plugin can perform several measurements at once. This is often necessary to
perform more complex state evaluations or improve latency. Consider a check that
determines both the number of total logged in users and the number of unique
logged in users.
A Resource implementation could look like this:
.. code-block:: python
class Users(nagiosplugin.Resource):
def __init__(self):
self.users = []
self.unique_users = set()
def list_users(self):
"""Return logged in users as list of user names."""
[...]
return users
def probe(self):
"""Return both total and unique user count."""
self.users = self.list_users()
self.unique_users = set(self.users)
return [nagiosplugin.Metric('total', len(self.users), min=0,
context='users'),
nagiosplugin.Metric('unique', len(self.unique_users), min=0,
context='users')]
The `probe()` method returns a list containing two metric objects.
Alternatively, the `probe()` method can act as generator and yield
metrics:
.. code-block:: python
def probe(self):
"""Return both total and unique user count."""
self.users = self.list_users()
self.unique_users = set(self.users)
yield nagiosplugin.Metric('total', len(self.users), min=0,
context='users')
yield nagiosplugin.Metric('unique', len(self.unique_users), min=0,
context='users')]
This may be more comfortable than constructing a list of metrics first and
returning them all at once.
To assign a :class:`~nagiosplugin.context.Context` to a
:class:`~nagiosplugin.metric.Metric`, pass the context's name in the metric's
**context** parameter. Both metrics use the same context "users". This way, the
main function must define only one context that applies the same thresholds to
both metrics:
.. code-block:: python
@nagiosplugin.guarded
def main():
argp = argparse.ArgumentParser()
[...]
args = argp.parse_args()
check = nagiosplugin.Check(
Users(),
nagiosplugin.ScalarContext('users', args.warning, args.critical,
fmt_metric='{value} users logged in'))
check.main()
Multiple contexts
-----------------
The above example defines only one context for all metrics. This may not be
practical. Each metric should get its own context now. By default, a metric is
matched by a context of the same name. So we just leave out the **context**
parameters:
.. code-block:: python
def probe(self):
[...]
return [nagiosplugin.Metric('total', len(self.users), min=0),
nagiosplugin.Metric('unique', len(self.unique_users), min=0)]
We then define two contexts (one for each metric) in the `main()` function:
.. code-block:: python
@nagiosplugin.guarded
def main():
[...]
args = argp.parse_args()
check = nagiosplugin.Check(
Users(),
nagiosplugin.ScalarContext('total', args.warning, args.critical,
fmt_metric='{value} users logged in'),
nagiosplugin.ScalarContext(
'unique', args.warning_unique, args.critical_unique,
fmt_metric='{value} unique users logged in'))
check.main(args.verbose, args.timeout)
Alternatively, we can require every context that fits in metric definitions.
Logging and verbosity levels
----------------------------
**nagiosplugin** integrates with the `logging`_ module from Python's standard
library. If the main function is decorated with `guarded` (which is heavily
recommended), the logging module gets automatically configured before the
execution of the `main()` function starts. Messages logged to the *nagiosplugin*
logger (or any sublogger) are processed with nagiosplugin's integrated logging.
Consider the following example check::
import argparse
import nagiosplugin
import logging
_log = logging.getLogger('nagiosplugin')
class Logging(nagiosplugin.Resource):
def probe(self):
_log.warning('warning message')
_log.info('info message')
_log.debug('debug message')
return [nagiosplugin.Metric('zero', 0, context='default')]
@nagiosplugin.guarded
def main():
argp = argparse.ArgumentParser()
argp.add_argument('-v', '--verbose', action='count', default=0)
args = argp.parse_args()
check = nagiosplugin.Check(Logging())
check.main(args.verbose)
if __name__ == '__main__':
main()
The verbosity level is set in the :meth:`check.main()` invocation depending on
the number of "-v" flags. Let's test this check:
.. code-block:: bash
$ check_verbose.py
LOGGING OK - zero is 0 | zero=0
warning message (check_verbose.py:11)
$ check_verbose.py -v
LOGGING OK - zero is 0
warning message (check_verbose.py:11)
| zero=0
$ check_verbose.py -vv
LOGGING OK - zero is 0
warning message (check_verbose.py:11)
info message (check_verbose.py:12)
| zero=0
$ check_verbose.py -vvv
LOGGING OK - zero is 0
warning message (check_verbose.py:11)
info message (check_verbose.py:12)
debug message (check_verbose.py:13)
| zero=0
When called with *verbose=0,* both the summary and the performance data are
printed on one line and the warning message is displayed. Messages logged with
*warning* or *error* level are always printed.
Setting *verbose* to 1 does not change the logging level but enable multi-line
output. Additionally, full tracebacks would be printed in the case of an
uncaught exception.
Verbosity levels of 2 and 3 enable logging with *info* or *debug* levels.
This behaviour conforms to the "Verbose output" suggestions found in the
`Nagios plug-in development guidelines`_.
The initial verbosity level is 1 (multi-line output). This means that tracebacks
are printed for uncaught exceptions raised in the initialization phase (before
:meth:`Check.main` is called). This is generally a good thing. To suppress
tracebacks during initialization, call :func:`~nagiosplugin.runtime.guarded`
with an optional `verbose` parameter. Example:
.. code-block:: python
@nagiosplugin.guarded(verbose=0)
def main():
[...]
.. note::
The initial verbosity level takes effect only until :meth:`Check.main`
is called with a different verbosity level.
It is advisable to sprinkle logging statements in the plugin code, especially
into the resource model classes. A logging example for a users check could look
like this:
.. code-block:: python
class Users(nagiosplugin.Resource):
[...]
def list_users(self):
"""Return list of logged in users."""
_log.info('querying users with "%s" command', self.who_cmd)
users = []
try:
for line in subprocess.check_output([self.who_cmd]).splitlines():
_log.debug('who output: %s', line.strip())
users.append(line.split()[0].decode())
except OSError:
raise nagiosplugin.CheckError(
'cannot determine number of users ({} failed)'.format(
self.who_cmd))
_log.debug('found users: %r', users)
return users
Interesting items to log are: the command which is invoked to query the
information from the system, or the raw result to verify that parsing works
correctly.
.. _logging: http://docs.python.org/3/library/logging.html
.. _Nagios plug-in development guidelines: http://nagiosplug.sourceforge.net/developer-guidelines.html#AEN39
.. vim: set spell spelllang=en:
|