Что такое Logsa: локальный индекс логов для разработки и отладки
Когда сервисов становится больше трёх, логи перестают быть файлом, который можно спокойно открыть глазами. Один процесс пишет JSON, второй плюёт plain text, третий живёт в Docker, четвёртый запускается локально через npm run dev. На проде эту проблему обычно закрывают Loki, Elasticsearch, OpenSearch или облачный сервис. В разработке часто получается странная середина: полноценный стек поднимать тяжело, а grep по пяти терминалам уже не помогает.
Я называю Logsa маленький локальный индекс для логов. Это не продукт и не замена observability-платформе. Это рабочая идея: собрать логи из разных файлов и контейнеров в простую SQLite-базу, нормализовать несколько полей и дать себе быстрый поиск по сервису, уровню, trace id и тексту сообщения.
Смысл Logsa не в том, чтобы хранить всё навсегда. Смысл в коротком цикле отладки: запустил систему локально, воспроизвёл ошибку, собрал последние десять минут логов, нашёл цепочку событий, удалил индекс. Такой инструмент особенно удобен, когда проект ещё не дорос до общего логирования, но уже дорос до боли от несвязанных stdout-потоков.
Какая проблема решается
Обычный grep хорош, пока вопрос звучит просто: найди строку с ошибкой. В реальной отладке вопросы длиннее:
- что происходило с request id abc-123 во всех сервисах;
- какой сервис первым вернул 500;
- почему воркер обработал задачу два раза;
- были ли warning перед падением;
- какие SQL-запросы шли рядом с таймаутом.
Можно отвечать на это цепочкой rg, awk и jq. Я так делал много раз. Но когда команда повторяется третий день подряд, проще признать, что нужен маленький индекс. Не кластер, не дашборд, не алертинг, а один локальный файл, который можно пересоздать за секунды.
Минимальная модель Logsa
Для первой версии достаточно одной таблицы. Поля лучше выбирать не по красоте схемы, а по тому, как ты реально ищешь ошибки. Почти всегда нужны время, сервис, уровень, trace id и сообщение. Всё остальное можно сложить в attrs как сырой JSON-текст.
create table logs (
id integer primary key autoincrement,
ts text not null,
service text not null,
level text not null,
trace_id text,
message text not null,
attrs text
);
create index logs_ts_idx on logs(ts);
create index logs_service_idx on logs(service);
create index logs_trace_idx on logs(trace_id);
create index logs_level_idx on logs(level);SQLite здесь подходит идеально. База лежит одним файлом, не требует демона, нормально переваривает сотни тысяч строк для локальной отладки и умеет полнотекстовый поиск, если он понадобится позже. Для старта хватает обычных индексов и like по message.
Как загружать логи
Самый удобный входной формат для Logsa — JSON lines: одна строка, один объект. Если сервисы уже пишут структурные логи, остаётся только договориться о названиях полей. Если не пишут, можно начать с адаптера, который вытаскивает минимум из plain text.
import json
import sqlite3
import sys
db = sqlite3.connect('logsa.db')
db.execute('''
create table if not exists logs (
id integer primary key autoincrement,
ts text not null,
service text not null,
level text not null,
trace_id text,
message text not null,
attrs text
)
''')
service = sys.argv[1]
path = sys.argv[2]
with open(path, encoding='utf-8') as file:
for line in file:
if not line.strip():
continue
raw = json.loads(line)
db.execute(
'insert into logs(ts, service, level, trace_id, message, attrs) values (?, ?, ?, ?, ?, ?)',
(
raw.get('ts') or raw.get('time'),
service,
raw.get('level', 'info').lower(),
raw.get('trace_id') or raw.get('request_id'),
raw.get('message') or raw.get('msg') or '',
json.dumps(raw, ensure_ascii=False),
),
)
db.commit()Этого кода мало для боевого инструмента, но достаточно для проверки идеи. Он показывает главное: Logsa не требует менять приложение. Можно взять существующий файл логов, прогнать через импорт и начать задавать вопросы к базе.
python logsa_import.py api ./logs/api.jsonl
python logsa_import.py worker ./logs/worker.jsonl
python logsa_import.py billing ./logs/billing.jsonlЗапросы, ради которых всё затевалось
После импорта поиск становится предсказуемым. Хочешь увидеть весь путь одного запроса — фильтруешь trace id и сортируешь по времени.
select ts, service, level, message
from logs
where trace_id = 'abc-123'
order by ts;Хочешь понять, кто шумит ошибками за последние минуты локального прогона — группируешь по сервису и уровню.
select service, level, count(*) as total
from logs
where level in ('error', 'warn')
group by service, level
order by total desc;Хочешь найти всё вокруг таймаута — ищешь по сообщению, а потом берёшь окно по времени. Это быстрее, чем прыгать между терминалами и прокручивать историю контейнеров.
select ts, service, level, trace_id, message
from logs
where message like '%timeout%'
order by ts desc
limit 50;Чем Logsa отличается от Loki или ELK
У Logsa другой масштаб и другая ответственность. Loki, Elasticsearch и похожие системы нужны для долгого хранения, многопользовательского доступа, алертов, ретеншена, дашбордов и продовой эксплуатации. Logsa живёт на ноутбуке разработчика и помогает разобрать конкретный локальный прогон.
Из-за этого требования сильно проще. Не нужно думать о кластере, компрессии сегментов, cardinality labels и правах доступа. Можно удалить базу и пересобрать её из файлов. Можно сделать несовместимое изменение схемы без миграции. Можно добавить поле под один проект, если оно помогает именно сейчас.
Цена тоже понятна: Logsa не подходит для расследования инцидентов на проде, если логи уже ротировались или не были сохранены. Он не заменяет trace-систему, потому что не измеряет спаны и зависимости сам. Он только приводит разрозненные строки к форме, в которой их удобно читать.
Как не превратить маленький инструмент в болото
У таких утилит есть типичная болезнь: начинаешь с одного файла, через месяц пишешь свой Kibana. Чтобы этого не случилось, я бы держал несколько жёстких ограничений:
- индекс локальный и временный;
- источник правды — исходные логи, а не база;
- схема содержит только поля, по которым реально ищут;
- импорт должен быть идемпотентным или хотя бы легко повторяемым;
- никакого веб-интерфейса, пока хватает SQL и CLI.
Если появляется потребность в пользователях, ролях, долгом хранении и графиках, это уже не Logsa. Это сигнал поднять нормальный observability-стек или подключить готовый сервис.
Практичный старт
Начать можно с трёх команд: собрать логи в JSON lines, импортировать в SQLite, открыть базу через sqlite3. Даже если после этого инструмент не приживётся, структурные логи останутся полезными сами по себе.
rm -f logsa.db
python logsa_import.py api ./logs/api.jsonl
sqlite3 logsa.dbВнутри sqlite3 можно сохранить частые запросы в отдельный файл и выполнять их через .read. Это скучно, зато прозрачно. Любой разработчик видит, какие именно поля используются и почему результат такой, а не другой.
Logsa хороша как промежуточная ступень между хаосом stdout и полноценной платформой логирования. Она не делает магии, не обещает идеальной наблюдаемости и не требует инфраструктуры. Просто берёт логи, кладёт их в маленький индекс и помогает быстрее ответить на вопрос: что произошло в этом прогоне и где оно сломалось.