Я думаю, многим знакомо устройство под названием сепаратор-то, что отделяет сливки от молока. Моя библиотека 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 -c819 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 45ms2026-04-21T14:32:01Z INFO uvicorn.access 127.0.0.1 - "GET /api/v1/users HTTP/1.1" 200 52ms2026-04-21T14:32:02Z INFO uvicorn.access 127.0.0.1 - "POST /api/v1/orders HTTP/1.1" 201 123ms2026-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 23. Отправляет сжатый лог в контекст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 OKINFO: 127.0.0.1:45679 - "GET /api/v1/status HTTP/1.1" 200 OKERROR: 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 compressresult = compress(open("app.log").read(), bpe_passes=2)print(result.render(with_preamble=True)) # → в Claudeprint(result.stats_str()) # → метрики
MCP
1. Установить бинарник
cargo install logzip
2.Добавить в ~/Library/Application Support/Claude/claude_desktop_config.json
3.Перезапустить Claude Code
Ссылки. Планы. Благодарности.
MIT лицензия.
Благодарю @sergeivsk за вдохновение
ссылка на оригинал статьи https://habr.com/ru/articles/1030964/