Я думаю, многим знакомо устройство под названием сепаратор-то, что отделяет сливки от молока. Моя библиотека 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 за вдохновение
Комментарии (4)

Danusha0000000
04.05.2026 05:42вообще не вижу проблем. топовые модели парсер и поиск используют если надо конкретно что то вычленить =)

CuriV
04.05.2026 05:42Прикольно! Для offensive security тоже полезно. Скан nmap по большому скоупа сжал почти в два раза
remzalp
Идея интересная, но не слишком универсальная. В целом логи смотрят (сужу по себе) для двух задач:
- мониторинг - но тут надо максимум информации для получения необходимых данных
- поиск ошибок - тут по большей части надо выделить только контекст ошибки, а это сама ошибка + цепочка событий, приводящая к ней, а вот тут Вы чуть-чуть не дожали.
Если следовать заветам OpenTelemetry, то каждый вызов и события, происходящие в пределах него должны быть помечены уникальным признаком, так что трассу можно отследить даже между несколькими микросервисами. А еще трейс из сообщения об ошибке можно развернуть до начала цепочки логов. Правда тут еще большой выбор форматор логов и где этот трейс искать.
И тут появляется новая концепция для logzip - сгруппировать по трейсам пакеты логов, устранить там переменные части (ид трейса, таймстамп лога, может быть что-то еще) и подвергнуть сжатию. В случае ошибок - сохранять максимум для первой однотипной, а повторяющиеся сжать.
В общем этакое RLE.