---
id: central-loki
title: "Loki"
shortTitle: "Loki"
track: central-logs
trackTitle: "Log tập trung"
order: 2
level: core
status: published
estimatedMinutes: 35
prerequisites:
  - central-elastic
next: central-graylog
reviewedAgainst: 2026-10-12
sources:
  - title: "Loki — overview"
    url: https://grafana.com/docs/loki/latest/get-started/overview/
  - title: "Loki — labels"
    url: https://grafana.com/docs/loki/latest/get-started/labels/
  - title: "LogQL query examples"
    url: https://grafana.com/docs/loki/latest/query/query_examples/
---

# Loki

Elasticsearch đánh index trên nội dung log. Loki thì chỉ đánh index trên label, còn nội dung được nén và cất trong object storage. Nếu team đã dùng Grafana cho metric, dùng thêm Loki giúp bạn khỏi phải mở một UI nữa.

## Để làm gì

Bạn sẽ viết một câu LogQL lọc theo label trước, rồi mới tìm chữ trong dòng log. Bạn cũng sẽ hiểu vì sao không được gắn user id vào label, và khi nào Loki không thay được Elastic.

## Gồm những gì

- Label và cardinality.
- LogQL: `{app="api"} |= "error"`.
- Những trường hợp nên ở lại với Elastic: search full-text nặng, nhiều field không cố định, công việc kiểu SIEM.

Lab không dựng cả stack Promtail và Loki, vì riêng một cụm Loki tối thiểu đã dài hơn cả bài này. Trong lab, bạn đọc câu query và chỉ ra label nào đặt sai.

## Dùng khi nào

Team đã có Prometheus và Grafana, log chủ yếu đến từ Kubernetes, và câu hỏi thường có dạng "Pod api trong namespace prod, những dòng có chữ timeout". Loki được thiết kế cho đúng kiểu câu hỏi đó.

Nếu câu hỏi là "tìm mọi log có số thẻ này trong 90 ngày" trên một đống field không cố định, Elastic hoặc một sản phẩm search khác sẽ đỡ vất vả hơn. Loki vẫn tìm được chữ trong dòng, nhưng nó chỉ tìm trong những stream đã được chọn bằng label, chứ không có index cho mọi từ trong toàn bộ log.

## Kiến thức cốt lõi

Theo overview của Grafana, Loki không index nội dung log mà chỉ index label. Label là cặp tên và giá trị gắn cho cả một stream, ví dụ `app=api`, `namespace=prod`.

Cardinality là số tổ hợp label khác nhau. `namespace` chỉ có vài giá trị, còn `user_id` có thể có hàng triệu. Mỗi tổ hợp label là một stream riêng. Docs labels khuyên chỉ dùng label có ít giá trị. Nếu nhét `user_id` vào label, index sẽ phình, ingester tốn bộ nhớ và query chậm hẳn. User id nên nằm trong nội dung dòng log, rồi bạn lọc bằng `|=` sau khi đã chọn stream.

```logql
{namespace="prod", app="api"} |= "timeout"
```

Câu này chọn các stream của api trong prod, rồi chỉ giữ những dòng có chữ `timeout`. `|=` khớp đúng chuỗi, không phải regex; muốn dùng regex thì dùng `|~`.

Thứ tự viết rất quan trọng. Nếu bỏ phần selector label và chỉ tìm chữ, Loki phải quét rất rộng. Luôn viết label trước.

Label nên lấy từ metadata của Kubernetes: namespace, pod, container, hoặc một label app bạn đã gắn. Đừng lấy label từ từng field trong JSON của user nếu field đó có nhiều giá trị.

## Lệnh và code mẫu

Lab không có server Loki. Bạn đọc mấy dòng log giả dưới đây rồi viết query.

```text
namespace=prod app=api request_id=8f3a message=upstream timeout
namespace=prod app=api request_id=8f3b message=handled
namespace=dev app=api request_id=1 message=upstream timeout
namespace=prod app=worker request_id=9 message=upstream timeout
```

Ở đây `request_id` nằm trong nội dung dòng, không phải label. Label chỉ có `namespace` và `app`.

Query đúng để tìm timeout của api trên production:

```logql
{namespace="prod", app="api"} |= "timeout"
```

Query này khớp dòng 1, không khớp dòng của dev và không khớp dòng của worker.

## Thực hành

1. Với bốn dòng trên, cho biết query nào trả về đúng một dòng.
2. Viết một label sai: `{request_id="8f3a"}`. Giải thích vì sao không làm vậy trên Loki thật, dù trong ví dụ chỉ có vài id.
3. Chọn một hệ thống bạn đang có. Liệt kê 3 label được phép dùng (ít giá trị) và 1 thứ chỉ được nằm trong message.
4. Nếu công ty đã có Grafana và Loki, chạy query `{namespace="prod"}` trong khoảng 15 phút. Không có thì dừng ở bước 3.

## Kết quả mong đợi

- Query có selector và `|= "timeout"` chỉ còn lại dòng của api trên prod.
- Bạn từ chối dùng `request_id` hay `user_id` làm label.
- Bạn có ba label ít giá trị, ví dụ `namespace`, `app`, `env`.

## Checklist

- [ ] Viết label trước, tìm chữ trong dòng sau.
- [ ] Không dùng user id hay request id làm label.
- [ ] Phân biệt được `|=` với `|~`.
- [ ] Biết Loki không thay được Elastic cho mọi kiểu search.
- [ ] Log vẫn phải có request id trong nội dung dòng.

## Lỗi thường gặp

**Dùng label `status_code` rồi gắn kèm thêm user.** Riêng `status_code` chỉ có vài chục giá trị nên chấp nhận được. Gắn thêm `user_id` thì không. Hãy nhìn vào số lượng giá trị, đừng quyết định theo cảm giác "cái này có vẻ hữu ích".

**Chạy `{app="api"} |= "error"` trên 30 ngày.** Loki vẫn phải đọc nội dung mọi dòng log của api trong 30 ngày đó. Hãy thu hẹp bằng `namespace` và rút ngắn khoảng thời gian.

**Kết luận rằng Grafana đã có Loki thì bỏ Prometheus được.** Loki lo log, Prometheus lo metric, nên các bài monitoring vẫn cần. Hai hệ cùng hiển thị trên Grafana nhưng không thay thế nhau.

## Nguồn

1. [Loki overview](https://grafana.com/docs/loki/latest/get-started/overview/) — Grafana, index label chứ không index full text.
2. [Labels](https://grafana.com/docs/loki/latest/get-started/labels/) — cardinality.
3. [LogQL examples](https://grafana.com/docs/loki/latest/query/query_examples/) — selector và filter dòng.

## Tiếp theo

[Graylog](./03-graylog.md)
