Dados e bancos de dados

Gerenciamento centralizado de logs com Elasticsearch, Logstash e Kibana

Intermediário9 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

Logs só se tornam úteis quando estão estruturados, centralizados e pesquisáveis poucos segundos depois de escritos. Este guia percorre a stack Elastic de ponta a ponta: emitir logs em JSON, enviá-los com Beats ou Elastic Agent, fazer o parsing com Logstash ou ingest pipelines, controlar os mappings com index templates, gerenciar a retenção com ILM e dimensionar os shards para manter o cluster saudável.

O que você vai aprender

  • Descrever o papel de cada componente da stack Elastic e escolher entre Logstash e ingest pipelines
  • Emitir logs JSON estruturados e alinhá-los a um esquema de campos consistente
  • Escrever regras de parsing com grok e dissect e enriquecer os eventos durante a ingestão
  • Controlar os mappings com index templates e component templates, e a retenção com index lifecycle management
  • Dimensionar shards e índices para que o cluster continue estável à medida que o volume de logs cresce

Um único servidor de aplicação escrevendo em um arquivo é fácil de depurar: você abre o arquivo, faz a busca e pronto. Vinte contêineres atrás de um load balancer, reiniciando a cada deploy e sumindo quando o número de instâncias diminui, são um problema completamente diferente. Quando você descobre que algo deu errado, o contêiner que registrou a causa pode nem existir mais. A centralização de logs existe para tornar esse cenário impossível: cada linha sai da máquina no momento em que é escrita, é quebrada em campos e fica pesquisável em todos os serviços ao mesmo tempo.

A stack Elastic (Elasticsearch para armazenamento e busca, Logstash para processamento, Kibana para consultas e visualização, e os agentes de envio Beats ou Elastic Agent) continua sendo a solução mais adotada. Este guia cobre a versão principal atual, Elastic 9, e foca nas decisões que determinam se a plataforma continua útil e com custo razoável à medida que o volume cresce.

O que cada componente faz

  • O Elasticsearch armazena documentos em índices e responde a consultas. É um mecanismo de busca distribuído construído sobre o Apache Lucene, portanto compartilha o índice invertido e o mecanismo de análise descritos no guia de Apache Solr, que não vamos repetir aqui. O que importa é que se trata de um armazenamento quase em tempo real: um documento se torna pesquisável depois de um refresh, por padrão em menos de um segundo.
  • O Kibana é a interface de consulta e visualização e também o console de administração de pipelines, políticas de ciclo de vida, regras de alerta e dashboards.
  • Os Beats são agentes de envio de propósito único escritos em Go: Filebeat para arquivos de log e contêineres, Metricbeat para métricas de sistema e de serviços, Heartbeat para verificações de disponibilidade, e Packetbeat e Auditbeat para dados de rede e de auditoria.
  • O Elastic Agent é o agente unificado, mais recente. Um único processo cuida de logs, métricas e dados de segurança, e baixa integrações (configuração de entrada, ingest pipelines, index templates e dashboards prontos, tudo empacotado) de um servidor Fleet central. Em um deploy novo, esse é o caminho padrão; os Beats continuam totalmente suportados e são mais simples quando você precisa de uma coisa só.
  • O Logstash é um pipeline de processamento independente, com entradas, filtros e saídas. Ele faz buffering, transforma e roteia, e consegue ler fontes que nenhum outro componente lê.

A stack é só metade do quadro. Logs, métricas e traces são três visões do mesmo sistema, e uma aplicação que expõe endpoints de saúde e de métricas, como descrito em Actuator e observabilidade, fornece as outras duas.

Gere logs na origem como se uma máquina fosse lê-los

A mudança de maior impacto em qualquer projeto de logging acontece antes de qualquer envio: fazer a aplicação emitir eventos estruturados em vez de prosa.

{
  "@timestamp": "2026-09-07T08:14:22.481Z",
  "log.level": "ERROR",
  "service.name": "payment-api",
  "service.version": "4.2.1",
  "trace.id": "3f1c0a9d5e6b47c8",
  "transaction.id": "9b2e77a1",
  "event.dataset": "payment.settlement",
  "http.request.method": "POST",
  "url.path": "/v1/settlements",
  "http.response.status_code": 502,
  "message": "Settlement rejected by acquirer",
  "error.type": "AcquirerTimeoutException",
  "labels": { "merchant_id": "M-88213" }
}

Cada campo pode ser consultado, agregado e colocado em gráfico sem nenhum parser no meio do caminho. Aplicações Java conseguem isso com um encoder JSON do Logback ou do Log4j2; os encoders compatíveis com ECS emitem diretamente os nomes de campo acima. Quando um documento parece errado, colá-lo em um formatador de JSON deixa evidente na hora um nível de aninhamento a mais ou uma chave duplicada.

O que leva à segunda decisão: adotar uma convenção de nomes de campos e mantê-la. O Elastic Common Schema define nomes para os conceitos que todo sistema registra (host.name, service.name, event.duration, error.stack_trace, user.id), e usá-lo significa que os dashboards e as regras de detecção prontos funcionam com os seus dados, e que os logs de dois times podem ser correlacionados. Invente seus próprios nomes e cada dashboard vira um trabalho sob medida.

Duas regras merecem entrar no padrão de código. Nunca registre segredos, números de cartão ou dados pessoais que você não colocaria em um banco de dados, porque uma plataforma de logs costuma ser menos protegida que o banco. E coloque os identificadores de correlação (trace id, request id, tenant id) em um mapped diagnostic context, para que todas as linhas os carreguem automaticamente.

Envio e parsing

O Filebeat acompanha os arquivos à medida que crescem, grava sua posição de leitura em um registro para que um restart não duplique nem perca dados, lida com stack traces multilinha e aplica backpressure quando o destino fica lento.

filebeat.inputs:
  - type: filestream
    id: payment-api
    paths: ["/var/log/payment-api/*.json"]
    parsers:
      - ndjson:
          target: ""
          overwrite_keys: true
          add_error_key: true

processors:
  - add_host_metadata: ~
  - add_kubernetes_metadata: ~
  - drop_event:
      when:
        equals:
          log.level: "TRACE"

output.elasticsearch:
  hosts: ["https://es01:9200"]
  api_key: "${ES_API_KEY}"

Os contêineres mudam o modelo de envio, não o princípio: escreva no stdout, deixe o runtime coletar e rode um agente por nó com autodiscovery que anexe os labels do pod, do namespace e do contêiner. Veja Fundamentos de Docker para entender por que gerar logs no stdout é o padrão nativo dos contêineres.

Para fontes que você não pode alterar (o feed syslog de um firewall, um appliance que emite linhas de largura fixa), o parsing é inevitável. Duas ferramentas fazem esse trabalho.

O Dissect divide por delimitadores fixos. É muito mais rápido que uma expressão regular e deve ser sua primeira escolha quando o formato é estável:

%{clientip} - %{user} [%{timestamp}] "%{method} %{path} %{httpversion}" %{status} %{bytes}

O Grok é uma biblioteca de expressões regulares nomeadas e lida com entradas de fato irregulares:

%{IPORHOST:source.ip} %{USER:user.name} \[%{HTTPDATE:event.created}\] "%{WORD:http.request.method} %{URIPATHPARAM:url.original}" %{NUMBER:http.response.status_code:int}

Os padrões grok também são a causa clássica de uma ingestão que desaba: um padrão sem âncora que entra em backtracking catastrófico satura um núcleo de CPU. Ancore os padrões, prefira dissect sempre que possível e teste no Grok Debugger do Kibana antes de fazer o deploy.

A próxima escolha é onde o parsing roda. Um ingest pipeline do Elasticsearch executa dentro do cluster, nos nós com o papel ingest, e não exige nenhum serviço extra:

PUT _ingest/pipeline/payment-logs
{
  "processors": [
    { "dissect": { "field": "message", "pattern": "%{ts} %{level} [%{thread}] %{logger} - %{msg}" } },
    { "date":    { "field": "ts", "formats": ["ISO8601"], "target_field": "@timestamp" } },
    { "geoip":   { "field": "source.ip", "target_field": "source.geo", "ignore_missing": true } },
    { "remove":  { "field": ["ts", "message"], "ignore_missing": true } }
  ],
  "on_failure": [
    { "set": { "field": "event.kind", "value": "pipeline_error" } },
    { "set": { "field": "error.message", "value": "{{ _ingest.on_failure_message }}" } }
  ]
}

Repare no bloco on_failure. Sem ele, uma linha malformada faz o documento ser rejeitado e você perde justamente o evento que mais queria ver. Marque as falhas e indexe-as mesmo assim.

O Logstash compensa o serviço extra quando você precisa de um buffer durável entre produtores barulhentos e o cluster (a persistent queue dele sobrevive a restarts), quando os eventos precisam ir para mais de um destino ou quando a transformação é pesada o bastante para você preferir não gastar CPU dos nós de dados com ela. Uma arquitetura comum em produção é Beats enviando para o Kafka como buffer, Logstash consumindo do Kafka e Elasticsearch como destino final.

Mappings, data streams e retenção

O Elasticsearch deduz um mapping para qualquer campo que ainda não conhece. Essa conveniência causa dois problemas crônicos: um campo que chega primeiro como número e depois como string é rejeitado, e uma explosão de mappings (milhares de campos criados dinamicamente a partir de chaves sem limite, como labels.<request-id>) incha o cluster state até deixar o cluster instável.

Isso se controla com component templates compostos em um index template:

PUT _index_template/logs-payment
{
  "index_patterns": ["logs-payment-*"],
  "data_stream": {},
  "priority": 200,
  "composed_of": ["logs-mappings", "logs-settings"],
  "template": {
    "settings": { "index.number_of_shards": 2, "index.number_of_replicas": 1,
                  "index.lifecycle.name": "logs-90d" },
    "mappings": {
      "dynamic": "strict",
      "properties": {
        "@timestamp":  { "type": "date" },
        "service.name":{ "type": "keyword" },
        "log.level":   { "type": "keyword" },
        "message":     { "type": "text" },
        "http.response.status_code": { "type": "integer" }
      }
    }
  }
}

A distinção entre keyword e text é a que precisa ficar clara: keyword armazena o valor completo para filtragem exata, agregação e ordenação; text é analisado em tokens para busca full-text e não pode ser agregado de forma eficiente. Nomes de serviço, níveis de log e nomes de host são keywords. Mensagens em texto livre são text.

Um data stream é o formato moderno para séries temporais só de inserção. Você escreve sempre em um único nome, logs-payment-prod, e o Elasticsearch gerencia por trás uma série de índices de apoio ocultos, fazendo rollover para um novo quando um limite de tamanho ou de idade é atingido. O rollover importa porque apagar dados antigos passa a significar remover um índice inteiro, o que é quase de graça, enquanto apagar documentos por consulta é caro.

O index lifecycle management automatiza todo o ciclo de vida desses dados:

PUT _ilm/policy/logs-90d
{
  "policy": { "phases": {
    "hot":    { "actions": { "rollover": { "max_primary_shard_size": "40gb", "max_age": "1d" } } },
    "warm":   { "min_age": "3d",  "actions": { "forcemerge": { "max_num_segments": 1 },
                                               "shrink": { "number_of_shards": 1 } } },
    "cold":   { "min_age": "15d", "actions": { "set_priority": { "priority": 0 } } },
    "delete": { "min_age": "90d", "actions": { "delete": {} } }
  }}
}

Os nós hot recebem as escritas em discos rápidos; os nós warm guardam dados recentes que quase não recebem escrita; os nós cold mantêm o que é consultado só de vez em quando. Defina a retenção por dataset (eventos de auditoria e logs de debug raramente justificam a mesma janela) e lembre-se de que o gigabyte mais barato é aquele que você nunca indexou. Descartar eventos TRACE e requisições de health check já no agente de envio muitas vezes corta o volume pela metade sem nenhuma perda para as investigações.

Consultas e dashboards

Na visão Discover do Kibana, a KQL cobre a maior parte da filtragem do dia a dia: service.name:"payment-api" and http.response.status_code >= 500 and not url.path:"/health". A sintaxe Lucene, mais antiga, continua disponível para expressões regulares e busca aproximada (fuzzy).

A ES|QL, linguagem de consulta com pipes da Elastic, é a grande mudança da geração atual. Ela expressa filtragem, transformação e agregação em uma única cadeia legível:

FROM logs-payment-*
| WHERE http.response.status_code >= 500 AND @timestamp > NOW() - 1 hour
| STATS failures = COUNT(*) BY service.name, error.type
| SORT failures DESC
| LIMIT 20

Monte os dashboards em torno de perguntas, e não dos campos disponíveis: taxa de erro por serviço, percentis de latência, endpoints que mais falham, marcações de deploy. As regras de alerta do Kibana transformam qualquer um desses indicadores em notificação quando um limite é ultrapassado. E use runtime fields para aquele atributo eventual que você esqueceu de extrair: eles são calculados na hora da consulta, então não custam nada até serem usados e poupam uma reindexação.

Dimensionamento e operação

A maioria dos clusters de logs com problemas sofre da mesma doença: shards demais. Cada shard é um índice Lucene com seu próprio custo de memória e de descritores de arquivo, e o cluster state que os acompanha precisa ser replicado em todos os nós.

  • Mire em 10–50 GB por shard e, aproximadamente, em 20 shards ou menos por gigabyte de heap em cada nó.
  • Configure o heap da JVM em cerca de metade da RAM da máquina e nunca acima de ~31 GB, para que os ponteiros continuem comprimidos; o restante da RAM alimenta o page cache do qual o Lucene depende.
  • Separe os papéis dos nós quando o cluster crescer: nós master-eligible dedicados (três, para ter quórum) mantêm o cluster state estável quando os nós de dados estão ocupados.
  • Fique de olho nos disk watermarks. Em 85%, o Elasticsearch para de alocar novos shards em um nó; em 95%, coloca os índices em modo somente leitura. Os dois pegam os times de surpresa às três da manhã.
  • Faça snapshots para um object storage. A replicação protege contra a perda de um nó, não contra um ingest pipeline com defeito nem contra uma exclusão por engano.

Duas alternativas merecem uma menção justa. O OpenSearch é um fork com licença Apache, API compatível e dashboards próprios, e é a escolha natural quando a licença da Elastic é um impeditivo. O Grafana Loki faz a aposta arquitetural oposta: indexar só um pequeno conjunto de labels, manter o conteúdo bruto dos logs comprimido em object storage e fazer o equivalente a um grep na hora da consulta. Isso sai muito mais barato em grande volume e é muito mais fraco em busca full-text sobre todos os dados. Se suas consultas são sempre do tipo “este serviço, esta janela de tempo”, o Loki serve; se são do tipo “encontre este token em qualquer lugar”, não serve. Quando o volume de logs passa do que um cluster de busca deveria guardar, os padrões de armazenamento e processamento do guia de arquitetura de big data se tornam a alternativa.

Principais pontos

  • Logs JSON estruturados, com um esquema consistente como o ECS, tiram o parsing do pipeline e tornam cada campo consultável e pronto para gráficos.
  • O Elastic Agent com as integrações do Fleet é o agente padrão para deploys novos; os Beats continuam simples, e o Logstash se justifica para buffering, envio a vários destinos e transformações pesadas.
  • Prefira dissect a grok quando o formato for fixo, ancore os padrões grok e sempre adicione um handler on_failure para que linhas com problema sejam marcadas em vez de descartadas.
  • Controle os mappings com index templates e component templates, diferencie keyword de text e evite explosões de mappings causadas por nomes de campo sem limite.
  • Escreva em data streams, faça rollover por tamanho ou idade e expresse a retenção como uma política de ILM com fases hot, warm, cold e delete para cada dataset.
  • Mantenha os shards entre 10 e 50 GB e o heap abaixo de cerca de 31 GB, acompanhe os disk watermarks e faça snapshots para object storage, porque réplicas não são backups.

Fontes e leituras complementares

Perguntas frequentes

Ainda preciso do Logstash agora que existem os ingest pipelines?
Muitas vezes, não. Os ingest pipelines do Elasticsearch cuidam do parsing, do enriquecimento e da extração de datas dentro do próprio cluster, e as integrações do Elastic Agent já vêm com pipelines prontos. O Logstash se justifica quando você precisa de um buffer antes do cluster, de tradução de protocolo para fontes que o Beats não consegue ler, de distribuir os eventos para vários destinos ou de transformações pesadas que você prefere não rodar nos nós de dados.
Por quanto tempo devo guardar os logs?
O suficiente para investigar os incidentes que você de fato investiga, mais o que a conformidade exigir: para a maioria dos times, alguns dias de dados quentes pesquisáveis e semanas ou meses de armazenamento mais frio. Transforme essa resposta em uma política de ILM, e não em um script de exclusão, e defina a retenção por tipo de dado: logs de requisições, registros de auditoria e traces de debug raramente merecem a mesma janela.
Por que meu cluster está amarelo ou vermelho?
Amarelo significa que alguns shards de réplica não estão alocados: os dados estão íntegros, mas sem redundância, algo comum em um cluster de desenvolvimento com um único nó, onde as réplicas nunca podem ser alocadas. Vermelho significa que um shard primário não está alocado e parte dos dados está indisponível. A API allocation explain informa o motivo exato, que em geral são os disk watermarks, um nó que saiu do cluster ou nós insuficientes para o número de réplicas solicitado.
A stack Elastic é a única opção para logs?
Não. O OpenSearch é um fork do Elasticsearch com licença Apache, API compatível e sua própria ferramenta de dashboards, e o Grafana Loki segue outro caminho: indexa apenas labels e guarda as linhas de log brutas em um object storage, o que sai bem mais barato em grande volume, mas deixa a desejar na busca full-text. Escolha com base nos seus padrões de consulta, no custo por gigabyte e nas ferramentas que seu time já opera.