Я думаю, многим знакомо устройство под названием сепаратор-то, что отделяет сливки от молока. Моя библиотека logzip занимается примерно тем же самым - отделяет сливки больших логов, оставляя самую суть перед подачей их на анализ в LLM.
Предупрежу сразу - я не писатель, я читатель, но не мог поделиться результатами своей работы. Так что не прошу судить строго за подачу материала.
Началось все с того, что я здесь на Хабре прочитал статью https://habr.com/ru/articles/1026040/ камрада @sergeivsk и как раз в тот момент у меня была проблема анализа относительно больших логов при отладке кода. При относительно длинных дистанциях отладки мой внутренний экономист начинает жалеть токены, потраченные впустую на отсеивание в LLM постоянно повторяющихся строк, не несущих никакой смысловой нагрузки. Так и родилась идея создания logzip. Исходники @sergeivskя не смотрел, была позаимствована только идея. Как оказалось потом реализация в чем то совпала.
Итак, ситуация: у вас падает сервис, вы открываете лог и видите.... ~48k строк, а это примерно 10 МБ сырого текста, или 2-3 млн токенов для Claude:
типичный лог
INFO: 127.0.0.1:45678 - "GET /api/v1/status HTTP/1.1" 200 OK [12 ms]
INFO: 127.0.0.1:45679 - "GET /api/v1/status HTTP/1.1" 200 OK [11 ms]
INFO: 127.0.0.1:45680 - "GET /api/v1/status HTTP/1.1" 200 OK [13 ms]
... (5000 одинаковых строк) ...
ERROR: 127.0.0.1:51234 - "POST /api/v1/submit HTTP/1.1" 500 timeout [5000 ms]
... (ещё 5000 успешных) ...
Первая проблема: Модель видит 5000+ успешных запросов и теряет одну критичную ошибку посередине. Контекст модели размазывается. Это известный эффект LLM - Lost in the Middle, когда информация в центре обрабатывается хуже чем в начале или в конце. Модель буквально тонет в сотнях однообразных строк.
Вторая проблема, исходящая из первой - вы платите за пустые строки не несущие никакой смысловой нагрузки. 90% лога - это однообразные INFO: 200 OK.
Некоторые скажут, "зачем еще один архиватор?", "есть grep! для таких вещей". И будут правы, но не во всем. Дело в том, что grep/gzip/zstd и logzip - это инструменты предназначенные для разных целей.
gzip < app.log | wc -c
819 KB #Сжатие на 90%! Супер!
Попробуйте скормить этот результат в тот же Claude. Модель откажет - она не умеет читать бинарные данные. Нам нужно именно текстовое сжатие, которое:
- выглядит как текст;
- остается человекочитаемым (до определенной степени);
- самое важное: сохраняет смысл аномалий;
- экономит токены.
grep -i "error" # пробуем грепнуть вышеприведенный пример
ERROR: 127.0.0.1:51234 - "POST /api/v1/submit HTTP/1.1" 500 timeout [5000 ms]
"А почему не старый добрый grep?" спросят олды. Проблема в том что grep слишком радикален. Когда вы вырезаете из лога только строки с ошибками, вы лишаете модель контекста.
-
Как происходило развитие событий?
-
Что происходило за секунду, минуту до ошибки?
-
Какие запросы шли параллельно?
-
Был ли всплеск нагрузки?
Вместо того, что бы скрывать всё, (как gzip), или вырезать точно ошибку (как grep), я решил скрывать повторяющийся мусор. Тут реализация оказалась такое же как и подход @sergeivsk:
-
Найти все повторяющиеся вхождения типа INFO, GET /api/v1/status, 127.0.0.1
-
Заменить их на короткие токены #0#, #1#, #2#
-
Хранить маппинг в LEGEND
-
Оставить аномалии и уникальные строки в BODY в исходном виде
До обработки:
2026-04-21T14:32:00Z INFO uvicorn.access 127.0.0.1 - "GET /api/v1/users HTTP/1.1" 200 45ms
2026-04-21T14:32:01Z INFO uvicorn.access 127.0.0.1 - "GET /api/v1/users HTTP/1.1" 200 52ms
2026-04-21T14:32:02Z INFO uvicorn.access 127.0.0.1 - "POST /api/v1/orders HTTP/1.1" 201 123ms
2026-04-21T14:32:03Z INFO uvicorn.access 127.0.0.1 - "GET /api/v1/status HTTP/1.1" 200 3ms
... (100 строк успешных) ...
2026-04-21T14:33:45Z ERROR uvicorn.error Database connection timeout after 30s
После обработки:
--- PREFIX ---
2026-04-21T14:32 INFO uvicorn.access 127.0.0.1 -
--- LEGEND ---
#0# = GET /api/v1/users HTTP/1.1" 200
#1# = GET /api/v1/orders HTTP/1.1" 201
#2# = GET /api/v1/status HTTP/1.1" 200
!1! = #0# 45ms ← второй проход: комбинации тегов
--- BODY ---
:00Z #0# 45ms
:01Z #0# 52ms
:02Z #1# 123ms
:03Z #2# 3ms
... (короче) ...
:45Z ERROR uvicorn.error Database connection timeout after 30s ← аномалия видна!
Результат:
-
размер 8 Мб сократился до 3,4 Мб (~58%)
-
Читаемость 10/10 (модель понимает слёту)
-
Видимость ошибок: 10/10 (они не закрыты мусором)
Как это работает.
Мною был выбран Rust + PyO3, потому что это:
1. Скорость ~200x по сравнению с чистым Python. На огромных логах это критично. Так, те же 8 МБ обрабатывались на чисто пайтоновской реализации около 2 минут.
2. Безопасность. Нет unsafe блоков. Memory safety гарантирована.
3. PyO3: Rust код оборачивается в Python API и работает в pip install logzip
Алгоритм:
raw log
↓
[1] Profile Detection ← определяем формат (journalctl/docker/uvicorn/pino)
↓
[2] Normalizer ← убираем ANSI, наносекунды, leading zeros
↓
[3] Frequency Analysis ← параллельный подсчёт n-грамм (rayon)
↓
[4] Legend Selection ← жадный алгоритм с позиционным индексом (O(N), не O(N²))
↓
[5] AhoCorasick Replace ← одноходная замена всех токенов
↓
[6] Recursive BPE ← второй проход: сжимаем комбинации токенов
↓
compressed text
Почему это быстро?
Узкое место (было): в Python версии я считал working.count(value) в цикле - O(N²) алгоритм. На 8 Мб это две минуты.
Решение: Построить позиционный индекс один раз O(N)), потом жадно выбирать кандидаты с мемоизацией блокировки. Итого O(N log N).
Результат: 2 минуты сократились до 0,4 секунд. Ускорение в 215 раз.
Второй проход -Recursive BPE
После первого сжатия текст выглядит так:
#0# #1# 200 45ms
#0# #1# 200 52ms
#0# #1# 200 48ms
Видно что последовательность #0# #1# 200 повторяется. Второй проход сжимает ее в !1!:
!1! 45ms
!1! 52ms
!1! 48ms
Это действие дает еще 5-10% экономии за 18 мс доп. времени. BPE (Byte Pair Encoding) позволяет находить повторяющиеся цепочки уже созданных токенов, превращая последовательности вроде #0# #1# в новый супер-токен !1!»
После деплоя 1 версии в GitHub и на PyPI я увидел первые скачивания в статистике и задумался - а почем бы не прикрутить MCP? Что нам стоит дом MCP построить? Сказано - сделано!
Был написан MCP сервер и встроен в Claude и Cursor.
{
"mcpServers": {
"logzip": {
"command": "logzip",
"args": ["mcp", "--allow-dir", "/var/log"]
}
}
}
MCP был успешно испытан на максимально доступных мне логах.
# Пользователь просто спрашивает:
> Analyze /var/log/app.log
# Claude автоматически:
1. Вызывает get_stats /var/log/app.log
→ Size: 15 MB (~3.7M tokens)
→ After compression: ~6.3 MB (~1.5M tokens)
2. Вызывает compress_file /var/log/app.log --quality balanced --bpe-passes 2
3. Отправляет сжатый лог в контекст
4. Начинает анализ
Бенчмарки и экономика
|
Benchmark на реальном ~8МБ логе (Uvicorn + Docker) |
||||
|
Режим |
Время (мс) |
Размер (КБ) |
Сжатие |
Комментарий |
|
fast |
200 |
4.900 |
~40% |
Срочный анализ |
|
balanced |
404 |
3.928 |
~52% |
Базовый выбор |
|
balanced+BPEх2 |
418 |
3.404 |
~58% |
Оптимум |
|
max |
507 |
3.511 |
~57% |
Максимальное |
Объяснение подвоха max: почему --quality max работает как --quality balanced?
Потому что:
1. После первого прохода с 512 entries мы уже раздавили 57% объема.
2. Второй проход работает БЕЗ того же материала.
3. Добавление 400 экстра записей в легенду- это просто раздуть вывод.
4. А bpe-passes делает второй ПРОХОД, который находит повторы в УЖЕ сжатом тексте. Зачем он нужен? Затем что второй проход ищет не новые "крупные" паттерны, а КОМБИНАЦИИ уже найденных тегов. Это более эффективно, чем просто добавить 400 редкоиспользуемых записей в легенду.
--quality max: 512 entries, 1 pass → 507ms, -57%
--quality balanced: 99 entries, 1 pass → 404ms, -52%
--quality balanced --bpe-passes 2: → 418ms, -58% ← ПОБЕДИТЕЛЬ
Вывод: --quality max - переплата за медлительность при поиске повторов.
Экономика
┌──────────────────────────────────────────┐
│ Сценарий: 10 анализов в день │
│ по 7.96 МБ логов каждый │
├──────────────────────────────────────────┤
│ │
│ БЕЗ logzip: │
│ • Размер: 8 МБ = ~1,960,000 токенов │
│ • На запрос: ~$2.00 │
│ • 10 запросов: $20/день = $600/месяц │
│ │
│ С logzip (balanced --bpe-passes 2): │
│ • Размер: 3.4 МБ = ~830,000 токенов │
│ • На запрос: ~$0.85 │
│ • 10 запросов: $8.50/день = $255/месяц │
│ │
│ Экономия: $345/месяц │
│ Инвестиция: 10 минут на интеграцию │
│ ROI: 2070% в месяц │
└──────────────────────────────────────────┘
Сырой лог:
... (3449 успешных запросов) ...
INFO: 127.0.0.1:45678 - "GET /api/v1/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:45679 - "GET /api/v1/status HTTP/1.1" 200 OK
ERROR: Database connection timeout (пропущена в шуме!)
INFO: 127.0.0.1:45681 - "GET /api/v1/status HTTP/1.1" 200 OK
... (ещё 1500 успешных) ...
после logzip:
--- LEGEND ---
#0# = INFO: 127.0.0.1:... - "GET /api/v1/status HTTP/1.1" 200 OK
--- BODY ---
#0#
#0#
#0#
ERROR: Database connection timeout ← Кричит на всю страницу!
#0#
#0#
...
Модель сразу видит ошибку не утонув в 5000 одинаковых 200 ОК.
Это позволяет экономить реальные деньги.
Было (пример взят с "потолка"): $20/месяц на анализ логов
Стало: 8.5$/месяц
Как использовать
Установка
pip install logzip
CLI
logzip compress --quality balanced --bpe-passes 2 < app.log | pbcopy
Python API
from logzip import compress
result = compress(open("app.log").read(), bpe_passes=2)
print(result.render(with_preamble=True)) # → в Claude
print(result.stats_str()) # → метрики
MCP
1. Установить бинарник
cargo install logzip
2.Добавить в ~/Library/Application Support/Claude/claude_desktop_config.json
3.Перезапустить Claude Code
Ссылки. Планы. Благодарности.
MIT лицензия.
Благодарю @sergeivsk за вдохновение
Автор: SurMaster
