From 5befa504db74e7ad6e41a17c04b7a48b124c8cef Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Wed, 13 Oct 2021 13:43:57 +0200
Subject: [PATCH] Allow logs to also be send to a logstash server (f.e. the
 LCU)

---
 logconfig.py     | 54 ++++++++++++++++++++++++++++++++++++++++++++++++
 pypcc2.py        | 16 ++++++++------
 requirements.txt |  1 +
 3 files changed, 65 insertions(+), 6 deletions(-)
 create mode 100644 logconfig.py

diff --git a/logconfig.py b/logconfig.py
new file mode 100644
index 0000000..b0d7182
--- /dev/null
+++ b/logconfig.py
@@ -0,0 +1,54 @@
+import logging
+
+logger = logging.getLogger()
+
+def configure_logger(logstash_host: str = "", level = logging.DEBUG, log_extra: dict = None):
+    """
+      Configure the output of Python's logging module.
+
+      logstash_host: Logstash server to forward a copy of the logs to (f.e. the LCU).
+      level:         Configure to use this log level (can be a string, or integer)
+      log_extra:     Extra dict to annotate log lines with, or None.
+    """
+
+    # By default we want to know everything
+    if isinstance(level, str):
+        level_nr = getattr(logging, level.upper(), None)
+        if not isinstance(level_nr, int):
+            raise ValueError('Invalid log level: %s' % level)
+
+        logger.setLevel(level_nr)
+    else:
+        logger.setLevel(level)
+
+    # remove spam from various libraries
+    logging.getLogger("asyncua").setLevel(logging.WARN)
+    logging.getLogger("opcua").setLevel(logging.WARN)
+    logging.getLogger("git").setLevel(logging.WARN)
+
+    # log to stderr, in a way that it can be understood by a human reader
+    handler = logging.StreamHandler()
+    formatter = logging.Formatter(fmt = '%(asctime)s [%(levelname)-8s,%(filename)-20s:%(lineno)-3d] %(message)s', datefmt = '%Y-%m-%dT%H:%M:%S')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+
+    # Log to ELK stack
+    if logstash_host:
+        try:
+            from logstash_async.handler import AsynchronousLogstashHandler, LogstashFormatter
+
+            logger.info(f"Sending logs to Logstash at {logstash_host}")
+
+            # log to the tcp_input of logstash, cache pending messages if we cannot reach the host
+            handler = AsynchronousLogstashHandler(logstash_host, 5959, database_path='pending_log_messages.db')
+
+            # configure log messages
+            formatter = LogstashFormatter(extra=log_extra, tags=["python", "lofar", "pypcc"])
+            handler.setFormatter(formatter)
+
+            # install the handler
+            logger.addHandler(handler)
+        except ImportError:
+            logger.exception("Cannot send logs to Logstash: logstash_async module not found.")
+        except Exception:
+            logger.exception(f"Cannot send logs to Logstash at {logstash_host}")
diff --git a/pypcc2.py b/pypcc2.py
index 0fb7bef..3bf0333 100644
--- a/pypcc2.py
+++ b/pypcc2.py
@@ -16,15 +16,19 @@ parser.add_argument("-t", "--test", help="Do not start OPC-UA server.", action="
 parser.add_argument("-p", "--port", help="Port number to listen on [%(default)s].", type=int, default=4842)
 parser.add_argument("-l", "--loglevel", help="Log level [%(default)s].", type=str, choices=["DEBUG","INFO","WARNING","ERROR"], default="INFO")
 parser.add_argument("-c", "--config", help="YAML config files, comma seperated [%(default)s]",type=str, default='RCU')
+parser.add_argument("--loghost", help="Logstash host to which to forward logs [%(default)s]",type=str, default='')
 args = parser.parse_args()
 
-# set log level
-loglevel_nr = getattr(logging, args.loglevel.upper(), None)
-if not isinstance(loglevel_nr, int):
-    raise ValueError('Invalid log level: %s' % args.loglevel)
-#logging.basicConfig(level=loglevel_nr, format="%(asctime)s [%(levelname)8s] %(message)s")
+from logconfig import configure_logger
+log_extra = {
+    "simulator": args.simulator, 
+    "test": args.test,
+    "port": args.port,
+    "config": args.config,
+    "lofar_id": f"pypcc - {args.config}",
+}
+configure_logger(logstash_host=args.loghost,level=args.loglevel, log_extra=log_extra)
 
-logging.basicConfig(level=loglevel_nr,format='%(asctime)s [%(levelname)-8s,%(filename)-20s:%(lineno)-3d] %(message)s')
 RunTimer=True;
 def signal_handler(sig, frame):
     logging.warn('Stop signal received!')
diff --git a/requirements.txt b/requirements.txt
index 4a48e3d..01abf7a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ opcua
 numpy
 recordclass
 pyyaml
+python-logstash-async
-- 
GitLab