MÓDULO 10 · CONCEITO 08 DE 12

Log Aggregation

ELK Stack, Grafana Loki, CloudWatch Logs — arquitetura de coleta, indexação, custo e LogQL vs KQL

Tempo de leitura ~35 min Pré-requisito 02 · Logging Estruturado Próximo 09 · APM e Ferramentas Comerciais

Em um sistema distribuído com dezenas de serviços, cada serviço gera logs em seu próprio nó ou container. Para investigar um incidente, você precisaria acessar manualmente cada container — inviável em escala e impossível após o container ser removido. A agregação de logs resolve isso centralizando todos os logs em um único sistema pesquisável com retenção configurável. A escolha do sistema de aggregation não é técnica apenas: ela define o custo de storage do time, a velocidade de debugging, e a complexidade operacional que você vai carregar.

Requisitos de um Sistema de Log Aggregation

ELK Stack (Elasticsearch + Logstash + Kibana)

O stack ELK (hoje chamado Elastic Stack) é o sistema de log aggregation mais maduro e amplamente adotado. Cada componente tem uma função específica na pipeline de ingestão, processamento e visualização.

Elasticsearch

Motor de busca e analytics baseado em Lucene. Armazena logs como documentos JSON e cria índices invertidos para busca eficiente por qualquer campo. Características críticas em produção:

Logstash vs Beats vs Fluent Bit

Logstash: processamento pesado (parse, transform, enrich) mas alto uso de memória e CPU — adequado para pipelines complexas de transformação. Beats (Filebeat, Metricbeat): agentes leves para coleta específica. Fluent Bit: ultra-leve (~450KB de binário, ~5MB de RAM), baixíssima latência, preferido em Kubernetes — um DaemonSet por nó que coleta logs de todos os containers do nó e encaminha para qualquer backend.

Kibana e KQL

UI de visualização e busca. KQL (Kibana Query Language) para busca ad-hoc — mais simples que Lucene syntax para a maioria dos casos. Dashboards, alertas, e desde a versão 8, integração com ML para detecção de anomalias em logs.

// Elasticsearch — ILM policy para logs de produção
PUT _ilm/policy/logs-production
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50gb",  // novo índice ao atingir 50GB
            "max_age": "1d"      // ou após 1 dia
          },
          "set_priority": { "priority": 100 }
        }
      },
      "warm": {
        "min_age": "3d",         // após 3 dias no hot
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 },
          "set_priority": { "priority": 50 }
        }
      },
      "cold": {
        "min_age": "14d",
        "actions": { "freeze": {} }  // libera heap, acesso mais lento
      },
      "delete": {
        "min_age": "30d",
        "actions": { "delete": {} }
      }
    }
  }
}

// KQL — exemplos de busca no Kibana
// Todos os erros de um serviço:
service.name: "order-service" AND level: "ERROR"

// Busca por trace ID (correlação com traces):
trace_id: "4bf92f3577b34da6a3ce929d0e0e4736"

// Full-text + filtros:
message: "payment failed" AND status_code: 500 AND NOT user_id: *
custo de operação do elasticsearch

O ES é poderoso mas operacionalmente complexo: gerenciamento de cluster, tuning de JVM heap, shard rebalancing, snapshot policies, ILM. Em Kubernetes, o ECK (Elastic Cloud on Kubernetes) reduz a complexidade. Para times sem expertise em ES, considere o Elastic Cloud (SaaS) ou OpenSearch gerenciado — o custo de operação frequentemente supera o custo do SaaS em equipes pequenas.

Grafana Loki

Loki foi projetado com uma filosofia radicalmente diferente do Elasticsearch: não indexe o conteúdo dos logs, apenas os labels. Isso reduz dramaticamente o custo de storage e a complexidade operacional — a troca é que busca full-text é mais lenta (Loki precisa escanear chunks não indexados dentro de cada stream selecionado pelos labels).

Modelo de dados: Streams e Labels

No Loki, logs são organizados em streams — conjuntos de logs com exatamente os mesmos labels. Labels são as dimensões indexadas: service, environment, pod, namespace. O conteúdo do log (a mensagem em si) não é indexado — é armazenado como chunks comprimidos em object storage (S3, GCS).

cardinalidade de labels

Nunca use campos de alta cardinalidade como labels: user_id, request_id, trace_id. Cada combinação única de labels cria um novo stream — com 1M de user_ids como label, você terá 1M de streams ativos. Loki fica inviável acima de algumas centenas de streams por tenant. Labels devem ser dimensões de baixa cardinalidade e estáveis: service, env, pod, region. O trace_id vai no conteúdo do log, não como label — e o LogQL filtra por ele com |= "trace_id_value".

LogQL

LogQL é a query language do Loki, com sintaxe inspirada em PromQL. Dois tipos de queries: log queries (retornam linhas de log filtradas) e metric queries (derivam métricas dos logs via funções como rate() e quantile_over_time()).

// LogQL — exemplos práticos

// Log query: selecionar stream + filtrar + parsear + reformatar
{service="order-service", environment="production"}
  |= "ERROR"                    // filtro de substring
  != "health"                   // excluir linhas com "health"
  | json                        // parsear linha como JSON
  | level="error"               // filtrar campo JSON parseado
  | line_format "{{.message}} traceId={{.trace_id}}"

// Filtros com campos parseados
{service="order-service"}
  | json
  | duration > 1s               // filtrar por duração
  | status_code >= 500

// Metric query: error rate derivada de logs (sem Prometheus)
sum(rate(
  {service="order-service"} |= "ERROR" [5m]
)) by (service)

// Latência P99 se logs têm campo duration
quantile_over_time(0.99,
  {service="order-service"}
  | json
  | unwrap duration [5m]
) by (service)

// Parse via regexp para logs não-JSON (ex: nginx)
{service="nginx"}
  | regexp `(?P<method>\w+) (?P<path>/[^\s]+) HTTP/[^\s]+ (?P<status>\d+)`
  | status >= "500"

// Buscar trace_id para correlação traces→logs
{service="order-service"}
  |= "4bf92f3577b34da6a3ce929d0e0e4736"
# Arquitetura Loki em produção (loki-values.yaml / Helm)
loki:
  storage:
    type: s3                    # object storage — custo muito baixo
    s3:
      bucketnames: my-loki-bucket
      region: us-east-1

  schemaConfig:
    configs:
      - from: 2024-01-01
        store: tsdb             # TSDB store — mais eficiente
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h

  limits_config:
    ingestion_rate_mb: 16
    ingestion_burst_size_mb: 32
    max_streams_per_user: 10000 # limite de cardinalidade por tenant
    retention_period: 744h      # 31 dias

# Modo microservices para alta escala:
# - Distributor: recebe logs, valida, hash por stream
# - Ingester: acumula chunks em memória, flushia para S3
# - Querier: lê de S3 para queries históricas
# - Query Frontend: cache, paralelização de queries grandes

CloudWatch Logs (AWS)

CloudWatch Logs é o sistema de log aggregation gerenciado da AWS. Integração nativa com todos os serviços AWS (Lambda, ECS, EKS via Fluent Bit, API Gateway, ALB, RDS). Zero operação — você paga por ingestão e armazenamento. Ideal para workloads AWS-native onde a integração automática justifica o custo.

// CloudWatch Logs Insights — queries práticas

// Distribuição de erros por endpoint (últimas 24h)
fields @timestamp, @message
| filter @message like /ERROR/
| parse @message "path=* status=*" as path, status
| stats count(*) as error_count by path
| sort error_count desc
| limit 20

// Latência P95 por endpoint
fields @timestamp, @message
| parse @message '"path":"*","duration":*' as path, duration_ms
| filter ispresent(duration_ms)
| stats pct(duration_ms, 95) as p95_ms by path
| sort p95_ms desc

// Buscar trace_id específico
fields @timestamp, @message
| filter @message like "4bf92f3577b34da6a3ce929d0e0e4736"
| sort @timestamp asc

// Análise de cold starts Lambda
filter @type = "REPORT"
| stats
    avg(initDuration) as avg_cold_start,
    count(initDuration) as cold_start_count,
    count(*) as total_invocations
  by bin(1h)
lambda powertools + EMF

No AWS Lambda, o Lambda Powertools (Python, TypeScript, Java, .NET) automatiza structured logging, correlação de trace IDs do X-Ray, e métricas EMF (Embedded Metrics Format). EMF permite embutir métricas no payload de log — CloudWatch extrai as métricas automaticamente sem metric filter, com latência menor. Custo de ingestão: $0.50/GB na us-east-1 — filtre logs de debug antes de enviar para controlar custo.

Comparação: ELK vs Loki vs CloudWatch

Critério ELK / Elasticsearch Grafana Loki CloudWatch Logs
Custo de storageAlto (SSD indexado)Muito baixo (S3 comprimido)Médio ($0.03/GB/mês)
Busca full-textExcelente (índice invertido)Boa (scan de chunks)Boa (Insights query)
Complexidade operacionalAlta (cluster ES)Baixa (S3 + stateless)Zero (gerenciado AWS)
Integração com métricasElasticsearch MetricsNativa com Prometheus/MimirCloudWatch Metrics
Integração com tracingElastic APM / JaegerNativa com TempoAWS X-Ray
Melhor paraLog analytics complexo, SIEMStack Grafana, KubernetesWorkloads AWS-native

Configuração de Coleta por Linguagem

C# — Serilog com sinks para Elasticsearch e Loki
// Packages:
// Serilog.Sinks.Elasticsearch
// Serilog.Sinks.Grafana.Loki
// Serilog.Enrichers.Environment

// appsettings.json — configuração declarativa
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
      }
    },
    "Enrich": ["FromLogContext", "WithMachineName", "WithProcessId"],
    "WriteTo": [
      {
        "Name": "Elasticsearch",
        "Args": {
          "nodeUris": "http://elasticsearch:9200",
          "indexFormat": "app-logs-{0:yyyy.MM.dd}",
          "autoRegisterTemplate": true,
          "autoRegisterTemplateVersion": "ESv8",
          "period": "00:00:05",
          "bufferBaseFilename": "/var/log/app/buffer"
        }
      },
      {
        "Name": "GrafanaLoki",
        "Args": {
          "uri": "http://loki:3100",
          "labels": [
            { "key": "service", "value": "order-service" },
            { "key": "environment", "value": "production" }
          ],
          "batchPostingLimit": 1000,
          "period": "00:00:02"
        }
      }
    ]
  }
}

// Program.cs — com trace_id automático via OTel bridge
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .Enrich.WithProperty("service.name", "order-service")
    .Enrich.WithProperty("service.version",
        Assembly.GetExecutingAssembly().GetName().Version?.ToString())
    .Enrich.WithOpenTelemetry()  // injeta trace_id e span_id automaticamente
    .CreateLogger();

bufferBaseFilename cria buffer em disco para o sink do ES — se o ES ficar indisponível, logs não são perdidos e são processados quando a conectividade é restaurada. Essencial para coleta confiável em produção.

Python — structlog com Fluent Bit para Loki
import structlog
import logging
from opentelemetry import trace

def add_trace_context(logger, method, event_dict):
    span = trace.get_current_span()
    if span and span.is_recording():
        ctx = span.get_span_context()
        event_dict["trace_id"] = format(ctx.trace_id, "032x")
        event_dict["span_id"] = format(ctx.span_id, "016x")
    return event_dict

def drop_health_check_logs(logger, method, event_dict):
    # não logar health checks — reduz volume sem valor observacional
    if event_dict.get("path") in ("/health", "/metrics", "/readiness"):
        raise structlog.DropEvent()
    return event_dict

structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        add_trace_context,
        drop_health_check_logs,
        structlog.processors.add_log_level,
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
        structlog.processors.JSONRenderer(),  # stdout JSON
    ],
    wrapper_class=structlog.BoundLogger,
    logger_factory=structlog.PrintLoggerFactory(),
)

# Em Kubernetes: logs vão para stdout → Fluent Bit DaemonSet
# captura automaticamente, adiciona metadados k8s, envia para Loki.
# O structlog só precisa escrever JSON em stdout — coleta é responsabilidade
# da infraestrutura, não da aplicação.

Em Kubernetes, a prática correta é logs para stdout (JSON) + Fluent Bit/Promtail no DaemonSet para coleta. A aplicação não sabe e não se importa com o destino — o pipeline de logs é configurado na infraestrutura.

Go — slog JSON + Fluent Bit DaemonSet
// logger/setup.go — slog com handler JSON + trace injection
package logger

import (
    "context"
    "log/slog"
    "os"
    "go.opentelemetry.io/otel/trace"
)

type traceHandler struct{ slog.Handler }

func (h *traceHandler) Handle(ctx context.Context, r slog.Record) error {
    span := trace.SpanFromContext(ctx)
    if span.IsRecording() {
        sCtx := span.SpanContext()
        r.AddAttrs(
            slog.String("trace_id", sCtx.TraceID().String()),
            slog.String("span_id", sCtx.SpanID().String()),
        )
    }
    return h.Handler.Handle(ctx, r)
}

func New(serviceName, version string) *slog.Logger {
    base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo,
        ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
            if a.Key == slog.MessageKey { a.Key = "message" }
            return a
        },
    })
    return slog.New(&traceHandler{base}).With(
        slog.String("service.name", serviceName),
        slog.String("service.version", version),
        slog.String("environment", os.Getenv("ENV")),
    )
}

// fluent-bit DaemonSet config (values.yaml / Helm)
// [INPUT]
//   Name              tail
//   Path              /var/log/containers/*.log
//   multiline.parser  docker, cri
//   Tag               kube.*
//
// [FILTER]
//   Name       kubernetes
//   Match      kube.*
//   Merge_Log  On   # merge JSON do container com metadados k8s
//
// [FILTER]
//   Name     grep
//   Match    kube.*
//   Exclude  $kubernetes['container_name'] fluent-bit
//
// [OUTPUT]
//   Name        loki
//   Match       kube.*
//   Host        loki.monitoring.svc.cluster.local
//   Port        3100
//   Labels      job=fluentbit, cluster=prod
//   Label_Keys  $kubernetes['namespace_name'],$kubernetes['pod_name']
//   Remove_Keys kubernetes,stream

Fluent Bit como DaemonSet é o pattern padrão para coleta em Kubernetes — um agente por nó, ~5MB de memória por nó. Merge_Log combina o JSON do container com metadados do Kubernetes (namespace, pod, container, labels).

Decisões de engenharia

Qual sistema de log aggregation escolher?

AWS-native (Lambda, ECS, EC2): CloudWatch Logs — zero operação, integração nativa com X-Ray, IAM e métricas. Stack Grafana em Kubernetes: Loki — custo de storage muito baixo, integração nativa com Tempo e Mimir, Fluent Bit como coletor. Analytics complexo, SIEM, compliance com busca full-text frequente: Elasticsearch — indexação rica, potência de busca, ML anomaly detection. Evite operar Elasticsearch sem expertise — o custo operacional de um cluster ES em HA é real e frequentemente subestimado.

Quanto tempo de retenção em hot storage?

7-14 dias cobre a vasta maioria dos casos de debugging — incidentes raramente são investigados mais de 2 semanas após o evento. Para compliance, archive para S3/GCS com retenção longa (1-7 anos) mas acesso raro e custo baixo. No Elasticsearch, use ILM para mover para cold/frozen storage após 14-30 dias — reduz custo 80-90% sem perder os dados. No Loki, configure retention_period e use bucket lifecycle policies no S3 para archive automático em Glacier.

Fluent Bit vs Logstash vs Promtail?

Fluent Bit: padrão para Kubernetes — ultra-leve (~450KB, ~5MB RAM), plugins para todos os backends (Loki, ES, CloudWatch, S3, Datadog). Promtail: específico para Loki, mais simples de configurar mas sem flexibilidade de múltiplos outputs. Logstash: quando você precisa de transformações complexas na pipeline (lookups em banco, enriquecimento com APIs externas, parsing multi-formato avançado) — mais poderoso mas muito mais pesado. Para a maioria dos times em Kubernetes: Fluent Bit como DaemonSet.

Como controlar custo de logs?

Em camadas: (1) Filtrar na origem: descartar antes da ingestão (health checks, debug em prod, bots/scanners) via filtros no Fluent Bit — pode eliminar 30-50% do volume; (2) Sampling de logs de sucesso: para HTTP 200/201 de alta frequência, amostrar 5-10% e descartar o resto — erros e warnings sempre mantidos; (3) Escolha de backend: Loki com S3 custa ~$0.023/GB/mês vs Elasticsearch com SSD que pode custar 10-20× mais; (4) Retenção curta + archive: 7-14 dias hot, archive para S3 Glacier para compliance; (5) Métricas em vez de logs: dados que podem ser agregados devem virar métricas Prometheus — uma série com 1000 observações/s custa centavos/mês; os logs equivalentes custariam gigabytes/hora.

Como praticar

  1. Configure um pipeline completo de log aggregation com Fluent Bit + Loki em Docker Compose: Fluent Bit coletando logs de containers, adicionando labels de low cardinality (service, environment), e enviando para Loki. Visualize no Grafana com pelo menos duas queries LogQL — uma log query com filtro e parse JSON, e uma metric query derivando error rate dos logs.
    Critério: logs dos containers aparecem no Loki em menos de 10 segundos; a log query retorna logs filtrados por nível e serviço corretamente; a metric query de error rate produz um gráfico de série temporal válido no Grafana; labels usados são todos de baixa cardinalidade (nenhum user_id ou request_id como label).
  2. Demonstre o impacto de alta cardinalidade no Loki: configure dois pipelines — um com labels de baixa cardinalidade (service, environment) e outro adicionando user_id como label. Compare o número de streams criados, o uso de memória do Loki, e o tempo de query entre as duas configurações. Documente por que a segunda abordagem é problemática.
    Critério: o pipeline com user_id como label cria N streams (onde N = número de usuários distintos); o pipeline correto tem ≤ 5 streams para o mesmo conjunto de logs; o tempo de query do pipeline correto é menor; o documento explica como buscar por user_id no conteúdo via LogQL (| json | user_id="123") sem usar label.
  3. Configure um Elasticsearch com ILM policy: crie a policy com fases hot (rollover em 10GB ou 1 dia), warm (shrink para 1 shard após 3 dias), cold (após 7 dias), delete (após 14 dias). Crie um índice de teste, ingira logs fictícios, e valide que o ILM move o índice pelas fases corretas. Escreva 3 queries KQL que você usaria durante um incidente.
    Critério: a ILM policy está configurada com todas as 4 fases; o índice de teste move para warm após o rollover ser forçado manualmente (POST index/_rollover); as 3 queries KQL incluem: busca por level+service, busca por trace_id específico, e busca por mensagem com filtro de time range; o explain da ILM mostra a fase atual e próxima ação.
  4. Implemente filtragem de logs antes da ingestão no Fluent Bit para reduzir volume: configure filtros para descartar logs de health check (/health, /readiness, /metrics), logs de nível DEBUG, e logs de um bot específico por User-Agent. Meça o volume de logs antes e depois dos filtros com a métrica fluentbit_output_proc_bytes_total.
    Critério: logs de health check não aparecem no Loki/ES; logs DEBUG não aparecem em produção; o volume reduzido é mensurável (pelo menos 20% de redução para uma app típica); a configuração dos filtros está documentada com justificativa para cada filtro.
  5. Configure correlação logs→traces em um stack Grafana+Loki+Tempo: injete trace_id nos logs da aplicação (via OTel Logs Bridge ou manualmente), configure "derived fields" no datasource Loki que reconhece o padrão de trace_id e gera link para o Tempo, e valide a navegação de um log específico para o trace correspondente.
    Critério: logs contêm o campo trace_id no formato hex de 32 caracteres; o Grafana detecta o campo e exibe botão "View Trace" no Loki Explore; clicar no botão abre o trace correto no Tempo; a navegação funciona mesmo para trace_ids de spans aninhados (não só o span raiz).

Perguntas de entrevista

    Qual a diferença fundamental entre Elasticsearch e Loki para logs? Quando usar cada um?

    A diferença é o modelo de indexação. Elasticsearch indexa o conteúdo completo dos logs em índices invertidos — cada campo de cada documento é indexado e pesquisável instantaneamente. Busca full-text extremamente rápida, mas custo de storage alto (tipicamente 10-30× o volume raw). Loki indexa apenas os labels — dimensões de baixa cardinalidade como service, env, pod. O conteúdo dos logs é armazenado em chunks comprimidos no object storage sem indexação. Busca por conteúdo requer escanear os chunks selecionados pelos labels, o que é mais lento, mas o custo de storage é drasticamente menor (próximo do S3 raw).

    Use ES quando: analytics ad-hoc de logs é frequente e crítica (compliance, security SIEM), o time tem expertise para operar cluster ES, ou você precisa de features como ML anomaly detection. Use Loki quando: stack Grafana (integração nativa com Tempo e Mimir), Kubernetes é o ambiente principal, quer operação simples e custo de storage baixo, e a correlação trace→log via trace_id é o caso de uso principal em vez de busca full-text frequente.

    O que é um "mapping explosion" no Elasticsearch e como prevenir?

    Mapping explosion ocorre quando o ES cria dinamicamente uma quantidade muito grande de campos no mapping de um índice. Acontece tipicamente quando: logs JSON têm campos de alta cardinalidade como chave (ex: {"user_123456": {...}} cria um campo por usuário), quando campos mudam de tipo entre documentos, ou quando arrays de objetos JSON complexos geram muitos campos aninhados.

    O problema: cada campo no mapping consome memória no heap do ES e degrada performance de queries e ingestão. Com dezenas de milhares de campos, o cluster pode ficar instável e inaceitavelmente lento.

    Prevenção: (1) definir explicit mappings — não usar dynamic mapping em produção ("dynamic": "strict" ou "false"); (2) nunca usar valores como chaves em JSON de logs — use {"key": "user_id", "value": "123"} em vez de {"user_id": "123"}; (3) usar flattened field type para objetos JSON dinâmicos — indexa tudo como flat strings sem explodir o mapping; (4) configurar index.mapping.total_fields.limit conservadoramente (padrão é 1000 — frequentemente precisa ser menos).

    Como você reduziria o custo de log aggregation em um sistema que gera 500GB de logs por dia?

    Em camadas: (1) Filtrar na origem: configurar Fluent Bit para descartar antes de enviar — logs de health check, logs de debug (level=DEBUG em produção), requests bem-sucedidos de bots/scanners. Isso pode eliminar 30-50% do volume. (2) Sampling de logs de sucesso: para HTTP 200/201 de endpoints de alta frequência, amostrar 5-10% e descartar o resto — erros e warnings sempre mantidos. (3) Escolha de backend: Loki com S3 storage custa ~$0.023/GB/mês vs Elasticsearch com SSD que pode custar 10-20× mais por GB. Com 500GB/dia e Loki, o custo de storage de 30 dias seria ~$345/mês; com ES, potencialmente $3.500-7.000/mês. (4) Retenção curta + archive: 7-14 dias hot, S3 Glacier para compliance. (5) Métricas em vez de logs: dados agregáveis (contagem de eventos por endpoint, histograma de latência) devem virar métricas Prometheus — uma série com 1000 observações/s custa centavos/mês; os logs equivalentes custariam gigabytes/hora.

    Como funciona o LogQL e em que ele difere do SQL ou KQL?

    LogQL tem dois modos: log queries (retornam linhas de log filtradas) e metric queries (derivam métricas dos logs). A sintaxe começa sempre com um stream selector usando labels entre chaves: {service="order-service", env="prod"} — isso é o equivalente do WHERE sobre campos indexados, e é obrigatório para restringir o scan a streams relevantes.

    Diferenças fundamentais vs SQL/KQL: (1) o stream selector é obrigatório (não pode fazer scan de todos os logs sem label); (2) filtros de conteúdo (|= "ERROR", | json | field > value) operam sobre o conteúdo não-indexado em pipeline — cada filtro reduz os logs antes do próximo; (3) metric queries derivam métricas de logs (rate, quantile_over_time, sum) com sintaxe análoga ao PromQL — a mesma fonte de logs pode produzir both linhas de log e métricas derivadas. O fluxo de uma query LogQL é: seleção de streams por label → scan de chunks → filtros em pipeline → parse → agregação.

    Como você desenharia o pipeline de log collection para 50 microserviços em Kubernetes?

    O design em camadas: (1) Aplicações: todos os serviços escrevem logs JSON estruturado em stdout — sem lógica de envio de logs na aplicação. O OTel Logs Bridge injeta trace_id e span_id automaticamente. (2) Coleta por nó: Fluent Bit como DaemonSet (um por nó). Cada Fluent Bit coleta logs de todos os containers do nó via tail, adiciona metadados Kubernetes (namespace, pod, service name via label) usando o filter Kubernetes, filtra health checks e logs DEBUG, e encaminha para Loki. (3) Loki: labels por namespace + service (baixa cardinalidade — ~50 serviços × ~5 ambientes = ~250 streams, bem dentro do limite). Retenção de 14 dias em hot storage, archive automático para S3. (4) Grafana: datasource Loki com derived fields para correlação com Tempo. Um dashboard de logs por serviço com variável $service. Considerations de escala: com 50 microserviços e 500GB/dia, o Loki em modo microservices (distributor + ingester + querier separados) garante escala independente de ingestão e query. Fluent Bit com back-pressure (configurar Mem_Buf_Limit e storage.type filesystem) evita perda de logs se o Loki ficar temporariamente indisponível.

Referências para aprofundar

  1. livro Logging in Action — Phil Wilkins (Manning, 2022). Guia abrangente de logging: structured logging, log aggregation, análise de logs, compliance. Cobre Fluentd, Elasticsearch e práticas de produção. Boa entrada para quem quer ir além de "adicione um logger".
  2. livro Distributed Systems Observability — Cindy Sridharan (O'Reilly, 2018). distributed-systems-observability.com — e-book gratuito. Capítulos 4-5 cobrem logging em sistemas distribuídos: o papel dos logs vs métricas vs traces, log aggregation como pilar de observabilidade, e quando usar cada sinal.
  3. docs Grafana Loki — Documentação oficial — Grafana Labs (2024). grafana.com/docs/loki/latest — Referência completa de arquitetura, configuração, LogQL, labels e limites. Inclui guias de deployment em Kubernetes com Helm e modos de operação (monolithic, scalable, microservices).
  4. docs LogQL — Query Language Reference — Grafana Labs (2024). grafana.com/docs/loki/latest/query — Referência completa de LogQL: stream selectors, pipeline stages (json, regexp, logfmt, unwrap), metric queries, funções de agregação. Inclui exemplos práticos para casos comuns.
  5. artigo Loki: Like Prometheus, but for Logs — Tom Wilkie, Goutham Raj (GrafanaCon, 2018). grafana.com/blog — A talk original que apresentou o Loki: a filosofia de "indexes only labels, not content", a comparação de custo com Elasticsearch, e a integração com o ecossistema Prometheus/Grafana. Explica o "porquê" por trás do design.
  6. docs Elasticsearch — Index Lifecycle Management — Elastic (2024). elastic.co/guide/en/elasticsearch/reference/current/ilm-index-lifecycle — Documentação completa de ILM: fases hot/warm/cold/frozen/delete, actions disponíveis em cada fase, configuração de rollover e shrink. Essencial para controlar custo de storage no ES.
  7. docs Elastic Common Schema (ECS) — Elastic (2024). elastic.co/guide/en/ecs — Convenção de nomenclatura de campos para logs e eventos: service.name, trace.id, http.request.method, error.message. Seguir ECS garante interoperabilidade entre ferramentas Elastic e facilita dashboards reutilizáveis.
  8. docs Fluent Bit — Manual oficial — Fluent Bit Project (2024). docs.fluentbit.io/manual — Referência completa de inputs, filters, outputs e configuração para Kubernetes. Inclui o filter Kubernetes para enriquecimento de metadados, o plugin Loki output, e configurações de backpressure para coleta confiável.
  9. docs CloudWatch Logs Insights — Query Syntax — AWS (2024). docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax — Referência da linguagem de queries do Insights: funções parse, filter, stats, sort, limit. Inclui exemplos para análise de Lambda, API Gateway, ALB e ECS.
  10. artigo Reducing CloudWatch Logs Costs — AWS Blog (2023). aws.amazon.com/blogs — Estratégias práticas para reduzir custo de ingestão e storage: filtrar antes de ingerir via Subscription Filters, usar S3 Export + Athena para queries históricas, configurar retenção por Log Group, e usar Metric Filters em vez de logs para dados agregáveis.
  11. docs Promtail — Grafana Agent para Loki — Grafana Labs (2024). grafana.com/docs/loki/latest/send-data/promtail — Documentação do Promtail: configuração de pipeline stages (json, regex, timestamp, labels), scraping de logs de Kubernetes e arquivos, e configuração de multi-line para stack traces. Alternativa ao Fluent Bit quando o stack é 100% Grafana.
  12. artigo High Cardinality in Loki: What It Is and How to Fix It — Grafana Labs Blog (2022). grafana.com/blog — Explica em detalhe o problema de alta cardinalidade no Loki: como identificar via métricas do próprio Loki, como corrigir (mover campos de alta cardinalidade de label para conteúdo do log), e boas práticas de design de labels para produção.