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

Проект доступен на Github

Благодарю @sergeivsk за вдохновение

Комментарии (4)


  1. remzalp
    04.05.2026 05:42

    Идея интересная, но не слишком универсальная. В целом логи смотрят (сужу по себе) для двух задач:
    - мониторинг - но тут надо максимум информации для получения необходимых данных
    - поиск ошибок - тут по большей части надо выделить только контекст ошибки, а это сама ошибка + цепочка событий, приводящая к ней, а вот тут Вы чуть-чуть не дожали.

    Если следовать заветам OpenTelemetry, то каждый вызов и события, происходящие в пределах него должны быть помечены уникальным признаком, так что трассу можно отследить даже между несколькими микросервисами. А еще трейс из сообщения об ошибке можно развернуть до начала цепочки логов. Правда тут еще большой выбор форматор логов и где этот трейс искать.

    И тут появляется новая концепция для logzip - сгруппировать по трейсам пакеты логов, устранить там переменные части (ид трейса, таймстамп лога, может быть что-то еще) и подвергнуть сжатию. В случае ошибок - сохранять максимум для первой однотипной, а повторяющиеся сжать.

    В общем этакое RLE.


  1. Danusha0000000
    04.05.2026 05:42

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


    1. SurMaster Автор
      04.05.2026 05:42

      Вы просто не работали с по настоящему большими логами


  1. CuriV
    04.05.2026 05:42

    Прикольно! Для offensive security тоже полезно. Скан nmap по большому скоупа сжал почти в два раза