Protokoll #23

ID23
Zeitstempel2025-12-20 15:11:39.268051
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens2,680 (Input: 32, Output: 2,648)
Dauer76 ms
Request-Zeit2025-12-20 15:11:39.268051
Response-Zeit2025-12-20 15:11:39.344127

Request

{
    "event": "PreToolUse",
    "tool_name": "Read",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/log_to_db.py"
    }
}

Response

{
    "tool_response": {
        "type": "text",
        "file": {
            "filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/log_to_db.py",
            "content": "#!\/usr\/bin\/env python3\n\"\"\"\nClaude Code Hook Logger v2 für KI-Protokoll-Datenbank\nVerbesserte Version mit Request\/Response-Pairing und Duration-Tracking\n\"\"\"\n\nimport json\nimport os\nimport sys\nimport re\nimport datetime\nimport hashlib\nimport pymysql\nimport tempfile\nfrom pathlib import Path\nfrom typing import Dict, Any, Optional\n\n# .env aus Hook-Verzeichnis laden\nfrom dotenv import load_dotenv\nload_dotenv(Path(__file__).parent \/ '.env')\n\n# Konfiguration aus Environment-Variablen\nDB_CONFIG = {\n    'host': os.environ.get('CLAUDE_DB_HOST', 'localhost'),\n    'port': int(os.environ.get('CLAUDE_DB_PORT', '3306')),\n    'user': os.environ.get('CLAUDE_DB_USER', 'claude_code'),\n    'password': os.environ.get('CLAUDE_DB_PASSWORD', ''),\n    'database': os.environ.get('CLAUDE_DB_NAME', 'ki_protokoll'),\n    'charset': 'utf8mb4'\n}\n\n# Session-Tracking im temporären Verzeichnis\nTEMP_DIR = Path(tempfile.gettempdir()) \/ \"claude_hooks\"\nTEMP_DIR.mkdir(exist_ok=True)\n\n# Sicherheitseinstellungen\nMAX_FIELD_LENGTH = 10000\nSENSITIVE_KEY_PATTERNS = re.compile(r\"(?i)(password|pass|secret|token|apikey|api_key|authorization|auth|bearer|credential)\")\nSENSITIVE_VALUE_PATTERNS = [\n    re.compile(r\"(?i)\\bAKIA[0-9A-Z]{16}\\b\"),\n    re.compile(r\"(?i)\\b(?:sk|rk|pk)[0-9A-Za-z]{20,}\\b\"),\n    re.compile(r\"(?i)\\beyJ[a-zA-Z0-9-]{10,}\\.[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}\\b\")\n]\n\ndef get_client_ip() -> str:\n    \"\"\"Ermittelt die Client-IP-Adresse\"\"\"\n    ssh_client = os.environ.get('SSH_CLIENT', '')\n    if ssh_client:\n        return ssh_client.split()[0]\n    ssh_connection = os.environ.get('SSH_CONNECTION', '')\n    if ssh_connection:\n        return ssh_connection.split()[0]\n    return '127.0.0.1'\n\ndef sanitize_data(obj: Any) -> Any:\n    \"\"\"Entfernt oder maskiert sensible Daten\"\"\"\n    if isinstance(obj, dict):\n        result = {}\n        for key, value in obj.items():\n            if SENSITIVE_KEY_PATTERNS.search(str(key)):\n                result[key] = '[REDACTED]'\n            else:\n                result[key] = sanitize_data(value)\n        return result\n    elif isinstance(obj, list):\n        return [sanitize_data(item) for item in obj]\n    elif isinstance(obj, str):\n        for pattern in SENSITIVE_VALUE_PATTERNS:\n            if pattern.search(obj):\n                return '[REDACTED]'\n        if len(obj) > MAX_FIELD_LENGTH:\n            hash_value = hashlib.sha256(obj.encode('utf-8', errors='ignore')).hexdigest()[:16]\n            return obj[:MAX_FIELD_LENGTH] + f'... [TRUNCATED-{hash_value}]'\n        return obj\n    return obj\n\ndef estimate_tokens(text: str) -> int:\n    \"\"\"Grobe Token-Schätzung (4 Zeichen = 1 Token)\"\"\"\n    if not text:\n        return 0\n    return max(1, len(text) \/\/ 4)\n\ndef get_session_tracking_key(data: Dict[str, Any]) -> str:\n    \"\"\"Erstellt einen eindeutigen Key für Session-Tracking\"\"\"\n    session_id = data.get('session_id', '')\n    event_name = data.get('hook_event_name', '')\n    tool_name = data.get('tool_name', '')\n\n    # Für Tool-Events: tool_name-spezifischer Key\n    if event_name in ['PreToolUse', 'PostToolUse'] and tool_name:\n        return f\"{session_id}_{tool_name}_{event_name}\"\n\n    # Für andere Events: event-spezifischer Key\n    return f\"{session_id}_{event_name}\"\n\ndef save_pending_request(data: Dict[str, Any], db_id: int) -> None:\n    \"\"\"Speichert pending Request für spätere Response-Zuordnung\"\"\"\n    try:\n        key = get_session_tracking_key(data)\n        tracking_file = TEMP_DIR \/ f\"{key}.json\"\n\n        tracking_data = {\n            'db_id': db_id,\n            'timestamp': datetime.datetime.now().isoformat(),\n            'event': data.get('hook_event_name'),\n            'tool_name': data.get('tool_name', ''),\n            'session_id': data.get('session_id', '')\n        }\n\n        with open(tracking_file, 'w') as f:\n            json.dump(tracking_data, f)\n\n    except Exception as e:\n        print(f\"Session tracking save error: {e}\", file=sys.stderr)\n\ndef find_matching_request(data: Dict[str, Any]) -> Optional[int]:\n    \"\"\"Findet matching Request für Response-Event\"\"\"\n    try:\n        event_name = data.get('hook_event_name', '')\n\n        # PostToolUse sucht nach PreToolUse\n        if event_name == 'PostToolUse':\n            search_data = dict(data)\n            search_data['hook_event_name'] = 'PreToolUse'\n            key = get_session_tracking_key(search_data)\n        else:\n            return None\n\n        tracking_file = TEMP_DIR \/ f\"{key}.json\"\n\n        if tracking_file.exists():\n            with open(tracking_file, 'r') as f:\n                tracking_data = json.load(f)\n\n            # Cleanup\n            tracking_file.unlink()\n            return tracking_data['db_id']\n\n    except Exception as e:\n        print(f\"Session tracking find error: {e}\", file=sys.stderr)\n\n    return None\n\ndef update_request_with_response(db_id: int, response_data: str) -> None:\n    \"\"\"Updated existing Request mit Response-Daten und berechnet Duration\"\"\"\n    try:\n        connection = pymysql.connect(**DB_CONFIG)\n\n        with connection.cursor() as cursor:\n            current_time = datetime.datetime.now()\n\n            # Erst Response und Timestamp setzen\n            tokens_output = estimate_tokens(response_data)\n\n            sql = \"\"\"\n                UPDATE protokoll\n                SET response = %s,\n                    response_timestamp = %s,\n                    tokens_output = %s,\n                    tokens_total = tokens_input + %s,\n                    status = 'completed'\n                WHERE id = %s\n            \"\"\"\n\n            cursor.execute(sql, (\n                response_data,\n                current_time,\n                tokens_output,\n                tokens_output,\n                db_id\n            ))\n\n            # Dann Duration aus Timestamps berechnen und setzen (mit Microsekunden-Präzision)\n            duration_sql = \"\"\"\n                UPDATE protokoll\n                SET duration_ms = ROUND(TIMESTAMPDIFF(MICROSECOND, request_timestamp, response_timestamp) \/ 1000, 3)\n                WHERE id = %s\n            \"\"\"\n\n            cursor.execute(duration_sql, (db_id,))\n\n            connection.commit()\n\n    except Exception as e:\n        print(f\"Database update error: {e}\", file=sys.stderr)\n    finally:\n        if 'connection' in locals():\n            connection.close()\n\n# Entfernt - Duration wird jetzt direkt in SQL berechnet\n\ndef log_to_database(data: Dict[str, Any]) -> Optional[int]:\n    \"\"\"Schreibt Protokoll-Eintrag in die Datenbank\"\"\"\n    try:\n        connection = pymysql.connect(**DB_CONFIG)\n\n        with connection.cursor() as cursor:\n            event_name = data.get('hook_event_name', 'Unknown')\n            session_id = data.get('session_id', '')\n            client_ip = get_client_ip()\n            client_name = os.environ.get('USER', 'unknown')\n            current_time = datetime.datetime.now()\n\n            # Prüfe auf matching Request für Response-Events\n            if event_name == 'PostToolUse':\n                matching_request_id = find_matching_request(data)\n                if matching_request_id:\n                    # Update existing request mit response\n                    tool_response = sanitize_data(data.get('tool_response', {}))\n                    response_str = json.dumps({'tool_response': tool_response}, ensure_ascii=False)\n\n                    # Duration wird automatisch aus Timestamps berechnet\n                    update_request_with_response(matching_request_id, response_str)\n                    return matching_request_id\n\n            # Normale Event-Verarbeitung für neue Einträge\n            request_data = {}\n            response_data = None\n            model_name = 'claude-sonnet-4-20250514'\n\n            if event_name == 'UserPromptSubmit':\n                request_data = {\n                    'event': event_name,\n                    'prompt': sanitize_data(data.get('prompt', ''))\n                }\n\n            elif event_name == 'PreToolUse':\n                tool_name = data.get('tool_name', '')\n                tool_input = sanitize_data(data.get('tool_input', {}))\n                request_data = {\n                    'event': event_name,\n                    'tool_name': tool_name,\n                    'tool_input': tool_input\n                }\n\n            elif event_name in ['Stop', 'SubagentStop']:\n                request_data = {\n                    'event': event_name,\n                    'stop_hook_active': data.get('stop_hook_active', False)\n                }\n                response_data = {'status': 'completed'}\n\n            elif event_name in ['SessionStart', 'SessionEnd']:\n                request_data = {\n                    'event': event_name,\n                    'source': data.get('source', data.get('reason', ''))\n                }\n                response_data = {'status': 'logged'}\n\n            else:\n                request_data = {\n                    'event': event_name,\n                    'raw_data': sanitize_data(data)\n                }\n\n            # JSON zu String konvertieren\n            request_str = json.dumps(request_data, ensure_ascii=False)\n            response_str = json.dumps(response_data, ensure_ascii=False) if response_data else None\n\n            # Token-Schätzung\n            tokens_input = estimate_tokens(request_str)\n            tokens_output = estimate_tokens(response_str) if response_str else 0\n            tokens_total = tokens_input + tokens_output\n\n            # Status bestimmen\n            status = 'completed' if response_str else 'pending'\n            response_timestamp = current_time if response_str else None\n\n            # SQL Insert\n            sql = \"\"\"\n                INSERT INTO protokoll (\n                    timestamp, request_ip, client_name, request, request_timestamp,\n                    response, response_timestamp, duration_ms, tokens_input,\n                    tokens_output, tokens_total, model_name, status\n                ) VALUES (\n                    %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s\n                )\n            \"\"\"\n\n            cursor.e... [TRUNCATED-f90f7fbb1e9159c5]",
            "numLines": 334,
            "startLine": 1,
            "totalLines": 334
        }
    }
}
← Vorheriger Zur Liste Nächster →