diff --git a/logs/.gitkeep b/logs/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/routstr/core/logging.py b/routstr/core/logging.py index 00474949..d88debb8 100644 --- a/routstr/core/logging.py +++ b/routstr/core/logging.py @@ -3,36 +3,38 @@ Logging configuration for Routstr. CRITICAL LOG MESSAGES FOR USAGE STATISTICS: =========================================== -The following log messages are parsed by the usage tracking system (routstr/core/admin.py). +The following log messages are parsed by the usage tracking system +(routstr/core/usage_analytics_store.py and routstr/core/log_manager.py). DO NOT modify or remove these messages without updating the usage tracking logic: 1. "Received proxy request" (INFO) - routstr/proxy.py - Used to count total incoming requests - Includes model information in context - 2. "Payment adjustment completed for streaming" (INFO) - routstr/upstream/base.py - "Payment adjustment completed for non-streaming" (INFO) - routstr/upstream/base.py +2. "Calculated token-based cost" (INFO) - routstr/auth.py - Used to track successful completions and revenue - - The 'cost_data.total_msats' field is extracted for revenue calculation - - Must include 'cost_data' in extra dict + - The 'token_cost' and 'model' fields are extracted for dashboard metrics -3. "Payment processed successfully" (INFO) - routstr/auth.py +3. "Max cost payment finalized" (INFO) - routstr/auth.py + - Used as the successful completion fallback when token usage is unavailable + - The 'charged_amount' and 'model' fields are extracted for dashboard metrics + +4. "Payment processed successfully" (INFO) - routstr/auth.py - Used to count successful payment processing events - Tracks payment-related metrics -4. "Upstream request failed, revert payment" (WARNING) - routstr/proxy.py +5. "Upstream request failed, revert payment" (WARNING) - routstr/proxy.py - Used to track failed requests and refunds - The 'max_cost_for_model' field is extracted for refund calculation - Must include 'max_cost_for_model' in extra dict -5. Any ERROR level logs with "upstream" in the message +6. Any ERROR level logs with "upstream" in the message - Used to count upstream provider errors - Helps identify service reliability issues If you need to modify these messages, ensure you also update the parsing logic in: -- routstr/core/admin.py:_aggregate_metrics_by_time() -- routstr/core/admin.py:_get_summary_stats() -- routstr/core/admin.py:get_revenue_by_model() +- routstr/core/usage_analytics_store.py +- routstr/core/log_manager.py """ import logging.config diff --git a/routstr/core/usage_analytics_store.py b/routstr/core/usage_analytics_store.py index ccea4938..c54c0d6d 100644 --- a/routstr/core/usage_analytics_store.py +++ b/routstr/core/usage_analytics_store.py @@ -21,7 +21,7 @@ class UsageAnalyticsStore: bytes. """ - SCHEMA_VERSION = "2" + SCHEMA_VERSION = "3" def __init__(self, logs_dir: Path, db_path: Path | None = None): self.logs_dir = logs_dir @@ -473,10 +473,7 @@ class UsageAnalyticsStore: elif level == "WARNING": bucket["warnings"] += 1 - completed = ( - "completed for streaming" in message - or "completed for non-streaming" in message - ) + completed, revenue_msats = self._extract_success_metrics(entry, message) if completed: bucket["total_requests"] += 1 bucket["successful_chat_completions"] += 1 @@ -484,13 +481,9 @@ class UsageAnalyticsStore: model_bucket["requests"] += 1 model_bucket["successful"] += 1 - cost_data = entry.get("cost_data") - if isinstance(cost_data, dict): - actual_cost = cost_data.get("total_msats", 0) - if isinstance(actual_cost, (int, float)) and actual_cost > 0: - cost_float = float(actual_cost) - bucket["revenue_msats"] += cost_float - model_bucket["revenue_msats"] += cost_float + if revenue_msats > 0: + bucket["revenue_msats"] += revenue_msats + model_bucket["revenue_msats"] += revenue_msats failed = ( "upstream request failed" in message @@ -988,6 +981,29 @@ class UsageAnalyticsStore: return None return f"{timestamp[:16]}:00" + def _extract_success_metrics( + self, entry: dict[str, Any], message: str + ) -> tuple[bool, float]: + # These auth logs are emitted once per successful settlement across providers + # and avoid duplicate counting from provider-specific completion logs. + logger_name = str(entry.get("name", "")) + if not logger_name.startswith("routstr.auth"): + return False, 0.0 + + if "calculated token-based cost" in message: + token_cost = entry.get("token_cost", 0) + if isinstance(token_cost, (int, float)) and token_cost > 0: + return True, float(token_cost) + return True, 0.0 + + if "max cost payment finalized" in message: + charged_amount = entry.get("charged_amount", 0) + if isinstance(charged_amount, (int, float)) and charged_amount > 0: + return True, float(charged_amount) + return True, 0.0 + + return False, 0.0 + def _new_minute_stats(self) -> dict[str, float]: return { "total_entries": 0.0, diff --git a/ui/components/landing/cheat-sheet.tsx b/ui/components/landing/cheat-sheet.tsx index 7004ec98..fa9aea87 100644 --- a/ui/components/landing/cheat-sheet.tsx +++ b/ui/components/landing/cheat-sheet.tsx @@ -66,9 +66,7 @@ function normalizeBaseUrl(url: string): string { } export function CheatSheet(): JSX.Element { - const [baseUrl, setBaseUrl] = useState(() => - typeof window === 'undefined' ? '' : ConfigurationService.getLocalBaseUrl() - ); + const [baseUrl, setBaseUrl] = useState(''); const [apiKeyInput, setApiKeyInput] = useState(''); const [walletInfo, setWalletInfo] = useState(null); const [refundReceipt, setRefundReceipt] = useState(