Improve logging and error handling across modules

- Added logging functionality to app.py and rns.py for better error tracking.
- Improved exception handling in RNSManager methods to log specific failures.
- Refactored code in various modules to ensure consistent logging practices.
- Updated UI components to handle exceptions with user feedback.
- Cleaned up formatting in several files for better readability.
This commit is contained in:
2025-11-30 15:55:30 -06:00
parent 1e39fe277e
commit a1480a5c1b
9 changed files with 83 additions and 54 deletions

View File

@@ -5,6 +5,7 @@ Ren Browser, a browser for the Reticulum Network built with Flet.
""" """
import argparse import argparse
import logging
import os import os
from pathlib import Path from pathlib import Path
@@ -19,6 +20,7 @@ from ren_browser.ui.ui import build_ui
RENDERER = "plaintext" RENDERER = "plaintext"
RNS_CONFIG_DIR = None RNS_CONFIG_DIR = None
RNS_INSTANCE = None RNS_INSTANCE = None
logger = logging.getLogger(__name__)
async def main(page: Page): async def main(page: Page):
@@ -63,7 +65,7 @@ async def main(page: Page):
ren_browser.logs.setup_rns_logging() ren_browser.logs.setup_rns_logging()
except Exception: except Exception:
pass logger.exception("Unable to configure RNS logging")
success = rns.initialize_reticulum(config_override) success = rns.initialize_reticulum(config_override)
if not success: if not success:

View File

@@ -45,7 +45,7 @@ def parse_micron_line(line: str) -> list:
"underline": underline, "underline": underline,
"color": color, "color": color,
"bgcolor": bgcolor, "bgcolor": bgcolor,
} },
) )
current_text = "" current_text = ""
@@ -95,7 +95,7 @@ def parse_micron_line(line: str) -> list:
"underline": underline, "underline": underline,
"color": color, "color": color,
"bgcolor": bgcolor, "bgcolor": bgcolor,
} },
) )
return spans return spans
@@ -196,8 +196,9 @@ def _render_micron_internal(content: str, on_link_click=None) -> ft.Control:
before = line[last_end : link_match.start()] before = line[last_end : link_match.start()]
if before: if before:
before_spans = parse_micron_line(before) before_spans = parse_micron_line(before)
for span in before_spans: row_controls.extend(
row_controls.append(create_text_span(span)) create_text_span(span) for span in before_spans
)
label = link_match.group(1) label = link_match.group(1)
url = link_match.group(2) url = link_match.group(2)
@@ -225,8 +226,9 @@ def _render_micron_internal(content: str, on_link_click=None) -> ft.Control:
after = line[last_end:] after = line[last_end:]
if after: if after:
after_spans = parse_micron_line(after) after_spans = parse_micron_line(after)
for span in after_spans: row_controls.extend(
row_controls.append(create_text_span(span)) create_text_span(span) for span in after_spans
)
if row_controls: if row_controls:
controls.append( controls.append(

View File

@@ -2,21 +2,23 @@
from __future__ import annotations from __future__ import annotations
import logging
import os import os
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from typing import Optional
import RNS import RNS
logger = logging.getLogger(__name__)
class RNSManager: class RNSManager:
"""Manage Reticulum lifecycle and configuration.""" """Manage Reticulum lifecycle and configuration."""
def __init__(self): def __init__(self):
self.reticulum = None self.reticulum = None
self.config_path: Optional[str] = None self.config_path: str | None = None
self.last_error: Optional[str] = None self.last_error: str | None = None
def _is_android(self) -> bool: def _is_android(self) -> bool:
vendor = getattr(RNS, "vendor", None) vendor = getattr(RNS, "vendor", None)
@@ -47,9 +49,8 @@ class RNSManager:
return Path(tempfile.gettempdir()) return Path(tempfile.gettempdir())
def _default_config_root(self) -> Path: def _default_config_root(self) -> Path:
override = ( override = os.environ.get("REN_BROWSER_RNS_DIR") or os.environ.get(
os.environ.get("REN_BROWSER_RNS_DIR") "REN_RETICULUM_CONFIG_DIR",
or os.environ.get("REN_RETICULUM_CONFIG_DIR")
) )
if override: if override:
return Path(override).expanduser() return Path(override).expanduser()
@@ -57,8 +58,10 @@ class RNSManager:
return self._android_storage_root() / "ren_browser" / "reticulum" return self._android_storage_root() / "ren_browser" / "reticulum"
return Path.home() / ".reticulum" return Path.home() / ".reticulum"
def _resolve_config_dir(self, preferred: Optional[str | Path]) -> Path: def _resolve_config_dir(self, preferred: str | Path | None) -> Path:
target = Path(preferred).expanduser() if preferred else self._default_config_root() target = (
Path(preferred).expanduser() if preferred else self._default_config_root()
)
allow_fallback = preferred is None allow_fallback = preferred is None
try: try:
@@ -128,7 +131,7 @@ class RNSManager:
config_file.write_text(base_content, encoding="utf-8") config_file.write_text(base_content, encoding="utf-8")
os.chmod(config_file, 0o600) os.chmod(config_file, 0o600)
except Exception: except Exception:
pass logger.exception("Failed to seed default config at %s", config_file)
def _ensure_default_tcp_interfaces(self) -> None: def _ensure_default_tcp_interfaces(self) -> None:
if not self.config_path: if not self.config_path:
@@ -152,7 +155,10 @@ class RNSManager:
cfg.write("\n") cfg.write("\n")
cfg.write("\n" + snippet + "\n") cfg.write("\n" + snippet + "\n")
except Exception: except Exception:
pass logger.exception(
"Failed to append default TCP interfaces to %s",
config_file,
)
def _get_or_create_config_dir(self) -> Path: def _get_or_create_config_dir(self) -> Path:
if self.config_path: if self.config_path:
@@ -162,7 +168,7 @@ class RNSManager:
self.config_path = str(resolved) self.config_path = str(resolved)
return resolved return resolved
def initialize(self, config_dir: Optional[str] = None) -> bool: def initialize(self, config_dir: str | None = None) -> bool:
"""Initialize the Reticulum instance.""" """Initialize the Reticulum instance."""
self.last_error = None self.last_error = None
try: try:
@@ -225,7 +231,7 @@ class RNSManager:
self.last_error = str(exc) self.last_error = str(exc)
return False return False
def get_config_path(self) -> Optional[str]: def get_config_path(self) -> str | None:
"""Return the directory holding the active Reticulum config.""" """Return the directory holding the active Reticulum config."""
if self.config_path: if self.config_path:
return self.config_path return self.config_path
@@ -240,7 +246,7 @@ class RNSManager:
"""Return the current Reticulum instance, if any.""" """Return the current Reticulum instance, if any."""
return self.reticulum return self.reticulum
def get_last_error(self) -> Optional[str]: def get_last_error(self) -> str | None:
"""Return the last recorded error string.""" """Return the last recorded error string."""
return self.last_error return self.last_error
@@ -248,7 +254,7 @@ class RNSManager:
rns_manager = RNSManager() rns_manager = RNSManager()
def initialize_reticulum(config_dir: Optional[str] = None) -> bool: def initialize_reticulum(config_dir: str | None = None) -> bool:
"""Initialize Reticulum using the shared manager.""" """Initialize Reticulum using the shared manager."""
return rns_manager.initialize(config_dir) return rns_manager.initialize(config_dir)
@@ -263,7 +269,7 @@ def get_reticulum_instance():
return rns_manager.get_reticulum_instance() return rns_manager.get_reticulum_instance()
def get_config_path() -> Optional[str]: def get_config_path() -> str | None:
"""Expose the active configuration directory.""" """Expose the active configuration directory."""
return rns_manager.get_config_path() return rns_manager.get_config_path()
@@ -278,7 +284,6 @@ def write_config_file(content: str) -> bool:
return rns_manager.write_config_file(content) return rns_manager.write_config_file(content)
def get_last_error() -> Optional[str]: def get_last_error() -> str | None:
"""Return the last recorded Reticulum error.""" """Return the last recorded Reticulum error."""
return rns_manager.get_last_error() return rns_manager.get_last_error()

View File

@@ -259,7 +259,8 @@ class StorageManager:
if self.page and hasattr(self.page, "client_storage"): if self.page and hasattr(self.page, "client_storage"):
self.page.client_storage.set( self.page.client_storage.set(
"ren_browser_settings", json.dumps(settings) "ren_browser_settings",
json.dumps(settings),
) )
return True return True

View File

@@ -61,7 +61,8 @@ class TabsManager:
default_content = ( default_content = (
render_micron( render_micron(
"Welcome to Ren Browser", on_link_click=handle_link_click_home "Welcome to Ren Browser",
on_link_click=handle_link_click_home,
) )
if app_module.RENDERER == "micron" if app_module.RENDERER == "micron"
else render_plaintext("Welcome to Ren Browser") else render_plaintext("Welcome to Ren Browser")

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import logging
import flet as ft import flet as ft
import RNS import RNS
@@ -13,6 +14,7 @@ from ren_browser.storage.storage import get_storage_manager
BUTTON_BG = "#0B3D91" BUTTON_BG = "#0B3D91"
BUTTON_BG_HOVER = "#082C6C" BUTTON_BG_HOVER = "#082C6C"
logger = logging.getLogger(__name__)
def _blue_button_style() -> ft.ButtonStyle: def _blue_button_style() -> ft.ButtonStyle:
@@ -52,14 +54,14 @@ def _get_interface_statuses():
for interface in interfaces: for interface in interfaces:
if interface is None: if interface is None:
continue continue
if ( if interface.__class__.__name__ == "LocalClientInterface" and getattr(
interface.__class__.__name__ == "LocalClientInterface" interface, "is_connected_to_shared_instance", False,
and getattr(interface, "is_connected_to_shared_instance", False)
): ):
continue continue
statuses.append( statuses.append(
{ {
"name": getattr(interface, "name", None) or interface.__class__.__name__, "name": getattr(interface, "name", None)
or interface.__class__.__name__,
"online": bool(getattr(interface, "online", False)), "online": bool(getattr(interface, "online", False)),
"type": interface.__class__.__name__, "type": interface.__class__.__name__,
"bitrate": getattr(interface, "bitrate", None), "bitrate": getattr(interface, "bitrate", None),
@@ -194,7 +196,9 @@ def _build_storage_field(storage):
def refresh(): def refresh():
info = storage.get_storage_info() info = storage.get_storage_info()
storage_field.value = "\n".join(f"{key}: {value}" for key, value in info.items()) storage_field.value = "\n".join(
f"{key}: {value}" for key, value in info.items()
)
refresh() refresh()
return storage_field, refresh return storage_field, refresh
@@ -245,8 +249,12 @@ def open_settings_tab(page: ft.Page, tab_manager):
try: try:
color_preview.bgcolor = page_bgcolor_field.value color_preview.bgcolor = page_bgcolor_field.value
page.update() page.update()
except Exception: except Exception as exc:
pass logger.warning(
"Ignoring invalid background color '%s': %s",
page_bgcolor_field.value,
exc,
)
page_bgcolor_field.on_change = on_bgcolor_change page_bgcolor_field.on_change = on_bgcolor_change
@@ -484,4 +492,3 @@ def open_settings_tab(page: ft.Page, tab_manager):
idx = len(tab_manager.manager.tabs) - 1 idx = len(tab_manager.manager.tabs) - 1
tab_manager.select_tab(idx) tab_manager.select_tab(idx)
page.update() page.update()

View File

@@ -85,7 +85,8 @@ def build_ui(page: Page):
if req.page_path.endswith(".mu"): if req.page_path.endswith(".mu"):
new_control = render_micron( new_control = render_micron(
result, on_link_click=handle_link_click result,
on_link_click=handle_link_click,
) )
else: else:
new_control = render_plaintext(result) new_control = render_plaintext(result)

View File

@@ -80,9 +80,9 @@ class TestStorageManager:
clear=True, clear=True,
), ),
patch("pathlib.Path.mkdir"), patch("pathlib.Path.mkdir"),
): patch(
with patch(
"ren_browser.storage.storage.StorageManager._ensure_storage_directory", "ren_browser.storage.storage.StorageManager._ensure_storage_directory",
),
): ):
storage = StorageManager() storage = StorageManager()
storage._storage_dir = storage._get_storage_directory() storage._storage_dir = storage._get_storage_directory()
@@ -99,9 +99,9 @@ class TestStorageManager:
clear=True, clear=True,
), ),
patch("pathlib.Path.mkdir"), patch("pathlib.Path.mkdir"),
): patch(
with patch(
"ren_browser.storage.storage.StorageManager._ensure_storage_directory", "ren_browser.storage.storage.StorageManager._ensure_storage_directory",
),
): ):
storage = StorageManager() storage = StorageManager()
storage._storage_dir = storage._get_storage_directory() storage._storage_dir = storage._get_storage_directory()

View File

@@ -107,7 +107,9 @@ class TestOpenSettingsTab:
"ren_browser.ui.settings.get_storage_manager", "ren_browser.ui.settings.get_storage_manager",
return_value=mock_storage_manager, return_value=mock_storage_manager,
), ),
patch("ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns"), patch(
"ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns",
),
patch("pathlib.Path.read_text", return_value="config content"), patch("pathlib.Path.read_text", return_value="config content"),
): ):
open_settings_tab(mock_page, mock_tab_manager) open_settings_tab(mock_page, mock_tab_manager)
@@ -130,7 +132,9 @@ class TestOpenSettingsTab:
"ren_browser.ui.settings.get_storage_manager", "ren_browser.ui.settings.get_storage_manager",
return_value=mock_storage_manager, return_value=mock_storage_manager,
), ),
patch("ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns"), patch(
"ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns",
),
patch("pathlib.Path.read_text", side_effect=Exception("File not found")), patch("pathlib.Path.read_text", side_effect=Exception("File not found")),
): ):
open_settings_tab(mock_page, mock_tab_manager) open_settings_tab(mock_page, mock_tab_manager)
@@ -155,7 +159,9 @@ class TestOpenSettingsTab:
"ren_browser.ui.settings.get_storage_manager", "ren_browser.ui.settings.get_storage_manager",
return_value=mock_storage_manager, return_value=mock_storage_manager,
), ),
patch("ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns"), patch(
"ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns",
),
patch("pathlib.Path.read_text", return_value="config"), patch("pathlib.Path.read_text", return_value="config"),
patch("pathlib.Path.write_text") as mock_write, patch("pathlib.Path.write_text") as mock_write,
): ):
@@ -194,7 +200,9 @@ class TestOpenSettingsTab:
"ren_browser.ui.settings.get_storage_manager", "ren_browser.ui.settings.get_storage_manager",
return_value=mock_storage_manager, return_value=mock_storage_manager,
), ),
patch("ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns"), patch(
"ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns",
),
patch("pathlib.Path.read_text", return_value="config"), patch("pathlib.Path.read_text", return_value="config"),
patch("pathlib.Path.write_text", side_effect=Exception("disk full")), patch("pathlib.Path.write_text", side_effect=Exception("disk full")),
): ):
@@ -229,7 +237,9 @@ class TestOpenSettingsTab:
"ren_browser.ui.settings.get_storage_manager", "ren_browser.ui.settings.get_storage_manager",
return_value=mock_storage_manager, return_value=mock_storage_manager,
), ),
patch("ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns"), patch(
"ren_browser.ui.settings.rns.get_config_path", return_value="/tmp/rns",
),
patch("pathlib.Path.read_text", return_value="config"), patch("pathlib.Path.read_text", return_value="config"),
): ):
open_settings_tab(mock_page, mock_tab_manager) open_settings_tab(mock_page, mock_tab_manager)