Сепаратор для логов. Сжимаем логи для контекста LLM без потери читаемости

Я думаю, многим знакомо устройство под названием сепаратор-то, что отделяет сливки от молока. Моя библиотека logzip занимается примерно тем же самым - отделяет сливки больших логов, оставляя самую суть перед подачей их на анализ в LLM.

Сепаратор для логов. Сжимаем логи для контекста 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. Начинает анализ

Бенчмарки и экономика

Сепаратор для логов. Сжимаем логи для контекста LLM без потери читаемости

Максимальное

Объяснение подвоха 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

Сепаратор для логов. Сжимаем логи для контекста LLM без потери читаемости
Начать дискуссию