Сепаратор для логов. Сжимаем логи для контекста LLM без потери читаемости
Я думаю, многим знакомо устройство под названием сепаратор-то, что отделяет сливки от молока. Моя библиотека logzip занимается примерно тем же самым - отделяет сливки больших логов, оставляя самую суть перед подачей их на анализ в LLM.
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper
Предупрежу сразу - я не писатель, я читатель, но не мог поделиться результатами своей работы. Так что не прошу судить строго за подачу материала.
Итак, ситуация: у вас падает сервис, вы открываете лог и видите.... ~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. Начинает анализ
Бенчмарки и экономика
Максимальное
Объяснение подвоха 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
Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper