Discover là cổng vào Kibana mà ai cũng biết — mở lên, gõ KQL, đọc log. Nhưng lúc gặp log schema “sai ý” (field bị encode, thiếu field cần filter), hoặc phải lọc object lồng nhau, nhiều dev nghĩ đến phương án nặng nề là reindex hoặc sửa pipeline ingestion. Thực tế Kibana cho bạn hàng loạt tính năng để xử tại chỗ, không đụng đến index — nhanh hơn và an toàn hơn nhiều.
Đây là bài thứ 3 trong series Kibana từ A đến Z. Sau bài này bạn sẽ:
- Tạo được Runtime field bằng Painless mà không cần reindex
- Filter nested object, mảng JSON, và dùng regex trong Discover
- Bật highlighting để mắt tự bắt từ khoá trong log dài
- Phân biệt Saved Query và Saved Search, chọn đúng loại để team reuse
- Dùng Inspect request để debug KQL và tối ưu query chậm
Phần 1: Runtime field — tạo field ảo on-the-fly
Runtime field là field tính lúc query, không lưu trong index. Đây là workaround nhanh nhất khi schema thiếu field cần filter hoặc cần derive.
Use case thực tế
- Tách
hour_of_daytừ@timestampđể tìm pattern traffic theo giờ. - Extract
subdomaintừ URL (https://api.example.com/...→api). - Combine 2 field:
user_id + tenant_idthành key duy nhất. - Mask PII: thay 4 số cuối của phone bằng
****. - Normalize case:
lowercase(service_name)khi logger sinh ra hỗn hợp.
Tạo qua Data View Management (persistent)
- ≡ → Management → Stack Management → Data Views → chọn data view → Add field.
- Đặt Name:
hour_of_day. - Type: Long.
- Set value: bật → paste Painless script:
emit(doc['@timestamp'].value.getHour());
- Save. Từ giờ field
hour_of_dayxuất hiện trong list field Discover, filter được như field thường:hour_of_day >= 9 and hour_of_day < 17.
Tạo ad-hoc (chỉ trong session hiện tại)
Discover → button + Add field ngay cạnh field list → điền tương tự. Field chỉ tồn tại trong tab đang mở, đóng là mất. Tiện để thử nghiệm.
Painless script mẫu
Extract subdomain từ URL:
if (doc['url.keyword'].size() == 0) return;
String url = doc['url.keyword'].value;
int start = url.indexOf("://") + 3;
int end = url.indexOf(".", start);
if (end > start) emit(url.substring(start, end));
Mask 4 số cuối phone:
if (doc['phone.keyword'].size() == 0) return;
String p = doc['phone.keyword'].value;
if (p.length() > 4) emit(p.substring(0, p.length() - 4) + "****");
Combine 2 field:
if (doc['tenant_id.keyword'].size() == 0 || doc['user_id.keyword'].size() == 0) return;
emit(doc['tenant_id.keyword'].value + ":" + doc['user_id.keyword'].value);
Trade-off phải biết
- Chậm hơn field thường. Runtime field tính lại mỗi query. Trên index 100M docs, filter theo runtime field có thể chậm 5-10x so với field indexed.
- Không dùng trong aggregation nặng. STATS trên runtime field của ES|QL chạy được nhưng không scale.
- Script lỗi = document bị skip, không lỗi query. Debug khó — kiểm tra script bằng Dev Tools trước khi save.
- Giải pháp dài hạn: nếu runtime field được dùng thường xuyên → backfill bằng update_by_query vào field thật, hoặc thêm vào pipeline ingestion.
Phần 2: Filter phức tạp
Nested object vs dot notation
Elasticsearch có 2 cách lưu object lồng:
- Flatten (mặc định):
user.name,user.email→ index như field riêng. Dot notation trong KQL đọc thẳng:user.name : "alice". - Nested type: mảng object giữ quan hệ 1-1 giữa các field trong cùng phần tử. Cần toán tử nested đặc biệt.
Mapping nested:
{
"properties": {
"comments": {
"type": "nested",
"properties": { "author": {"type": "keyword"}, "score": {"type": "integer"} }
}
}
}
Filter comments.author == "alice" AND comments.score > 5 cùng 1 comment với flatten sẽ ra sai (match bất kỳ author alice + bất kỳ score > 5 trong cả array). Cần nested query DSL:
{
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{"term": {"comments.author": "alice"}},
{"range": {"comments.score": {"gt": 5}}}
]
}
}
}
}
Trong KQL (8.x hỗ trợ nested):
comments: { author : "alice" and score > 5 }
Cú pháp field : { ... } giữ constraint trong cùng phần tử.
Regex filter
KQL hỗ trợ regex qua /pattern/:
Message : /timeout \d{4,}ms/
Lưu ý: regex chạy trên text field đã analyzed không phải raw. Muốn regex exact → Message.keyword:
Message.keyword : /timeout \d{4,}ms/
Regex trên index lớn rất chậm. Tránh regex cho filter thường xuyên — dùng wildcard hoặc runtime field đã normalize.
Filter theo script (Dev Tools)
Khi KQL không đủ, dùng ES Query DSL trong Dev Tools hoặc filter script trong GUI:
Discover → Add filter → Edit as Query DSL:
{
"script": {
"script": {
"source": "doc['response_time_ms'].value > params.threshold",
"params": { "threshold": 1000 }
}
}
}
Script filter không dùng index — cực chậm trên dữ liệu lớn. Chỉ hợp khi không thể viết bằng KQL/range query.
Phần 3: Highlighting
Mặc định Discover không highlight từ khoá search. Khi log dài vài kilobyte, tìm đoạn match bằng mắt rất mỏi.
Bật highlighting
Kibana 8.x bật mặc định trên hầu hết data view. Nếu không thấy highlight:
- Advanced Settings →
doc_table:highlight→ settrue. - Reload Discover.
Custom tag
Mặc định Kibana dùng <mark> CSS. Muốn đổi màu:
/* user CSS trong Space Settings hoặc deploy custom */
.kbnDocViewer__buttonGroup mark {
background: #fef08a;
color: #854d0e;
padding: 0 2px;
border-radius: 2px;
}
Highlight trong ES|QL
ES|QL chưa hỗ trợ highlight native (8.14). Muốn xem match trong bảng ES|QL, dùng EVAL + SUBSTRING để trích đoạn quanh match, hoặc quay về Discover cho view chi tiết.
Document context
Click 1 row trong Discover → View surrounding documents. Kibana sẽ hiện 5 doc trước + 5 doc sau theo timestamp, hữu ích để trace bối cảnh xung quanh 1 error cụ thể.
Phần 4: Saved Query vs Saved Search
Hai khái niệm hay nhầm:
| Saved Query | Saved Search | |
|---|---|---|
| Lưu cái gì | Chỉ KQL + filters | KQL + filters + columns + sort + data view + time range |
| Phạm vi | Dùng lại query ở nhiều Saved Search / Dashboard | Mở Discover là thấy nguyên view |
| Tạo từ | Menu “Saved queries” trong search bar | Button Save ở góc phải Discover |
| Embed vào dashboard | KHÔNG (chỉ apply filter) | CÓ (Add from library) |
Khi nào dùng Saved Query
Khi cùng 1 query filter được dùng ở nhiều nơi:
- Dashboard Errors dùng
Level : ("Error" or "Fatal"). - Dashboard Tenant A dùng cùng filter trên tenant A.
- Alert rule dùng cùng filter.
Tạo 1 Saved Query “Production Errors”, áp dụng mọi nơi → sửa 1 chỗ, các nơi update theo.
Khi nào dùng Saved Search
Khi team cần 1 view đầy đủ để mở nhanh: cột cụ thể, sort cụ thể, time range ngầm định. Như bài 1 đã làm với Saved Search “All Errors”.
Tạo Saved Query
- Discover → gõ KQL → click icon disc cạnh search bar → Save current query.
- Đặt Title, description, chọn Include filters + Include time filter nếu muốn.
Phần 5: Inspect request — debug KQL như pro
Discover có menu ẩn cho phép xem ES Query DSL Kibana thực sự gửi đi. Dùng khi:
- KQL ra 0 kết quả mà không rõ sao.
- Query chậm bất thường.
- Cần copy DSL để dùng ở ES client khác (Python, Java).
Cách mở
- Trong Discover, click nút Inspect (góc phải trên) hoặc menu ba chấm → Inspect.
- Tab Request hiển thị DSL. Tab Response hiển thị raw ES response.
- Tab Statistics có
took(thời gian),hits.total, shard info.
Checklist khi query chậm
- Mở Inspect → Statistics → xem
took(ms). - Nếu
took> 1s: copy DSL sang Dev Tools → Profile (POST /<index>/_search?_profile=true). - Xem
profile.shards[].searches[].query[]để biết phase nào chậm. - Dấu hiệu thường gặp:
wildcardquery trên large text field → đổi sangmatchhoặc thêm keyword sub-field.- Scripted query → thay bằng runtime field indexed.
- Nhiều
shouldclause → gom lạitermsquery.
Ví dụ: tối ưu filter chậm
KQL gốc:
Message : *timeout* and Level : "Error"
DSL sau khi inspect:
{
"query": {
"bool": {
"must": [
{"query_string": {"query": "*timeout*", "default_field": "Message"}},
{"term": {"Level.keyword": "Error"}}
]
}
}
}
Wildcard leading *timeout* tắt toàn bộ inverted index optimization — ES phải scan từng doc. Thay bằng:
Message : "timeout" and Level : "Error"
(phrase match dùng index được) hoặc thêm field has_timeout (runtime hoặc indexed) để filter boolean.
Cheatsheet
| Nhu cầu | Công cụ | Cách làm |
|---|---|---|
| Field không tồn tại trong index | Runtime field | Painless emit(...), trade-off chậm hơn |
| Filter object lồng nhau | Nested KQL | field: { subfield: value } |
| Regex exact | .keyword + /pattern/ | Tránh trên index lớn |
| Script filter phức tạp | Add filter → Edit as Query DSL | Rất chậm, last resort |
| Highlight từ khoá | Advanced Settings | doc_table:highlight = true |
| Document context | Row → View surrounding | Trace bối cảnh quanh error |
| Query reusable nhiều nơi | Saved Query | Disc icon cạnh search bar |
| View Discover đầy đủ | Saved Search | Button Save góc phải |
| Debug KQL ra 0 kết quả | Inspect → Request | Xem DSL thực |
| Query chậm | Inspect → Statistics → Profile API | Tìm wildcard, script filter |
Lời kết
Khi Discover “không chịu làm theo ý”, 90% case không cần reindex — runtime field xử lý field thiếu, nested KQL xử lý array object, inspect request chỉ ra lý do query chậm. Dev backend nắm được 5 kỹ thuật này sẽ tự chủ với log schema khó, không còn phải chờ DevOps sửa pipeline ingestion cho những việc tương đối nhỏ.
Bài tiếp theo trong series Kibana từ A đến Z sẽ bước sang nhóm Visualization: dùng Lens để dựng chart từ drag-drop cho tới Formula phức tạp (error rate, SLO burn rate, time-shift so sánh tuần trước), annotation layer cho deploy marker, và reference line cho threshold. Nếu có topic Discover cụ thể bạn muốn mình viết sâu hơn — ví dụ “runtime field hay indexed field: benchmark thực tế”, “migrate từ Lucene sang KQL”, “Discover với data stream và time series” — cứ drop comment hoặc email.