omdnotificationforwarder 1.0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. omdnotificationforwarder-1.0.0.1/.gitignore +5 -0
  2. omdnotificationforwarder-1.0.0.1/PKG-INFO +21 -0
  3. omdnotificationforwarder-1.0.0.1/README.md +2 -0
  4. omdnotificationforwarder-1.0.0.1/pyproject.toml +55 -0
  5. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/baseclass.py +365 -0
  6. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/email/forwarder.py +0 -0
  7. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/example/formatter.py +18 -0
  8. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/example/forwarder.py +36 -0
  9. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/rabbitmq/formatter.py +29 -0
  10. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/rabbitmq/forwarder.py +58 -0
  11. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/syslog/formatter.py +10 -0
  12. omdnotificationforwarder-1.0.0.1/src/notificationforwarder/syslog/forwarder.py +42 -0
  13. omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split1/forwarder.py +13 -0
  14. omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split2/formatter.py +14 -0
  15. omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split2/forwarder.py +13 -0
  16. omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split3/formatter.py +14 -0
  17. omdnotificationforwarder-1.0.0.1/tests/pythonpath/lib/python/notificationforwarder/split3/forwarder.py +13 -0
  18. omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split1/formatter.py +14 -0
  19. omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py +13 -0
  20. omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py +14 -0
  21. omdnotificationforwarder-1.0.0.1/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py +13 -0
  22. omdnotificationforwarder-1.0.0.1/tests/test_classes.py +185 -0
  23. omdnotificationforwarder-1.0.0.1/tests/test_package.py +7 -0
  24. omdnotificationforwarder-1.0.0.1/tests/test_paths.py +121 -0
  25. omdnotificationforwarder-1.0.0.1/tests/tmp/split3-flush.lock +0 -0
  26. omdnotificationforwarder-1.0.0.1/tests/var/log/notificationforwarder_split3.log +5 -0
  27. omdnotificationforwarder-1.0.0.1/tests/var/tmp/split3-notifications.db +0 -0
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ dist
4
+ sdist
5
+ *.egg
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.1
2
+ Name: omdnotificationforwarder
3
+ Version: 1.0.0.1
4
+ Summary: A framework for notification scripts for OMD
5
+ Project-URL: Homepage, https://github.com/lausser/noteventificationforhandlerwarder
6
+ Project-URL: Bug Tracker, https://github.com/lausser/noteventificationforhandlerwarder/issues
7
+ Author-email: Gerhard Lausser <lausser@yahoo.com>
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.7
12
+ Requires-Dist: coshsh
13
+ Requires-Dist: jinja2
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest-cov; extra == 'test'
16
+ Requires-Dist: pytest>=2.7.3; extra == 'test'
17
+ Requires-Dist: unittest; extra == 'test'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # noteventificationforhandlerwarder
21
+ Frickel für Notifications und Eventhandler
@@ -0,0 +1,2 @@
1
+ # noteventificationforhandlerwarder
2
+ Frickel für Notifications und Eventhandler
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling", "coshsh"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build]
6
+ include = [
7
+ "src/**/*.py",
8
+ "/tests",
9
+ ]
10
+ exclude = [
11
+ "__pacache__/",
12
+ "**/.pyc",
13
+ ]
14
+
15
+ [tool.hatch.build.targets.wheel]
16
+ packages = ["src/notificationforwarder"]
17
+
18
+ [project]
19
+ name = "omdnotificationforwarder"
20
+ version = "1.0.0.1"
21
+ authors = [
22
+ { name="Gerhard Lausser", email="lausser@yahoo.com" },
23
+ ]
24
+ description = "A framework for notification scripts for OMD"
25
+ readme = "README.md"
26
+ requires-python = ">=3.7"
27
+ classifiers = [
28
+ "Programming Language :: Python :: 3",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: OS Independent",
31
+ ]
32
+ dependencies = [
33
+ "coshsh",
34
+ "jinja2",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ test = [
39
+ "unittest",
40
+ "pytest >= 2.7.3",
41
+ "pytest-cov",
42
+ ]
43
+
44
+ [project.urls]
45
+ "Homepage" = "https://github.com/lausser/noteventificationforhandlerwarder"
46
+ "Bug Tracker" = "https://github.com/lausser/noteventificationforhandlerwarder/issues"
47
+
48
+ [tool.pytest.ini_options]
49
+ addopts = "-ra -q --import-mode=importlib"
50
+ pythonpath = [
51
+ "src/"
52
+ ]
53
+ testpaths = [
54
+ "tests",
55
+ ]
@@ -0,0 +1,365 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from importlib import import_module
3
+ import os
4
+ import socket
5
+ import traceback
6
+ import signal
7
+ import functools
8
+ import errno
9
+ import fcntl
10
+ import time
11
+ try:
12
+ import simplejson as json
13
+ except ImportError:
14
+ import json
15
+ from importlib import import_module
16
+ from importlib.util import find_spec, module_from_spec
17
+
18
+ import sqlite3
19
+ import logging
20
+ from coshsh.util import setup_logging
21
+
22
+
23
+ MAXAGE = 5
24
+
25
+
26
+ logger = None
27
+
28
+ def new(target_name, tag, verbose, debug, receiveropts):
29
+
30
+ if verbose:
31
+ scrnloglevel = logging.INFO
32
+ else:
33
+ scrnloglevel = 100
34
+ if debug:
35
+ scrnloglevel = logging.DEBUG
36
+ txtloglevel = logging.DEBUG
37
+ else:
38
+ txtloglevel = logging.INFO
39
+ if tag:
40
+ logger_name = "notificationforwarder_"+target_name+"_"+tag
41
+ else:
42
+ logger_name = "notificationforwarder_"+target_name
43
+
44
+ setup_logging(logdir=os.environ["OMD_ROOT"]+"/var/log", logfile=logger_name+".log", scrnloglevel=scrnloglevel, txtloglevel=txtloglevel, format="%(asctime)s %(process)d - %(levelname)s - %(message)s")
45
+ logger = logging.getLogger(logger_name)
46
+ try:
47
+ if '.' in target_name:
48
+ module_name, class_name = target_name.rsplit('.', 1)
49
+ else:
50
+ module_name = target_name
51
+ class_name = target_name.capitalize()
52
+ forwarder_module = import_module('notificationforwarder.'+module_name+'.forwarder', package='notificationforwarder.'+module_name)
53
+ forwarder_class = getattr(forwarder_module, class_name)
54
+
55
+ instance = forwarder_class(receiveropts)
56
+ instance.__module_file__ = forwarder_module.__file__
57
+ instance.name = target_name
58
+ if tag:
59
+ instance.tag = tag
60
+ # so we can use logger.info(...) in the single modules
61
+ forwarder_module.logger = logging.getLogger(logger_name)
62
+ base_module = import_module('.baseclass', package='notificationforwarder')
63
+ base_module.logger = logging.getLogger(logger_name)
64
+
65
+ except Exception as e:
66
+ raise ImportError('{} is not part of our forwarder collection!'.format(target_name))
67
+ else:
68
+ if not issubclass(forwarder_class, NotificationForwarder):
69
+ raise ImportError("We currently don't have {}, but you are welcome to send in the request for it!".format(forwarder_class))
70
+
71
+ return instance
72
+
73
+ class ForwarderTimeoutError(Exception):
74
+ pass
75
+
76
+ def timeout(seconds, error_message="Timeout"):
77
+ def decorator(func):
78
+ @functools.wraps(func)
79
+ def wrapper(*args, **kwargs):
80
+ def handler(signum, frame):
81
+ raise ForwarderTimeoutError(error_message)
82
+
83
+ original_handler = signal.signal(signal.SIGALRM, handler)
84
+ signal.alarm(seconds)
85
+ try:
86
+ result = func(*args, **kwargs)
87
+ finally:
88
+ signal.signal(signal.SIGALRM, original_handler)
89
+ signal.alarm(0)
90
+ return result
91
+ return wrapper
92
+ return decorator
93
+
94
+
95
+ class NotificationForwarder(object):
96
+ """This is the base class where all Forwardes inherit from"""
97
+ __metaclass__ = ABCMeta # replace with ...BaseClass(metaclass=ABCMeta):
98
+
99
+ def __init__(self, opts):
100
+ self.queued_events = []
101
+ self.max_queue_length = 10
102
+ self.sleep_after_flush = 0
103
+ self.baseclass_logs_summary = True
104
+ for opt in opts:
105
+ setattr(self, opt, opts[opt])
106
+
107
+ def probe(self):
108
+ """Checks if a forwarder is principally able to submit an event.
109
+ It is mostly used to contact an api and confirm that it is alive.
110
+ After failed attempts, when there are spooled events in the database,
111
+ a call to probe() can tell the forwarder that the events now can
112
+ be flushed.
113
+ """
114
+ return True
115
+
116
+ def init_queue(self, maxlength=10, sleepttime=0):
117
+ self.max_queue_length = maxlength
118
+ self.sleep_after_flush = sleepttime
119
+
120
+ def flush_queue(self):
121
+ if not getattr(self, "can_queue", False):
122
+ logger.critical("forwarder {} can not flush_queue events".format(self.__class__.__name__.lower()))
123
+ return
124
+ logger.debug("flush remaining {}".format(len(self.queued_events)))
125
+ if self.queued_events:
126
+ formatted_squashed_event = self.squash_queued_events()
127
+ logger.debug("merge {} queued events and flush".format(len(self.queued_events)))
128
+ self.forward_formatted(formatted_squashed_event)
129
+ self.queued_events = []
130
+ time.sleep(self.sleep_after_flush)
131
+
132
+
133
+ def forward_queued(self, raw_event):
134
+ if not getattr(self, "can_queue", False):
135
+ logger.critical("forwarder {} can not queue events".format(self.__class__.__name__.lower()))
136
+ return
137
+ try:
138
+ formatted_event = self.format_event(raw_event)
139
+ if formatted_event:
140
+ self.queued_events.append(formatted_event)
141
+ except Exception as e:
142
+ logger.critical("formatter error: "+str(e))
143
+ if len(self.queued_events) >= self.max_queue_length:
144
+ formatted_squashed_event = self.squash_queued_events()
145
+ logger.debug("merge {} queued events and flush".format(self.max_queue_length))
146
+ self.forward_formatted(formatted_squashed_event)
147
+ self.queued_events = []
148
+
149
+ def squash_queued_events(self):
150
+ instance = self.formatter()
151
+ return instance.squash_queued_events(self.queued_events)
152
+ return None
153
+
154
+ def forward(self, raw_event):
155
+ self.initdb()
156
+ try:
157
+ if not "omd_site" in raw_event:
158
+ raw_event["omd_site"] = os.environ.get("OMD_SITE", "get https://omd.consol.de/docs/omd")
159
+ raw_event["originating_host"] = socket.gethostname()
160
+ raw_event["originating_fqdn"] = socket.getfqdn()
161
+ formatted_event = self.format_event(raw_event)
162
+ if not hasattr(formatted_event, "payload") and not hasattr(formatted_event, "summary"):
163
+ logger.critical("a formatted event must have the attributes payload and summary")
164
+ formatted_event = None
165
+ except Exception as e:
166
+ logger.critical("formatter error: "+str(e))
167
+ formatted_event = None
168
+
169
+ self.forward_formatted(formatted_event)
170
+
171
+ def forward_formatted(self, formatted_event):
172
+ try:
173
+ if self.probe():
174
+ self.flush()
175
+ except Exception as e:
176
+ logger.critical("flush probe failed with exception <{}>")
177
+
178
+ format_exception_msg = None
179
+ try:
180
+ if formatted_event == None:
181
+ success = True
182
+ else:
183
+ success = self.submit(formatted_event)
184
+ except Exception as e:
185
+ success = False
186
+ format_exception_msg = str(e)
187
+
188
+ if success:
189
+ if self.baseclass_logs_summary:
190
+ logger.info("forwarded {}".format(formatted_event.summary))
191
+ else:
192
+ if format_exception_msg:
193
+ logger.critical("forward failed with exception <{}>, spooled <{}>".format(format_exception_msg, formatted_event.summary))
194
+ elif self.baseclass_logs_summary:
195
+ logger.warning("forward failed, spooled {}".format(formatted_event.summary))
196
+ self.spool(formatted_event)
197
+
198
+ def formatter(self):
199
+ try:
200
+ module_name = self.__class__.__name__.lower()
201
+ class_name = self.__class__.__name__+"Formatter"
202
+ formatter_module = import_module('.formatter', package='notificationforwarder.'+module_name)
203
+ formatter_module.logger = logger
204
+ formatter_class = getattr(formatter_module, class_name)
205
+ instance = formatter_class()
206
+ instance.__module_file__ = formatter_module.__file__
207
+ return instance
208
+ except ImportError:
209
+ logger.debug("there is no module "+module_name)
210
+ return None
211
+ except Exception as e:
212
+ logger.critical("formatter error: "+str(e))
213
+ return None
214
+
215
+ def format_event(self, raw_event):
216
+ instance = self.formatter()
217
+ return instance.format_event(raw_event)
218
+
219
+ def connect(self):
220
+ return True
221
+
222
+ def disconnect(self):
223
+ return True
224
+
225
+ def initdb(self):
226
+ db_file = os.environ["OMD_ROOT"] + '/var/tmp/' + self.name + '-notifications.db'
227
+ self.table_name = "events_"+self.name
228
+ sql_create = """CREATE TABLE IF NOT EXISTS """+self.table_name+""" (
229
+ id INTEGER PRIMARY KEY,
230
+ payload TEXT NOT NULL,
231
+ summary TEXT NOT NULL,
232
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
233
+ )"""
234
+ try:
235
+ self.dbconn = sqlite3.connect(db_file)
236
+ self.dbcurs = self.dbconn.cursor()
237
+ self.dbcurs.execute(sql_create)
238
+ self.dbconn.commit()
239
+ except Exception as e:
240
+ logger.info("error initializing database {}: {}".format(db_file, str(e)))
241
+
242
+ def num_spooled_events(self):
243
+ sql_count = "SELECT COUNT(*) FROM "+self.table_name
244
+ spooled_events = 999999999
245
+ try:
246
+ self.dbcurs.execute(sql_count)
247
+ spooled_events = self.dbcurs.fetchone()[0]
248
+ except Exception as e:
249
+ logger.critical("database error "+str(e))
250
+ return spooled_events
251
+
252
+
253
+ def spool(self, event):
254
+ sql_insert = "INSERT INTO "+self.table_name+"(payload, summary) VALUES (?, ?)"
255
+ try:
256
+ num_spooled_events = 0
257
+ if type(event.payload) != list:
258
+ text = json.dumps(event.payload)
259
+ summary = event.summary
260
+ self.dbcurs.execute(sql_insert, (text, summary))
261
+ self.dbconn.commit()
262
+ # has already been logged in forward_formatted
263
+ # logger.warning("spooled "+summary)
264
+ num_spooled_events += 1
265
+ else:
266
+ for subevent in event.payload:
267
+ text = json.dumps(subevent)
268
+ summary = event.summary.pop(0)
269
+ sefl.dbcurs.execute(sql_insert, (text, summary))
270
+ self.dbconn.commit()
271
+ log.warning("spooled "+summary)
272
+ num_spooled_events += 1
273
+ spooled_events = self.num_spooled_events()
274
+ logger.warning("spooling queue length is {}".format(spooled_events))
275
+ except Exception as e:
276
+ logger.critical("database error "+str(e))
277
+ logger.info(event.__dict__)
278
+
279
+ def flush(self):
280
+ sql_delete = "DELETE FROM "+self.table_name+" WHERE CAST(STRFTIME('%s', timestamp) AS INTEGER) < ?"
281
+ sql_count = "SELECT COUNT(*) FROM "+self.table_name
282
+ sql_select = "SELECT id, payload, summary FROM "+self.table_name+" ORDER BY id LIMIT 10"
283
+ sql_delete_id = "DELETE FROM "+self.table_name+" WHERE id = ?"
284
+ with open(os.environ["OMD_ROOT"]+"/tmp/"+self.name+"-flush.lock", "w") as lock_file:
285
+ try:
286
+ fcntl.lockf(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
287
+ logger.debug("flush lock set")
288
+ locked = True
289
+ except IOError as e:
290
+ logger.debug("flush lock failed: "+str(e))
291
+ locked = False
292
+ if locked:
293
+ try:
294
+ outdated = int(time.time() - 60*MAXAGE)
295
+ self.dbcurs.execute(sql_delete, (outdated,))
296
+ dropped = self.dbcurs.rowcount
297
+ if dropped:
298
+ logger.info("dropped {} outdated events".format(dropped))
299
+ last_spooled_events = 0
300
+ while True:
301
+ self.dbcurs.execute(sql_count)
302
+ spooled_events = self.dbcurs.fetchone()[0]
303
+ if spooled_events:
304
+ logger.info("there are {} spooled events to be re-sent".format(spooled_events))
305
+ else:
306
+ break
307
+ if last_spooled_events == spooled_events:
308
+ if spooled_events != 0:
309
+ logger.critical("{} spooled events could not be submitted".format(last_spooled_events))
310
+ break
311
+ else:
312
+ self.dbcurs.execute(sql_select)
313
+ id_events = self.dbcurs.fetchall()
314
+ for id, payload, summary in id_events:
315
+ event = FormattedEvent()
316
+ event.is_heartbeat = False
317
+ event.payload = json.loads(payload)
318
+ event.summary = summary
319
+ if self.submit(event):
320
+ self.dbcurs.execute(sql_delete_id, (id, ))
321
+ logger.info("delete spooled event {}".format(id))
322
+ self.dbconn.commit()
323
+ else:
324
+ logger.critical("event {} spooled again".format(id))
325
+ last_spooled_events = spooled_events
326
+ self.dbconn.commit()
327
+ except Exception as e:
328
+ logger.critical("database flush failed")
329
+ logger.critical(e)
330
+ else:
331
+ logger.debug("missed the flush lock")
332
+
333
+ def no_more_logging(self):
334
+ # this is called in the forwarder. If the forwarder already wrote
335
+ # it's own logs and writing the summary by the baseclass is not
336
+ # desired.
337
+ self.baseclass_logs_summary = False
338
+
339
+ def __del__(self):
340
+ try:
341
+ if self.dbcursor:
342
+ self.dbcursor.close()
343
+ if self.dbconn:
344
+ self.dbconn.commit()
345
+ self.dbconn.close()
346
+ except Exception as a:
347
+ # don't care, we're finished anyway
348
+ pass
349
+
350
+ class NotificationFormatter(metaclass=ABCMeta):
351
+ @abstractmethod
352
+ def format_event(self):
353
+ pass
354
+
355
+
356
+ class FormattedEvent(metaclass=ABCMeta):
357
+ def __init__(self):
358
+ self.payload = None
359
+ self.summary = "empty event"
360
+
361
+ def set_payload(self, payload):
362
+ self.payload = payload
363
+
364
+ def set_summary(self, summary):
365
+ self.summary = summary
@@ -0,0 +1,18 @@
1
+ import time
2
+ import os
3
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
4
+
5
+ class ExampleFormatter(NotificationFormatter):
6
+
7
+ def format_event(self, raw_event):
8
+ event = FormattedEvent()
9
+ json_payload = {
10
+ 'timestamp': time.time(),
11
+ }
12
+ json_payload['description'] = raw_event['description']
13
+ if 'signature' in raw_event:
14
+ json_payload['signature'] = raw_event['signature']
15
+ event.set_payload(json_payload)
16
+ event.set_summary("sum: "+json_payload['description'])
17
+ return event
18
+
@@ -0,0 +1,36 @@
1
+ import json
2
+ import time
3
+ import logging
4
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
5
+
6
+
7
+ class Example(NotificationForwarder):
8
+ def __init__(self, opts):
9
+ super(self.__class__, self).__init__(opts)
10
+ setattr(self, "username", getattr(self, "username", "guest"))
11
+ setattr(self, "delay", int(getattr(self, "delay", 0)))
12
+ setattr(self, "fail", getattr(self, "fail", None))
13
+ setattr(self, "signaturefile", getattr(self, "signaturefile", "/tmp/notificationforwarder_example.txt"))
14
+ self.parameter = "sample"
15
+
16
+ @timeout(2, error_message="submit ran into a timeout")
17
+ def submit(self, event):
18
+ time.sleep(self.delay)
19
+ if True: # for example if self.connect()
20
+ try:
21
+ logger.info("{} submits {}".format(self.username, event.__dict__))
22
+ if self.fail:
23
+ logger.critical("sample api does not accept the payload")
24
+ return False
25
+ elif "signature" in event.payload:
26
+ with open(self.signaturefile, "a") as f:
27
+ print(event.payload["signature"], file=f)
28
+ return True
29
+ else:
30
+ return True
31
+ except Exception as e:
32
+ logger.critical("sample api post had an exception: {} with payload {}".format(str(e), str(event.payload)))
33
+ return False
34
+ else:
35
+ logger.critical("could not connect to the ticket system")
36
+ return False
@@ -0,0 +1,29 @@
1
+ import time
2
+ import os
3
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
4
+
5
+ class RabbitmqFormatter(NotificationFormatter):
6
+
7
+ def format_event(self, raw_event):
8
+ event = FormattedEvent()
9
+ print("formatter ", event.__dict__)
10
+ print("formatter ", self.__dict__)
11
+ print("formatter ", raw_event)
12
+ json_payload = {
13
+ 'omd_site': os.environ["OMD_SITE"],
14
+ 'platform': 'Naemon',
15
+ 'host_name': raw_event["HOSTNAME"],
16
+ 'notification_type': raw_event["NOTIFICATIONTYPE"],
17
+ 'timestamp': time.time(),
18
+ }
19
+ if "SERVICEDESC" in raw_event:
20
+ json_payload['service_description'] = raw_event['service_description']
21
+ json_payload['state'] = raw_event["state"]
22
+ json_payload['output'] = raw_event["output"]
23
+ else:
24
+ json_payload['state'] = raw_event["state"]
25
+ json_payload['output'] = raw_event["output"]
26
+ event.set_payload(json_payload)
27
+ event.set_summary(json_payload)
28
+ return event
29
+
@@ -0,0 +1,58 @@
1
+ import pika
2
+ import json
3
+ import logging
4
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
5
+
6
+
7
+ class Rabbitmq(NotificationForwarder):
8
+ def __init__(self, opts):
9
+ super(self.__class__, self).__init__(opts)
10
+ setattr(self, "port", int(getattr(self, "port", 5672)))
11
+ setattr(self, "server", getattr(self, "server", "localhost"))
12
+ setattr(self, "vhost", getattr(self, "vhost", "/"))
13
+ setattr(self, "queue", getattr(self, "queue", "AE"))
14
+ setattr(self, "username", getattr(self, "username", "guest"))
15
+ setattr(self, "password", getattr(self, "password", "guest"))
16
+
17
+ credentials = pika.PlainCredentials(self.username, self.password)
18
+ self.connectionparameters = pika.ConnectionParameters(self.server, self.port, self.vhost, credentials)
19
+
20
+ def connect(self):
21
+ try:
22
+ self.connection = pika.BlockingConnection(self.connectionparameters)
23
+ self.channel = self.connection.channel()
24
+ self.channel.queue_declare(queue=self.queue, durable=True)
25
+ logger.debug('Connected to {}:{}'.format(
26
+ self.connectionparameters.host,
27
+ self.connectionparameters.port))
28
+ return True
29
+ except Exception as e:
30
+ logger.critical("connect said: "+ str(e))
31
+ return False
32
+
33
+ def disconnect():
34
+ try:
35
+ self.connection.close()
36
+ except Exception as e:
37
+ pass
38
+
39
+ @timeout(30)
40
+ def submit(self, payload):
41
+ if self.connect():
42
+ try:
43
+ logger.info("submit "+payload)
44
+ self.channel = self.connection.channel()
45
+ self.channel.queue_declare(queue=self.queue, durable=True)
46
+ for event in payload:
47
+ if "service_description" in event:
48
+ logger.info("host: {}, service: {}, state: {}, output: {}".format(event["host_name"], event["service_description"], event["state"], event["output"]))
49
+ else:
50
+ logger.info("host: {}, state: {}, output: {}".format(event["host_name"], event["state"], event["output"]))
51
+ logger.debug(json.dumps(event))
52
+ self.channel.basic_publish(exchange='', routing_key=MQQUEUE, body=json.dumps(event))
53
+ return True
54
+ except Exception as e:
55
+ logger.critical("rabbitmq post had an exception: {} wit payload {}".format(str(e), str(payload)))
56
+ return False
57
+
58
+
@@ -0,0 +1,10 @@
1
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
2
+
3
+ class SyslogFormatter(NotificationFormatter):
4
+
5
+ def format_event(self, raw_event):
6
+ if "service_description" in raw_event:
7
+ return("host: {}, service: {}, state: {}, output: {}".format(raw_event["host_name"], raw_event["service_description"], raw_event["state"], raw_event["output"]))
8
+ else:
9
+ return("host: {}, state: {}, output: {}".format(raw_event["host_name"], raw_event["state"], raw_event["output"]))
10
+
@@ -0,0 +1,42 @@
1
+ import syslog
2
+ import logging
3
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
4
+
5
+
6
+ class Syslog(NotificationForwarder):
7
+ def __init__(self, opts):
8
+ super(self.__class__, self).__init__(opts)
9
+ setattr(self, "port", int(getattr(self, "port", 514)))
10
+ setattr(self, "server", getattr(self, "server", "localhost"))
11
+ setattr(self, "facility", getattr(self, "facility", "log_local0"))
12
+ setattr(self, "priority", getattr(self, "priority", "info"))
13
+ if self.facility in logging.handlers.SysLogHandler.facility_names:
14
+ self.facility = logging.handlers.SysLogHandler.facility_names[self.facility]
15
+ elif "log_"+self.facility in known_facilities:
16
+ self.facility = logging.handlers.SysLogHandler.facility_names["log_"+self.facility]
17
+ else:
18
+ self.facility = logging.handlers.SysLogHandler.LOG_DAEMON
19
+ if self.priority in logging.handlers.SysLogHandler.priority_names:
20
+ self.priority = logging.handlers.SysLogHandler.priority_names[self.priority]
21
+ elif "log_"+self.priority in logging.handlers.SysLogHandler.priority_names:
22
+ self.priority = logging.handlers.SysLogHandler.priority_names["log_"+self.priority]
23
+ else:
24
+ self.priority = logging.handlers.SysLogHandler.LOG_INFO
25
+ self.syslogger = logging.getLogger('Syslogger')
26
+ # do not suppress anything. (normal logger and syslog have
27
+ # completely different levels. setLevel(NOTSET) does not work.
28
+ self.syslogger.setLevel(-1)
29
+ handler = logging.handlers.SysLogHandler(address=(self.server, self.port), facility=self.facility)
30
+ self.syslogger.addHandler(handler)
31
+
32
+ @timeout(30)
33
+ def submit(self, payload):
34
+ try:
35
+ logger.info("submit "+payload)
36
+ self.syslogger.log(self.priority, payload)
37
+ return True
38
+ except Exception as e:
39
+ logger.critical("syslog forwarding had an error: {}".format(str(e)))
40
+ return False
41
+
42
+
@@ -0,0 +1,13 @@
1
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
2
+
3
+
4
+ class Split1(NotificationForwarder):
5
+ def __init__(self, opts):
6
+ super(self.__class__, self).__init__(opts)
7
+ self.url = "https://split1.com"
8
+
9
+ @timeout(30)
10
+ def submit(self, payload):
11
+ logger.info("forwarder "+self.__module_file__)
12
+ return True
13
+
@@ -0,0 +1,14 @@
1
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
2
+
3
+
4
+ class Split2Formatter(NotificationFormatter):
5
+
6
+ def __init__(self):
7
+ pass
8
+
9
+ def format_event(self, raw_event):
10
+ logger.info("formatter "+self.__module_file__)
11
+ event = FormattedEvent()
12
+ event.payload = str(raw_event)
13
+ event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
14
+ return event
@@ -0,0 +1,13 @@
1
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
2
+
3
+
4
+ class Split2(NotificationForwarder):
5
+ def __init__(self, opts):
6
+ super(self.__class__, self).__init__(opts)
7
+ self.url = "https://split1.com"
8
+
9
+ @timeout(30)
10
+ def submit(self, payload):
11
+ logger.info("forwarder "+self.__module_file__)
12
+ return True
13
+
@@ -0,0 +1,14 @@
1
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
2
+
3
+
4
+ class Split3Formatter(NotificationFormatter):
5
+
6
+ def __init__(self):
7
+ pass
8
+
9
+ def format_event(self, raw_event):
10
+ logger.info("formatter "+self.__module_file__)
11
+ event = FormattedEvent()
12
+ event.payload = str(raw_event)
13
+ event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
14
+ return event
@@ -0,0 +1,13 @@
1
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
2
+
3
+
4
+ class Split3(NotificationForwarder):
5
+ def __init__(self, opts):
6
+ super(self.__class__, self).__init__(opts)
7
+ self.url = "https://split1.com"
8
+
9
+ @timeout(30)
10
+ def submit(self, payload):
11
+ logger.info("forwarder "+self.__module_file__)
12
+ return True
13
+
@@ -0,0 +1,14 @@
1
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
2
+
3
+
4
+ class Split1Formatter(NotificationFormatter):
5
+
6
+ def __init__(self):
7
+ pass
8
+
9
+ def format_event(self, raw_event):
10
+ logger.info("formatter "+self.__module_file__)
11
+ event = FormattedEvent()
12
+ event.payload = str(raw_event)
13
+ event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
14
+ return event
@@ -0,0 +1,13 @@
1
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
2
+
3
+
4
+ class Split2(NotificationForwarder):
5
+ def __init__(self, opts):
6
+ super(self.__class__, self).__init__(opts)
7
+ self.url = "https://split1.com"
8
+
9
+ @timeout(30)
10
+ def submit(self, payload):
11
+ logger.info("forwarder "+self.__module_file__)
12
+ return True
13
+
@@ -0,0 +1,14 @@
1
+ from notificationforwarder.baseclass import NotificationFormatter, FormattedEvent
2
+
3
+
4
+ class Split3Formatter(NotificationFormatter):
5
+
6
+ def __init__(self):
7
+ pass
8
+
9
+ def format_event(self, raw_event):
10
+ logger.info("formatter "+self.__module_file__)
11
+ event = FormattedEvent()
12
+ event.payload = str(raw_event)
13
+ event.summary = "_".join(["{}={}".format(k, raw_event) for k in raw_event])
14
+ return event
@@ -0,0 +1,13 @@
1
+ from notificationforwarder.baseclass import NotificationForwarder, NotificationFormatter, timeout
2
+
3
+
4
+ class Split3(NotificationForwarder):
5
+ def __init__(self, opts):
6
+ super(self.__class__, self).__init__(opts)
7
+ self.url = "https://split1.com"
8
+
9
+ @timeout(30)
10
+ def submit(self, payload):
11
+ logger.info("forwarder "+self.__module_file__)
12
+ return True
13
+
@@ -0,0 +1,185 @@
1
+ import pytest
2
+ import os
3
+ import re
4
+ import shutil
5
+ import hashlib, secrets
6
+ import notificationforwarder
7
+ import logging
8
+
9
+
10
+ def _setup():
11
+ omd_root = os.path.dirname(__file__)
12
+ os.environ["OMD_ROOT"] = omd_root
13
+ shutil.rmtree(omd_root+"/var", ignore_errors=True)
14
+ os.makedirs(omd_root+"/var/log", 0o755)
15
+ shutil.rmtree(omd_root+"/var", ignore_errors=True)
16
+ os.makedirs(omd_root+"/var/tmp", 0o755)
17
+ shutil.rmtree(omd_root+"/tmp", ignore_errors=True)
18
+ os.makedirs(omd_root+"/tmp", 0o755)
19
+ if os.path.exists("/tmp/notificationforwarder_example.txt"):
20
+ os.remove("/tmp/notificationforwarder_example.txt")
21
+
22
+ @pytest.fixture
23
+ def setup():
24
+ _setup()
25
+ yield
26
+
27
+ def get_logfile(forwarder):
28
+ logger_name = "notificationforwarder_"+forwarder.name
29
+ logger = logging.getLogger(logger_name)
30
+ return [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
31
+
32
+
33
+ def test_example_forwarder(setup):
34
+ reveiveropts = {
35
+ "username": "i_bims",
36
+ "password": "dem_is_geheim"
37
+ }
38
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
39
+ assert example.__class__.__name__ == "Example"
40
+ assert example.password == "dem_is_geheim"
41
+ assert example.queued_events == []
42
+
43
+
44
+ def _test_example_formatter(setup):
45
+ example = notificationforwarder.baseclass.new("example", None, True, True, {})
46
+ fexample = example.formatter()
47
+ assert fexample.__class__.__name__ == "ExampleFormatter"
48
+
49
+
50
+ def test_example_logging(setup):
51
+ example = notificationforwarder.baseclass.new("example", None, True, True, {})
52
+ logger_name = "notificationforwarder_"+example.name
53
+ logger = logging.getLogger(logger_name)
54
+ assert logger != None
55
+ assert logger.name == "notificationforwarder_example"
56
+ assert len([h for h in logger.handlers]) == 2
57
+ logfile = [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
58
+ assert logfile.endswith("notificationforwarder_example.log")
59
+
60
+ example = notificationforwarder.baseclass.new("example", "2", True, True, {})
61
+ logger_name = "notificationforwarder_"+example.name+"_"+example.tag
62
+ logger = logging.getLogger(logger_name)
63
+ assert logger != None
64
+ assert logger.name == "notificationforwarder_example_2"
65
+ assert len(logger.handlers) == 2
66
+ logfile = [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
67
+ assert logfile.endswith("notificationforwarder_example_2.log")
68
+
69
+
70
+ def _test_example_formatter_format_event(setup):
71
+ example = notificationforwarder.baseclass.new("example", None, True, True, {})
72
+ fexample = example.formatter()
73
+ raw_event = {
74
+ "description": "halo i bims 1 alarm vong naemon her",
75
+ }
76
+ event = fexample.format_event(raw_event)
77
+ assert event.summary == "this is an example"
78
+ assert event.payload["description"] == "halo i bims 1 alarm vong naemon her"
79
+ assert event.payload["timestamp"] == pytest.approx(time.time(), abs=5)
80
+
81
+
82
+ def test_example_forwarder_forward(setup):
83
+ reveiveropts = {
84
+ "username": "i_bims",
85
+ "password": "i_bims_1_i_bims",
86
+ }
87
+ eventopts = {
88
+ "description": "halo i bims 1 alarm vong naemon her",
89
+ }
90
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
91
+ example.forward(eventopts)
92
+ log = open(get_logfile(example)).read()
93
+ assert "INFO - i_bims submits" in log
94
+ assert "'description': 'halo i bims 1 alarm vong naemon her'" in log
95
+ # this is the global log, written by the baseclass
96
+ assert "INFO - forwarded sum: halo i bims 1 alarm vong naemon her" in log
97
+
98
+ _setup() # delete logfile
99
+ # we need to reinitialize, because the logger has the (deleted) file
100
+ # still open and further writes would end up in nirvana.
101
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
102
+ eventopts = {
103
+ "description": "halo i bims 1 alarm vong naemon her again",
104
+ }
105
+ example.no_more_logging()
106
+ example.forward(eventopts)
107
+ log = open(get_logfile(example)).read()
108
+ # the formatter's logs are still there
109
+ assert "INFO - i_bims submits" in log
110
+ assert "'description': 'halo i bims 1 alarm vong naemon her again'" in log
111
+ # but not the baseclasse's log
112
+ assert "INFO - forwarded sum: halo i bims 1 alarm vong naemon her" not in log
113
+
114
+ def test_example_forwarder_forward_success(setup):
115
+ reveiveropts = {
116
+ "username": "i_bims",
117
+ "password": "i_bims_1_i_bims",
118
+ }
119
+ signature = hashlib.sha256(secrets.token_bytes(32)).hexdigest()
120
+ eventopts = {
121
+ "description": "halo i bims 1 alarm vong naemon her",
122
+ "signature": signature,
123
+ }
124
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
125
+ example.forward(eventopts)
126
+ assert os.path.exists(example.signaturefile)
127
+ sig = open(example.signaturefile).read().strip()
128
+ assert sig == signature
129
+
130
+ def test_example_forwarder_forward_timeout(setup):
131
+ signatures = [
132
+ hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
133
+ hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
134
+ hashlib.sha256(secrets.token_bytes(32)).hexdigest(),
135
+ ]
136
+ reveiveropts = {
137
+ "username": "i_bims",
138
+ "password": "i_bims_1_i_bims",
139
+ "delay": 60,
140
+ }
141
+ eventopts = {
142
+ "description": "halo i bims 1 alarm vong naemon her",
143
+ "signature": signatures[0],
144
+ }
145
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
146
+ example.forward(eventopts)
147
+ log = open(get_logfile(example)).read()
148
+ # this is the global log, written by the baseclass
149
+ assert "submit ran into a timeout" in log
150
+ assert "spooled <sum: halo i bims 1 alarm vong naemon her>" in log
151
+ assert "WARNING - spooling queue length is 1" in log
152
+ eventopts = {
153
+ "description": "halo i bim au 1 alarm vong naemon her",
154
+ "signature": signatures[1],
155
+ }
156
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
157
+ example.forward(eventopts)
158
+ log = open(get_logfile(example)).read()
159
+ assert "spooled <sum: halo i bim au 1 alarm vong naemon her>" in log
160
+ assert "WARNING - spooling queue length is 2" in log
161
+
162
+ # now the last two events were spooled and are in the database
163
+ reveiveropts = {
164
+ "username": "i_bims",
165
+ "password": "i_bims_1_i_bims",
166
+ "delay": 0,
167
+ }
168
+ eventopts = {
169
+ "description": "i druecke dem spuelung",
170
+ "signature": signatures[2],
171
+ }
172
+ example = notificationforwarder.baseclass.new("example", None, True, True, reveiveropts)
173
+ example.forward(eventopts)
174
+ log = open(get_logfile(example)).read()
175
+ assert re.search(r'.*i_bims submits.*i druecke dem spuelung.*', log, re.MULTILINE)
176
+ assert "forwarded sum: i druecke dem spuelung" in log
177
+ assert "DEBUG - flush lock set" in log
178
+ assert "INFO - there are 2 spooled events to be re-sent" in log
179
+ assert "INFO - delete spooled event 1" in log
180
+ assert "INFO - delete spooled event 2" in log
181
+ assert re.search(r'.*i_bims submits.*halo i bims 1 alarm vong naemon her.*', log, re.MULTILINE)
182
+ assert re.search(r'.*i_bims submits.*halo i bim au 1 alarm vong naemon her.*', log, re.MULTILINE)
183
+ sigs = [l.strip() for l in open(example.signaturefile).readlines()]
184
+ # flushing first, then the new event
185
+ assert sigs == signatures
@@ -0,0 +1,7 @@
1
+ import pytest
2
+
3
+ def test_import():
4
+ import notificationforwarder
5
+ assert hasattr(notificationforwarder, "baseclass")
6
+ assert hasattr(notificationforwarder.baseclass, "new")
7
+
@@ -0,0 +1,121 @@
1
+ import pytest
2
+ import inspect
3
+ import sys
4
+ import os
5
+ import re
6
+ import shutil
7
+ import hashlib, secrets
8
+ import logging
9
+
10
+ omd_root = os.path.dirname(__file__)
11
+ os.environ["OMD_ROOT"] = omd_root
12
+ if not [p for p in sys.path if "pythonpath" in p]:
13
+ sys.path.append(os.environ["OMD_ROOT"]+"/pythonpath/local/lib/python")
14
+ sys.path.append(os.environ["OMD_ROOT"]+"/pythonpath/lib/python")
15
+ print("PYTHONPATH="+":".join(sys.path))
16
+ import notificationforwarder.baseclass
17
+
18
+
19
+ def _setup():
20
+ omd_root = os.path.dirname(__file__)
21
+ os.environ["OMD_ROOT"] = omd_root
22
+ shutil.rmtree(omd_root+"/var", ignore_errors=True)
23
+ os.makedirs(omd_root+"/var/log", 0o755)
24
+ shutil.rmtree(omd_root+"/var", ignore_errors=True)
25
+ os.makedirs(omd_root+"/var/tmp", 0o755)
26
+ shutil.rmtree(omd_root+"/tmp", ignore_errors=True)
27
+ os.makedirs(omd_root+"/tmp", 0o755)
28
+ if os.path.exists("/tmp/notificationforwarder_example.txt"):
29
+ os.remove("/tmp/notificationforwarder_example.txt")
30
+
31
+ @pytest.fixture
32
+ def setup():
33
+ _setup()
34
+ yield
35
+
36
+ def get_logfile(forwarder):
37
+ logger_name = "notificationforwarder_"+forwarder.name
38
+ logger = logging.getLogger(logger_name)
39
+ return [h.baseFilename for h in logger.handlers if hasattr(h, "baseFilename")][0]
40
+
41
+
42
+ def test_split1_forwarder(setup):
43
+ # lib local/lib
44
+ # forwarder formatter
45
+ print(sys.path)
46
+ reveiveropts = {
47
+ "username": "i_bims",
48
+ "password": "dem_is_geheim"
49
+ }
50
+ eventopts = {
51
+ "description": "halo i bims 1 alarm vong naemon her",
52
+ }
53
+ split1 = notificationforwarder.baseclass.new("split1", None, True, True, reveiveropts)
54
+ assert split1.__class__.__name__ == "Split1"
55
+ assert split1.__module_file__.endswith("pythonpath/lib/python/notificationforwarder/split1/forwarder.py")
56
+ assert split1.password == "dem_is_geheim"
57
+ assert split1.queued_events == []
58
+ fsplit1 = split1.formatter()
59
+ assert fsplit1.__class__.__name__ == "Split1Formatter"
60
+ assert fsplit1.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split1/formatter.py")
61
+ split1.forward(eventopts)
62
+ log = open(get_logfile(split1)).read()
63
+ print(log)
64
+ assert re.search(r'forwarder '+split1.__module_file__, log)
65
+ assert re.search(r'formatter '+fsplit1.__module_file__, log)
66
+
67
+
68
+ def test_split2_forwarder(setup):
69
+ # lib local/lib
70
+ # forwarder forwarder
71
+ # formatter
72
+ print(sys.path)
73
+ reveiveropts = {
74
+ "username": "i_bims",
75
+ "password": "dem_is_geheim"
76
+ }
77
+ eventopts = {
78
+ "description": "halo i bims 1 alarm vong naemon her",
79
+ }
80
+ split2 = notificationforwarder.baseclass.new("split2", None, True, True, reveiveropts)
81
+ assert split2.__class__.__name__ == "Split2"
82
+ assert split2.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split2/forwarder.py")
83
+ assert split2.password == "dem_is_geheim"
84
+ assert split2.queued_events == []
85
+ fsplit2 = split2.formatter()
86
+ assert fsplit2.__class__.__name__ == "Split2Formatter"
87
+ assert fsplit2.__module_file__.endswith("pythonpath/lib/python/notificationforwarder/split2/formatter.py")
88
+ split2.forward(eventopts)
89
+ log = open(get_logfile(split2)).read()
90
+ print(log)
91
+ assert re.search(r'forwarder '+split2.__module_file__, log)
92
+ assert re.search(r'formatter '+fsplit2.__module_file__, log)
93
+
94
+
95
+ def test_split3_forwarder(setup):
96
+ # lib local/lib
97
+ # forwarder forwarder
98
+ # formatter formatter
99
+ print(sys.path)
100
+ reveiveropts = {
101
+ "username": "i_bims",
102
+ "password": "dem_is_geheim"
103
+ }
104
+ eventopts = {
105
+ "description": "halo i bims 1 alarm vong naemon her",
106
+ }
107
+ split3 = notificationforwarder.baseclass.new("split3", None, True, True, reveiveropts)
108
+ assert split3.__class__.__name__ == "Split3"
109
+ assert split3.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py")
110
+ assert split3.password == "dem_is_geheim"
111
+ assert split3.queued_events == []
112
+ fsplit3 = split3.formatter()
113
+ assert fsplit3.__class__.__name__ == "Split3Formatter"
114
+ assert fsplit3.__module_file__.endswith("pythonpath/local/lib/python/notificationforwarder/split3/formatter.py")
115
+ split3.forward(eventopts)
116
+ log = open(get_logfile(split3)).read()
117
+ print(log)
118
+ assert re.search(r'forwarder '+split3.__module_file__, log)
119
+ assert re.search(r'formatter '+fsplit3.__module_file__, log)
120
+
121
+
@@ -0,0 +1,5 @@
1
+ 2023-10-10 18:33:50,168 3511560 - DEBUG - Logger initialized.
2
+ 2023-10-10 18:33:50,197 3511560 - INFO - formatter /home/lausser/git/noteventificationforhandlerwarder/notificationforwarder/tests/pythonpath/local/lib/python/notificationforwarder/split3/formatter.py
3
+ 2023-10-10 18:33:50,197 3511560 - DEBUG - flush lock set
4
+ 2023-10-10 18:33:50,197 3511560 - INFO - forwarder /home/lausser/git/noteventificationforhandlerwarder/notificationforwarder/tests/pythonpath/local/lib/python/notificationforwarder/split3/forwarder.py
5
+ 2023-10-10 18:33:50,198 3511560 - INFO - forwarded description={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_omd_site={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_originating_host={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}_originating_fqdn={'description': 'halo i bims 1 alarm vong naemon her', 'omd_site': 'my_devel_site', 'originating_host': 'HULSE', 'originating_fqdn': 'HULSE.consol.lan'}