format('YmdH'); $prefix = sprintf('ai-editing:obs:tenant:%d:event:%d:hour:%s', $request->tenant_id, $request->event_id, $bucket); Cache::add($prefix.':total', 0, now()->addHours(8)); $total = (int) Cache::increment($prefix.':total'); if ($status === AiEditRequest::STATUS_SUCCEEDED) { Cache::add($prefix.':succeeded', 0, now()->addHours(8)); Cache::increment($prefix.':succeeded'); } elseif ($status === AiEditRequest::STATUS_BLOCKED) { Cache::add($prefix.':blocked', 0, now()->addHours(8)); Cache::increment($prefix.':blocked'); } elseif ($status === AiEditRequest::STATUS_FAILED) { Cache::add($prefix.':failed', 0, now()->addHours(8)); Cache::increment($prefix.':failed'); } if ($moderationBlocked) { Cache::add($prefix.':moderation_blocked', 0, now()->addHours(8)); Cache::increment($prefix.':moderation_blocked'); } if (is_int($durationMs) && $durationMs > 0) { Cache::add($prefix.':duration_total_ms', 0, now()->addHours(8)); Cache::increment($prefix.':duration_total_ms', $durationMs); $latencyWarningMs = max(500, (int) config('ai-editing.observability.latency_warning_ms', 15000)); if ($durationMs >= $latencyWarningMs) { Log::warning('AI provider latency warning', [ 'tenant_id' => $request->tenant_id, 'event_id' => $request->event_id, 'request_id' => $request->id, 'duration_ms' => $durationMs, 'threshold_ms' => $latencyWarningMs, 'stage' => $stage, ]); } } $this->checkFailureRateAlert($request, $prefix, $total, $stage); } private function checkFailureRateAlert(AiEditRequest $request, string $prefix, int $total, string $stage): void { $minSamples = max(1, (int) config('ai-editing.observability.failure_rate_min_samples', 10)); if ($total < $minSamples) { return; } $failed = (int) (Cache::get($prefix.':failed', 0) ?: 0); $threshold = (float) config('ai-editing.observability.failure_rate_alert_threshold', 0.35); $failureRate = $total > 0 ? ($failed / $total) : 0.0; if ($failureRate < $threshold) { return; } $cooldownKey = $prefix.':failure_rate_alert'; if (! Cache::add($cooldownKey, 1, now()->addMinutes(30))) { return; } Log::warning('AI failure-rate alert threshold reached', [ 'tenant_id' => $request->tenant_id, 'event_id' => $request->event_id, 'failure_rate' => round($failureRate, 5), 'failed' => $failed, 'total' => $total, 'threshold' => $threshold, 'stage' => $stage, ]); } }