From af8b58408feddadc44746f881cddddc475ea9a05 Mon Sep 17 00:00:00 2001 From: unl0ck Date: Tue, 12 Nov 2024 08:39:07 +0000 Subject: [PATCH] #42 fixed --- .bumpversion-dev.toml | 2 +- GridboxConnectorAddon-dev/CHANGELOG.md | 8 ++ .../Dockerfile.standalone | 8 +- .../GridboxConnector/__main__.py | 21 ++++- .../GridboxConnector/requirements.txt | 5 + .../GridboxConnector/telemetry.py | 92 +++++++++++++++++++ .../GridboxConnector/utils.py | 17 +++- GridboxConnectorAddon-dev/cloudSettings.json | 2 +- GridboxConnectorAddon-dev/config.yaml | 2 +- .../s6-overlay/s6-rc.d/gridboxconnector/run | 2 + .../rootfs/share/cloudSettings.json | 2 +- .../translations/de.yaml | 16 ++++ .../translations/en.yaml | 6 ++ .../GridboxConnector/utils.py | 4 +- .../GridboxConnector/utils.py | 4 +- 15 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 GridboxConnectorAddon-dev/GridboxConnector/telemetry.py create mode 100644 GridboxConnectorAddon-dev/translations/de.yaml diff --git a/.bumpversion-dev.toml b/.bumpversion-dev.toml index c143659..6364758 100644 --- a/.bumpversion-dev.toml +++ b/.bumpversion-dev.toml @@ -1,5 +1,5 @@ [tool.bumpversion] -current_version = "2.0.7" +current_version = "2.9.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" diff --git a/GridboxConnectorAddon-dev/CHANGELOG.md b/GridboxConnectorAddon-dev/CHANGELOG.md index bdf8d6e..6d22c47 100644 --- a/GridboxConnectorAddon-dev/CHANGELOG.md +++ b/GridboxConnectorAddon-dev/CHANGELOG.md @@ -1,7 +1,15 @@ +## 2.10.0 + +### 🚀 Added + +- Opentelemetry Class to collect exceptions + ## 2.9.0 +### 🔨 Fixed + - bump viessmann-connector to 1.5.1 ## 2.8.5 diff --git a/GridboxConnectorAddon-dev/Dockerfile.standalone b/GridboxConnectorAddon-dev/Dockerfile.standalone index 6c26add..85ab81d 100644 --- a/GridboxConnectorAddon-dev/Dockerfile.standalone +++ b/GridboxConnectorAddon-dev/Dockerfile.standalone @@ -1,5 +1,5 @@ # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-dockerfile -FROM python:3.10-alpine +FROM python:3.10-slim # Build arguments ARG BUILD_DATE @@ -17,9 +17,13 @@ LABEL \ org.opencontainers.image.revision=${BUILD_REF} \ org.opencontainers.image.version=${BUILD_VERSION} +RUN apt-get update && \ + apt-get install -y gcc g++ && \ + rm -rf /var/lib/apt/lists/* + # Copy root filesystem COPY GridboxConnectorAddon-edge/GridboxConnector/ /build/GridboxConnector RUN cd /build/GridboxConnector && pip install -r requirements.txt -ENTRYPOINT ["python3", "/build/GridboxConnector"] \ No newline at end of file +ENTRYPOINT ["python3", "/build/GridboxConnector"] diff --git a/GridboxConnectorAddon-dev/GridboxConnector/__main__.py b/GridboxConnectorAddon-dev/GridboxConnector/__main__.py index 4f8c43b..2e25b25 100644 --- a/GridboxConnectorAddon-dev/GridboxConnector/__main__.py +++ b/GridboxConnectorAddon-dev/GridboxConnector/__main__.py @@ -6,7 +6,8 @@ from ha_viessmann_gridbox_connector import HAViessmannGridboxConnector import logging from importlib.resources import files -from utils import SensitiveDataFilter +from utils import SensitiveDataFilter, get_bool_env +from telemetry import Telemetry opens_file_path = '/data/options.json' #logging.basicConfig(format='%(asctime)s %(filename)s:%(lineno)d %(levelname)s - %(message)s', level=logging.getLevelName(os.getenv('LOG_LEVEL', 'INFO'))) logger = logging.getLogger(__name__) @@ -24,6 +25,20 @@ def load_gridbox_config(): data = json.load(json_file) return data +def run_telemetry(): + enable_telemetry = get_bool_env('ENABLE_TELEMETRY', False) + telemetry = None + if enable_telemetry: + #otel_server = os.getenv('TelemetryServer', "https://otel.helming.xyz") + #if otel_server == "": + otel_server = "https://otel.helming.xyz" + telemetry = Telemetry(otel_server, "homeassistant-addon-viessmann-gridbox") + telemetry.log_as_span("Telemetry enabled", level=logger.level) + return telemetry + + + + def run_addon(): gridbox_config = load_gridbox_config() options_file = '' @@ -40,6 +55,7 @@ def run_addon(): mqtt_pw = os.getenv('MqttPw', "") mqtt_server = os.getenv('MqttServer', "") mqtt_port = os.getenv('MqttPort', "") + if not USER or not PASSWORD: logger.error("Username or Password not set") exit(1) @@ -53,6 +69,8 @@ def run_addon(): mqtt_settings = Settings.MQTT(host=mqtt_server, username=mqtt_user, password=mqtt_pw, port=mqtt_port) viessmann_gridbox_connector = HAViessmannGridboxConnector(mqtt_settings) gridboxConnector = GridboxConnector(gridbox_config) + + while True: measurement = gridboxConnector.retrieve_live_data() if len(measurement) > 0: @@ -68,5 +86,6 @@ def run_addon(): time.sleep(WAIT) if __name__ == '__main__': + telemetry = run_telemetry() run_addon() #run_test_log() \ No newline at end of file diff --git a/GridboxConnectorAddon-dev/GridboxConnector/requirements.txt b/GridboxConnectorAddon-dev/GridboxConnector/requirements.txt index 475df85..959dd06 100644 --- a/GridboxConnectorAddon-dev/GridboxConnector/requirements.txt +++ b/GridboxConnectorAddon-dev/GridboxConnector/requirements.txt @@ -1,3 +1,8 @@ ha-mqtt-discoverable==0.13.1 requests==2.32.2 viessmann-gridbox-connector==1.5.1 +opentelemetry-api +opentelemetry-sdk +opentelemetry-exporter-otlp-proto-grpc +opentelemetry-instrumentation-requests +opentelemetry-instrumentation-logging diff --git a/GridboxConnectorAddon-dev/GridboxConnector/telemetry.py b/GridboxConnectorAddon-dev/GridboxConnector/telemetry.py new file mode 100644 index 0000000..5e40e73 --- /dev/null +++ b/GridboxConnectorAddon-dev/GridboxConnector/telemetry.py @@ -0,0 +1,92 @@ +import logging +import requests +import uuid +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.instrumentation.requests import RequestsInstrumentor +from opentelemetry.instrumentation.logging import LoggingInstrumentor +from opentelemetry.sdk.resources import Resource +from opentelemetry.semconv.resource import ResourceAttributes + +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler + +class Telemetry: + def __init__(self, collector_endpoint, service_name): + self.service_instance_id = str(uuid.uuid4()) + self.resource = Resource(attributes={ + ResourceAttributes.SERVICE_NAME: service_name, + ResourceAttributes.SERVICE_INSTANCE_ID: self.service_instance_id + }) + + # Instrument the logging module + LoggingInstrumentor().instrument(set_logging_format=True, log_level=logging.DEBUG) + # Set up logging + logging.basicConfig(level=logging.DEBUG) + + # Set up the tracer provider and exporter + trace.set_tracer_provider(TracerProvider(resource=self.resource)) + self.tracer = trace.get_tracer(__name__) + + # Set up the OTLP exporter + otlp_exporter = OTLPSpanExporter(endpoint=collector_endpoint, insecure=True) + span_processor = BatchSpanProcessor(otlp_exporter) + trace.get_tracer_provider().add_span_processor(span_processor) + + # Instrument the requests library + RequestsInstrumentor().instrument() + + # Create and set the logger provider + logger_provider = LoggerProvider(resource=self.resource) + set_logger_provider(logger_provider) + + exporter = OTLPLogExporter(endpoint=collector_endpoint, insecure=True) + logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter)) + handler = LoggingHandler(level=logging.DEBUG, logger_provider=logger_provider) + + # Attach OTLP handler to root logger + logging.getLogger().addHandler(handler) + self.logger = logging.getLogger("telemetry") + + def log_as_span(self, message, level=logging.INFO): + with self.tracer.start_as_current_span("logging") as span: + if level == logging.DEBUG: + self.logger.debug(message) + elif level == logging.INFO: + self.logger.info(message) + elif level == logging.WARNING: + self.logger.warning(message) + elif level == logging.ERROR: + self.logger.error(message) + elif level == logging.CRITICAL: + self.logger.critical(message) + span.add_event("log_event", {"message": message, "level": level}) + + def make_request(self, url): + try: + with self.tracer.start_as_current_span("http_request") as span: + response = requests.get(url) + self.logger.info(f"Received response: {response.status_code}") + span.add_event("Received response", {"status_code": response.status_code, "body": response.text}) + return response + except Exception as e: + self.logger.exception(f"Exception during request: {e}") + raise + +def main(): + collector_endpoint = "https://otel.helming.xyz" + service_name = "test_telemetry" + telemetry = Telemetry(collector_endpoint, service_name) + telemetry.log_as_span("This is a test log message", level=logging.INFO) + try: + response = telemetry.make_request("https://httpbin.org/get") + telemetry.log_as_span(f"Request successful with status code: {response.status_code}", level=logging.INFO) + except Exception as e: + telemetry.log_as_span(f"Request failed with exception: {e}", level=logging.ERROR) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/GridboxConnectorAddon-dev/GridboxConnector/utils.py b/GridboxConnectorAddon-dev/GridboxConnector/utils.py index b42b25b..4349377 100644 --- a/GridboxConnectorAddon-dev/GridboxConnector/utils.py +++ b/GridboxConnectorAddon-dev/GridboxConnector/utils.py @@ -1,6 +1,7 @@ import json import logging import ast +import os class SensitiveDataFilter(logging.Filter): def filter(self, record): message = record.getMessage() @@ -19,8 +20,18 @@ def filter(self, record): literal_msg['client_id'] = '***' # Das modifizierte Dictionary zurück in einen String konvertieren record.msg = json.dumps(literal_msg) - except json.JSONDecodeError: + except Exception as e: # Wenn die Nachricht kein JSON ist, nichts tun - logging.error('Could not parse message as JSON') + logging.error(f"Error filtering sensitive data: {e}") pass - return True \ No newline at end of file + return True + +def get_bool_env(var, default=False): + value = os.getenv(var, default) + if isinstance(value, str): + value = value.lower() + if value in ["1", "true"]: + return True + if value in ["0", "false"]: + return False + return bool(value) \ No newline at end of file diff --git a/GridboxConnectorAddon-dev/cloudSettings.json b/GridboxConnectorAddon-dev/cloudSettings.json index e533cd8..d8cfe68 100644 --- a/GridboxConnectorAddon-dev/cloudSettings.json +++ b/GridboxConnectorAddon-dev/cloudSettings.json @@ -1,5 +1,5 @@ { - "version": "2.9.0", + "version": "2.10.1", "urls": { "login": "https://gridx.eu.auth0.com/oauth/token", "gateways": "https://api.gridx.de/gateways", diff --git a/GridboxConnectorAddon-dev/config.yaml b/GridboxConnectorAddon-dev/config.yaml index 7caf9b2..ba4b73f 100644 --- a/GridboxConnectorAddon-dev/config.yaml +++ b/GridboxConnectorAddon-dev/config.yaml @@ -1,6 +1,6 @@ # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config name: Viessmann Gridbox Connector (dev) -version: "2.9.0" +version: "2.10.1" slug: "gridbox_connector_dev" description: Development version of Viessmann Gridbox Connector url: "https://github.com/unl0ck/homeassistant-addon-viessmann-gridbox/tree/main/GridboxConnectorAddon-dev" diff --git a/GridboxConnectorAddon-dev/rootfs/etc/s6-overlay/s6-rc.d/gridboxconnector/run b/GridboxConnectorAddon-dev/rootfs/etc/s6-overlay/s6-rc.d/gridboxconnector/run index 99d50e3..dda3958 100755 --- a/GridboxConnectorAddon-dev/rootfs/etc/s6-overlay/s6-rc.d/gridboxconnector/run +++ b/GridboxConnectorAddon-dev/rootfs/etc/s6-overlay/s6-rc.d/gridboxconnector/run @@ -11,6 +11,7 @@ export MqttUser=$(bashio::config 'OverrideMqttUser') export MqttPw=$(bashio::config 'OverrideMqttPw') export MqttServer=$(bashio::config 'OverrideMqttServer') export MqttPort=$(bashio::config 'OverrideMqttPort') +export TelemetryServer=$(bashio::config 'OverrideTelemetryUrl') test "$MqttUser" = "null" && export MqttUser=$(bashio::services "mqtt" "username") test "$MqttPw" = "null" && export MqttPw=$(bashio::services "mqtt" "password") @@ -19,6 +20,7 @@ test "$MqttPort" = "null" && export MqttPort=$(bashio::services "mqtt" "port") export USERNAME=$(bashio::config 'username') export PASSWORD=$(bashio::config 'password') export LOG_LEVEL=$(bashio::config 'log_level') +export ENABLE_TELEMETRY=$(bashio::config 'enable_telemetry') ls -lash /data cd /build/ ls -lash diff --git a/GridboxConnectorAddon-dev/rootfs/share/cloudSettings.json b/GridboxConnectorAddon-dev/rootfs/share/cloudSettings.json index e533cd8..d8cfe68 100644 --- a/GridboxConnectorAddon-dev/rootfs/share/cloudSettings.json +++ b/GridboxConnectorAddon-dev/rootfs/share/cloudSettings.json @@ -1,5 +1,5 @@ { - "version": "2.9.0", + "version": "2.10.1", "urls": { "login": "https://gridx.eu.auth0.com/oauth/token", "gateways": "https://api.gridx.de/gateways", diff --git a/GridboxConnectorAddon-dev/translations/de.yaml b/GridboxConnectorAddon-dev/translations/de.yaml new file mode 100644 index 0000000..1729950 --- /dev/null +++ b/GridboxConnectorAddon-dev/translations/de.yaml @@ -0,0 +1,16 @@ +configuration: + username: + name: Username + description: gib hier deine E-Mail-Adresse ein + password: + name: Password + description: gib hier dein Passwort ein + wait_time: + name: Wait + description: Setze die Zeit in Sekunden, wie lange gewartet wird, bis neue Daten abgerufen werden + log_level: + name: Log Level + description: Setze das Log-Level + enable_telemetry: + name: Enable Telemetry + description: Aktiviere das Senden von Telemetriedaten an den Entwickler für Debugging oder Ausnahmehandhabung diff --git a/GridboxConnectorAddon-dev/translations/en.yaml b/GridboxConnectorAddon-dev/translations/en.yaml index c0b41d0..483b491 100644 --- a/GridboxConnectorAddon-dev/translations/en.yaml +++ b/GridboxConnectorAddon-dev/translations/en.yaml @@ -8,3 +8,9 @@ configuration: wait_time: name: Wait description: Set time in seconds how long will wait until fetch new data + log_level: + name: Log Level + description: Set the log level + enable_telemetry: + name: Enable Telemetry + description: Enable sending telemetry data to the developer for debugging or exception handling diff --git a/GridboxConnectorAddon-edge/GridboxConnector/utils.py b/GridboxConnectorAddon-edge/GridboxConnector/utils.py index fe22800..4349377 100644 --- a/GridboxConnectorAddon-edge/GridboxConnector/utils.py +++ b/GridboxConnectorAddon-edge/GridboxConnector/utils.py @@ -20,9 +20,9 @@ def filter(self, record): literal_msg['client_id'] = '***' # Das modifizierte Dictionary zurück in einen String konvertieren record.msg = json.dumps(literal_msg) - except json.JSONDecodeError: + except Exception as e: # Wenn die Nachricht kein JSON ist, nichts tun - logging.error('Could not parse message as JSON') + logging.error(f"Error filtering sensitive data: {e}") pass return True diff --git a/GridboxConnectorAddon/GridboxConnector/utils.py b/GridboxConnectorAddon/GridboxConnector/utils.py index b42b25b..b4e6c1f 100644 --- a/GridboxConnectorAddon/GridboxConnector/utils.py +++ b/GridboxConnectorAddon/GridboxConnector/utils.py @@ -19,8 +19,8 @@ def filter(self, record): literal_msg['client_id'] = '***' # Das modifizierte Dictionary zurück in einen String konvertieren record.msg = json.dumps(literal_msg) - except json.JSONDecodeError: + except Exception as e: # Wenn die Nachricht kein JSON ist, nichts tun - logging.error('Could not parse message as JSON') + logging.error(f"Error filtering sensitive data: {e}") pass return True \ No newline at end of file