From 384a8d5422d6a1e6d5ab165206c1127442aeeb6c Mon Sep 17 00:00:00 2001 From: prash3r Date: Wed, 19 Apr 2023 17:33:18 +0200 Subject: [PATCH] moves CSV Sensor logging into core extension --- cbpi/controller/log_file_controller.py | 27 +--------- cbpi/extension/ConfigUpdate/__init__.py | 4 +- .../extension/SensorLogTarget_CSV/__init__.py | 52 +++++++++++++++++++ .../extension/SensorLogTarget_CSV/config.yaml | 3 ++ .../SensorLogTarget_InfluxDB/__init__.py | 1 + 5 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 cbpi/extension/SensorLogTarget_CSV/__init__.py create mode 100644 cbpi/extension/SensorLogTarget_CSV/config.yaml diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index b55c7eb..634f110 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -46,33 +46,14 @@ class LogController: for id, method in self.sensor_data_listeners.items(): asyncio.create_task(method(self.cbpi, id, value, formatted_time, name)) - def log_data(self, id: str, value: str) -> None: - # check which default log targets are enabled: - self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - # ^^ both legacy log targets should probably be implemented as a core plugin each unsing the hook instead - - # CSV target: - if self.logfiles == "Yes": - if id not in self.datalogger: - max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) - backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) - - data_logger = logging.getLogger('cbpi.sensor.%s' % id) - data_logger.propagate = False - data_logger.setLevel(logging.DEBUG) - handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) - data_logger.addHandler(handler) - self.datalogger[id] = data_logger - - self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) - + def log_data(self, id: str, value: str) -> None: # all plugin targets: if self.sensor_data_listeners: # true if there are listners try: sensor=self.cbpi.sensor.find_by_id(id) if sensor is not None: name = sensor.name.replace(" ", "_") + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) asyncio.create_task(self._call_sensor_data_listeners(id, value, formatted_time, name)) except Exception as e: logging.error("sensor logging listener exception: {}".format(e)) @@ -171,10 +152,6 @@ class LogController: def clear_log(self, name:str ) -> str: all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*")) - if name in self.datalogger: - self.datalogger[name].removeHandler(self.datalogger[name].handlers[0]) - del self.datalogger[name] - for f in all_filenames: try: os.remove(f) diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 0aab218..ebc7a85 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -205,7 +205,7 @@ class ConfigUpdate(CBPiExtension): if logfiles is None: logger.info("INIT CSV logfiles") try: - await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles", + await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles (enabling requires restart)", [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -215,7 +215,7 @@ class ConfigUpdate(CBPiExtension): if influxdb is None: logger.info("INIT Influxdb") try: - await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb", + await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb (enabling requires restart)", [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: diff --git a/cbpi/extension/SensorLogTarget_CSV/__init__.py b/cbpi/extension/SensorLogTarget_CSV/__init__.py new file mode 100644 index 0000000..6983021 --- /dev/null +++ b/cbpi/extension/SensorLogTarget_CSV/__init__.py @@ -0,0 +1,52 @@ + +# -*- coding: utf-8 -*- +import os +from logging.handlers import RotatingFileHandler +import logging +from unittest.mock import MagicMock, patch +import asyncio +import random +from cbpi.api import * +from cbpi.api.config import ConfigType +import urllib3 +import base64 + +logger = logging.getLogger(__name__) + +class SensorLogTargetCSV(CBPiExtension): + + def __init__(self, cbpi): # called from cbpi on start + self.cbpi = cbpi + self.datalogger = {} + self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") + if self.logfiles == "No": + return # never run() + self._task = asyncio.create_task(self.run()) # one time run() only + + + async def run(self): # called by __init__ once on start if CSV is enabled + self.listener_ID = self.cbpi.log.add_sensor_data_listener(self.log_data_to_CSV) + logger.info("CSV sensor log target listener ID: {}".format(self.listener_ID)) + + async def log_data_to_CSV(self, cbpi, id:str, value:str, formatted_time, name): # called by log_data() hook from the log file controller + self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") + if self.logfiles == "No": + # We intentionally do not unsubscribe the listener here because then we had no way of resubscribing him without a restart of cbpi + # as long as cbpi was STARTED with CSVLOGFILES set to Yes this function is still subscribed, so changes can be made on the fly. + # but after initially enabling this logging target a restart is required. + return + if id not in self.datalogger: + max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) + backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) + + data_logger = logging.getLogger('cbpi.sensor.%s' % id) + data_logger.propagate = False + data_logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) + data_logger.addHandler(handler) + self.datalogger[id] = data_logger + + self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) + +def setup(cbpi): + cbpi.plugin.register("SensorLogTargetCSV", SensorLogTargetCSV) diff --git a/cbpi/extension/SensorLogTarget_CSV/config.yaml b/cbpi/extension/SensorLogTarget_CSV/config.yaml new file mode 100644 index 0000000..d396a0c --- /dev/null +++ b/cbpi/extension/SensorLogTarget_CSV/config.yaml @@ -0,0 +1,3 @@ +name: SensorLogTargetCSV +version: 4 +active: true diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py index 2a35d67..44d68f9 100644 --- a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py +++ b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py @@ -39,6 +39,7 @@ class SensorLogTargetInfluxDB(CBPiExtension): if self.influxdb == "No": # We intentionally do not unsubscribe the listener here because then we had no way of resubscribing him without a restart of cbpi # as long as cbpi was STARTED with INFLUXDB set to Yes this function is still subscribed, so changes can be made on the fly. + # but after initially enabling this logging target a restart is required. return self.influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", "No") self.influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None)