Selection for module added

This commit is contained in:
Dirk Alders 2025-07-24 22:41:25 +02:00
parent 4d937f14b3
commit 88b826414a
3 changed files with 101 additions and 24 deletions

2
loggy
View File

@ -2,5 +2,5 @@
# #
BASEPATH=$(dirname $0) BASEPATH=$(dirname $0)
$BASEPATH/venv/bin/python $BASEPATH/loggy.py $BASEPATH/venv/bin/python $BASEPATH/loggy.py $*

119
loggy.py
View File

@ -1,5 +1,6 @@
import logging import logging
import pickle import pickle
import re
import socket import socket
import struct import struct
import threading import threading
@ -7,7 +8,7 @@ from datetime import datetime
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Vertical from textual.containers import Vertical
from textual.widgets import Footer, Header, Input, RichLog, Checkbox from textual.widgets import Footer, Header, Input, RichLog, Checkbox, Select
# Mapping von Log-Level-Namen zu Farben für die Anzeige # Mapping von Log-Level-Namen zu Farben für die Anzeige
LEVEL_STYLES = { LEVEL_STYLES = {
@ -55,6 +56,68 @@ class LogReceiver(threading.Thread):
logging.error(f"LogReceiver-Fehler: {e}", exc_info=True) logging.error(f"LogReceiver-Fehler: {e}", exc_info=True)
class OptionSelectList(dict):
def __init__(self):
super().__init__(self)
#
self.__default_value__ = True
self.__selection_regex__ = ""
def __sorted_keys__(self):
rv = list(self.keys())
rv.sort()
return rv
def SetSelectionRegEx(self, regex: str) -> None:
self.__selection_regex__ = regex
def AddEntry(self, entry) -> None:
if entry not in self:
self[entry] = self.__default_value__
def Toggle(self, entry_num):
if entry_num < 0:
self.__default_value__ = entry_num == -1
for key in self:
self[key] = self.__default_value__
elif entry_num > len(self):
raise ValueError(f"The Entry '{entry} is not in the list")
else:
entry = self.__sorted_keys__()[entry_num]
self[entry] = not self[entry]
def GetSelectList(self):
rv = []
if len(self) > 2:
rv.append(('All', -1))
rv.append(('None', -2))
for index, key in enumerate(self.__sorted_keys__()):
try:
match = len(re.findall(self.__selection_regex__, key)) > 0
except re.error:
match = True # No valid regular expression
if match:
prefix = "\\[X] " if self[key] else "\\[-] "
rv.append((prefix + key, index))
return rv
def IsSelected(self, entry):
return self.get(entry, False)
class MySelect(Select):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__new_options__ = []
def set_options(self, options):
self.__new_options__ = options
def _on_enter(self, event):
super().set_options(self.__new_options__)
return super()._on_enter(event)
class LogViewerApp(App): class LogViewerApp(App):
"""Eine Textual-App zum Anzeigen und Filtern von Python-Logs.""" """Eine Textual-App zum Anzeigen und Filtern von Python-Logs."""
@ -68,8 +131,10 @@ class LogViewerApp(App):
super().__init__() super().__init__()
# #
self.all_logs = [] self.all_logs = []
self.module_filter = "" self.__module_select_list__ = OptionSelectList()
self.level_filter = "" self.__module_selection__ = MySelect((), prompt="Module", id="module_filter")
self.__level_select_list__ = OptionSelectList()
self.__level_selection__ = MySelect((), prompt="Level", id="level_filter")
self.force_critical = True self.force_critical = True
self.force_error = True self.force_error = True
self.force_warning = False self.force_warning = False
@ -81,8 +146,9 @@ class LogViewerApp(App):
with Vertical(id="app-grid"): with Vertical(id="app-grid"):
yield self.log_display yield self.log_display
with Vertical(id="bottom-bar"): with Vertical(id="bottom-bar"):
yield Input(placeholder="module filter...", id="module_filter") yield self.__module_selection__
yield Input(placeholder="level filter...", id="level_filter") yield Input(placeholder="Filter", id="select_filter")
yield self.__level_selection__
yield Checkbox("CRITICAL", self.force_critical, id="force_critical") yield Checkbox("CRITICAL", self.force_critical, id="force_critical")
yield Checkbox("ERROR", self.force_error, id="force_error") yield Checkbox("ERROR", self.force_error, id="force_error")
yield Checkbox("WARNING", self.force_warning, id="force_warning") yield Checkbox("WARNING", self.force_warning, id="force_warning")
@ -95,6 +161,10 @@ class LogViewerApp(App):
def add_log(self, record: logging.LogRecord) -> None: def add_log(self, record: logging.LogRecord) -> None:
"""Fügt einen neuen Log-Eintrag hinzu und aktualisiert die Anzeige.""" """Fügt einen neuen Log-Eintrag hinzu und aktualisiert die Anzeige."""
self.__module_select_list__.AddEntry(record.name)
self.__module_selection__.set_options(self.__module_select_list__.GetSelectList())
self.__level_select_list__.AddEntry(record.levelname)
self.__level_selection__.set_options(self.__level_select_list__.GetSelectList())
self.all_logs.append(record) self.all_logs.append(record)
self._apply_filters_to_log(record) self._apply_filters_to_log(record)
@ -107,13 +177,8 @@ class LogViewerApp(App):
def _apply_filters_to_log(self, record: logging.LogRecord): def _apply_filters_to_log(self, record: logging.LogRecord):
"""Prüft einen einzelnen Log-Eintrag gegen die Filter und zeigt ihn ggf. an.""" """Prüft einen einzelnen Log-Eintrag gegen die Filter und zeigt ihn ggf. an."""
module_match = False module_match = self.__module_select_list__.IsSelected(record.name)
for module_filter in self.module_filter.split(","): level_match = self.__level_select_list__.IsSelected(record.levelname)
module_match |= module_filter.lower() in record.name.lower()
level_match = False
for level_filter in self.level_filter.split(","):
level_match |= level_filter.lower() in record.levelname.lower()
if self._force(record.levelname.lower()) or (module_match and level_match): if self._force(record.levelname.lower()) or (module_match and level_match):
level_style = LEVEL_STYLES.get(record.levelname, "white") level_style = LEVEL_STYLES.get(record.levelname, "white")
@ -140,15 +205,6 @@ class LogViewerApp(App):
for record in self.all_logs: for record in self.all_logs:
self._apply_filters_to_log(record) self._apply_filters_to_log(record)
def on_input_changed(self, message: Input.Changed) -> None:
"""Aktualisiert die Filter und die Anzeige, wenn der Benutzer tippt."""
if message.input.id == "module_filter":
self.module_filter = message.value
elif message.input.id == "level_filter":
self.level_filter = message.value
self._update_display()
def on_checkbox_changed(self, message: Checkbox.Changed) -> None: def on_checkbox_changed(self, message: Checkbox.Changed) -> None:
if message.checkbox.id == "force_critical": if message.checkbox.id == "force_critical":
self.force_critical = message.value self.force_critical = message.value
@ -159,6 +215,27 @@ class LogViewerApp(App):
self._update_display() self._update_display()
def on_input_changed(self, message: Input.Changed) -> None:
"""Update the tui inputs and execute task, if requireed."""
if message.input.id == "select_filter":
self.__module_select_list__.SetSelectionRegEx(message.value)
self.__module_selection__.set_options(self.__module_select_list__.GetSelectList())
self.__module_selection__.prompt = "Full" if not message.value else "Filtered"
def on_select_changed(self, message: Select.Changed) -> None:
if message.select.id == 'module_filter':
if type(message.value) is int:
self.__module_select_list__.Toggle(message.value)
self.__module_selection__.clear()
self.__module_selection__.set_options(self.__module_select_list__.GetSelectList())
elif message.select.id == 'level_filter':
if type(message.value) is int:
self.__level_select_list__.Toggle(message.value)
self.__level_selection__.clear()
self.__level_selection__.set_options(self.__level_select_list__.GetSelectList())
self._update_display()
if __name__ == "__main__": if __name__ == "__main__":
app = LogViewerApp() app = LogViewerApp()

View File

@ -7,8 +7,8 @@
#bottom-bar { #bottom-bar {
layout: grid; layout: grid;
grid-size: 5; grid-size: 6;
grid-columns: 1fr 1fr 15 15 15; grid-columns: 4fr 1fr 2fr 15 15 15;
height: 4; height: 4;
border-top: solid $primary; border-top: solid $primary;
} }