aboutsummaryrefslogtreecommitdiff
path: root/api/debug/ingest.php
blob: 363dd094a595d17c32011f11c6995c5c7aa12698 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php
require_once __DIR__ . '/config.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    error_response('Method not allowed', 405);
}

$raw = file_get_contents('php://input');
if (!$raw) error_response('Empty request body');

$payload = json_decode($raw, true);
if (!$payload || !isset($payload['errors']) || !is_array($payload['errors'])) {
    error_response('Invalid payload: expected { errors: [...], session: {...} }');
}

$errors = $payload['errors'];
$session = $payload['session'] ?? [];
$clientVersion = $payload['version'] ?? 'unknown';

// Validate batch size
if (count($errors) > MAX_ERRORS_PER_BATCH) {
    $errors = array_slice($errors, 0, MAX_ERRORS_PER_BATCH);
}
if (strlen($raw) > MAX_BATCH_SIZE_KB * 1024) {
    error_response('Payload too large (max ' . MAX_BATCH_SIZE_KB . 'KB)', 413);
}

// Load existing data
$allErrors = load_json(ERRORS_FILE);
$sessions = load_json(SESSIONS_FILE);

$sessionId = $session['id'] ?? ('anon_' . bin2hex(random_bytes(4)));
$ingested = 0;
$now = date('c');

// Update session metadata
if (!isset($sessions[$sessionId])) {
    $sessions[$sessionId] = [
        'id' => $sessionId,
        'first_seen' => $now,
        'version' => $clientVersion,
        'modules' => $session['modules'] ?? [],
        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
        'error_count' => 0,
    ];
}
$sessions[$sessionId]['last_seen'] = $now;
$sessions[$sessionId]['error_count'] += count($errors);
$sessions[$sessionId]['version'] = $clientVersion;

// Build a lookup of existing hashes for dedup
$existingHashes = [];
foreach ($allErrors as $idx => $existing) {
    $h = $existing['hash'] ?? '';
    if ($h) {
        $existingHashes[$h] = $idx;
    }
}

// Ingest each error (dedup by hash)
foreach ($errors as $e) {
    $hash = $e['hash'] ?? md5(($e['message'] ?? '') . '|' . ($e['source'] ?? '') . '|' . ($e['type'] ?? ''));
    
    if (isset($existingHashes[$hash])) {
        // Update existing entry
        $idx = $existingHashes[$hash];
        $allErrors[$idx]['count'] += $e['count'] ?? 1;
        $allErrors[$idx]['last_seen'] = $e['lastSeen'] ?? $now;
        $allErrors[$idx]['ingested_at'] = $now;
        if (!empty($e['stack'])) {
            $allErrors[$idx]['stack'] = substr($e['stack'], 0, 2000);
        }
        $ingested++;
        continue;
    }
    
    $entry = [
        'hash' => $hash,
        'message' => substr($e['message'] ?? '', 0, 500),
        'type' => $e['type'] ?? 'unknown',
        'severity' => $e['severity'] ?? 'error',
        'category' => $e['category'] ?? 'runtime',
        'source' => $e['source'] ?? '',
        'module' => $e['module'] ?? null,
        'stack' => isset($e['stack']) ? substr($e['stack'], 0, 2000) : null,
        'session_id' => $sessionId,
        'count' => $e['count'] ?? 1,
        'first_seen' => $e['firstSeen'] ?? $now,
        'last_seen' => $e['lastSeen'] ?? $now,
        'ingested_at' => $now,
    ];
    
    $allErrors[] = $entry;
    $existingHashes[$hash] = count($allErrors) - 1;
    $ingested++;
}

// Trim to last 10,000 errors (ring buffer)
if (count($allErrors) > 10000) {
    $allErrors = array_slice($allErrors, -10000);
}

save_json(ERRORS_FILE, $allErrors);
save_json(SESSIONS_FILE, $sessions);

// Rebuild clusters asynchronously (flag file)
$rebuildFlag = DEBUG_DATA_DIR . '/.rebuild_clusters';
file_put_contents($rebuildFlag, time());

json_response([
    'success' => true,
    'ingested' => $ingested,
    'session_id' => $sessionId,
    'total_errors' => count($allErrors),
    'total_sessions' => count($sessions),
]);