/
mqtt_zabbix_sender.py
115 lines (86 loc) · 3.23 KB
/
mqtt_zabbix_sender.py
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
import json
import logging
import sys
import paho.mqtt.client as mqtt
import pyjq
import yaml
from pyzabbix import ZabbixMetric, ZabbixSender
# noinspection PyShadowingNames
def read_config(path: str) -> dict:
with open(path) as f:
cfg = yaml.load(f, Loader=yaml.SafeLoader)
# Pre-compile all JQ queries
for topic, items in cfg["topics"].items():
for item in items:
if "jq" in item:
item["jq"]["query"] = pyjq.compile(item["jq"]["query"])
return cfg
# noinspection PyProtectedMember
def apply_jq(payload: str, jq: dict):
if jq["return"] not in ("first", "all"):
raise ValueError("jq return value must be either 'first' or 'all'")
# Dump JSON
if jq.get("unmarshal", True):
data = json.loads(payload)
else:
data = payload
# Retrieve function (first/all)
function = getattr(jq["query"], jq["return"])
# Apply to input
result = function(data)
# Convert result back to JSON
if jq.get("marshal", True):
return json.dumps(result)
else:
return result
# noinspection PyShadowingNames
class MQTTZabbixSender:
def __init__(self, config: dict):
self._cfg = config
self._client = None
def connect(self):
self._client = mqtt.Client(client_id=self._cfg.get("client_id", ""))
self._client.on_connect = self.on_connect
self._client.on_message = self.on_message
self._client.connect(self._cfg["host"], self._cfg["port"], 60)
logging.debug(f"Connected to broker {self._cfg['host']}")
def loop_forever(self):
self._client.loop_forever()
def on_connect(self, client: mqtt.Client, userdata, flags, rc):
for topic in self._cfg["topics"].keys():
client.subscribe(topic, 1)
logging.debug(f"Subscribed to {topic}")
def on_message(self, client: mqtt.Client, userdata, msg: mqtt.MQTTMessage):
if msg.topic not in self._cfg["topics"]:
logging.warning(f"Ignoring unrequested topic: {msg.topic}")
return
else:
items = self._cfg["topics"][msg.topic]
metrics = []
for item in items:
payload = msg.payload.decode()
if "jq" in item:
try:
payload = apply_jq(payload, item["jq"])
except Exception as e:
logging.error("Failed to apply JQ to payload, skipping", e)
continue
metrics.append(ZabbixMetric(item["host"], item["item"], payload))
logging.debug(f"{msg.topic} -> {item['item']}@{item['host']}{{{payload}}}")
try:
# noinspection PyTypeChecker
result = ZabbixSender(use_config=True).send(metrics)
except Exception as e:
logging.error("Failed to send metrics to Zabbix agent", e)
def main(args=tuple(sys.argv)):
if len(args) < 2:
raise SystemExit(f"Usage: {sys.argv[0]} [config]")
cfg_path = args[1]
cfg = read_config(cfg_path)
log_level = getattr(logging, cfg.get("log_level", "INFO").upper(), logging.INFO)
logging.basicConfig(level=log_level)
sender = MQTTZabbixSender(cfg)
sender.connect()
sender.loop_forever()
if __name__ == "__main__":
main()