better app logs
This commit is contained in:
parent
6ddb533fe6
commit
f2d565e7d9
|
|
@ -12,12 +12,15 @@ from starlette.middleware.sessions import SessionMiddleware
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import bcrypt
|
import bcrypt
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
|
logger = logging.getLogger("uvicorn.error")
|
||||||
from slowapi import Limiter, _rate_limit_exceeded_handler
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
||||||
from slowapi.util import get_remote_address
|
from slowapi.util import get_remote_address
|
||||||
from slowapi.errors import RateLimitExceeded
|
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:
|
if 'AUTHENTICATION_ENABLED' in os.environ:
|
||||||
auth_enabled = os.getenv('AUTHENTICATION_ENABLED', 'false').lower() in ('true', '1', 'yes')
|
auth_enabled = os.getenv('AUTHENTICATION_ENABLED', 'false').lower() in ('true', '1', 'yes')
|
||||||
config['authentication']['enabled'] = auth_enabled
|
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:
|
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:
|
# Password configuration priority:
|
||||||
# 1. AUTHENTICATION_PASSWORD env var (hashed at startup)
|
# 1. AUTHENTICATION_PASSWORD env var (hashed at startup)
|
||||||
|
|
@ -94,9 +97,9 @@ if 'AUTHENTICATION_PASSWORD' in os.environ:
|
||||||
plain_password.encode('utf-8'),
|
plain_password.encode('utf-8'),
|
||||||
bcrypt.gensalt()
|
bcrypt.gensalt()
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
print("🔑 Password loaded from AUTHENTICATION_PASSWORD env var")
|
logger.info("Password loaded from AUTHENTICATION_PASSWORD env var")
|
||||||
else:
|
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():
|
elif config.get('authentication', {}).get('password', '').strip():
|
||||||
plain_password = config['authentication']['password'].strip()
|
plain_password = config['authentication']['password'].strip()
|
||||||
config['authentication']['password_hash'] = bcrypt.hashpw(
|
config['authentication']['password_hash'] = bcrypt.hashpw(
|
||||||
|
|
@ -104,12 +107,12 @@ elif config.get('authentication', {}).get('password', '').strip():
|
||||||
bcrypt.gensalt()
|
bcrypt.gensalt()
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
del config['authentication']['password']
|
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)
|
# Allow secret key to be set via environment variable (for session security)
|
||||||
if 'AUTHENTICATION_SECRET_KEY' in os.environ:
|
if 'AUTHENTICATION_SECRET_KEY' in os.environ:
|
||||||
config['authentication']['secret_key'] = os.getenv('AUTHENTICATION_SECRET_KEY')
|
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.)
|
# API key configuration for external integrations (MCP servers, scripts, etc.)
|
||||||
# Priority: AUTHENTICATION_API_KEY env var > authentication.api_key in config.yaml
|
# 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()
|
api_key_value = os.getenv('AUTHENTICATION_API_KEY', '').strip()
|
||||||
if api_key_value:
|
if api_key_value:
|
||||||
config['authentication']['api_key'] = 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:
|
else:
|
||||||
config['authentication']['api_key'] = ''
|
config['authentication']['api_key'] = ''
|
||||||
elif config.get('authentication', {}).get('api_key', '').strip():
|
elif config.get('authentication', {}).get('api_key', '').strip():
|
||||||
print("🔑 API key loaded from config.yaml")
|
logger.info("API key loaded from config.yaml")
|
||||||
else:
|
else:
|
||||||
config['authentication']['api_key'] = ''
|
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')
|
_is_default_secret = _secret_key in ('', 'change_this_to_a_random_secret_key_in_production')
|
||||||
|
|
||||||
if not _has_password and not _has_api_key:
|
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:
|
else:
|
||||||
if not _has_password:
|
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:
|
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:
|
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
|
# OpenAPI tag metadata for grouping endpoints in Swagger UI
|
||||||
tags_metadata = [
|
tags_metadata = [
|
||||||
|
|
@ -178,7 +181,7 @@ app.add_middleware(
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
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)}"
|
error_details = f"{type(error).__name__}: {str(error)}"
|
||||||
|
|
||||||
# Always log the full error server-side
|
# 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
|
# In debug mode, return detailed error to help with development
|
||||||
if config.get('server', {}).get('debug', False):
|
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"])
|
limiter = Limiter(key_func=get_remote_address, default_limits=["200/hour"])
|
||||||
app.state.limiter = limiter
|
app.state.limiter = limiter
|
||||||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
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:
|
else:
|
||||||
# Production/self-hosted mode - no restrictions
|
# Production/self-hosted mode - no restrictions
|
||||||
# Create a dummy limiter that doesn't actually limit
|
# Create a dummy limiter that doesn't actually limit
|
||||||
|
|
@ -416,7 +419,7 @@ def verify_password(password: str) -> bool:
|
||||||
try:
|
try:
|
||||||
return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
|
return bcrypt.checkpw(password.encode('utf-8'), password_hash.encode('utf-8'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Password verification error: {e}")
|
logger.error("Password verification error: %s", e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1832,13 +1835,15 @@ app.include_router(pages_router)
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
def _warmup_note_index() -> None:
|
def _warmup_note_index() -> None:
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
def _build() -> None:
|
def _build() -> None:
|
||||||
|
t0 = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
ensure_index_built(config['storage']['notes_dir'])
|
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:
|
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()
|
threading.Thread(target=_build, name="note-index-warmup", daemon=True).start()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ Plugins can hook into events like note save, delete, etc.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import importlib.util
|
import importlib.util
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Callable
|
from typing import List, Dict, Callable
|
||||||
|
|
||||||
|
logger = logging.getLogger("uvicorn.error")
|
||||||
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
"""Base plugin class"""
|
"""Base plugin class"""
|
||||||
|
|
@ -113,7 +116,7 @@ class PluginManager:
|
||||||
plugin = module.Plugin()
|
plugin = module.Plugin()
|
||||||
self.plugins[plugin_file.stem] = plugin
|
self.plugins[plugin_file.stem] = plugin
|
||||||
except Exception as e:
|
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):
|
def _create_example_plugin(self):
|
||||||
"""Create an example plugin to show developers how to build plugins"""
|
"""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:
|
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to load plugin config: {e}")
|
logger.error("Failed to load plugin config: %s", e)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _save_config(self):
|
def _save_config(self):
|
||||||
|
|
@ -180,7 +183,7 @@ class Plugin:
|
||||||
with open(self.config_file, 'w', encoding='utf-8') as f:
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f, indent=2)
|
||||||
except Exception as e:
|
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):
|
def _apply_saved_state(self):
|
||||||
"""Apply saved plugin states after loading plugins"""
|
"""Apply saved plugin states after loading plugins"""
|
||||||
|
|
@ -188,7 +191,7 @@ class Plugin:
|
||||||
for plugin_id, enabled in saved_config.items():
|
for plugin_id, enabled in saved_config.items():
|
||||||
if plugin_id in self.plugins:
|
if plugin_id in self.plugins:
|
||||||
self.plugins[plugin_id].enabled = enabled
|
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):
|
def enable_plugin(self, plugin_id: str):
|
||||||
"""Enable a plugin and persist the state"""
|
"""Enable a plugin and persist the state"""
|
||||||
|
|
@ -238,7 +241,7 @@ class Plugin:
|
||||||
method(**kwargs)
|
method(**kwargs)
|
||||||
|
|
||||||
except Exception as e:
|
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
|
return result if 'content' in kwargs else None
|
||||||
|
|
||||||
|
|
@ -266,7 +269,7 @@ class Plugin:
|
||||||
if 'initial_content' in kwargs and result is not None:
|
if 'initial_content' in kwargs and result is not None:
|
||||||
kwargs['initial_content'] = result
|
kwargs['initial_content'] = result
|
||||||
except Exception as e:
|
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 the final modified value
|
||||||
return kwargs.get('initial_content', '')
|
return kwargs.get('initial_content', '')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue