From e7beefd594d34cfc30cf980fab0ef02bcae8b921 Mon Sep 17 00:00:00 2001 From: hskwon Date: Mon, 15 Dec 2025 16:33:58 +0900 Subject: [PATCH] =?UTF-8?q?API=20=EB=A1=9C=EA=B7=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B0=9C=EC=84=A0:=20=ED=85=8C=EB=84=8C=ED=8A=B8/?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=91=9C=EC=8B=9C,=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=ED=95=91,=20AI=20=EB=B6=84=EC=84=9D=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 테넌트/사용자 컬럼 추가 (관계 eager loading) - 그룹 ID로 연관 API 호출 필터링 및 상세 페이지에서 그룹 목록 표시 - 상태 400 이상일 때 AI 분석용 복사 버튼 추가 - Tenant 모델 네임스페이스 수정 (Tenants\Tenant) --- app/Http/Controllers/ApiLogController.php | 27 ++++- app/Models/ApiRequestLog.php | 57 ++++++++++- resources/views/api-logs/index.blade.php | 45 ++++++-- resources/views/api-logs/show.blade.php | 119 ++++++++++++++++++++-- 4 files changed, 230 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/ApiLogController.php b/app/Http/Controllers/ApiLogController.php index 4ccebcd7..753af441 100644 --- a/app/Http/Controllers/ApiLogController.php +++ b/app/Http/Controllers/ApiLogController.php @@ -13,7 +13,9 @@ class ApiLogController extends Controller */ public function index(Request $request): View { - $query = ApiRequestLog::query()->orderByDesc('created_at'); + $query = ApiRequestLog::query() + ->with(['tenant', 'user']) + ->orderByDesc('created_at'); // 필터: HTTP 메서드 if ($request->filled('method')) { @@ -37,6 +39,16 @@ public function index(Request $request): View $query->where('url', 'like', '%' . $request->search . '%'); } + // 필터: 그룹 ID + if ($request->filled('group_id')) { + $query->where('group_id', $request->group_id); + } + + // 필터: 테넌트 + if ($request->filled('tenant_id')) { + $query->where('tenant_id', $request->tenant_id); + } + // 통계 $stats = [ 'total' => ApiRequestLog::count(), @@ -56,9 +68,18 @@ public function index(Request $request): View */ public function show(int $id): View { - $log = ApiRequestLog::findOrFail($id); + $log = ApiRequestLog::with(['tenant', 'user'])->findOrFail($id); - return view('api-logs.show', compact('log')); + // 같은 그룹의 다른 요청들 + $groupLogs = []; + if ($log->group_id) { + $groupLogs = ApiRequestLog::where('group_id', $log->group_id) + ->where('id', '!=', $log->id) + ->orderBy('created_at') + ->get(); + } + + return view('api-logs.show', compact('log', 'groupLogs')); } /** diff --git a/app/Models/ApiRequestLog.php b/app/Models/ApiRequestLog.php index 11e71fb8..245d9252 100644 --- a/app/Models/ApiRequestLog.php +++ b/app/Models/ApiRequestLog.php @@ -21,11 +21,12 @@ * @property string|null $user_agent * @property int|null $user_id * @property int|null $tenant_id + * @property string|null $group_id * @property \Carbon\Carbon $created_at */ class ApiRequestLog extends Model { - protected $table = 'api_request_logs'; + protected $table = 'api_request_logs'; public $timestamps = false; @@ -43,6 +44,7 @@ class ApiRequestLog extends Model 'user_agent', 'user_id', 'tenant_id', + 'group_id', ]; protected $casts = [ @@ -99,4 +101,57 @@ public static function pruneOldLogs(): int { return static::where('created_at', '<', now()->subDay())->delete(); } + + /** + * 테넌트 관계 + */ + public function tenant() + { + return $this->belongsTo(\App\Models\Tenants\Tenant::class); + } + + /** + * 사용자 관계 + */ + public function user() + { + return $this->belongsTo(\App\Models\User::class); + } + + /** + * AI 분석용 요약 텍스트 생성 + */ + public function getAiAnalysisSummaryAttribute(): string + { + $responseData = json_decode($this->response_body, true); + $errorMessage = $responseData['message'] ?? $responseData['error'] ?? ''; + + $summary = "## API 에러 분석 요청\n\n"; + $summary .= "### 요청 정보\n"; + $summary .= "- **메서드**: {$this->method}\n"; + $summary .= "- **URL**: {$this->url}\n"; + $summary .= "- **라우트**: " . ($this->route_name ?? 'N/A') . "\n"; + $summary .= "- **상태 코드**: {$this->response_status}\n"; + $summary .= "- **응답 시간**: {$this->duration_ms}ms\n"; + $summary .= "- **요청 시간**: {$this->created_at->format('Y-m-d H:i:s')}\n\n"; + + if ($this->request_body && count($this->request_body) > 0) { + $summary .= "### 요청 바디\n```json\n"; + $summary .= json_encode($this->request_body, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $summary .= "\n```\n\n"; + } + + $summary .= "### 응답 내용\n```json\n"; + if ($responseData) { + $summary .= json_encode($responseData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } else { + $summary .= $this->response_body ?? 'N/A'; + } + $summary .= "\n```\n\n"; + + $summary .= "### 분석 요청\n"; + $summary .= "위 API 에러의 원인을 분석하고 해결 방법을 제안해주세요.\n"; + + return $summary; + } } \ No newline at end of file diff --git a/resources/views/api-logs/index.blade.php b/resources/views/api-logs/index.blade.php index f6a4d993..f2547054 100644 --- a/resources/views/api-logs/index.blade.php +++ b/resources/views/api-logs/index.blade.php @@ -87,6 +87,14 @@ class="w-full border rounded-lg px-3 py-2 text-sm"> + +@if(request('group_id')) +
+ 그룹 ID: {{ request('group_id') }} 필터 적용 중 + 필터 해제 +
+@endif +
@@ -96,15 +104,16 @@ class="w-full border rounded-lg px-3 py-2 text-sm"> - - - + + + + @forelse($logs as $log) - + @@ -113,7 +122,7 @@ class="w-full border rounded-lg px-3 py-2 text-sm"> {{ $log->method }} - + @empty - diff --git a/resources/views/api-logs/show.blade.php b/resources/views/api-logs/show.blade.php index 6d15fe1f..a5506225 100644 --- a/resources/views/api-logs/show.blade.php +++ b/resources/views/api-logs/show.blade.php @@ -13,6 +13,14 @@

API 로그 상세

+ @if($log->response_status >= 400) + + @endif @@ -49,7 +57,7 @@ -
+
라우트
{{ $log->route_name ?? '-' }}
@@ -59,16 +67,98 @@
{{ $log->ip_address ?? '-' }}
-
User ID
-
{{ $log->user_id ?? 'guest' }}
+
테넌트
+
+ @if($log->tenant) + {{ $log->tenant->company_name }} (#{{ $log->tenant_id }}) + @else + {{ $log->tenant_id ?? '-' }} + @endif +
-
Tenant ID
-
{{ $log->tenant_id ?? '-' }}
+
사용자
+
+ @if($log->user) + {{ $log->user->name ?? $log->user->email }} (#{{ $log->user_id }}) + @else + {{ $log->user_id ? "#{$log->user_id}" : 'guest' }} + @endif +
+
+
+
그룹 ID
+
+ @if($log->group_id) + + {{ Str::limit($log->group_id, 20) }} + + @else + - + @endif +
+ +@if($log->group_id && count($groupLogs) > 0) +
+

+ 같은 그룹의 요청 ({{ count($groupLogs) }}개) + + 전체 보기 + +

+
+
메서드 URL 상태시간IPUser응답테넌트사용자그룹
{{ $log->created_at->format('H:i:s') }} + {{ $log->path }} @@ -125,10 +134,30 @@ class="w-full border rounded-lg px-3 py-2 text-sm"> {{ number_format($log->duration_ms) }}ms - {{ $log->ip_address ?? '-' }} + @if($log->tenant) + {{ Str::limit($log->tenant->company_name, 10) }} + @else + - + @endif - {{ $log->user_id ?? 'guest' }} + @if($log->user) + {{ $log->user->name ?? $log->user->email }} + @else + guest + @endif + + @if($log->group_id) + + + + + + @else + - + @endif @@ -138,7 +167,7 @@ class="w-full border rounded-lg px-3 py-2 text-sm">
+ 로그가 없습니다.
+ + + + + + + + + + + + @foreach($groupLogs as $gLog) + + + + + + + + + @endforeach + +
시간메서드URL상태응답
+ {{ $gLog->created_at->format('H:i:s.u') }} + + + {{ $gLog->method }} + + + {{ $gLog->path }} + + + {{ $gLog->response_status }} + + + {{ number_format($gLog->duration_ms) }}ms + + + 상세 + +
+
+ +@endif + @if($log->user_agent)
@@ -125,4 +215,21 @@
@endif -@endsection \ No newline at end of file + +@if($log->response_status >= 400) + + + + +@endif +@endsection