ELK Log Visualization
В данном практическом занятии рассмотрим возможности визуализации логов собственного приложения используя ELK стек.
Vagrant
Для работы будем использовать следующий Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.define "logging" do |c|
c.vm.provider "virtualbox" do |v|
v.cpus = 2
v.memory = 4096
end
c.vm.box = "ubuntu/lunar64"
c.vm.hostname = "logging"
c.vm.network "forwarded_port", guest: 8888, host: 8888
c.vm.network "forwarded_port", guest: 8889, host: 8889
c.vm.provision "shell", inline: <<-SHELL
apt-get update -q
apt-get install -yq docker.io docker-compose-v2
usermod -a -G docker vagrant
SHELL
end
end
Данная конфигурация установит на виртуальную машину docker и docker compose, с помощью которых в дальнейшем будут развернуты остальные компоненты.
Application
Создадим простое приложение main.go
на golang для отправки логов по протоколу
syslog в формате json
:
package main
import (
"io"
"log/slog"
"log/syslog"
"math/rand"
"net/http"
"os"
"time"
)
func main() {
logstash, err := syslog.Dial("udp", "logstash:5044", syslog.LOG_INFO, "test")
if err != nil {
slog.Error("error syslog dial", "error", err)
return
}
slog.SetDefault(slog.New(slog.NewJSONHandler(io.MultiWriter(os.Stdout, logstash), nil)))
slog.Info("start")
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
if rand.Intn(10) > 0 {
w.WriteHeader(200)
w.Write([]byte("OK\n"))
slog.Info("OK", "user", r.UserAgent(), "path", r.URL.Path, "duration", time.Since(start), "code", 200)
return
}
w.WriteHeader(500)
w.Write([]byte("NE OK\n"))
slog.Error("NE OK", "user", r.UserAgent(), "path", r.URL.Path, "duration", time.Since(start),"code", 500)
}))
http.ListenAndServe(":8080", nil)
}
Данное приложение принимает http запросы и с некоторой вероятностью возвращает ошибку.
В конце обработки выводится событие в стандартный вывод, а также отправляется
в logstash. Логируются следующий параметры: текст, уровень важности, код возврата,
юзер агент и время обработки. Также добавим к нему Dockerfile
:
FROM golang:1.21 as build
WORKDIR /src
COPY main.go /src/main.go
RUN go mod init example \
&& go mod tidy \
&& CGO_ENABLED=0 go build -o /bin/app ./main.go
FROM scratch
COPY --from=build /bin/app /app
CMD ["/app"]
Compose
Опишем компоненты ELK стека для нашего приложения и саму сборку приложения в
compose.yaml
файле:
version: '3'
services:
elasticsearch:
image: elasticsearch:7.9.1
container_name: elasticsearch
ports:
- "8889:9200"
- "9300:9300"
volumes:
- test_data:/usr/share/elasticsearch/data/
- ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
environment:
- discovery.type=single-node
- http.host=0.0.0.0
- transport.host=0.0.0.0
- xpack.security.enabled=false
- xpack.monitoring.enabled=false
- cluster.name=elasticsearch
- bootstrap.memory_lock=true
- ES_JAVA_OPTS=-Xms256m -Xmx256m
networks:
- elk
logstash:
image: logstash:7.9.1
container_name: logstash
user: "0"
ports:
- "5044:5044/udp"
- "9600:9600"
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
- ./logstash.yml:/usr/share/logstash/config/logstash.yml
- ls_data:/usr/share/logstash/data
- /var/lib/docker/containers:/var/lib/docker/containers
networks:
- elk
depends_on:
- elasticsearch
kibana:
image: kibana:7.9.1
container_name: kibana
ports:
- "8888:5601"
volumes:
- ./kibana.yml:/usr/share/kibana/config/kibana.yml
- kb_data:/usr/share/kibana/data
networks:
- elk
depends_on:
- elasticsearch
app:
image: test
container_name: app
build: .
ports:
- 8080:8080
networks:
- elk
depends_on:
- logstash
networks:
elk:
driver: bridge
volumes:
test_data:
ls_data:
kb_data:
А также конфигурации для этих компонентов.
elasticsearch.yml
:
cluster.name: "elasticsearch"
network.host: localhost
logstash.yml
:
http.host: 0.0.0.0
xpack.monitoring.elasticsearch.hosts: ["http://elasticsearch:9200"]
kibana.yml
:
server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://elasticsearch:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
logstash.conf
:
input {
syslog {
port => 5044
}
}
filter {
json {
source => "message"
}
mutate {
remove_field => ["message"]
}
}
output {
elasticsearch {
hosts => "http://elasticsearch:9200"
index => "test-logs-%{+YYYY.MM.DD}"
}
}
После чего запустим:
$ docker compose up -d
[+] Running 8/8
✔ Network vagrant_elk Created 0.0s
✔ Volume "vagrant_ls_data" Created 0.0s
✔ Volume "vagrant_kb_data" Created 0.0s
✔ Volume "vagrant_test_data" Created 0.0s
✔ Container elasticsearch Started 0.4s
✔ Container logstash Started 0.9s
✔ Container kibana Started 0.9s
✔ Container app Started 1.2s
Visualization
Проверим работу нашего приложения отправив запрос:
$ curl localhost:8080
OK
$ docker logs app
{"time":"2024-03-31T18:15:30.382334348Z","level":"INFO","msg":"start"}
{"time":"2024-03-31T18:50:27.186771729Z","level":"INFO","msg":"OK","user":"curl/7.88.1","path":"/","duration":7103,"code":200}
И посмотрим результат в kibana
на странице
/app/discover, первоначально создав index pattern:
Запустим цикл запросов для генерации логов:
$ while sleep 1;do curl -H "user-agent: user$(($RANDOM%10))" localhost:8080/$(($RANDOM%5));done
OK
OK
OK
OK
OK
OK
OK
OK
OK
NE OK
NE OK
OK
...
В данном цикле мы указываем случайный user-agent
от 0 до 9 и путь запрос от 0 до 4.
Выберем в левой панели поля, которые хотим отображать:
level
, code
, msg
, path
, user
и duration
.
Dashboard
На странице /app/dashboards можно создать дашборд с различной визуализацией событий приложения для анализа его работы. Добавим новый дашборд и новую визуализацию в нем:
Первую визуализацию добавим типа vertical bar, добавив по оси X - Date Histogram:
И сохраним:
Далее добавим визуализацию для отображения количества событий по уровням, также используя vertical bar, также сделав по оси X - Date Histogram, а по оси Y сделаем агрегации Sum Bucket по уровню логирования:
Также добавим понятные цвета и сохраним:
Далее можем отобразить количество сообщений в зависимости от пути запроса, для этого возьмем data table:
И получим:
Далее добавим визуализацию в виде pie chart по http кодам:
Далее отобразим количество сообщений по разным временам в виде horizontal bar:
И добавим последнюю визуализацию в виде облака тегов, на котором отобразим какие встречаются user-agent:
По итогу мы получим дашборд следующего содержимого:
Визуализации позволяют интерактивно менять фильтры кликая по элементам, например,
если кликнуть в pie chart на часть с кодом 500
и в облаке тегов на user1
,
то получим дашборд отфильтрованный по данным параметрам: