Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api/
PORT=3000
PORT=3000
# Next App FRONTEND Instrumentation
NEXT_PUBLIC_FARO_URL=http://localhost:12347/collect
NEXT_PUBLIC_FARO_APP_NAME=next-frontend
NEXT_PUBLIC_FARO_APP_NAMESPACE=nextjs-example
NEXT_PUBLIC_FARO_APP_VERSION=1.0.0
NEXT_PUBLIC_APP_ENV=development

# Next App BACKEND Instrumentation
## Example assumes that the collector is running on the same machine
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
## Force protobuf
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
## Set Backend service name
OTEL_SERVICE_NAME=next-backend
## Customize resource attributes, namespace is a recommended attribute
OTEL_RESOURCE_ATTRIBUTES=service.namespace=nextjs-example

# OTel collector
GRAFANA_CLOUD_USERNAME=
GRAFANA_CLOUD_API_KEY=
GRAFANA_CLOUD_ENDPOINT=
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'app/styles/global.css';
import { QueryProvider } from 'shared/providers';
import { Toaster, TooltipProvider } from 'shared/ui';
import FrontendObservability from 'shared/config/metrics/FrontendObservability';

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<FrontendObservability />
<QueryProvider>
<TooltipProvider>{children}</TooltipProvider>
</QueryProvider>
Expand Down
18 changes: 18 additions & 0 deletions infra/observability/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Grafana
GRAFANA_PORT=3010
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin

# Loki
LOKI_HTTP_PORT=3100
LOKI_GRPC_PORT=9096

# Tempo
TEMPO_PORT=3200
TEMPO_OTLP_GRPC_PORT=4317
TEMPO_OTLP_HTTP_PORT=4318

# Alloy
ALLOY_UI_PORT=12345
ALLOY_FARO_PORT=12347
ALLOY_OTLP_GRPC_PORT=4319
59 changes: 59 additions & 0 deletions infra/observability/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Observability stack

Локальный стек для логов, трейсов и frontend observability:

- Grafana
- Loki
- Tempo
- Grafana Alloy

## Запуск

Из корня репозитория.

Создайте локальный env-файл:

```bash
cp ./infra/observability/.env.example ./infra/observability/.env
```

Запустите стек:

```bash
docker compose -f ./infra/observability/compose.observability.yaml up -d
```

Остановить стек:

```bash
docker compose -f ./infra/observability/compose.observability.yaml down
```

Проверить статус контейнеров:

```bash
docker compose -f ./infra/observability/compose.observability.yaml ps
```

## Адреса

- Grafana: `http://localhost:3010`
- Loki: `http://localhost:3100`
- Tempo: `http://localhost:3200`
- Alloy UI: `http://localhost:12345`
- Faro endpoint: `http://localhost:12347/collect`

Логин Grafana по умолчанию: `admin` / `admin`.

Порты и credentials можно переопределить в `infra/observability/.env`.

## Дашборды

Grafana автоматически подхватывает provisioned dashboards из `infra/observability/grafana/dashboards`.

Основные дашборды:

- `Task Tracker / Frontend`
- `Task Tracker / Frontend Traces (Tempo)`

Если в Grafana пустой список дашбордов, проверьте, что стек поднят командой из корня репозитория. Это важно для корректных bind mounts из `infra/observability/grafana/*`.
97 changes: 97 additions & 0 deletions infra/observability/alloy/config.alloy
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
discovery.docker "docker_containers" {
host = "unix:///var/run/docker.sock"
}

discovery.relabel "docker_containers" {
targets = discovery.docker.docker_containers.targets

rule {
source_labels = ["__meta_docker_container_name"]
target_label = "container"
}
}

loki.source.docker "docker_logs" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.docker_containers.output
forward_to = [loki.process.docker_logs.receiver]
}

loki.process "docker_logs" {
stage.docker {}

forward_to = [loki.write.local_loki.receiver]
}

loki.process "frontend_faro_logs" {
stage.logfmt {
mapping = {
app_name = "",
kind = "",
session_id = "",
browser_name = "",
browser_version = "",
os_name = "",
screen_width = "",
screen_height = "",
}
}

stage.template {
source = "screen_resolution"
template = "{{ if and .screen_width .screen_height }}{{ .screen_width }}x{{ .screen_height }}{{ end }}"
}

stage.labels {
values = {
app = "app_name",
kind = "kind",
session_id = "session_id",
browser_name = "browser_name",
browser_version = "browser_version",
os_name = "os_name",
screen_resolution = "screen_resolution",
}
}

forward_to = [loki.write.local_loki.receiver]
}

loki.write "local_loki" {
endpoint {
url = "http://" + sys.env("LOKI_HOST") + ":" + sys.env("LOKI_HTTP_PORT") + "/loki/api/v1/push"
}
}

faro.receiver "frontend" {
server {
listen_address = "0.0.0.0"
listen_port = sys.env("ALLOY_FARO_PORT")
cors_allowed_origins = ["*"]
}

output {
logs = [loki.process.frontend_faro_logs.receiver]
traces = [otelcol.exporter.otlp.tempo.input]
}
}

otelcol.receiver.otlp "ingest" {
grpc {
endpoint = "0.0.0.0:" + sys.env("ALLOY_OTLP_GRPC_PORT")
}

output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}

otelcol.exporter.otlp "tempo" {
client {
endpoint = sys.env("TEMPO_HOST") + ":" + sys.env("TEMPO_OTLP_GRPC_PORT")

tls {
insecure = true
}
}
}
102 changes: 102 additions & 0 deletions infra/observability/compose.observability.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: task-tracker-observability

services:
grafana:
image: grafana/grafana-enterprise:latest
container_name: grafana
env_file:
- .env
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
ports:
- '${GRAFANA_PORT:-3010}:3000'
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/etc/grafana/dashboards
depends_on:
- loki
- tempo
networks:
- observability

loki:
image: grafana/loki:latest
container_name: loki
command:
- -config.file=/etc/loki/local-config.yaml
- -config.expand-env=true
env_file:
- .env
environment:
LOKI_HTTP_PORT: ${LOKI_HTTP_PORT:-3100}
LOKI_GRPC_PORT: ${LOKI_GRPC_PORT:-9096}
ports:
- '${LOKI_HTTP_PORT:-3100}:3100'
- '${LOKI_GRPC_PORT:-9096}:9096'
volumes:
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
- loki_data:/loki
networks:
- observability

tempo:
image: grafana/tempo:latest
container_name: tempo
command:
- -config.file=/etc/tempo/tempo.yaml
- -config.expand-env=true
env_file:
- .env
environment:
TEMPO_PORT: ${TEMPO_PORT:-3200}
TEMPO_OTLP_GRPC_PORT: ${TEMPO_OTLP_GRPC_PORT:-4317}
TEMPO_OTLP_HTTP_PORT: ${TEMPO_OTLP_HTTP_PORT:-4318}
ports:
- '${TEMPO_PORT:-3200}:3200'
- '${TEMPO_OTLP_GRPC_PORT:-4317}:4317'
- '${TEMPO_OTLP_HTTP_PORT:-4318}:4318'
volumes:
- ./tempo/tempo-config.yaml:/etc/tempo/tempo.yaml
- tempo_data:/var/tempo
networks:
- observability

alloy:
image: grafana/alloy:latest
container_name: alloy
env_file:
- .env
environment:
LOKI_HOST: loki
LOKI_HTTP_PORT: ${LOKI_HTTP_PORT:-3100}
TEMPO_HOST: tempo
TEMPO_OTLP_GRPC_PORT: ${TEMPO_OTLP_GRPC_PORT:-4317}
ALLOY_OTLP_GRPC_PORT: ${ALLOY_OTLP_GRPC_PORT:-4319}
command:
- run
- --server.http.listen-addr=0.0.0.0:${ALLOY_UI_PORT:-12345}
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
ports:
- '${ALLOY_UI_PORT:-12345}:${ALLOY_UI_PORT:-12345}'
- '${ALLOY_FARO_PORT:-12347}:12347'
- '${ALLOY_OTLP_GRPC_PORT:-4319}:4319'
volumes:
- ./alloy/config.alloy:/etc/alloy/config.alloy
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- loki
- tempo
networks:
- observability

volumes:
grafana_data:
loki_data:
tempo_data:

networks:
observability:
name: task-tracker-observability
Loading
Loading