Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Application configuration."""
import os
from pathlib import Path

# Project root (parent of ``kernel_ai`` package)
PROJECT_ROOT = Path(__file__).resolve().parent.parent


class Config:
"""Flask config loaded via app.config.from_object(Config)."""

DEBUG = os.getenv("FLASK_DEBUG", "False").lower() == "true"
ENV = os.getenv("FLASK_ENV", "production" if not DEBUG else "development")
STATIC_FOLDER = "static"
TEMPLATES_FOLDER = "templates"
API_PREFIX = "/api"
SEND_FILE_MAX_AGE_DEFAULT = 0 if not DEBUG else 31536000
PROJECT_ROOT = PROJECT_ROOT
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"))
36 changes: 36 additions & 0 deletions sentry_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Sentry initialization helpers for Flask app."""

from __future__ import annotations

import logging

logger = logging.getLogger(__name__)


def init_sentry(app) -> bool:
"""Initialize Sentry from app config.

Returns True when initialization succeeds and False otherwise.
"""
dsn = str(app.config.get("SENTRY_DSN", "") or "").strip()
if not dsn:
logger.info("Sentry disabled: SENTRY_DSN is empty")
return False

try:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
except Exception as exc: # pragma: no cover - import failure is deployment issue
logger.warning("Sentry SDK not available: %s", exc)
return False

sentry_sdk.init(
dsn=dsn,
integrations=[FlaskIntegration()],
environment=app.config.get("SENTRY_ENVIRONMENT"),
release=app.config.get("SENTRY_RELEASE"),
send_default_pii=bool(app.config.get("SENTRY_SEND_DEFAULT_PII", False)),
traces_sample_rate=float(app.config.get("SENTRY_TRACES_SAMPLE_RATE", 0.0)),
)
logger.info("Sentry initialized")
return True
80 changes: 80 additions & 0 deletions webapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
Linux Kernel Visualization Backend
Thin Flask bootstrap module.
"""

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():
_prom_mpdir = os.environ["PROMETHEUS_MULTIPROC_DIR"].strip()
os.makedirs(_prom_mpdir, exist_ok=True)


def _register_error_handlers(app):
@app.errorhandler(404)
def not_found(error):
return jsonify(build_error_payload("Not found", "not_found")), 404

@app.errorhandler(500)
def internal_error(error):
return jsonify(build_error_payload("Internal server error", "internal_error")), 500


def create_app():
"""Application factory that builds and configures a Flask instance."""
_ensure_prometheus_mpdir()
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
app = Flask(
__name__,
static_folder=os.path.join(root, "static"),
template_folder=os.path.join(root, "templates"),
)
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)
register_http_routes(app)
_register_error_handlers(app)
return app


# Keep module-level app for Gunicorn and backward-compatible imports.
app = create_app()
Loading