From f2d565e7d96bc945b7d796aba50398cc29def7ed Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Tue, 30 Jun 2026 16:13:01 +0200 Subject: [PATCH] better app logs --- backend/main.py | 41 +++++++++++++++++++++++------------------ backend/plugins.py | 15 +++++++++------ 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/backend/main.py b/backend/main.py index 9a41d38..230c629 100644 --- a/backend/main.py +++ b/backend/main.py @@ -12,12 +12,15 @@ from starlette.middleware.sessions import SessionMiddleware import os import yaml import json +import logging from pathlib import Path from typing import List, Optional import aiofiles from datetime import datetime import bcrypt import secrets + +logger = logging.getLogger("uvicorn.error") from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded @@ -79,9 +82,9 @@ with open(version_path, 'r', encoding='utf-8') as f: if 'AUTHENTICATION_ENABLED' in os.environ: auth_enabled = os.getenv('AUTHENTICATION_ENABLED', 'false').lower() in ('true', '1', 'yes') config['authentication']['enabled'] = auth_enabled - print(f"🔐 Authentication {'ENABLED' if auth_enabled else 'DISABLED'} (from AUTHENTICATION_ENABLED env var)") + logger.info("Authentication %s (from AUTHENTICATION_ENABLED env var)", 'ENABLED' if auth_enabled else 'DISABLED') else: - print(f"🔐 Authentication {'ENABLED' if config.get('authentication', {}).get('enabled', False) else 'DISABLED'} (from config.yaml)") + logger.info("Authentication %s (from config.yaml)", 'ENABLED' if config.get('authentication', {}).get('enabled', False) else 'DISABLED') # Password configuration priority: # 1. AUTHENTICATION_PASSWORD env var (hashed at startup) @@ -94,9 +97,9 @@ if 'AUTHENTICATION_PASSWORD' in os.environ: plain_password.encode('utf-8'), bcrypt.gensalt() ).decode('utf-8') - print("🔑 Password loaded from AUTHENTICATION_PASSWORD env var") + logger.info("Password loaded from AUTHENTICATION_PASSWORD env var") else: - print("⚠️ WARNING: AUTHENTICATION_PASSWORD env var is empty - ignoring") + logger.warning("AUTHENTICATION_PASSWORD env var is empty - ignoring") elif config.get('authentication', {}).get('password', '').strip(): plain_password = config['authentication']['password'].strip() config['authentication']['password_hash'] = bcrypt.hashpw( @@ -104,12 +107,12 @@ elif config.get('authentication', {}).get('password', '').strip(): bcrypt.gensalt() ).decode('utf-8') del config['authentication']['password'] - print("🔑 Password loaded from config.yaml") + logger.info("Password loaded from config.yaml") # Allow secret key to be set via environment variable (for session security) if 'AUTHENTICATION_SECRET_KEY' in os.environ: config['authentication']['secret_key'] = os.getenv('AUTHENTICATION_SECRET_KEY') - print("🔐 Secret key loaded from AUTHENTICATION_SECRET_KEY env var") + logger.info("Secret key loaded from AUTHENTICATION_SECRET_KEY env var") # API key configuration for external integrations (MCP servers, scripts, etc.) # Priority: AUTHENTICATION_API_KEY env var > authentication.api_key in config.yaml @@ -117,11 +120,11 @@ if 'AUTHENTICATION_API_KEY' in os.environ: api_key_value = os.getenv('AUTHENTICATION_API_KEY', '').strip() if api_key_value: config['authentication']['api_key'] = api_key_value - print("🔑 API key loaded from AUTHENTICATION_API_KEY env var") + logger.info("API key loaded from AUTHENTICATION_API_KEY env var") else: config['authentication']['api_key'] = '' elif config.get('authentication', {}).get('api_key', '').strip(): - print("🔑 API key loaded from config.yaml") + logger.info("API key loaded from config.yaml") else: config['authentication']['api_key'] = '' @@ -133,15 +136,15 @@ if config.get('authentication', {}).get('enabled', False): _is_default_secret = _secret_key in ('', 'change_this_to_a_random_secret_key_in_production') if not _has_password and not _has_api_key: - print("🚨 CRITICAL: Authentication enabled but NO auth methods configured - ALL access will be denied!") + logger.critical("Authentication enabled but NO auth methods configured - ALL access will be denied!") else: if not _has_password: - print("⚠️ WARNING: No password configured - web UI login will not work") + logger.warning("No password configured - web UI login will not work") if not _has_api_key: - print("⚠️ WARNING: No API key configured - external integrations will require session cookies") + logger.warning("No API key configured - external integrations will require session cookies") if _is_default_secret: - print("🚨 SECURITY WARNING: Using default secret_key - sessions can be forged! Change it in config.yaml") + logger.critical("Using default secret_key - sessions can be forged! Change it in config.yaml") # OpenAPI tag metadata for grouping endpoints in Swagger UI tags_metadata = [ @@ -178,7 +181,7 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) -print(f"🌐 CORS allowed origins: {allowed_origins}") +logger.info("CORS allowed origins: %s", allowed_origins) # =========================================================== # ================= @@ -201,7 +204,7 @@ def safe_error_message(error: Exception, user_message: str = "An error occurred" error_details = f"{type(error).__name__}: {str(error)}" # Always log the full error server-side - print(f"⚠️ [ERROR] {error_details}") + logger.error(error_details) # In debug mode, return detailed error to help with development if config.get('server', {}).get('debug', False): @@ -247,7 +250,7 @@ if DEMO_MODE: limiter = Limiter(key_func=get_remote_address, default_limits=["200/hour"]) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) - print("🎭 DEMO MODE enabled - Rate limiting active") + logger.info("DEMO MODE enabled - Rate limiting active") else: # Production/self-hosted mode - no restrictions # Create a dummy limiter that doesn't actually limit @@ -416,7 +419,7 @@ def verify_password(password: str) -> bool: try: return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8')) except Exception as e: - print(f"Password verification error: {e}") + logger.error("Password verification error: %s", e) return False @@ -1832,13 +1835,15 @@ app.include_router(pages_router) @app.on_event("startup") def _warmup_note_index() -> None: import threading + import time def _build() -> None: + t0 = time.perf_counter() try: ensure_index_built(config['storage']['notes_dir']) - print("✅ Note index warmed up") + logger.info("Note index ready (%.2fs)", time.perf_counter() - t0) except Exception as exc: - print(f"⚠️ Note index warmup failed (will build on first request): {exc}") + logger.warning("Note index build failed (will retry on first request): %s", exc) threading.Thread(target=_build, name="note-index-warmup", daemon=True).start() diff --git a/backend/plugins.py b/backend/plugins.py index 73a8a03..3f9cd91 100644 --- a/backend/plugins.py +++ b/backend/plugins.py @@ -5,10 +5,13 @@ Plugins can hook into events like note save, delete, etc. import os import json +import logging import importlib.util from pathlib import Path from typing import List, Dict, Callable +logger = logging.getLogger("uvicorn.error") + class Plugin: """Base plugin class""" @@ -113,7 +116,7 @@ class PluginManager: plugin = module.Plugin() self.plugins[plugin_file.stem] = plugin except Exception as e: - print(f"Failed to load plugin {plugin_file.stem}: {e}") + logger.error("Failed to load plugin %s: %s", plugin_file.stem, e) def _create_example_plugin(self): """Create an example plugin to show developers how to build plugins""" @@ -167,7 +170,7 @@ class Plugin: with open(self.config_file, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: - print(f"Failed to load plugin config: {e}") + logger.error("Failed to load plugin config: %s", e) return {} def _save_config(self): @@ -180,7 +183,7 @@ class Plugin: with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2) except Exception as e: - print(f"Failed to save plugin config: {e}") + logger.error("Failed to save plugin config: %s", e) def _apply_saved_state(self): """Apply saved plugin states after loading plugins""" @@ -188,7 +191,7 @@ class Plugin: for plugin_id, enabled in saved_config.items(): if plugin_id in self.plugins: self.plugins[plugin_id].enabled = enabled - print(f"Plugin '{plugin_id}': {'enabled' if enabled else 'disabled'} (from config)") + logger.info("Plugin '%s': %s (from config)", plugin_id, 'enabled' if enabled else 'disabled') def enable_plugin(self, plugin_id: str): """Enable a plugin and persist the state""" @@ -238,7 +241,7 @@ class Plugin: method(**kwargs) except Exception as e: - print(f"Plugin {plugin.name} error in {hook_name}: {e}") + logger.error("Plugin %s error in %s: %s", plugin.name, hook_name, e) return result if 'content' in kwargs else None @@ -266,7 +269,7 @@ class Plugin: if 'initial_content' in kwargs and result is not None: kwargs['initial_content'] = result except Exception as e: - print(f"Plugin {plugin.name} error in {hook_name}: {e}") + logger.error("Plugin %s error in %s: %s", plugin.name, hook_name, e) # Return the final modified value return kwargs.get('initial_content', '')