Skip to main content

Eventhandler

The Event Handler Framework provides a streamlined approach for writing event handler scripts in Nagios monitoring environments. With this framework, users can easily define conditions for handling events and specify actions to be taken based on those conditions, leveraging two core components: the Decider and the Runner. These components work together seamlessly to evaluate conditions, prepare parameters, and execute appropriate actions in response to events.

define command{
    command_name    handle_omd_restart
    command_line    $USER1$/eventhandler \
                        --runner ssh \
                        --runnertag omd_restart \
                        --runneropt hostname='$HOSTADDRESS$' \
...
                        --decider omd_site_self_heal \
                        --eventopt site_name='$HOSTNAME$' \
                        --eventopt HOSTNAME='$HOSTNAME$' \
                        --eventopt HOSTSTATE='$HOSTSTATE$' \
                        --eventopt HOSTADDRESS='$HOSTADDRESS$' \
                        --eventopt SERVICEDESC='$SERVICEDESC$' \
                        --eventopt SERVICESTATE='$SERVICESTATE$' \
                        --eventopt SERVICEOUTPUT='$SERVICEOUTPUT$' \
                        --eventopt LONGSERVICEOUTPUT='$LONGSERVICEOUTPUT$' \
                    >> $USER4$/var/log/eventhandler_errors.log 2>&1

Decider

The Decider component evaluates whether an event should be handled based on the specified conditions. It considers factors such as current state, state type, attempt count, downtime, etc., and returns a set of parameters indicating the action to be taken. The binary lib/monitoring-plugins/eventhandler takes multiple --eventopt KEY=VALUE, which are usually Naemon macros. These key-value pairs are found in the dict event.eventopts.

Runtime behavior is split into clear checkpoints: event enrichment, decider loading, decision preparation, runner execution, optional forwarding, and logging. When something fails, the runtime reports which component failed and which resolution rule it attempted. Logging uses the framework's native setup with the standard logfile naming, format, and --debug / --verbose behavior.

Here is an example for a decider which is used in a self-monitoring setup. Its job is to restart an OMD instance or at least stopped/failed processes.

from eventhandler.baseclass import EventhandlerDecider

class OmdSiteSelfHealDecider(EventhandlerDecider):

    def decide_and_prepare(self, event):
        if event.eventopts["HOSTDOWNTIME"] or event.eventopts["SERVICEDOWNTIME"]:
            event.summary = "{} / {} is in downtime".format(event.eventopts["SERVICEDESC"], event.eventopts["HOSTNAME"])
            event.discard(silently=False)
        elif event.eventopts["SERVICESTATE"] == "OK":
            event.summary = "{} / {} has recovered".format(event.eventopts["SERVICEDESC"], event.eventopts["HOSTNAME"])
            event.discard(silently=False)
        elif event.eventopts["SERVICEATTEMPT"] == 1:
            event.summary = "Restarting {} / {}".format(event.eventopts["SERVICEDESC"], event.eventopts["HOSTNAME"])
            event.payload = {
                'user': event.eventopts["site_name"],
                'command': "lib/nagios/plugins/check_omd --heal",
            }
        elif event.eventopts["SERVICEATTEMPT"] == 2:
            event.summary = "Restart of {} / {} did not help".format(event.eventopts["SERVICEDESC"], event.eventopts["HOSTNAME"])
            event.discard(silently=False)
        else:
            event.summary = "Unhandled state {}".format(event.eventopts)
            event.discard(silently=False)

The string you assign to event.summary is used for logging. If you want to abort the event handler so that the runner will not do anything, call event.discard(). It's called with silently=False here just for demonstration purposes and can be left away. If you want to abort event handling without even leaving a trace in the log file, use event.discard(silently=True).

Runner

The Runner component executes the appropriate script when an event meets the defined conditions, using the parameters the Decider puts into the dict event.payload. Either the runner executes python code in order to fix the problem or it creates a command line which will be executed by the framework in a subprocess. Here is an example for a runner.

from eventhandler.baseclass import EventhandlerRunner

class SshRunner(EventhandlerRunner):

    def __init__(self, opts):
        super(self.__class__, self).__init__(opts)
        setattr(self, "username", getattr(self, "username", None))
        setattr(self, "hostname", getattr(self, "hostname", "localhost"))
        setattr(self, "port", getattr(self, "port", None))
        setattr(self, "identity_file", resolve_identity_file(getattr(self, "identity_file", None)))
        setattr(self, "command", getattr(self, "command", "exit 0"))

    def run(self, event):
        cmd = "ssh"
        if self.username:
            cmd += f" -l {self.username}"
        if self.port:
            cmd += f" -p {self.port}"
        if self.identity_file:
            cmd += f" -i {self.identity_file}"
        cmd += " {} '{}'".format(self.hostname, self.command)
        return cmd

The previous example with runner ssh and decider omd_site_self_heal can be used out of the box in an OMD environment. (There are ~/lib/python/eventhandler/ssh/runner.py and ~/lib/python/eventhandler/omd_site_self_heal/decider.py) For your own deciders and runners, create a folder in ~/local/lib/python/eventhandler and put a decider.py or runner.py in it.

Extension rules:

  • module names follow the package path under eventhandler.<name>.<kind>
  • class names default to title-cased module names plus Decider, Runner, or Logger
  • deciders and runners mutate event.payload / event.summary in place
  • runner return values stay compatible: command string, True/False, or None for intentional abort
  • logger loading falls back to text logging when the requested logger cannot be imported

Builtin runners you can just use without writing code yourself are:

  • ssh The ssh runner takes these parameters (either through --runneropt or event.payload) hostname - mandatory, the name of the ssh server. username - the username on the ssh server side. (default: the client username) port - the port where sshd is listening. (default: 22) identity_file - the private ke file. command - the command with arguments which will be executed on the ssh server side.

  • nsc_web hostname - mandatory, the host where NSClient++/SNClient is running. port - the port, where SNClient is listening. password - the SCNlient+ password. command - the command to call on the SNClient. arguments - the arguments for the command.

  • bash command - the command to run locally in a bash.

All the attributs you initialize in the __init__ method will be overwritten if they exist in the event.payload created by the decider. (Precedence: default set by __init__, argument from runneropt, key/value from event.payload)

There is a decider default, which triggers the runner on SOFT;1. This and the runner nsc_web or ssh can be used without any need to write you own code if you call eventhandler with a suitable list or --runneropt parameters..

Loggers

The framework uses a modular logging architecture similar to runners, deciders, and formatters. By default, eventhandler uses text format logging - you don't need to do anything, logging works exactly as it did before. The traditional text format is backward compatible with all existing installations.

If a requested logger cannot be resolved, the runtime falls back to text logging and records the fallback in the log file.

Why JSON Logging?

In enterprise environments, the gateway from monitoring systems to incident management platforms like Remedy, ServiceNow, or other ITSM tools is crucial for operational reliability. For comprehensive monitoring and troubleshooting of this critical path, logs need to be ingested into log aggregation systems like Splunk for analysis, alerting, and correlation.

The JSON logger provides structured logging optimized for ingestion into Splunk and other log management systems. It outputs single-line JSON with:

  • Splunk-friendly underscore field naming (e.g., event_host_name, event_service_name)
  • Complete event details including state, output, and summary
  • Operational metrics and execution details
  • Structured exception traces
  • Timezone-aware timestamps

Usage

Default (text logging):

$USER1$/eventhandler \
    --runner ssh \
    --decider default \
    --eventopt HOSTNAME='$HOSTNAME$' \
    --eventopt SERVICESTATE='$SERVICESTATE$'

JSON logging for Splunk ingestion:

$USER1$/eventhandler \
    --runner ssh \
    --decider default \
    --logger json \
    --eventopt HOSTNAME='$HOSTNAME$' \
    --eventopt SERVICESTATE='$SERVICESTATE$'

Custom logger:

$USER1$/eventhandler \
    --runner ssh \
    --logger mycustomlogger \
    --eventopt HOSTNAME='$HOSTNAME$'

Example Log Output

Text format (default):

2025-11-13 17:00:57,987 3468977 - INFO - executed action for dbserver02.example.com/MySQL: WARNING - Slow queries

JSON format:

{
  "timestamp": "2025-11-13T17:00:57.987487+01:00",
  "host_name": "oasch.example.com",
  "version": "2.9",
  "level": "INFO",
  "logger": "eventhandler_ssh",
  "omd_site": "demo_site",
  "event_host_name": "dbserver02.example.com",
  "event_service_name": "MySQL",
  "event_state": "WARNING",
  "event_notification_type": "PROBLEM",
  "event_service_output": "MySQL WARNING - Slow queries detected",
  "event_summary": "dbserver02.example.com/MySQL: WARNING - Slow queries",
  "msg": {
    "message": "executed action",
    "status": "success"
  }
}

Custom Loggers

You can create custom loggers by:

  1. Creating ~/local/lib/python/eventhandler/mylogger/logger.py
  2. Inheriting from EventhandlerLogger base class
  3. Implementing the log(level, message, context) method
from eventhandler.baseclass import EventhandlerLogger

class MyloggerLogger(EventhandlerLogger):
    def log(self, level, message, context=None):
        # Custom logging implementation
        pass

Metadata

Release files for omdeventhandler 2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for omdeventhandler 2.2
File Size Uploaded Provenance Actions
omdeventhandler-2.2.tar.gz 21.7 kB Without provenance Details for omdeventhandler-2.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for omdeventhandler 2.2
File Interpreter ABI Platform Actions
omdeventhandler-2.2-py3-none-any.whl Python 3 none any Details for omdeventhandler-2.2-py3-none-any.whl

Total release size: 41.3 kB

Release files / omdeventhandler-2.2.tar.gz

Download URL omdeventhandler-2.2.tar.gz
Size 21.7 kB
Tags Source
SHA-256 checksum
How to use checksums
44a7bec07c16f94650b90f3e2c92100e690ef52858714ea030c5e4ddea68c9e6
BLAKE2b-256 checksum
How to use checksums
7f02f8b25a33f24946f87ff5704bacb397eb385f5475eb9ee733eca4df1144f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.8

Release files / omdeventhandler-2.2-py3-none-any.whl

Download URL omdeventhandler-2.2-py3-none-any.whl
Size 19.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0cced39146e8df39cdb1b60a008580cafc69a824d704e77105d21318efe45c35
BLAKE2b-256 checksum
How to use checksums
f39dbc0734872591b10e2dee839e924964560d36fd3c3f3bc38719a3973e7488
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.8

Security for omdeventhandler 2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page