|
| 1 | +# LogT — Современный Explorer логов (TUI) |
| 2 | + |
| 3 | +> **Легковесная альтернатива lnav** с упором на UX, авто-парсинг JSON и мгновенную фильтрацию. |
| 4 | +
|
| 5 | + |
| 6 | + |
| 7 | +[]() |
| 8 | + |
| 9 | +## 🚀 Возможности |
| 10 | + |
| 11 | +### Основные |
| 12 | +- **Мульти-source tailing** — Слежение за несколькими файлами по шаблону (`./logs/*.log`) |
| 13 | +- **Ring Buffer** — Хранит последние 5000 строк в памяти (настраивается) |
| 14 | +- **Stdin Support** — `cat app.log | logt` |
| 15 | +- **Log Forwarding** — Экспорт отфильтрованных логов в файл (`--forward`) или stdout (`--forward -`) |
| 16 | + |
| 17 | +### Просмотр |
| 18 | +- **Live Tail** — Автопрокрутка при поступлении новых строк |
| 19 | +- **Подсветка синтаксиса** — Цветовая кодировка уровней (INFO=синий, WARN=желтый, ERROR=красный) |
| 20 | +- **JSON Expand** — Нажмите Enter на JSON строке для разворачивания в полноэкранное дерево |
| 21 | + |
| 22 | +### Интерактивность |
| 23 | +- **Fuzzy Filter** — Нажмите `/` для мгновенной фильтрации |
| 24 | +- **Regex Filter** — Нажмите `r` для режима регулярных выражений |
| 25 | +- **Pause/Resume** — Нажмите `Space` для паузы автопрокрутки |
| 26 | +- **Source Toggle** — Нажмите `Tab` для показа/скрытия панели источников |
| 27 | +- **JSON Explorer** — Нажмите `Enter` на JSON строке для просмотра ключей |
| 28 | + |
| 29 | +## 🚀 Тесты производительности |
| 30 | + |
| 31 | +Протестировано на **Intel Core i3-10100 @ 3.6 GHz**: |
| 32 | + |
| 33 | +| Операция | Скорость | Примечания | |
| 34 | +|-----------|-------|-------| |
| 35 | +| RingBuffer Add | **~27M ops/sec** | Потокобезопасный, блокировка-free чтение | |
| 36 | +| JSON Парсинг | **~314K строк/sec** | Авто-детект + парсинг | |
| 37 | +| Fuzzy Фильтр | **~3.7M совпадений/sec** | Без учёта регистра | |
| 38 | +| Определение уровня | **~394K строк/sec** | На основе регулярных выражений | |
| 39 | +| IsValidJSON | **~1.3M проверок/sec** | Быстрая валидация | |
| 40 | + |
| 41 | +**Память**: Ring buffer ограничен ~2MB для 5000 строк (настраивается) |
| 42 | + |
| 43 | +*LogT разработан для высоконагруженных окружений с минимальным потреблением CPU/RAM.* |
| 44 | + |
| 45 | +## 📦 Установка |
| 46 | + |
| 47 | +### Из исходников |
| 48 | +```bash |
| 49 | +# Клонировать репозиторий |
| 50 | +git clone https://github.com/turkprogrammer/logt.git |
| 51 | +cd logt |
| 52 | + |
| 53 | +# Собрать бинарник |
| 54 | +go build -o logt ./cmd/logt |
| 55 | + |
| 56 | +# Или установить в GOPATH |
| 57 | +go install ./cmd/logt |
| 58 | +``` |
| 59 | + |
| 60 | +### Предсобранные бинарники |
| 61 | +Скачать с [Releases](https://github.com/turkprogrammer/logt/releases) |
| 62 | + |
| 63 | +> **Примечание:** Если репозиторий ещё не опубликован, используйте локальную разработку: |
| 64 | +> ```bash |
| 65 | +> go build -o logt ./cmd/logt |
| 66 | +> ``` |
| 67 | +
|
| 68 | +## 🛠️ Использование |
| 69 | +
|
| 70 | +### Базовое |
| 71 | +```bash |
| 72 | +# Следить за одним файлом |
| 73 | +logt ./app.log |
| 74 | +
|
| 75 | +# Следить за несколькими файлами по шаблону |
| 76 | +logt ./logs/*.log |
| 77 | +
|
| 78 | +# Фильтр по уровню |
| 79 | +logt --level error ./app.log |
| 80 | +
|
| 81 | +# Stdin pipe |
| 82 | +cat app.log | logt |
| 83 | +``` |
| 84 | +
|
| 85 | +### Тестовые логи |
| 86 | + |
| 87 | +#### Windows (PowerShell) |
| 88 | +```powershell |
| 89 | +# Простой JSON (с кавычками) |
| 90 | +echo '{"level":"info","msg":"Server started"}' | Out-File -FilePath app.log -Encoding utf8 |
| 91 | +
|
| 92 | +# JSON с доп. полями |
| 93 | +echo '{"level":"error","msg":"Connection failed","host":"db1","port":5432}' | Out-File app.log -Encoding utf8 |
| 94 | +
|
| 95 | +# Несколько строк |
| 96 | +@' |
| 97 | +{"level":"info","msg":"Starting"} |
| 98 | +{"level":"warn","msg":"Low memory"} |
| 99 | +{"level":"error","msg":"OOM killed"} |
| 100 | +{"level":"debug","msg":"GC done"} |
| 101 | +'@ | Out-File app.log -Encoding utf8 |
| 102 | +
|
| 103 | +# Запуск |
| 104 | +.\logt.exe app.log |
| 105 | +
|
| 106 | +# Pipe |
| 107 | +Get-Content app.log | .\logt.exe |
| 108 | +``` |
| 109 | + |
| 110 | +#### Windows (CMD) |
| 111 | +```cmd |
| 112 | +# Простой JSON |
| 113 | +echo {"level":"info","msg":"Server started"} > app.log |
| 114 | +
|
| 115 | +# JSON с доп. полями - используем printf для кавычек |
| 116 | +printf "{\"level\":\"error\",\"msg\":\"Connection failed\",\"host\":\"db1\",\"port\":5432}\n" > app.log |
| 117 | +
|
| 118 | +# Несколько строк (CMD) |
| 119 | +( |
| 120 | + echo {"level":"info","msg":"Starting"} |
| 121 | + echo {"level":"warn","msg":"Low memory"} |
| 122 | + echo {"level":"error","msg":"OOM killed"} |
| 123 | + echo {"level":"debug","msg":"GC done"} |
| 124 | +) > app.log |
| 125 | +
|
| 126 | +# Запуск |
| 127 | +logt.exe app.log |
| 128 | +
|
| 129 | +# Pipe |
| 130 | +type app.log | logt.exe |
| 131 | +``` |
| 132 | + |
| 133 | +#### Linux / macOS |
| 134 | +```bash |
| 135 | +# Простой JSON |
| 136 | +echo '{"level":"info","msg":"Server started"}' > app.log |
| 137 | + |
| 138 | +# JSON с доп. полями |
| 139 | +echo '{"level":"error","msg":"Connection failed","host":"db1","port":5432}' > app.log |
| 140 | + |
| 141 | +# Logfmt |
| 142 | +echo 'level=info msg="Server started"' > app.log |
| 143 | +echo 'level=error msg="Connection failed" host=db1 port=5432' > app.log |
| 144 | + |
| 145 | +# Plain text с уровнем |
| 146 | +echo '2024-01-15 10:30:00 INFO Server started' > app.log |
| 147 | +echo '2024-01-15 10:30:01 ERROR Connection failed' > app.log |
| 148 | + |
| 149 | +# Несколько строк |
| 150 | +cat > app.log << 'EOF' |
| 151 | +{"level":"info","msg":"Starting application"} |
| 152 | +{"level":"info","msg":"Loading config from /etc/app.conf"} |
| 153 | +{"level":"warn","msg":"Config key 'debug' not found, using default"} |
| 154 | +{"level":"debug","msg":"Initializing database connection pool"} |
| 155 | +{"level":"info","msg":"Connected to postgres://localhost:5432/db"} |
| 156 | +{"level":"error","msg":"Query failed: relation 'users' does not exist"} |
| 157 | +{"level":"info","msg":"Running migrations..."} |
| 158 | +{"level":"info","msg":"Migration v001_create_users completed"} |
| 159 | +{"level":"info","msg":"Server listening on :8080"} |
| 160 | +{"level":"error","msg":"Unhandled panic: index out of range"} |
| 161 | +{"level":"warn","msg":"Retry attempt 1/3 for external API"} |
| 162 | +{"level":"error","msg":"External API still unavailable after 3 retries"} |
| 163 | +EOF |
| 164 | + |
| 165 | +# Запуск |
| 166 | +./logt app.log |
| 167 | + |
| 168 | +# Pipe |
| 169 | +cat app.log | ./logt |
| 170 | + |
| 171 | +# С другими командами |
| 172 | +kubectl logs deployment/myapp | ./logt |
| 173 | +journalctl -f | ./logt |
| 174 | +docker logs -f mycontainer | ./logt |
| 175 | +``` |
| 176 | + |
| 177 | +#### Мульти-формат (mixed sources) |
| 178 | +```bash |
| 179 | +# Создаём разные файлы |
| 180 | +echo '{"level":"info","msg":"API log"}' > api.log |
| 181 | +echo 'level=warn msg="Auth warning"' > auth.log |
| 182 | +echo '2024-01-15 ERROR Database connection lost' > db.log |
| 183 | + |
| 184 | +# Следим за всеми сразу |
| 185 | +logt api.log auth.log db.log |
| 186 | +# или |
| 187 | +logt ./*.log |
| 188 | +``` |
| 189 | + |
| 190 | +### Примеры |
| 191 | +```bash |
| 192 | +# Следить за всеми логами сервисов |
| 193 | +logt /var/log/services/*.log |
| 194 | + |
| 195 | +# Найти только ошибки |
| 196 | +logt --level error ./app.log |
| 197 | + |
| 198 | +# С настроенным буфером |
| 199 | +logt --buffer 10000 --max-buffer 20000 ./app.log |
| 200 | + |
| 201 | +# Экспорт отфильтрованных логов в файл |
| 202 | +logt --forward filtered.log ./app.log |
| 203 | + |
| 204 | +# Экспорт в stdout (pipe) |
| 205 | +logt --forward - ./app.log | grep ERROR |
| 206 | + |
| 207 | +# С источниками из конфига |
| 208 | +logt |
| 209 | + |
| 210 | +# Stdin от другой команды |
| 211 | +kubectl logs deployment/app | logt |
| 212 | +``` |
| 213 | + |
| 214 | +## ⌨️ Горячие клавиши |
| 215 | + |
| 216 | +| Клавиша | Действие | |
| 217 | +|-----|--------| |
| 218 | +| `Space` | Пауза/Продолжить автопрокрутку | |
| 219 | +| `/` | Открыть Fuzzy фильтр | |
| 220 | +| `r` | Переключить Fuzzy/Regex фильтр | |
| 221 | +| `Enter` | Применить фильтр / Открыть JSON | |
| 222 | +| `Backspace` | Удалить символ из фильтра | |
| 223 | +| `Esc` | Очистить фильтр / Закрыть JSON просмотр | |
| 224 | +| `↑ / ↓` | Прокрутка вверх/вниз | |
| 225 | +| `PgUp / PgDn` | Прокрутка по страницам | |
| 226 | +| `Home / End` | Перейти в начало/конец | |
| 227 | +| `g` | Перейти в начало (less-style) | |
| 228 | +| `G` | Перейти в конец (less-style) | |
| 229 | +| `n` | Следующее совпадение | |
| 230 | +| `N` | Предыдущее совпадение | |
| 231 | +| `Tab` | Переключить панель источников | |
| 232 | +| `q` | Выход | |
| 233 | + |
| 234 | +## 🏗️ Архитектура |
| 235 | + |
| 236 | +``` |
| 237 | +logt/ |
| 238 | +├── cmd/logt/main.go # Точка входа, CLI |
| 239 | +├── internal/ |
| 240 | +│ ├── config/config.go # Загрузка конфигурации (yaml, env) |
| 241 | +│ ├── domain/domain.go # Модели, Парсеры, RingBuffer |
| 242 | +│ ├── provider/provider.go # Провайдеры файлов и stdin |
| 243 | +│ └── ui/ |
| 244 | +│ ├── model.go # Состояние Bubble Tea |
| 245 | +│ ├── update.go # Обработчики сообщений |
| 246 | +│ └── view.go # Рендеринг через Lip Gloss |
| 247 | +├── config.example.yaml # Пример конфигурации |
| 248 | +└── README.md |
| 249 | +``` |
| 250 | + |
| 251 | +### Проектные решения |
| 252 | + |
| 253 | +1. **Гексагональная архитектура** — Домен-ориентированный дизайн с чёткими границами |
| 254 | +2. **Channel-based Concurrency** — Безопасные обновления UI через Go каналы |
| 255 | +3. **Throttling** — UI обновляется максимум 20 раз/сек для предотвращения CPU spike |
| 256 | +4. **Без внешней tail библиотеки** — Собственная реализация через polling |
| 257 | + |
| 258 | +## 🧪 Тестирование |
| 259 | + |
| 260 | +```bash |
| 261 | +# Запустить все тесты |
| 262 | +go test ./... |
| 263 | + |
| 264 | +# С бенчмарками |
| 265 | +go test -bench=. ./... |
| 266 | + |
| 267 | +# Конкретный тест |
| 268 | +go test -v -run TestRingBuffer ./... |
| 269 | +``` |
| 270 | + |
| 271 | +### Покрытие тестами |
| 272 | +- RingBuffer overflow (100k → 5k limit) |
| 273 | +- Конкурентный доступ (thread-safety) |
| 274 | +- JSON/Logfmt/Plain парсеры с fallback |
| 275 | +- Fuzzy filter matching |
| 276 | +- Определение уровня (case-insensitive) |
| 277 | +- Конфигурация (yaml, env, flags) |
| 278 | + |
| 279 | +## 📊 Сравнение |
| 280 | + |
| 281 | +| Возможность | LogT | lnav | |
| 282 | +|---------|------|------| |
| 283 | +| Размер бинарника | ~6MB | ~15MB | |
| 284 | +| Время старта | <100ms | ~200ms | |
| 285 | +| JSON поддержка | Нативная | Ограниченная | |
| 286 | +| Fuzzy Filter | ✓ | ✗ | |
| 287 | +| Regex Filter | ✓ | ✓ | |
| 288 | +| YAML конфиг | ✓ | ✗ | |
| 289 | +| Требует конфиг | ✗ | ✓ | |
| 290 | + |
| 291 | +## 🔧 Конфигурация |
| 292 | + |
| 293 | +LogT работает **без конфига** из коробки. Для кастомизации создайте файл: |
| 294 | + |
| 295 | +```yaml |
| 296 | +# ~/.config/logt/config.yaml или ./logt.yaml |
| 297 | +buffer-size: 5000 # Размер буфера |
| 298 | +buffer-max: 10000 # Максимальный размер |
| 299 | +theme: dark # Тема (dark) |
| 300 | +forward: filtered.log # Файл для экспорта |
| 301 | +sources: # Источники по умолчанию |
| 302 | + - /var/log/*.log |
| 303 | +``` |
| 304 | +
|
| 305 | +### Переменные окружения |
| 306 | +```bash |
| 307 | +LOGT_BUFFER_SIZE=10000 |
| 308 | +LOGT_LEVEL=error |
| 309 | +LOGT_FORWARD=filtered.log |
| 310 | +LOGT_THEME=dark |
| 311 | +``` |
| 312 | + |
| 313 | +### Флаги командной строки |
| 314 | +``` |
| 315 | +-p, --path string Пути к файлам или шаблоны |
| 316 | +-l, --level string Фильтр по уровню |
| 317 | +-b, --buffer int Размер буфера (по умолчанию: 5000) |
| 318 | +-m, --max-buffer int Максимальный размер буфера |
| 319 | +-f, --forward string Экспорт логов (файл или stdout) |
| 320 | +-t, --theme string Тема (dark) |
| 321 | +-v, --version Версия |
| 322 | +-h, --help Помощь |
| 323 | +``` |
| 324 | + |
| 325 | +## 📝 Поддерживаемые форматы логов |
| 326 | + |
| 327 | +### Авто-определение |
| 328 | +- **JSON** — `{"level": "error", "message": "..."}` |
| 329 | +- **Logfmt** — `level=error msg="..."` |
| 330 | +- **Plain** — `2024-01-01 10:00:00 ERROR message` |
| 331 | + |
| 332 | +### Определение уровня |
| 333 | +Без учёта регистра: |
| 334 | +- `FATAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` |
| 335 | + |
| 336 | +## 🐛 Известные ограничения |
| 337 | + |
| 338 | +- Windows-only file watching (polling, не inotify) |
| 339 | +- Нет удалённой поддержки логов (в планах: HTTP forwarding) |
| 340 | +- TUI only (без headless режима) |
| 341 | + |
| 342 | +## 🤝 Вклад |
| 343 | + |
| 344 | +1. Fork |
| 345 | +2. Создайте feature branch |
| 346 | +3. Запустите тесты: `go test ./...` |
| 347 | +4. Отправьте PR |
| 348 | + |
| 349 | +## 📄 Лицензия |
| 350 | + |
| 351 | +MIT License - см. [LICENSE](LICENSE) |
0 commit comments