Visualização e Análise de Logs do Nginx com Grafana e ELK

Configuração do Nginx

O primeiro passo consiste em ajustar o formato de logs no nginx.conf. Definimos um formato personalizado para capturar os campos essenciais para a análise posterior.

http {
    # ...
    log_format  grafana_monitor  '$remote_addr - $remote_user [$time_local] '
                                 '"$request" $status $body_bytes_sent '
                                 '"$http_referer" "$http_user_agent"';
    
    access_log  /var/log/nginx/access.log  grafana_monitor;
    # ...
}

Configuração do Fielbeat

No arquivo filebeat.yml, configuramos o shipper para ler os arquivos de logs e enviá-los para o Logstash. É importante adicionar campos personalizados para identificar o tipo de serviço.

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /var/log/nginx/access.log
  fields:
    service: nginx-web
  fields_under_root: true
  encoding: utf-8

output.logstash:
  hosts: ["192.168.1.100:5044"]

processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~

Configuração do Logstash

O Logstash será responsável por filtrar e estruturar os dados. Primeiro, instale o plugin de geo-localização:

bin/logstash-plugin install logstash-filter-geoip

Em seguida, configure o pipeline (pipeline.conf). Utilizaremos o filtro grok para parsear a mensagem de log baseada no formato definido no Nginx e o geoip para obter dados de localização.

input {
  beats {
    port => 5044
  }
}

filter {
  if [service] == "nginx-web" {
    grok {
      match => {
        "message" => "%{IP:client_ip} - %{DATA:user_id} \[%{HTTPDATE:access_timestamp}\] \"%{WORD:http_method} %{NOTSPACE:request_path} HTTP/%{NUMBER:http_version}\" %{NUMBER:response_code} %{NUMBER:bytes_sent} \"%{DATA:http_referrer}\" \"%{DATA:user_agent}\""
      }
    }
    
    geoip {
      source => "client_ip"
      fields => ["city_name", "country_name", "region_name", "location"]
    }
    
    mutate {
      convert => { "bytes_sent" => "integer" }
      remove_field => ["host", "agent", "ecs", "log"]
    }
  }
}

output {
  if [service] == "nginx-web" {
    elasticsearch {
      hosts => ["http://192.168.1.100:9200"]
      index => "nginx-logs-%{+YYYY.MM.dd}"
      user => "elastic"
      password => "changeme"
    }
  }
}

Elasticsearch e Grafana

Certifique-se de que o cluster Elasticsearch esteja operacional para receber os índices. No Grafana, adicione o Elasticsearch como fonte de dados (Datasource), apontando para a URL da instância e configurando o padrão de índice como nginx-logs-*.

Configuração do Painel (JSON)

Abaixo está a configuração JSON para o painel do Grafana. Este exemplo foi adaptado para utilizar os novos nomes de campos definidos na configuração do Logstash (client_ip, response_code, etc.) e visualiza métricas como volume de logs, visitantes únicos e distribuição geográfica.

{
  "annotations": {
    "list": []
  },
  "editable": true,
  "gnetId": null,
  "graphTooltip": 0,
  "id": null,
  "links": [],
  "panels": [
    {
      "datasource": {
        "type": "elasticsearch",
        "uid": "P8079B216C365C7D9"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              }
            ]
          }
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 6,
        "x": 0,
        "y": 0
      },
      "id": 1,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "8.3.0",
      "targets": [
        {
          "alias": "Total de Solicitações",
          "bucketAggs": [
            {
              "field": "@timestamp",
              "id": "2",
              "settings": {
                "interval": "1h"
              },
              "type": "date_histogram"
            }
          ],
          "datasource": {
            "type": "elasticsearch",
            "uid": "P8079B216C365C7D9"
          },
          "metrics": [
            {
              "id": "1",
              "type": "count"
            }
          ],
          "query": "",
          "refId": "A",
          "timeField": "@timestamp"
        }
      ],
      "title": "Volume de Logs",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "elasticsearch",
        "uid": "P8079B216C365C7D9"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "custom": {
            "align": "auto",
            "displayMode": "auto"
          },
          "mappings": []
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "response_code"
            },
            "properties": [
              {
                "id": "custom.width",
                "value": 150
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 18,
        "x": 6,
        "y": 0
      },
      "id": 2,
      "options": {
        "showHeader": true
      },
      "pluginVersion": "8.3.0",
      "targets": [
        {
          "alias": "",
          "bucketAggs": [
            {
              "field": "response_code",
              "id": "2",
              "settings": {
                "size": "10"
              },
              "type": "terms"
            }
          ],
          "datasource": {
            "type": "elasticsearch",
            "uid": "P8079B216C365C7D9"
          },
          "metrics": [
            {
              "id": "1",
              "type": "count"
            }
          ],
          "query": "",
          "refId": "A"
        }
      ],
      "title": "Status HTTP",
      "type": "table"
    },
    {
      "datasource": {
        "type": "elasticsearch",
        "uid": "P8079B216C365C7D9"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "from": 0,
                "result": {
                  "text": "Baixo"
                },
                "to": 100
              },
              "type": "range"
            }
          ]
        }
      },
      "gridPos": {
        "h": 9,
        "w": 24,
        "x": 0,
        "y": 8
      },
      "id": 3,
      "options": {
        "basemap": {
          "name": "Layer 0",
          "type": "esri-xyz"
        },
        "view": {
          "id": "min_zoom",
          "lat": 0,
          "lon": 0,
          "zoom": 2
        }
      },
      "targets": [
        {
          "bucketAggs": [],
          "datasource": {
            "type": "elasticsearch",
            "uid": "P8079B216C365C7D9"
          },
          "metrics": [
            {
              "id": "1",
              "settings": {
                "size": "5000"
              },
              "type": "raw_data"
            }
          ],
          "query": "",
          "refId": "A"
        }
      ],
      "title": "Mapa Geográfico de IPs",
      "type": "geomap"
    },
    {
      "datasource": {
        "type": "elasticsearch",
        "uid": "P8079B216C365C7D9"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "hideFrom": {
              "tooltip": false,
              "viz": false,
              "legend": false
            }
          }
        }
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 0,
        "y": 17
      },
      "id": 4,
      "targets": [
        {
          "alias": "Top 10 IPs",
          "bucketAggs": [
            {
              "field": "client_ip",
              "id": "2",
              "settings": {
                "size": "10"
              },
              "type": "terms"
            }
          ],
          "metrics": [
            {
              "id": "1",
              "type": "count"
            }
          ],
          "refId": "A"
        }
      ],
      "title": "Origens por IP",
      "type": "piechart"
    },
    {
      "datasource": {
        "type": "elasticsearch",
        "uid": "P8079B216C365C7D9"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          }
        }
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 12,
        "y": 17
      },
      "id": 5,
      "targets": [
        {
          "alias": "User Agents",
          "bucketAggs": [
            {
              "field": "user_agent",
              "id": "2",
              "settings": {
                "size": "10"
              },
              "type": "terms"
            }
          ],
          "metrics": [
            {
              "id": "1",
              "type": "count"
            }
          ],
          "refId": "A"
        }
      ],
      "title": "Principais User Agents",
      "type": "bargauge"
    }
  ],
  "refresh": "5m",
  "schemaVersion": 36,
  "style": "dark",
  "title": "Nginx Dashboard",
  "uid": "nginx-analytics",
  "version": 1
}

Tags: nginx Grafana elasticsearch Logstash filebeat

Publicado em 9-14 17:20