# Log Görüntüleyici

`storage/logs/` altındaki Laravel log dosyalarını okumak, aramak ve silmek için yalnızca bakım rolüne açık bir admin bölümü. Kendi içinde tamamlanmış bir domain modülü olarak gelir; ek bağımlılık gerektirmez.

## Yetkilendirme

Tüm modül **yalnızca `system_admin` rolüne** açıktır — `config/permission-resources.php` içinde girdi yoktur. `system_admin` olmayan kullanıcılar route'a 403 alır ve menü öğesini hiç görmez; yani özellik onlara görünmez.

`routes/web/log-route.php` dosyası `routes/web.php` içindeki `$routesWithoutPermissionMiddleware` listesinde olduğu için bu grup için dinamik `check.permission` middleware'i atlanır.

## Route'lar

Route dosyası, authenticated web grubunun içinde yüklenir. İsimli route'lar:

- `logs.index` — `GET /logs` (Inertia)
- `logs.dtApi` — `GET /logs/dt` (JSON datatable feed)
- `logs.show` — `GET /logs/{filename}` (Inertia)
- `logs.entries` — `GET /logs/{filename}/entries` (JSON, sayfalı)
- `logs.destroy` — `DELETE /logs` (JSON, toplu silme)

`filename` parametreleri `where('filename', '[A-Za-z0-9._-]+\.log')` kısıtıyla sınırlandırıldığı için path traversal ve `.log` olmayan istekler controller'a hiç ulaşmaz.

## UI

`resources/js/pages/Admin/Logs/` altında iki Inertia sayfası vardır:

### `Index.vue`

`logs.dtApi` üzerinden beslenen `SkDatatable`. Kolonlar:

- `name` — dosya adı (sortable, substring araması)
- `channel_type` — `daily` (`laravel-YYYY-MM-DD.log` formatına uyanlar), `single` (`laravel.log`) veya `other`
- `size_bytes` — KB / MB / GB olarak formatlanır
- `modified_at` — relatif zaman + tooltip'te mutlak zaman
- `is_active` — dosya canlı günlük dosyası ya da son 5 saniyedir yazılıyorsa chip görünür

Satır aksiyonu: **Sil** (`is_active` ise pasif). Bulk select toolbar'ında **Seçiliyi sil** aksiyonu vardır; ikisi de `logs.destroy` üzerinden akar ve `useConfirm` ile onaylatılır.

### `Show.vue`

Tek dosya için filtre paneli + sayfalı görüntüleyici. Filtreler:

- `levels[]` — sekiz Laravel/PSR-3 seviyesinden çoklu seçim (`emergency`, `alert`, `critical`, `error`, `warning`, `notice`, `info`, `debug`)
- `from`, `to` — ISO tarih aralığı
- `keyword` — mesaj + stack trace içinde case-insensitive substring araması

Kayıtlar **en yeniden eskiye** listelenir. Filtre değişiklikleri `useApi` üzerinden `logs.entries` çağrısı yapar, listeyi yeniler ve cursor'ı sıfırlar. **Daha fazla yükle**, önceki yanıttaki `next_cursor` ile bir sonraki (daha eski) sayfayı listenin altına ekler. `eof` flag'i true ise butonu kapatır.

Her kayıt level chip + timestamp + mesajın baş kısmı olarak çökertilmiş gelir. Açıldığında tam mesaj, JSON pretty-print edilmiş `context` (varsa) ve stack trace görünür.

## Domain Katmanı

Logs domain'i **vendor-resident**'tir — runtime katmanı paket içinden çalışır (`src/Domain/Logs/`, namespace `Lvntr\StarterKit\Domain\Logs\`) ve uygulamanıza scaffold edilmez. `App\Domain\Logs\...` import'ları `class_alias` ile çalışmaya devam eder.

```
src/Domain/Logs/   (Lvntr\StarterKit\Domain\Logs\)
├── DTOs/
│   ├── LogFileDTO.php          # name, path, size_bytes, modified_at, channel_type, is_active
│   ├── LogEntryDTO.php         # timestamp, level, env, message, context, stack, is_raw
│   ├── LogEntryFilterDTO.php   # levels, from, to, keyword, cursor, per_page
│   └── DeleteLogFilesDTO.php   # filenames[]
├── Queries/
│   ├── LogFileQuery.php        # storage/logs/'i listeler; filtre/sıralama/sayfalama in-memory
│   └── LogEntryQuery.php       # tek dosyayı cursor sayfalama ile stream eder
├── Actions/
│   └── DeleteLogFilesAction.php # active-file koruması ile toplu silme
├── Events/
│   └── LogFilesDeleted.php     # silinen filenames[] + causer id taşır
├── Listeners/
│   └── LogActivityForLogFilesDeleted.php  # her batch için bir spatie/activitylog kaydı yazar
└── Services/
    └── LaravelLogParser.php    # stateless satır parser'ı; çok satırlı stack trace farkındalığı var
```

`LogFilesDeleted → LogActivityForLogFilesDeleted` eşleşmesi `StarterKitServiceProvider::registerEventListeners()` (vendor) içinde register edilir; her iki taraf da vendor FQCN'ine bağlanır, böylece dispatch edilen event registration anahtarıyla eşleşir.

### Streaming Kayıt Okuyucu

`LogEntryQuery::paginate()` dosyayı `fopen('rb')` ile açar ve satır başına 64KB ile sınırlı `fgets()` döngüsünü kullanır. Log dosyası sonundan büyüdüğü için sayfa **geriye doğru** kurulur: pencere `cursor` offset'inde biter (hariç) ve dosya başına doğru 64KB'lık adımlarla, istek başına 2MB tavanına kadar genişler. Pencere sonra ileri yönde parse edilir (kayıtlar yalnızca yazım sırasına göre okunabilir) ve ters çevrilerek döndürülür. Cursor, sayfanın EN ESKİ kaydının başladığı byte offset'idir; bir sonraki sayfa tek `fseek` ile devam eder. Bellek kullanımı dosya boyutundan bağımsız olarak sabit kalır.

Eşleşmeyen satırlar konumlarına göre işlenir:

- bir başlığın ardından gelen satırlar — mevcut kaydın `stack` alanına eklenir; çok satırlı exception'ların bütünlüğü korunur
- ilk başlıktan önceki (veya hiç Laravel-format başlığı içermeyen dosyalarda kalan) satırlar — buffer'lanır ve tek bir raw `LogEntryDTO` olarak basılır (`is_raw = true`, `level = 'raw'`, sentinel epoch-0 timestamp). UI raw entry'lerin zaman damgasını gizler ve gri chip ile gösterir; böylece içerik sessizce kaybolmak yerine görünür kalır. Yapısal filtre uygulandığı an (level / from / to / keyword) raw entry'ler doğal olarak listeden düşer.

### Aktif Dosya Koruması

Bir dosya iki koşuldan **biri** sağlanırsa "aktif" sayılır ve silinmez:

- bugünün günlük dosyasıdır (`laravel-{today}.log`), veya
- `mtime`'ı son 5 saniye içindedir (başka bir kanal şu an dosyaya yazıyor olabilir).

Aktif dosyalar `failed[]` listesinde `reason: 'active_file_protected'` ile döner; böylece kısmi toplu silme geri kalan dosyalar için başarılı olur.

### Path-Traversal Koruması

Güvenli dosya adı regex'i `^[A-Za-z0-9._-]+\.log$` üç farklı yerde zorlanır: route parameter kısıtı, `DeleteLogFilesRequest` validasyonu ve `DeleteLogFilesAction` (defence in depth). Geri kalan her şey `log.invalid_filename` döner.

## Activity Log

Bir silme batch'i en az bir dosya silebildiğinde `LogFilesDeleted` dispatch edilir. Listener `log_name = 'system'`, subject yok, silinen dosyalar `properties.filenames` altında ve causer olarak mevcut kullanıcı ile bir `spatie/activitylog` kaydı yazar. Kayıt mevcut **Admin → Activity Logs** sayfasında otomatik görünür.

## i18n

Tüm metinler `lang/en/sk-log.php` ve `lang/tr/sk-log.php` altında. Menü key'i (`sk-menu.logs`) mevcut menü çeviri dosyalarında. Action'ın döndüğü hata sebep kodları (`invalid_filename`, `not_found`, `active_file_protected`, `delete_failed`) bire bir UI tarafındaki `sk-log.reason_*` key'lerine eşlenir.

## Wayfinder

Route'lar tiplidir: `import logs from '@/routes/logs'` ile `logs.index.url()`, `logs.show.url({ filename })` vs. erişilir. Frontend'de URL hardcoded değildir.

## Kapsam Dışı (v1)

Yapılmadı; ihtiyaç duyulursa follow-up issue olarak açılabilir:

- live tail / WebSocket streaming
- tüm log dosyalarında cross-file arama
- zaman tabanlı toplu temizlik ("N günden eski dosyaları sil") — günlük kanal bunu zaten yapıyor
- `.zip` export / indirme
- Laravel-dışı log formatları (Apache, JSON channel)
- `system_admin` dışında kullanıcı tabanlı yetki ayrımı
