用 SPL 查询、租户与登录、上传 CSV、图表与仪表盘
Osmanthe 开发预览版 · 2026-10-11 · 返回文档目录
1. 直接用 Splunk SPL 搜索
不用学新语言。搜索页查询框左边有 SPL / LogsQL 切换,默认就是 SPL。像在 Splunk 里一样写,例如:
index=demo level=error | stats count by service | sort -count
网关会把 SPL 翻译成底层引擎(VictoriaLogs)的 LogsQL 再执行。每次搜索下方都可以展开「对应的 LogsQL」,看到实际执行的查询;结果和你在 Splunk 里预期的一样,翻译不了的写法会直接报错并说明原因,不会悄悄返回不同的结果。
点搜索框旁的「语法」,有十几个常用例子,点一下就能直接搜。
index=demo "card declined"index=demo level=error service=paymentindex=demo | timechart count by serviceindex=demo | stats count by http.status_code,然后切到「饼图」index=demo | chart count over service by level
index、sourcetype 是怎么来的
- Splunk HEC 发来的数据,
index、sourcetype、host、source原样保留,所以 Splunk 里的搜索条件可以照搬。 - OpenTelemetry 发来的数据没有 index,用
service.name=xxx这类字段过滤,或者写index=*。 - 上传的 CSV 用你在上传页填的 index 和 sourcetype(默认 main 和 csv)。
_raw对应原始日志正文,_time是事件时间。
2. 支持哪些命令和函数
下表每一行的例子都在演示数据上实际跑过。
| 命令 | 例子 | 说明 |
|---|---|---|
| 搜索条件 | index=demo timeout NOT level=info | 关键词、短语、field=value、!=、> >= < <=、通配符 *、AND / OR / NOT、括号、IN (...) |
| earliest / latest | earliest=-1d@d latest=@d | 和页面上选的时间范围取交集;支持 -15m、-1h、-7d,以及 @d、@h、@w1、@mon、@q、@y 取整(按手机时区) |
| where | | where duration_ms>1000 AND like(http.path, "/api/cart%") | 比较、AND/OR/NOT,函数 isnull、isnotnull、like、match、searchmatch、cidrmatch |
| stats | | stats count, avg(duration_ms) as avg, max(duration_ms) as max by service | 见下面的统计函数 |
| eventstats | | eventstats avg(duration_ms) as svc_avg by service | 统计结果加到每一行,原始事件保留 |
| streamstats / accum | | streamstats count as n by host | 累计统计,按当前顺序算(原始事件默认从新到旧,和 Splunk 一致);不支持 window= |
| metadata / eventcount | | metadata type=hosts index=demo | 写在最前面;metadata 支持 hosts、sources、sourcetypes |
| tstats | | tstats count where index=* by host | 写在最前面;where 后面是普通搜索条件,by 支持 _time span=1h;不支持 from datamodel= |
| timechart | | timechart span=1h count by service | 不写 span 会按时间范围自动选;by 的系列默认只保留总和最大的 10 个,其余合成 OTHER(limit=0 不限,useother=f 直接去掉;OTHER 只支持 count、sum、avg、min、max,dc 等函数要加 useother=f 或 limit=0) |
| chart | | chart count over service by level | 交叉表,可画堆叠柱状图;by 的系列同样默认保留 10 个;span=10 可把 over 的数字字段分成区间 |
| top / rare | | top limit=5 error.type | 也支持 countfield= |
| sort | | sort -count | - 降序,+ 升序,多个字段 |
| head / tail | | head 20 | |
| table / fields | | table _time, service, _raw | fields - x 去掉字段 |
| rename | | rename service as svc | |
| dedup | | dedup error.type | 每个值保留最新一条 |
| eval | | eval lvl=case(http.status_code>=500, "5xx", http.status_code>=400, "4xx", true(), "ok") | if、case(含 true() 兜底)、coalesce、len、lower、upper、round、abs、floor、ceil、tostring、tonumber、replace、substr、trim、split、now、四则运算、字符串拼接 .(同一个表达式里不能既算数又拼接,分两次 eval) |
| rex | | rex "user=(?<uid>\w+)" | 支持 field=;mode=sed 支持 "s/正则/替换/g" |
| regex | | regex host!="^test-" | 按正则保留或排除事件,区分大小写 |
| extract / kv | | extract | stats count by user | 从正文自动提取 key=value |
| replace | | replace "*.local" WITH "*" IN host | 整值替换,支持 * 通配 |
| makemv / mvexpand | | makemv delim="," tags | mvexpand tags | 拆成多值,再展开成多行 |
| strcat / rangemap / addtotals | | rangemap field=duration_ms fast=0-200 slow=201-100000 | addtotals 需要写出字段名 |
| reverse | | reverse | 原始事件改成从旧到新 |
| spath | | spath input=payload | 展开 JSON 字段 |
| fillnull | | fillnull value=0 bytes | |
| bin / bucket | | bin _time span=6h | stats count by _time | |
| lookup | | lookup http_status.csv status OUTPUT desc AS status_desc | 用查找表补字段,见 第 6 节 |
| inputlookup | | inputlookup http_status where status>=400 | 写在最前面,按文件顺序列出查找表;支持 start=、max=。写在中间要加 append=t,把表的行接到前面结果的后面 |
| outputlookup | ... | stats count by host | outputlookup hosts.csv | 把结果存成查找表;append=t 追加。和 Splunk 一样后面可以接命令,结果原样往下传。只在点「搜索」时执行,API 要用 POST |
统计函数(stats、timechart、chart 通用)
count、count(eval(条件))、dc(distinct_count)、sum、avg(mean)、min、max、median、stdev、perc95 / p95 / perc99(任意百分位)、values、list、earliest(_time)、latest(_time)、first(_time)、last(_time)。first/last 取当前顺序里第一行/最后一行的时间(原始事件默认从新到旧),行不是按 _time 排的时候会报错(inputlookup append=t 接上表的行之后也算,要先 sort _time)。eventstats、streamstats 只能用 count、sum、avg、min、max 和 earliest/latest/first/last(_time),其他函数(dc、median、perc、values、list、stdev、count(eval()))会报错,可以先用 stats 算出来。
暂不支持(会明确报错)
Splunk 的 100 多个命令逐个列在 SPL 命令覆盖清单 里,每个都标了“已支持 / 部分支持 / 计划做 / 不打算做”。最常用的几个还没做的是:transaction、join / append 和子搜索、makeresults、xyseries / transpose、convert。
在别的产品里用 SPL 语法有没有法律风险,见 法律风险梳理。
3. 和 Splunk 不一样的地方
- 时间范围:以页面上选的时间为准,查询里的 earliest/latest 再在这个范围内缩小。默认是最近 24 小时,旧数据要选“最近 7 天”或“全部”。
- 大小写:和 Splunk 一样,关键词和 field=value 不区分大小写;字段名区分大小写。
- stats 结果顺序:按 by 字段排序,和 Splunk 一致;要按数量排就加
| sort -count。 - 字段自动提取:Splunk 会从文本里自动提取 key=value;这里只用写入时就有的字段(HEC 的 fields、OTel 属性、CSV 列、JSON 正文)。文本里的值用 rex 提取。
- 统计数字:返回的是精确值;dc 也是精确去重。
- tstats:Splunk 的 tstats 只能用索引字段(host、source、sourcetype 等),这里任何字段都能用,结果和同样条件的 stats 完全一样,速度也一样(没有单独的加速摘要)。summariesonly= 等加速选项会被忽略。
- lookup 的多值:一个值匹配到查找表里多行时,输出字段和 Splunk 一样是多值,这里写成 JSON 数组(例如
["alice","al"]),可以直接| mvexpand展开。OUTPUTNEW 按字段判断:事件里已有的字段不动,没有的才补上。 - 中文输入法:全角符号会自动换成半角,例如
|()=,和中文引号 “ ”,所以index=* |stats count也能正常执行。双引号里的内容保持原样,用来搜索全角字符本身。
4. 租户、账号和登录
一台服务器可以给很多客户用,每个客户是一个租户。租户的数据单独存放(VictoriaLogs 里各自的 account_id),查询里写什么都看不到别的租户的数据。打开首页先看到的是登录页,没登录什么也看不到。
三种人,按这个顺序出场:
- 超级管理员(平台方,账号 superadmin):登录后只看到「租户」页。在那里新建租户,同时填好它的管理员用户名和初始密码,设配额;新建后页面给出这个租户写日志用的 API Key 和接入地址,交给客户。还可以改配额、停用/恢复租户、给租户加管理员或重设管理员密码(客户的管理员忘了密码时)。超级管理员看不到任何租户的数据。
- 租户管理员(比如店主):用超级管理员给的账号登录,在「用户与 Key」里给员工开账号、决定每人能看哪些 index,查看和新建/删除写日志用的 API Key,看自己的配额和今天的用量。
- 普通用户(员工):用管理员给的账号登录,只能看分给他的 index。
- API Key 只能写日志:HEC 里是
Authorization: Splunk <key>,OpenTelemetry 里是Authorization: Bearer <key>,也能用来上传 CSV。它不能搜索、不能管理用户;拿 Key 去搜会被拒绝(403)。脚本要搜索,先POST /api/v1/login拿到登录令牌,放在X-API-Key里。 - 配额(超级管理员给每个租户设,留空不限,改了立刻生效):每天最多写入多少 MB(按 UTC 日期,北京时间早上 8 点清零;超过后这一批数据不保存,返回 429,第二天恢复);同时最多几个搜索(多出来的排队,等 20 秒还没轮到就返回 429);能查最近几天(更早的数据在这个租户的所有搜索里都看不到。数据本身还按网关统一的保留期存放,目前 31 天)。
- 停用租户:它的 Key 马上不能写,用户马上退出、不能登录;数据保留,恢复后照常。
- 超级管理员的密码:由网关的环境变量
OSMANTHE_SUPERADMIN_PASSWORD决定(测试环境的默认值是dev-superadmin-change-me,正式用必须改)。不设这个变量时,网关第一次启动会随机生成一个密码,写在用户数据卷的superadmin-password.txt里。 - 配置文件里的
[[tenants]](demo、other 等)照样有效,在租户页标着「配置文件」,它们的 Key 只能在配置文件里改。两个租户用了同一个 account_id(会互相看到日志)时,网关拒绝启动;页面上新建的租户自动用下一个没人用的编号。 - 还没做的:Key 加密存储、删除租户(现在只能停用)、按租户限制存储总量。
用户登录和 index 权限
每个人都用用户名和密码登录。管理员能看整个租户,普通用户只看分给他的 index。
- 管理员登录后点上方的「用户与 Key」。
- 在「添加用户」里填用户名、初始密码(至少 8 位)、角色和能看的 index(逗号分开;下面会列出已有的 index,点一下就加进去)。
- 把用户名和密码告诉对方。对方在首页登录,页面上方显示「名字(角色)· 租户」,下面写着他能看哪些 index。
- 两种角色:管理员能看所有 index、管理用户和写日志用的 API Key、设置助理的模型和 Key;普通用户只能看列出的 index。
*表示全部,app_*表示所有以 app_ 开头的 index。 - 在哪里生效:网关把权限加在每一次读数据上(底层用 VictoriaLogs 的 extra_filters,子查询 union、join、in(...) 也受限制)。搜索、直方图、字段列表、仪表盘、助理的每一个工具都一样,查询里写
index=别的也只会得到空结果。 - 改动立刻生效:改了 index 或角色,已经登录的人下一次请求就按新权限;删除用户或重设密码会让他马上退出。
- 普通用户不能做的:管理用户、改助理的模型和 Key、上传或删除查找表、执行 outputlookup(查找表整个租户共用;能看全部 index 的普通用户也不行)、把 CSV 上传到没有权限的 index。
- 查找表谁能用:每张表有「谁能用」:只有管理员、租户里所有人,或能看指定 index 的用户(要能看列出的每一个)。新上传的表默认只有管理员能用,同名再上传不改设置;outputlookup 存的结果可能含任何 index 的数据,所以每次保存后都只有管理员能用。管理员在「上传数据 → 查找表」列表里点「谁能用」修改。普通用户看不到、也用不了没分给他的表,lookup / inputlookup / 下载会提示「不存在或没有权限」。
- 换账号:登录、退出或登录失效时,页面会清掉助理对话、搜索结果、查找表列表,以及上传页选好的文件、预览和上传结果,上一个账号还在路上的回复会被丢弃。管理员改了某个用户能看的 index 后,他的助理对话会重新开始,旧对话内容不再发给模型。
- 没有 index 字段的数据(例如 OpenTelemetry 发来的日志)只有管理员和权限是
*的用户能看。想按 index 分权限,在采集端给它们加一个 index 属性(OTel Collector 的 resource 属性index)。 - 登录有效期:12 小时不用就过期,最长 7 天;网关重启后要重新登录。同一个用户名连续输错 5 次密码,要等 1 分钟。
- 登录不能用来发数据:HEC 和 OTLP 只认 API Key。
- 「我的账号」里可以改自己的密码,改完其他设备上的登录会退出。
5. 上传 CSV 建立索引
相当于 Splunk 的「添加数据 → 上传」。
- 点顶部「上传数据」,选文件(.csv、.tsv、.txt,或者 .gz 压缩包)。
- 页面会先在本地预览前 5 行,自动识别分隔符、编码和时间列。中文 Excel 导出的 GBK 编码也能识别。
- 填 index 和 sourcetype(相当于 Splunk 的索引名和来源类型),确认时间列和时区。
- 点「上传并建立索引」,完成后点「搜索这些数据」直接跳到搜索页。
每一行变成一条事件,每一列变成一个字段,可以直接 | stats sum(金额) by 城市。整行文本也能做关键词搜索。文件边上传边写入,不受内存限制,单个文件上限 2GB(解压后)。时间解析失败的行会用上传时间,并在结果里告诉你有多少行。
用命令行上传:
curl -X POST --data-binary @orders.csv \
-H 'Authorization: Splunk demo-key-change-me' \
'https://你的地址/api/v1/upload/csv?index=sales&sourcetype=orders&tz=%2B08:00'
可选参数:time_field(指定时间列的字段名,空字符串表示用上传时间;source 等保留名会改成 csv_source,重名列会加 _N 后缀)、time_col(按列号指定时间列,从 1 开始,和 time_field 二选一)、tz(无时区的时间按哪个时区算)、delimiter(, ; tab |)、encoding(utf-8 或 gbk;不写时按文件里第一段中文等非英文字节自动判断)。文件里有和编码对不上的字节时,上传会失败并指出是第几行,不会存成乱码。
6. 查找表(lookup)
查找表是一张 CSV 对照表,用来给事件补字段。例如日志里只有 status=404,对照表里写着 404 对应 “Not Found”,查询时就能多出一列说明。
上传和管理
- 点「上传数据」,在下面的「查找表」里选 CSV 文件,填表名(不填就用文件名),点「上传查找表」。第一行是表头,同名上传会覆盖。某一行的列数比表头多(多出的格子不是空的)会报错并指出是第几行,原来的表不变。
- 列表里可以「查看」(打开
| inputlookup 表名)、「下载」、「删除」。 - 查找表按租户隔离,别的租户看不到也用不了。
http_status.csv和http_status指的是同一张表。
命令行上传(要管理员登录,TOKEN 是 POST /api/v1/login 返回的 token,API Key 不行):
curl -X PUT --data-binary @http_status.csv \
-H "X-API-Key: $TOKEN" \
'https://你的地址/api/v1/lookups/http_status'
写法
| 写法 | 结果 |
|---|---|
| lookup http_status status OUTPUT desc | 用事件的 status 去匹配表里的 status 列,把 desc 列加到事件上。事件里原来的 desc 会被覆盖;匹配不到时 desc 被清空 |
| lookup http_status status OUTPUTNEW desc | 只给还没有 desc 的事件补上,已有的不动 |
| lookup http_status status | 不写 OUTPUT:表里除匹配列以外的所有列都加上(覆盖) |
| lookup users uid AS user_id OUTPUT name AS owner, dept | 表里的列名和事件字段名不一样时用 AS;可以写多个匹配列,全部相等才算匹配 |
| inputlookup users where dept=web | 写在搜索最前面(和 Splunk 一样以 | 开头),直接看表的内容,可以接其他命令,例如 | stats count by dept。不受页面时间范围影响,租户里还没有日志也能用。输入 | inputlookup 后会自动提示表名 |
index=web | stats count by host | inputlookup append=t web_hosts | 先列出搜索结果,再把表的行按文件顺序接在后面,后面的命令(例如 | stats sum(count) by host)对两部分一起算。接在普通事件后面时,事件仍按从新到旧排在前面,表的行(没有时间)排在最后。可以连着接多张表,按写的顺序排;之后的 head、tail、reverse、dedup、fields、table 都按这个顺序处理。也支持 where、start=、max= |
index=web | stats count by host | outputlookup web_hosts | 把搜索结果存成查找表(覆盖);append=t 追加到原表后面;override_if_empty=f 没有结果时保留原表 |
index=web | stats count by host | outputlookup web_hosts | where count>100 | outputlookup 后面接命令:表里存的是全部主机,页面上只显示 count>100 的 |
改几行
和 Splunk 的 CSV 查找表一样,有两种改法,写法不变:
- 在 Excel 里改好,同名再上传一次;
- 用搜索改:
| inputlookup alerts | eval status=if(id="42", "closed", status) | outputlookup alerts。
不同的是保存方式:网关把每张表逐行存在数据库(SQLite)里,保存时先和原表比对,只写入新增、修改、删除的那几行,其余行不动。所以 50 万行的表改一行,只写一行。上传结果和搜索页状态栏会显示「改动 N 行」。下载拿到的仍是 CSV。只有改了表头(加列、删列、改列名)时才会整表重写。
匹配规则和 Splunk 的 CSV 查找表默认设置一样:整值相等、区分大小写;表里空着的格子算没有值;一个值匹配多行时得到多值(按表里的顺序,最多 1000 行)。
为什么快
查找发生在存储引擎里:网关只取出这次查询用到的列,按匹配列合并好,随查询一起交给 VictoriaLogs,在那边做哈希关联,再接着做后面的 stats 等命令。所以 | lookup ... | stats count by 新字段 扫几百万条事件也不用把事件传回网关。实测 660 万条事件关联 1000 行的表约 0.15 秒,关联 10 万行的表约 1 秒。查找表不写进日志库,不受日志保留期影响,也不会出现在普通搜索结果里。
限制
- 每张表最多 50 万行,上传文件最大 64MB。
- 保存时只写改动的行,但比对仍要读整张表(在内存里,大表约零点几秒)。
- Splunk 在 transforms.conf 里配置的通配符、CIDR 匹配、按时间查找、KV Store、外部脚本查找还没有;不区分大小写匹配也还没有,需要的话可以先
| eval 字段=lower(字段)再查。
7. 图表和仪表盘
凡是统计类查询(stats、timechart、chart、top、rare),结果会自动画图,图表上方可以切换:
- 折线、面积、柱状、条形、饼图、单值,多系列可以勾选「堆叠」;点图例可以隐藏/显示某个系列,手指按住或鼠标悬停显示具体数值。
- timechart 默认折线图,top 默认条形图,只有一个数字的结果默认单值大字。
- 也可以切到「统计表」或「JSON」看原始结果。
- 普通搜索上方的柱状图显示事件随时间的分布,点一根柱子缩小到那段时间。
仪表盘
- 在搜索页做出图表后点「加入仪表盘」,图表就会出现在「仪表盘」页。
- 「载入示例仪表盘」一键生成 8 个面板:错误数、各服务请求量、错误数排行、状态码分布、P95 延迟、延迟趋势、错误类型、服务 × 级别。
- 可以统一设置时间范围、每 1 或 5 分钟自动刷新、调整面板顺序、点「打开」回到搜索页修改。
- 目前仪表盘保存在浏览器本地,换手机就看不到;下一步改成存到服务端,可以分享给同租户的人。
8. 怎样超越 Splunk:后续计划
先把 Splunk 用户最常用的部分做到“搬过来就能用”,再在 Splunk 做得不好的地方拉开差距。下面是建议的顺序,你挑优先级:
第一步:模仿到位
- 补 SPL 命令:transaction、join/append 子搜索、makeresults、xyseries/transpose(eventstats、streamstats 已做好)。
- 仪表盘存到服务端、可分享,能导入 Splunk 的 Simple XML 仪表盘。
- 保存的搜索和告警(定时跑 SPL,超阈值发 Telegram / 邮件 / Webhook)。
- 账号、角色、Key 管理页面。
第二步:超越
- 成本:存储压缩比和查询速度已经实测比 ClickHouse、OpenObserve 好;按量计费可以比 Splunk 便宜一个数量级。
- OpenTelemetry 原生:Splunk 接 OTel 要绕一层,这里直接收,trace_id 可以一键串起一次请求的所有日志。
- 自然语言查询:用中文说「过去一小时支付服务的错误按类型统计」,自动生成 SPL,并显示出来让你核对。
- 迁移工具:导入 Splunk 的保存搜索、仪表盘和告警,自动翻译并对比结果。
- 手机体验:Splunk 手机端很弱,这里所有页面都按手机竖屏设计。