$context */ public static function send(string $summary, array $context = []): void { Log::channel('monitoring')->critical('[monitoring] '.$summary, $context); if (! config('monitoring.alerts.enabled')) { return; } self::mail($summary, $context); self::push($summary, $context); } /** * @param array $context */ private static function mail(string $summary, array $context): void { $recipient = config('monitoring.alerts.mail_to'); if (empty($recipient)) { return; } try { Mail::to($recipient)->send(new OperationalAlertMail($summary, $context)); } catch (Throwable $e) { Log::channel('monitoring')->error('[monitoring] Alert-Mail konnte nicht versendet werden.', [ 'summary' => $summary, 'error' => $e->getMessage(), ]); } } /** * JSON-Publish an die ntfy-Basis-URL (UTF-8-sicher, anders als Header). * * @param array $context */ private static function push(string $summary, array $context): void { $url = (string) config('monitoring.alerts.ntfy.url'); $topic = (string) config('monitoring.alerts.ntfy.topic'); if ($url === '' || $topic === '') { return; } $details = collect($context) ->map(fn (mixed $value, string $key): string => $key.': '.(is_scalar($value) || $value === null ? (string) $value : json_encode($value))) ->implode("\n"); try { Http::timeout(5) ->connectTimeout(3) ->when(filled(config('monitoring.alerts.ntfy.token')), fn ($request) => $request->withToken((string) config('monitoring.alerts.ntfy.token'))) ->post(rtrim($url, '/'), [ 'topic' => $topic, 'title' => config('app.name').' ('.app()->environment().'): Betriebs-Alert', 'message' => Str::limit(trim($summary."\n\n".$details), 3500), 'priority' => (int) config('monitoring.alerts.ntfy.priority'), 'tags' => ['rotating_light'], ]) ->throw(); } catch (Throwable $e) { Log::channel('monitoring')->error('[monitoring] ntfy-Push konnte nicht versendet werden.', [ 'summary' => $summary, 'error' => $e->getMessage(), ]); } } }