Logging strategies: structured JSON logs, levels, correlation IDs, sampling, PII redaction, retention tiers, Loki vs Elasticsearch ADR
Логи — самый старый и самый дорогой telemetry-сигнал. Без дисциплины они превращаются в бесполезный шум (миллионы строк plain text, никто не grepает в incident) или в астрономический bill ($50K-200K/мес в Datadog Logs только потому, что кто-то воткнул log.debug() в hot path и забыл выключить). Грамотная стратегия даёт три вещи одновременно: на вопрос «что произошло с этим запросом» вы отвечаете за минуты, cost растёт сублинейно с трафиком, а PII не утекает в архивы на 7 лет вперёд. Это не «давайте писать в файлик», это набор конкретных инженерных решений: формат, levels per env, correlation IDs, sampling, retention tiers, sensitive data masking.
«Логи — это wide structured events с trace_id. Один event = одна JSON-строка с полями (level, service, trace_id, business-context), не свободный текст. Без trace_id лог — сирота: не корреллируется с trace и метриками. Без структуры — grep-hell, который не масштабируется выше 100K строк/час.»
Три рычага, которыми вы управляете: формат (JSON vs plain), громкость (level + sampling), срок жизни (hot/warm/cold). Каждый — независимая ось оптимизации.
Четыре слоя — стандартный production logging pipeline:
svc-checkout, svc-orders, svc-payments пишут структурированный JSON в stdout (12-factor), и svc-legacy — антипаттерн с printf без полей.Fluent Bit как DaemonSet читает /var/log/containers/*.log, добавляет k8s metadata; Kafka buffer защищает от backpressure если processor лёг; Vector / OTel collector парсит и обогащает; отдельная нода PII redactor — defensive layer перед storage.Loki для bulk (hot, 7d, labels-only index, chunks в S3), Elasticsearch для security/full-text (warm, 30d), S3/Glacier для compliance (cold, 7y), Splunk/Datadog как managed alternative.Grafana/Kibana для investigation, Alertmanager для log-based alerts, on-call engineer как пользователь, security audit тянет из cold tier для compliance reviews.Edges — физические соединения (stdout → shipper → buffer → processor → redact → storage; consumers → storage). Никаких shortcuts.
Structured + trace_id search. Сервис эмитит одну JSON-строку с trace_id, проходит через Fluent Bit (добавляет pod/namespace), kafka, Vector (env, region), PII redactor, и оседает в Loki. Когда пользователь жалуется «checkout failed at 11
{service="checkout"} |= "failed", находит trace_id=4bf92..., затем {trace_id="4bf92..."} показывает полную цепочку через все сервисы: svc-a OK 12ms → svc-b OK 45ms → svc-c TIMEOUT 5000ms на Stripe API. MTTR — 90 секунд. Без trace_id это были бы greps по трём сервисам и догадки на 30+ минут. Антипример svc-legacy с printf("checkout failed for %s", user) не агрегируется и при 1M строк/час превращается в 10+ минут на grep.
Sampling: 1/10 INFO, keep ERROR. svc-payments льёт 10K INFO + 50 ERROR/sec. Fluent Bit читает поле level: INFO → trace-aware sample 10% (consistent hash на trace_id, не random per-line — иначе orphan logs), ERROR → 100% всегда. На выходе 1050/s вместо 10050/s — 90% reduction без потери ценного. Математика: 10K/s × 86400s × 1KB = 860GB/день; Datadog $0.10/GB ingest = $86/день = $2.6K/мес на один сервис на одну реплику. После sampling — $270/мес. Trace-aware критично: все 7 logs одного trace либо все sampled, либо все dropped, иначе вы не восстановите chain. Реальный кейс с HN: включили DEBUG глобально для расследования, забыли выключить → $200K bill за месяц. Фикс — TTL на log-level changes (auto-revert через 1h) и per-service quotas в backend.
PII auto-redact в processor. Разработчик случайно залогировал req.body: email, full PAN, JWT. Fluent Bit не редактирует — только собирает. Vector через remap-rules: credit_card → ****-****-****-1234, email → a***@acme.com, JWT/Bearer → [REDACTED], EU-IP → /24 или drop (GDPR). В Loki оседает уже чистое. Параллельно redact.matched_total{rule="credit_card"} — метрика, по которой алертим если rate высокий (значит код где-то льёт, ищем виновника). Defense-in-depth: маскировать на трёх уровнях — SDK (pino-redact), processor (Vector), backend (Datadog Sensitive Data Scanner). Реальный кейс: Twitter 2018, bug писал passwords plaintext во внутренние логи, 330M пользователей попросили reset. Root cause — не было автоматической redaction на framework level. Правило: никогда не доверяй «разработчик не залогирует пароль». Маскируй на pipeline.
ADR: Loki vs Elasticsearch vs Datadog. Контекст: 50 сервисов, ~5TB/мес после sampling, бюджет $5K/мес, нужны trace_id search, alerting, 30d retention. Loki: дёшево (S3 backing), LogQL знакомый после Prometheus, но full-text медленный (brute scan) и операционно сложный. ES: лучший full-text, но дорогой и cluster operations болезненные. Datadog: zero ops, $13K/мес → не влезает. Решение — hybrid: Loki для 95% bulk app logs (labels: service, level, env), ES для 5% security/audit (нужен complex query, низкий объём), S3 Glacier для 7y cold compliance. Итог $850/мес. Trade: full-text по app logs медленный — приемлемо, потому что 95% запросов в Grafana — это trace_id lookup и фильтр по labels. Review через 6 мес: если query pattern дрейфует к ad-hoc full-text → мигрировать в ES.
Решение 1: JSON structured vs plain text. JSON выигрывает безоговорочно для агрегации (Loki | json | error_code="card_declined" за миллисекунды vs grep | awk | uniq -c за минуты), но платите 2-3x объёмом (поля, кавычки, escape). Mitigation: gzip-compression в chunks снижает overhead до ~30%. На локальной dev-машине JSON нечитаем — используйте pino-pretty или jq как post-processor.
Решение 2: Sampling head vs tail. Head-sampling (решение в SDK/Fluent Bit) — дёшево, простая логика, но решает «вслепую»: если sampled trace оказался ERROR, у вас нет всех его logs. Tail-sampling (решение после полного buffering всего trace, как OTel tail sampler) — даёт «оставить весь trace где был ERROR», но требует state в processor и full buffering каждого trace (memory cost). Hybrid: head для INFO (10%), 100% для ERROR/WARN — покрывает 95% потребностей без tail-sampler complexity.
Решение 3: Per-tenant vs per-service quotas. Per-tenant защищает SaaS-bill от одного злого клиента, но требует tagging каждого log с tenant_id. Per-service защищает от bug в одном сервисе — проще, но шумный tenant утопит весь бюджет. В multi-tenant SaaS — обязательно per-tenant.
Решение 4: Маскировать в коде vs в pipeline. В коде (pino-redact) — ниже latency, но зависит от дисциплины каждой команды. В pipeline (Vector) — единая точка контроля и audit, но дополнительный network hop. Defense-in-depth = оба.
Решение 5: Hot retention. 7 дней — стандарт для incident response (большинство incidents находятся в течение 24-72h). Меньше — рискуете не восстановить hindsight на week-old issue. Больше — дорогой SSD стоит впустую. Cold tier должен закрывать compliance window (SOC2 — 1 год, PCI — 1 год, SOX — 7 лет для financial trail).
tenant_id за секунды вместо grep-цепочек за минуты.console.log / printf в production — нет уровней, нет фильтрации, невозможно отключить per-component.log.info("user " + uid + " did X") теряет тип, не агрегируется. Делайте log.info({user_id: uid, action: "X"}, "did X").LOG_LEVEL=DEBUG глобально в prod — главная причина $$$ explosions. DEBUG в prod — только через runtime flag с TTL.log.error(err) без structured context — что за пользователь? какой запрос? какой tenant? Нужны поля.user_id или request_id в labels убивает Loki как и Prometheus (index explosion → OOM). Кладите их в payload, индекс — только {service, level, env}.Не используйте логи там, где должна быть метрика. «log.info("request done")» в hot path — это вы платите за хранение того, что должно быть http_requests_total counter. Правило: если вы хотите посчитать частоту — это метрика, если хотите разобрать конкретный случай — это лог, если хотите причинно-следственную цепочку запроса — это trace. Логи дороже метрик в 10-100x за единицу инсайта.
Не используйте логи как audit trail для compliance без отдельного pipeline. Audit (SOX, HIPAA, PCI) требует write-once, tamper-evident, retention 7+ лет. Application logs — best-effort, теряются при сбое, могут быть переписаны. Audit идёт в отдельный stream с immutable storage (S3 Object Lock, WORM).
Не используйте логи как замену distributed tracing. Логи дают «точки», trace даёт «связи между точками». При 50+ сервисах ручное reconstruction цепочки из логов не масштабируется. Trace + log correlation (trace_id) — гораздо мощнее.
Не используйте Loki, если основной паттерн — ad-hoc full-text по всему объёму. Brute scan chunks slow на TB-scale. ES/Splunk/ClickHouse — правильный выбор.