webhookUrl = env('SLACK_ALERT_WEBHOOK_URL') ?: env('LOG_SLACK_WEBHOOK_URL'); $this->serverName = env('SLACK_ALERT_SERVER_NAME', config('app.env', 'unknown')); $this->enabled = (bool) env('SLACK_ALERT_ENABLED', false); } /** * 일반 알림 전송 */ public function sendAlert(string $title, string $message, string $severity = 'critical'): void { $color = match ($severity) { 'critical' => '#FF0000', 'warning' => '#FFA500', default => '#3498DB', }; $emoji = match ($severity) { 'critical' => ':rotating_light:', 'warning' => ':warning:', default => ':information_source:', }; $this->send([ 'attachments' => [ [ 'color' => $color, 'blocks' => [ [ 'type' => 'header', 'text' => [ 'type' => 'plain_text', 'text' => "{$emoji} {$title}", 'emoji' => true, ], ], [ 'type' => 'section', 'fields' => [ [ 'type' => 'mrkdwn', 'text' => "*서버:*\n{$this->serverName}", ], [ 'type' => 'mrkdwn', 'text' => '*시간:*\n' . now()->format('Y-m-d H:i:s'), ], ], ], [ 'type' => 'section', 'text' => [ 'type' => 'mrkdwn', 'text' => "*상세:*\n{$message}", ], ], [ 'type' => 'context', 'elements' => [ [ 'type' => 'mrkdwn', 'text' => "환경: `" . config('app.env') . "` | 심각도: `{$severity}`", ], ], ], ], ], ], ]); } /** * 백업 실패 알림 */ public function sendBackupAlert(string $title, string $message): void { $this->sendAlert("[SAM 백업 실패] {$title}", $message, 'critical'); } /** * 통계/모니터링 알림 */ public function sendStatAlert(string $title, string $message, string $domain): void { $this->sendAlert("[SAM {$domain}] {$title}", $message, 'critical'); } /** * Slack 웹훅으로 메시지 전송 */ private function send(array $payload): void { if (! $this->enabled) { Log::debug('Slack 알림 비활성화 상태 (SLACK_ALERT_ENABLED=false)'); return; } if (empty($this->webhookUrl)) { Log::warning('Slack 웹훅 URL 미설정'); return; } try { $response = Http::timeout(10)->post($this->webhookUrl, $payload); if (! $response->successful()) { Log::error('Slack 알림 전송 실패', [ 'status' => $response->status(), 'body' => $response->body(), ]); } } catch (\Throwable $e) { Log::error('Slack 알림 전송 예외', [ 'error' => $e->getMessage(), ]); } } }