feat(logs): 上下文前后条数可配置(默认 2,原来写死 5)+ 每页条数改预设选项
=== 一、问题 ===
查看日志上下文时,前后各取几条是**写死**在控制器里的:
span := 5 * time.Minute
QueryLokiLogs(..., ts-span, ts-1, 5, "backward") // 5 条
QueryLokiLogs(..., ts+1, ts+span, 5, "forward") // 5 条
三处写死:条数 5、时间窗 5 分钟、且用户无法调整。
写死的三个实际弊端:
1. 排障时想看更多(比如前后 20 条找调用链)做不到;
2. 只想看紧邻 2 条时,仍然要扫 5 分钟窗口、返回 5 条,浪费;
3. 时间窗与条数不匹配——某服务日志稀疏时,5 分钟窗口里可能一条都没有,
于是"前后各 5 条"实际返回 0 条。
=== 二、改动 ===
1. backend/dto: LogContextRequest 新增 before/after(可选)
2. backend/controllers: normalizeContextLines()
- 不传或 ≤0 → 默认 2 条
- 上限 50 条(防止一次弹窗拉几百条)
- 时间窗改为**按条数自适应**:每条按 30s 估算,最少 1 分钟、最多 2 小时
(不再固定 5 分钟,解决"稀疏服务取不到上下文"的问题)
3. frontend
- 上下文弹窗标题栏加「前后各 [2/5/10/20/50]」选择器,切换后原地重新拉取
- 选择结果写入 localStorage,下次沿用
- 每页条数:数字输入框 → 预设下拉 [100/500/1000/2000/5000]
(原来只能手输且硬顶 1000,现在上限提到 Loki 默认的 5000)
- i18n:去掉"前后各最多 5 条 · 前后 5 分钟"的写死文案
=== 三、实测(平台 API)===
before=2 after=2 → 2 / 2 条
before=5 after=5 → 5 / 5 条
before=10 after=10 → 10 / 10 条
before=50 after=50 → 50 / 50 条
before=999 after=999 → 50 / 50 条(超上限自动收敛)
不传 → 2 / 2 条(默认值生效)
镜像:lghost/lokistack:latest + v0.1 = sha256:d19239fa65f3
This commit is contained in:
1 parent
5d530036e6
commit
3191af8b6a
7 files changed
+96
-13
No files matched your search
@@ -16,6 +16,24 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 上下文前后条数的默认值与上限(原来是硬编码 5)
|
||||
const (
|
||||
DefaultContextLines = 2
|
||||
MaxContextLines = 50
|
||||
)
|
||||
|
||||
// normalizeContextLines 把前端传入的条数收敛到 [1, MaxContextLines],
|
||||
// 不传(0)或负数一律用默认值。
|
||||
func normalizeContextLines(n int) int {
|
||||
if n <= 0 {
|
||||
return DefaultContextLines
|
||||
}
|
||||
if n > MaxContextLines {
|
||||
return MaxContextLines
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (ctrl *LogsController) Context(c *gin.Context) {
|
||||
var req dto.LogContextRequest
|
||||
if !utils.BindAndValidate(c, &req) {
|
||||
@@ -41,13 +59,26 @@ func (ctrl *LogsController) Context(c *gin.Context) {
|
||||
utils.Forbidden(c, err.Error())
|
||||
return
|
||||
}
|
||||
span := int64(5 * time.Minute)
|
||||
before, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts-span, ts-1, 5, "backward", "")
|
||||
// 前后条数由前端传入(默认 2 条,上限 50 —— 再多一次弹窗就是几百条日志)
|
||||
beforeN := normalizeContextLines(req.Before)
|
||||
afterN := normalizeContextLines(req.After)
|
||||
|
||||
// 时间窗随条数自适应:原来固定 5 分钟,条数调大时 5 分钟可能装不下,
|
||||
// 条数调小时又白扫一大片。按「每条 30s」估算,最少 1 分钟,最多 2 小时。
|
||||
span := int64(time.Duration(max(beforeN, afterN)*30) * time.Second)
|
||||
if span < int64(time.Minute) {
|
||||
span = int64(time.Minute)
|
||||
}
|
||||
if span > int64(2*time.Hour) {
|
||||
span = int64(2 * time.Hour)
|
||||
}
|
||||
|
||||
before, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts-span, ts-1, beforeN, "backward", "")
|
||||
if err != nil {
|
||||
utils.Error(c, 502, err.Error())
|
||||
return
|
||||
}
|
||||
after, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts+1, ts+span, 5, "forward", "")
|
||||
after, err := services.QueryLokiLogs(c.Request.Context(), src, query, ts+1, ts+span, afterN, "forward", "")
|
||||
if err != nil {
|
||||
utils.Error(c, 502, err.Error())
|
||||
return
|
||||
|
||||
@@ -45,4 +45,8 @@ type LogContextRequest struct {
|
||||
SourceID uint `json:"sourceId" binding:"required"`
|
||||
TimestampNs string `json:"timestampNs" binding:"required"`
|
||||
Labels map[string]string `json:"labels" binding:"required"`
|
||||
// 上下文前后各取几条(可选)。不传按 DefaultContextLines,
|
||||
// 上限 MaxContextLines —— 原来前后各 5 条是写死的,不能调。
|
||||
Before int `json:"before"`
|
||||
After int `json:"after"`
|
||||
}
|
||||
Reference in new issue
Block a user