Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log rules improvements #686

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 26 additions & 23 deletions doc/user/source/referenz/items/standard_attribute/log_change.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ vor dem Schreiben eines jeden Logging-Eintrags neu evaluiert.
Attribut *log_text*
===================

Das Attribut **log_text** ermöglicht es einen eigenen Text für den Logeintrag festzulegen. **log_text** kann dabei
eine Reihe von Variablen und eval-Ausdrücken enthalten.
Das Attribut **log_text** ermöglicht es, einen eigenen Text für den Logeintrag festzulegen.
Wird das Attribut nicht angegeben, wird versucht, das im smarthome.yaml hinterlegte Attribut ``default_logtext``
heranzuziehen. Dieses kann die unten angegebenen Variablen enthalten. Ist auch dieses
Attribut nicht angegeben, wird folgende Muster herangezogen:
``Item Change: {id} = {value} - caller: {caller}{source}{destination}``

Wird **log_text** angegeben, kann dabei eine Reihe von Variablen und eval-Ausdrücken genutzt werden.


.. attention::
Expand Down Expand Up @@ -130,40 +135,38 @@ Attribut *log_mapping*
======================

Über das **log_mapping** Attribut kann festgelegt werden, auf welche Werte/Strings der Wert eines Items für das
Logging gemappt werden soll. Das Attribut **log_mapping** enthält dazu in einem String die Beschreibung eines
dicts. Wobei der Key den zu übersetzenden/mappenden Wert angibt und der dazu gehörige Value des dicts den String
angibt, der über die Variable ``{mvalue}`` ausgegeben wird.
Logging gemappt werden soll. Das Attribut **log_mapping** enthält dazu eine Liste mit Wertzuweisungen im folgenden Format:
zu übersetzender/mappender Wert: String, der über die Variable ``{mvalue}`` ausgegeben wird

**Beispiel:**

.. code-block:: yaml

log_mapping: "{
'1': 'Eins',
'2': 'Zwei',
'3': 'Drei'
}"
log_mapping:
- 1: 'Eins'
- 2: 'Zwei'
- 3: 'Drei'


Attribut *log_rules*
====================

Über das **log_rules** Attribut kann festgelegt werden, welche zusätzliche Regeln für das Erzeugen des Log-Eintrages
anzuwenden sind. Das Attribut **log_rules** enthält dazu in einem String die Beschreibung eines dicts.
anzuwenden sind. Das Attribut **log_rules** enthält dazu eine Liste mit den folgenden möglichen Definitionen:
``lowlimit``, ``highlimit``, ``filter``, ``exclude``, ``itemvalue``

**Beispiel:**

.. code-block:: yaml

item:
type: num
log_rules: "{
'lowlimit' : -1.0,
'highlimit': 10.0,
'filter': [1, 2, 5],
'exclude': '.exclude_values',
'itemvalue': '.text'
}"
log_rules:
- 'lowlimit' : -1.0
- 'highlimit': 10.0
- 'filter': [1, 2, 5]
- 'exclude': '.exclude_values'
- 'itemvalue': '.text'

exclude_values:
type: list
Expand All @@ -186,7 +189,7 @@ highlimit weitere Werte zulassen würden bzw. exclude einen der Werte ausschlie
lowlimit
--------

``lowlimit`` Ein Wert, der angibt, unterhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll.
Ein Wert, der angibt, unterhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll.
Werte werden geschrieben, Wenn **lowlimit** <= **value** ist.

**low_limit** kann nur auf Items vom Typ **num** angewendet werden.
Expand All @@ -195,7 +198,7 @@ Werte werden geschrieben, Wenn **lowlimit** <= **value** ist.
highlimit
---------

``highlimit`` Ein Wert, der angibt, oberhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll.
Ein Wert, der angibt, oberhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll.
Werte werden geschrieben, Wenn **value** < **highlimit** ist.

**highlimit** kann nur auf Items vom Typ **num** angewendet werden.
Expand All @@ -204,7 +207,7 @@ Werte werden geschrieben, Wenn **value** < **highlimit** ist.
filter
------

``filter`` Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag geschrieben werden soll.
Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag geschrieben werden soll.

Wenn das Item vom Typ **num** ist, muss die Liste auch numerische Werte (int oder float) enthalten
(``'filter': [1, 2, 5, 2.1]``). Falls das Item von einem anderen Datentyp ist, muss die Liste Strings
Expand All @@ -214,7 +217,7 @@ enthalten (``'filter': ['1', '2', '5']``).
exclude
-------

``exclude`` Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag nicht geschrieben werden soll.
Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag nicht geschrieben werden soll.

Wenn das Item vom Typ **num** ist, muss die Liste auch numerische Werte (int oder float) enthalten
(``'exclude': [1, 2, 5, 2.1]``). Falls das Item von einem anderen Datentyp ist, muss die Liste Strings
Expand All @@ -224,5 +227,5 @@ enthalten (``'exclude': ['1', '2', '5']``).
itemvalue
---------

``itemvalue`` Der absolute oder relative Pfad zu einem Item, dessen Wert ausgelesen werden soll.
Der absolute oder relative Pfad zu einem Item, dessen Wert ausgelesen werden soll.
Dies kann beispielsweise dazu genutzt werden, die Lognachricht zur Laufzeit anzupassen.
3 changes: 1 addition & 2 deletions etc/smarthome.yaml.default
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ elev: 30 # elevation
tz: 'Europe/Berlin' # timezone, the example will be fine for most parts of central Europe

default_language: de # default_language is used, where multiple languages are supported (de, if not specified)

#default_logtext: "Item {id} wurde geändert."

# control type casting when assiging values to items
# latest or compat_1.2 (compat_1.2 is default for shNG v1.3, latest is default for higher versions)
Expand Down Expand Up @@ -62,4 +62,3 @@ default_language: de # default_language is used, where multiple languages ar
#db:
# - sqlite:sqlite3
# - mysql:pymysql

14 changes: 9 additions & 5 deletions lib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@
KEY_ON_UPDATE = 'on_update'
KEY_ON_CHANGE = 'on_change'

KEY_LOG_CHANGE = 'log_change'
KEY_LOG_LEVEL = 'log_level'
KEY_LOG_TEXT = 'log_text'
KEY_LOG_CHANGE = 'log_change'
KEY_LOG_LEVEL = 'log_level'
KEY_LOG_TEXT = 'log_text'
KEY_LOG_MAPPING = 'log_mapping'
KEY_LOG_RULES = 'log_rules'

KEY_LOG_RULES = 'log_rules'
KEY_LOG_RULES_LOWLIMIT = 'lowlimit'
KEY_LOG_RULES_HIGHLIMIT = 'highlimit'
KEY_LOG_RULES_FILTER = 'filter'
KEY_LOG_RULES_EXCLUDE = 'exclude'
KEY_LOG_RULES_ITEMVALUE = 'itemvalue'
KEY_HYSTERESIS_INPUT = 'hysteresis_input'
KEY_HYSTERESIS_UPPER_THRESHOLD = 'hysteresis_upper_threshold'
KEY_HYSTERESIS_LOWER_THRESHOLD = 'hysteresis_lower_threshold'
Expand Down
73 changes: 48 additions & 25 deletions lib/item/item.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@
KEY_EVAL, KEY_EVAL_TRIGGER, KEY_TRIGGER, KEY_CONDITION, KEY_NAME, KEY_DESCRIPTION, KEY_TYPE,
KEY_STRUCT, KEY_REMARK, KEY_INSTANCE, KEY_VALUE, KEY_INITVALUE, PLUGIN_PARSE_ITEM,
KEY_AUTOTIMER, KEY_ON_UPDATE, KEY_ON_CHANGE, KEY_LOG_CHANGE, KEY_LOG_LEVEL, KEY_LOG_TEXT,
KEY_LOG_MAPPING, KEY_LOG_RULES, KEY_THRESHOLD, KEY_EVAL_TRIGGER_ONLY,
KEY_ATTRIB_COMPAT, ATTRIB_COMPAT_V12, ATTRIB_COMPAT_LATEST, PLUGIN_REMOVE_ITEM,
KEY_HYSTERESIS_INPUT, KEY_HYSTERESIS_UPPER_THRESHOLD, KEY_HYSTERESIS_LOWER_THRESHOLD,
ATTRIBUTE_SEPARATOR)
KEY_LOG_MAPPING, KEY_LOG_RULES, KEY_LOG_RULES_LOWLIMIT, KEY_LOG_RULES_HIGHLIMIT,
KEY_LOG_RULES_FILTER, KEY_LOG_RULES_EXCLUDE, KEY_LOG_RULES_ITEMVALUE, KEY_THRESHOLD,
KEY_EVAL_TRIGGER_ONLY, KEY_ATTRIB_COMPAT, ATTRIB_COMPAT_V12, ATTRIB_COMPAT_LATEST,
PLUGIN_REMOVE_ITEM, KEY_HYSTERESIS_INPUT, KEY_HYSTERESIS_UPPER_THRESHOLD,
KEY_HYSTERESIS_LOWER_THRESHOLD, ATTRIBUTE_SEPARATOR)


from lib.utils import Utils
Expand Down Expand Up @@ -260,7 +261,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None):
self._eval_unexpanded = ''
self._eval_trigger = False
self._eval_on_trigger_only = False
self._trigger = False
self._trigger = None
self._trigger_unexpanded = []
self._trigger_condition_raw = []
self._trigger_condition = None
Expand Down Expand Up @@ -293,6 +294,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None):
self._log_level_name = None
self._log_mapping = {}
self._log_rules = {}
self._log_rules_cache = {}
self._log_text = None
self._fading = False
self._items_to_trigger = []
Expand Down Expand Up @@ -379,6 +381,8 @@ def __init__(self, smarthome, parent, path, config, items_instance=None):
#############################################################
for attr, value in config.items():
if not isinstance(value, dict):
log_rules_keys = [KEY_LOG_RULES_LOWLIMIT, KEY_LOG_RULES_HIGHLIMIT, KEY_LOG_RULES_EXCLUDE,
KEY_LOG_RULES_FILTER, KEY_LOG_RULES_ITEMVALUE]
if attr in [KEY_NAME, KEY_DESCRIPTION, KEY_TYPE, KEY_STRUCT, KEY_VALUE, KEY_INITVALUE, KEY_EVAL_TRIGGER_ONLY]:
if attr == KEY_INITVALUE:
attr = KEY_VALUE
Expand Down Expand Up @@ -440,14 +444,32 @@ def __init__(self, smarthome, parent, path, config, items_instance=None):
setattr(self, '_log_level_name', 'INFO')
setattr(self, '_log_level', logging.getLevelName('INFO'))
elif attr in [KEY_LOG_MAPPING]:
if value != '':
if isinstance(value, list):
try:
value_dict = {k: v for od in value for k, v in od.items()}
setattr(self, '_log_mapping', value_dict)
except Exception as e:
logger.warning(f"Item {self._path}: Invalid list data for attribute '{KEY_LOG_MAPPING}': {value} - Exception: {e}")
elif value != '':
try:
value_dict = ast.literal_eval(value)
setattr(self, '_log_mapping', value_dict)
except Exception as e:
logger.warning(f"Item {self._path}: Invalid data for attribute '{KEY_LOG_MAPPING}': {value} - Exception: {e}")
elif attr in [KEY_LOG_RULES]:
if value != '':
if isinstance(value, list):
try:
value_dict = {}
for od in value:
for k, v in od.items():
if k in log_rules_keys:
value_dict[k] = v
else:
logger.warning(f"Item {self._path}: Ignoring '{k}' as it is not a valid log rule")
setattr(self, '_log_rules', value_dict)
except Exception as e:
logger.warning(f"Item {self._path}: Invalid list data for attribute '{KEY_LOG_RULES}': {value} - Exception: {e}")
elif value != '':
try:
value_dict = ast.literal_eval(value)
setattr(self, '_log_rules', value_dict)
Expand Down Expand Up @@ -2156,7 +2178,9 @@ def __run_on_change(self, value=None, caller=None, source=None, dest=None):


def _log_build_standardtext(self, value, caller, source=None, dest=None):

if self._sh.get_defaultlogtext() is not None:
self._log_text = self._sh.get_defaultlogtext().replace("'", '"')
return self._log_build_text(value, caller, source, dest)
log_src = ''
if source is not None:
log_src += ' (' + source + ')'
Expand Down Expand Up @@ -2185,11 +2209,10 @@ def _log_build_text(self, value, caller, source=None, dest=None):
pname = self.__parent._name
pid = self.__parent._path
mvalue = self._log_mapping.get(value, value)
lowlimit = self._get_rule('lowlimit')
highlimit = self._get_rule('highlimit')
filter = self._get_rule('filter')
exclude = self._get_rule('exclude')

lowlimit = self._log_rules_cache.get('lowlimit')
highlimit = self._log_rules_cache.get('highlimit')
filter = self._log_rules_cache.get('filter')
exclude = self._log_rules_cache.get('exclude')
sh = self._sh
shtime = self.shtime
time = shtime.now().strftime("%H:%M:%S")
Expand All @@ -2201,7 +2224,7 @@ def _log_build_text(self, value, caller, source=None, dest=None):
try:
entry = self._log_rules.get('itemvalue', None)
if entry is not None:
item = self.get_absolutepath(entry.strip().replace("sh.", ""), "log_rules")
item = self.get_absolutepath(entry.strip().replace("sh.", ""), KEY_LOG_CHANGE)
itemvalue = str(_items_instance.return_item(item).property.value)
else:
itemvalue = None
Expand All @@ -2213,7 +2236,7 @@ def _log_build_text(self, value, caller, source=None, dest=None):
import math
import lib.userfunctions as uf
env = lib.env

self._log_text = self._log_text.replace("'", '"')
try:
#logger.warning(f"self._log_text: {self._log_text}, type={type(self._log_text)}")
txt = eval(f"f'{self._log_text}'")
Expand All @@ -2229,14 +2252,15 @@ def convert_entry(entry, to):
if isinstance(returnvalue, str) and to != "str":
try:
# try to get value from item
item = self.get_absolutepath(entry.strip().replace("sh.", ""), "log_rules")
item = self.get_absolutepath(entry.strip().replace("sh.", ""), KEY_LOG_CHANGE)
returnvalue = _items_instance.return_item(item).property.value
except Exception as e:
pass
except Exception:
if to == "list":
returnvalue = [entry]
if isinstance(returnvalue, (str, int)) and to == "num":
try:
returnvalue = float(returnvalue)
except ValueError as e:
except ValueError:
returnvalue = None
elif isinstance(entry, list):
entry = [convert_entry(val, self._type) for val in entry]
Expand All @@ -2259,21 +2283,21 @@ def _log_on_change(self, value, caller, source=None, dest=None):
Write log, if Item has attribute log_change set
:return:
"""

if self._log_change_logger is not None:
filter_list = self._get_rule('filter')
exclude_list = self._get_rule('exclude')
low_limit = self._get_rule('lowlimit')
high_limit = self._get_rule('highlimit')
self._log_rules_cache = {'filter': filter_list, 'exclude': exclude_list, 'lowlimit': low_limit, 'highlimit': high_limit}
if filter_list != [] and exclude_list != []:
logger.warning(f"Defining filter AND exclude does not work. "
logger.warning(f"Item {self._path}: Defining filter AND exclude does not work. "
f"Ignoring exclude list {exclude_list} "
f"Using filter: {filter_list}")

if self._type == 'num':
low_limit = self._get_rule('lowlimit')
if low_limit is not None:
if low_limit > float(value):
return
high_limit = self._get_rule('highlimit')
if high_limit is not None:
if high_limit <= float(value):
return
Expand All @@ -2290,7 +2314,6 @@ def _log_on_change(self, value, caller, source=None, dest=None):
elif exclude_list != []:
if value in exclude_list:
return

if self._log_text is None:
txt = self._log_build_standardtext(value, caller, source, dest)
else:
Expand All @@ -2308,7 +2331,7 @@ def _log_on_change(self, value, caller, source=None, dest=None):
log_level = eval(f"f'{val}'")
except Exception as e:
log_level = self._log_level_attrib
logger.error(f"{id}: Invalid log_level template '{log_level}' - (Exception: {e})")
logger.error(f"Item {self._path}: Invalid log_level template '{log_level}' - (Exception: {e})")
level = log_level.upper()
level_name = level
if Utils.is_int(level):
Expand Down
10 changes: 5 additions & 5 deletions lib/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,15 +556,15 @@ def get_files_to_delete(self):
def custom_replace(match):
# Replace based on different patterns
if any(match.group(i) for i in (1, 2, 3)):
return '\d{4}'
return r'\d{4}'
elif any(match.group(i) for i in (4, 7, 10, 13)):
return '\d{1,2}'
return r'\d{1,2}'
elif any(match.group(i) for i in (6, 9, 12, 15)):
return '\d{2}'
return r'\d{2}'
elif any(match.group(i) for i in (5, 8, 11, 14)):
return '\d{1}'
return r'\d{1}'
elif match.group(16):
return '\d+'
return r'\d+'
else:
return '[0-9.]'

Expand Down
16 changes: 13 additions & 3 deletions lib/smarthome.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class SmartHome():
_default_language = 'de'
_fallback_language_order = 'en,de'
_threadinfo_export = False
_default_logtext = None

# for scheduler
_restart_on_num_workers = 30
Expand Down Expand Up @@ -307,7 +308,6 @@ def __init__(self, MODE, extern_conf_dir='', config_etc=False):
# setup logging
logsetup = self.init_logging(self._log_conf_basename, MODE)


#############################################################
# get shng version information
# shngversion.get_plugins_version() may only be called after logging is initialized
Expand Down Expand Up @@ -495,14 +495,24 @@ def get_defaultlanguage(self):
"""
return self._default_language


def set_defaultlanguage(self, language):
"""
Returns the configured default language of SmartHomeNG
Sets the default language of SmartHomeNG
"""
self._default_language = language
lib.translation.set_default_language(language)

def get_defaultlogtext(self):
"""
Returns the configured default logtext for log_change of SmartHomeNG
"""
return self._default_logtext

def set_defaultlogtext(self, log_text):
"""
Sets the default logtext for log_change of SmartHomeNG
"""
self._default_logtext = log_text

def get_basedir(self):
"""
Expand Down
Loading
Loading