diff --git a/kernel_ai/config.py b/kernel_ai/config.py index ff071c3..7bb3682 100644 --- a/kernel_ai/config.py +++ b/kernel_ai/config.py @@ -19,3 +19,8 @@ class Config: LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") LOG_FORMAT = os.getenv("LOG_FORMAT", "json") LOG_SERVICE_NAME = os.getenv("LOG_SERVICE_NAME", "kernel-ai-backend") + SENTRY_DSN = os.getenv("SENTRY_DSN", "").strip() + SENTRY_ENVIRONMENT = os.getenv("SENTRY_ENVIRONMENT", ENV) + SENTRY_RELEASE = os.getenv("SENTRY_RELEASE", "").strip() or None + SENTRY_SEND_DEFAULT_PII = os.getenv("SENTRY_SEND_DEFAULT_PII", "false").lower() == "true" + SENTRY_TRACES_SAMPLE_RATE = float(os.getenv("SENTRY_TRACES_SAMPLE_RATE", "0.0")) diff --git a/kernel_ai/http/common.py b/kernel_ai/http/common.py index ca526ae..3b17a24 100644 --- a/kernel_ai/http/common.py +++ b/kernel_ai/http/common.py @@ -1,6 +1,7 @@ """Common HTTP helpers.""" from flask import g, has_request_context, jsonify +from kernel_ai.sentry_helpers import capture_exception def build_error_payload(message, code, details=None): @@ -36,6 +37,13 @@ def api_json(producer, error_status=500, error_extra=None, exception_statuses=No error_code = "not_found" elif status == 503: error_code = "service_unavailable" + # Capture only server-side failures; 4xx branches can be expected. + if status >= 500: + capture_exception( + e, + where="http.common.api_json", + extra={"status": status, "error_code": error_code}, + ) payload = build_error_payload(str(e), error_code) if error_extra: payload.update(error_extra) diff --git a/kernel_ai/services/execution.py b/kernel_ai/services/execution.py index c7aca77..9b3e041 100644 --- a/kernel_ai/services/execution.py +++ b/kernel_ai/services/execution.py @@ -9,6 +9,8 @@ import psutil +from kernel_ai.sentry_helpers import capture_exception + def get_execution_context_data(syscall_names, map_interrupt_to_subsystem_fn, exec_context_prev): """Collect execution context payload used by Ring-1 visualization.""" @@ -284,8 +286,8 @@ def get_kernel_dna_data(get_real_system_calls_fn, map_syscall_to_subsystem_fn, m "timestamp": datetime.now().isoformat(), } ) - except Exception: - pass + except Exception as exc: + capture_exception(exc, where="services.execution.get_kernel_dna_data.syscalls") try: with open("/proc/interrupts", "r", encoding="utf-8", errors="ignore") as f: @@ -357,8 +359,8 @@ def get_kernel_dna_data(get_real_system_calls_fn, map_syscall_to_subsystem_fn, m "timestamp": datetime.now().isoformat(), } ) - except Exception: - pass + except Exception as exc: + capture_exception(exc, where="services.execution.get_kernel_dna_data.locks_fallback") dna_data["genes"] = [ {"name": "sched", "start": 0, "end": 0.2, "color": "#58b6d8"}, diff --git a/kernel_ai/services/process_inspect.py b/kernel_ai/services/process_inspect.py index 363890d..f6b4b42 100644 --- a/kernel_ai/services/process_inspect.py +++ b/kernel_ai/services/process_inspect.py @@ -8,6 +8,7 @@ import psutil from kernel_ai.services import system_view as _system_view_service +from kernel_ai.sentry_helpers import capture_exception def get_ipc_links_summary(max_pairs=120, max_nodes=24): @@ -215,6 +216,7 @@ def get_process_threads_info(pid): except (psutil.NoSuchProcess, psutil.AccessDenied) as e: return {"error": str(e)} except Exception as e: + capture_exception(e, where="services.process_inspect.get_process_threads_info") return {"error": str(e)} @@ -249,6 +251,7 @@ def get_process_cpu_info(pid): except (psutil.NoSuchProcess, psutil.AccessDenied) as e: return {"error": str(e)} except Exception as e: + capture_exception(e, where="services.process_inspect.get_process_cpu_info") return {"error": str(e)} @@ -314,4 +317,5 @@ def get_process_fds_info(pid): except (psutil.NoSuchProcess, psutil.AccessDenied) as e: return {"error": f"Access denied or process not found: {str(e)}"} except Exception as e: + capture_exception(e, where="services.process_inspect.get_process_fds_info") return {"error": f"Error getting FDs: {str(e)}"} diff --git a/kernel_ai/services/syscalls.py b/kernel_ai/services/syscalls.py index 7bd0598..4c36c2c 100644 --- a/kernel_ai/services/syscalls.py +++ b/kernel_ai/services/syscalls.py @@ -6,6 +6,8 @@ import platform from datetime import datetime +from kernel_ai.sentry_helpers import capture_exception + def _kernel_dna_read_proc_vmstat(): """Parse /proc/vmstat into a dict of int counters.""" @@ -139,7 +141,8 @@ def get_real_system_calls(syscall_names, map_syscall_to_subsystem_fn, kernel_dna merged.sort(key=lambda x: x["count"], reverse=True) return merged[:20] return [] - except Exception: + except Exception as exc: + capture_exception(exc, where="services.syscalls.get_real_system_calls") return [] if platform.system() == "Linux" else fallback_mock_calls_fn() diff --git a/kernel_ai/webapp.py b/kernel_ai/webapp.py index e2cbda0..0070543 100644 --- a/kernel_ai/webapp.py +++ b/kernel_ai/webapp.py @@ -5,16 +5,21 @@ """ import os +import logging from flask import Flask, jsonify from kernel_ai.config import Config from kernel_ai.hooks import register_hooks from kernel_ai.http.common import build_error_payload from kernel_ai.http.register import register_http_routes +from kernel_ai.logging_helpers import log_event from kernel_ai.logging_setup import configure_logging from kernel_ai.prometheus_setup import init_prometheus +from kernel_ai.sentry_setup import init_sentry from kernel_ai.state import attach_state_container +logger = logging.getLogger(__name__) + def _ensure_prometheus_mpdir(): # Gunicorn -w N: set PROMETHEUS_MULTIPROC_DIR before workers import this module. if os.environ.get("PROMETHEUS_MULTIPROC_DIR", "").strip(): @@ -43,6 +48,26 @@ def create_app(): ) app.config.from_object(Config) configure_logging(app) + sentry_enabled = init_sentry(app) + log_event( + logger, + "INFO", + "app_startup", + event_dataset="kernel_ai.app", + component="webapp", + operation="create_app", + event_data={ + "service.environment": app.config.get("ENV", "production"), + "debug": bool(app.config.get("DEBUG", False)), + "api_prefix": app.config.get("API_PREFIX", "/api"), + "prometheus_multiproc_enabled": bool( + os.environ.get("PROMETHEUS_MULTIPROC_DIR", "").strip() + ), + "log_level": app.config.get("LOG_LEVEL"), + "log_format": app.config.get("LOG_FORMAT"), + "sentry_enabled": sentry_enabled, + }, + ) attach_state_container(app) init_prometheus(app) register_hooks(app)