.. _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: