Dozzle SQL Engine 实战指南:用 DuckDB WebAssembly 在浏览器中对容器日志执行 SQL 分析
【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle
Dozzle 的 SQL Engine(SQL Analytics)让你可以直接在浏览器中对容器 JSON 日志执行 SQL 查询,底层由 DuckDB 编译为 WebAssembly 运行,所有数据始终留在本机。本文以 docs/guide/sql-engine.md 为主线,结合前端分析组件、SQL 编辑器与后端日志接口的源码实现,完整讲解启用前提、工作原语、示例查询与限制边界,帮助你在不接触外部数据库的前提下快速上手实时日志分析。
功能定位与适用场景
SQL Engine 是 Dozzle 内置的一项面向日志数据的 SQL 分析能力:针对已经熟悉 SQL 的用户,把日志查看从"滚动翻页"升级为"按需查询"。它处于 beta 阶段并对所有用户开放,核心价值在于:
- 用你熟悉的
SELECT / WHERE / GROUP BY语法直接分析容器日志; - 全程在浏览器本地执行,数据不离开你的机器,无需把日志同步到外部数据库;
- 适合临时性的聚合、过滤、分组与时间窗口统计等分析场景。
启用前提与快速开始
前提:日志必须是 JSON 格式
SQL Engine 只能查询结构化(JSON)日志。如果你的容器输出的是纯文本日志,需要先让应用以 JSON 形式输出日志(例如让日志行带有level、message、timestamp等字段),引擎才会解析出可查询的列。这一限制同时决定了:日志行解析得到的字段越多,SQL 分析能做的文章就越大。
入口:下拉菜单与快捷键
准备好 JSON 日志后:
- 在对应容器的日志视图上展开下拉菜单,选择SQL Analytics;
- 也可以直接使用快捷键
Ctrl+Shift+F(macOS 为Cmd+Shift+F)快速打开。
打开后默认查询为SELECT * FROM logs LIMIT 100,即先预览前 100 行,便于确认表结构和字段命名。
工作原理:WebAssembly + DuckDB 的本地 SQL 引擎
整体流程
SQL Engine 的完整链路是"后端取数 → 前端建表 → 浏览器内查询":
- 打开 SQL Analytics 时,前端通过
/api/hosts/{host}/containers/{containerId}/logs?stdout=1&stderr=1&everything&jsonOnly接口拉取该容器的全量日志(见 LogAnalytics.vue),jsonOnly查询参数保证只返回 JSON 日志; - 浏览器首次加载
@duckdb/duckdb-wasm,把 DuckDB 的 WASM 模块下载并初始化到本地; - 日志流被完整读取并注册为 DuckDB 的虚拟文件
logs.json,随后执行建表语句,把日志展开成一张名为logs的表; - 此后所有
conn.query()都在浏览器进程内对这张内存表执行,结果实时呈现。
建表语句的源码细节
文档中给出的初始建表语句是:
CREATE TABLE logs AS SELECT unnest(m) FROM 'logs.json'而 LogAnalytics.vue 中实际执行的语句更为完整:
CREATE TABLE logs AS SELECT unnest(m) FROM read_json('logs.json', ignore_errors = true, format = 'newline_delimited', map_inference_threshold = -1)几个关键参数的语义:
read_json(...):以 DuckDB 的 JSON 读取器读取logs.json虚拟文件;format = 'newline_delimited':按 NDJSON(每行一个 JSON 对象)解析容器日志流;ignore_errors = true:跳过个别无法解析的行,避免单条坏日志中断建表;map_inference_threshold = -1:让 DuckDB 在推断嵌套字段类型时不做数量截断,确保深层嵌套字段也能被展开成独立列;unnest(m):把每行 JSON 展开(unnest)成表的一行记录,m是日志事件中承载消息载荷的字段。
建表完成后,前端还会执行DESCRIBE logs获取列名与列类型(LogAnalytics.vue),用于编辑器自动补全、列名列表展示以及示例查询的动态生成。
DuckDB 实例与清理
DuckDB 的加载位于 duckdb.ts:
- 通过
duckdb.getJsDelivrBundles()与selectBundle()选定 WASM 资源包,再以 Blob URL 创建 Worker 线程承载AsyncDuckDB; - 首次打开时下载 WASM 模块可能需要等待,网络较慢时尤其明显;
- 组件卸载时依次执行
conn.close()、db.terminate()、worker.terminate()释放内存与 Worker(duckdb.ts),避免重复打开造成资源泄漏。
这也解释了文档"内存不足时需要刷新页面"的建议:刷新页面会重建整个 DuckDB 实例,从而彻底清空内存表。
安全策略佐证
DuckDB WASM 需要从 CDN 加载并在浏览器中运行,internal/web/csp.go 中 Dozzle 的 Content-Security-Policy 为此显式放行了https://cdn.jsdelivr.net、https://*.duckdb.org、blob:以及'wasm-unsafe-eval',进一步印证了"查询全部在浏览器本地执行"的架构。
查询编辑器:补全、语法高亮与运行
SQL 编辑器基于 CodeMirror 6 构建,实现在 sqlEditor.ts,为日志分析场景做了针对性增强:
- 表结构与列补全:自动补全源会读取
getColumns()(即DESCRIBE logs的产物),把logs表的列名、类型作为补全项;对包含.、@、空格等字符的列名,会按 DuckDB 规则自动加双引号(quoteColumn,sqlEditor.ts),例如message.path这类嵌套字段; - 常用分析函数提示:内置
count(DISTINCT ...)、median()、quantile_cont(..., 0.95)、epoch_ms()、date_trunc('minute', ...)、regexp_extract(...)等面向日志的函数模板(sqlEditor.ts),按 Tab 即可填充; - SQL 关键字高亮:用轻量 tokenizer 对
SELECT/FROM/WHERE/GROUP BY/HAVING/LIMIT/JOIN等关键字、字符串、注释、数字与操作符做语法着色; Ctrl/Cmd + Enter运行:该快捷键被绑定为执行当前查询(sqlEditor.ts),而普通Enter仍用于换行;- 错误展示:查询出错时,会在编辑器下方以等宽字体完整展示 DuckDB 返回的错误信息(例如未加引号的非法列名),错误行采用可换行的样式以免关键信息被截断(LogAnalytics.vue)。
编辑器由 useSQLEditorField.ts 负责挂载与销毁:CodeMirror 是懒加载的,若在编辑器就绪前就点击了示例芯片,待编辑器挂载完成后会先应用待写入内容再继续使用,避免竞态问题。
示例查询实战
以下示例均以建表后的logs表为对象。日志 JSON 通常包含level、message、timestamp等顶层字段,以及以message.*形式展开的嵌套字段。
统计日志总数
SELECT COUNT(*) FROM logslogs表中每一行对应一条日志,这是最快验证建表是否成功、总数据量级如何的查询。
按字段过滤
SELECT * FROM logs WHERE level = 'error'仅返回level字段为error的日志。在 UI 上,这等价于把错误日志单独抽出来做进一步分析。
按字段分组
SELECT level, COUNT(*) FROM logs GROUP BY level按日志级别分组统计数量,快速回答"错误、警告、信息各有多少条"。
查询嵌套 JSON 字段
SELECT message.path, message.status, message.duration FROM logs WHERE message.status >= 400 ORDER BY message.duration DESC利用 DuckDB 的嵌套字段展开能力,直接对message对象内部的字段做过滤与排序——例如找出所有状态码 ≥ 400 的请求,并按耗时降序排列。需要注意:字段名中若含有.等特殊字符,DuckDB 要求用双引号包裹(编辑器的自动补全会自动处理,手工输入时请留意)。
按时间窗口聚合
SELECT date_trunc('minute', timestamp) AS minute, COUNT(*) AS error_count FROM logs WHERE level = 'error' GROUP BY minute ORDER BY minute DESCdate_trunc('minute', timestamp)把时间戳截断到分钟粒度,再按分钟分组统计错误数,并按时间倒序排列,非常适合观察错误随时间的分布趋势。date_trunc支持秒/分钟/小时/天等粒度,是日志时序分析最常用的函数之一。
其他高频函数
结合编辑器内置提示,以下函数在日志场景中非常实用(sqlEditor.ts):
| 函数 | 用途 |
|---|---|
count(DISTINCT col) | 统计去重后的取值个数 |
avg() / min() / max() / median() | 数值字段的分布统计 |
quantile_cont(col, 0.95) | 95 分位数,观察长尾耗时 |
epoch_ms() / to_timestamp() | 毫秒/秒时间戳转时间 |
strftime(ts, '%H:%M') | 时间格式化 |
regexp_matches() / regexp_extract() | 正则匹配与捕获 |
coalesce(col, default) | 空值兜底 |
cast(col AS VARCHAR) | 类型转换 |
结果查看与导出
查询结果展示在编辑器下方的结果区域(LogAnalytics.vue):
- 顶部会显示"共 N 条记录",如果结果超过分页上限(每页 1000 行),会提示"仅显示前 1000 条";
- 查询执行中、建表过程中都有对应加载状态提示("Fetching container logs..."/"Creating temporary table..."/"Evaluating query...");
- 结果表支持导出为 CSV或JSON:导出按钮仅在查询有结果且非执行中时可用,导出文件名格式为
{容器名}-query.{csv|json},CSV 导出会正确处理含逗号、引号、换行的字段转义(LogAnalytics.vue); - 点击Examples(示例)芯片可一键填入并执行内置查询:"All logs"(
SELECT * FROM logs LIMIT 100)、"Row count"(SELECT count(*) AS total FROM logs)、"Count by {column}"(按level/severity/status等检测到的级别字段分组计数,自动选择第一个命中的级别列)(LogAnalytics.vue); - 点击Columns(列)区域可展开当前表的全部列,点击任一列名即插入到光标处,插入时同样遵循"合法标识符不加引号、否则加引号"的规则(LogAnalytics.vue)。
限制与注意事项
文档明确指出了三类限制,结合实现可以这样理解:
- 仅支持结构化数据:SQL Engine 只能分析 JSON 日志;纯文本日志无法建表查询。这是
jsonOnly接口参数与newline_delimited解析方式的必然结果; - 查询仅在浏览器内执行:DuckDB 运行在浏览器沙箱中,无法访问外部数据库、网络资源或其他文件。因此不要期望用 SQL 引擎 JOIN 外部数据源——它只服务于本容器日志的内存分析;
- 最多约 4GB 内存:WebAssembly 内存上限为 4GB。日志量极大或查询聚合开销很高时可能耗尽内存,此时需要刷新页面重建 DuckDB 实例以释放内存(DuckDB 实例在组件卸载时会自动
terminate,但页内无法手动回收单次查询占用的内存)。
小结
Dozzle 的 SQL Engine 用"JSON 日志 + DuckDB WASM + 浏览器内存表"的组合,把容器日志分析变成了一条即开即用的 SQL 工作流:无需部署外部数据库、无需导出数据、数据全程不出本机。上手时只需记住三个要点:日志必须是 JSON;用Ctrl+Shift+F(Cmd+Shift+F)打开入口;嵌套字段查询注意加引号。想深入实现细节,可以继续阅读 LogAnalytics.vue、duckdb.ts、sqlEditor.ts 以及后端日志接口 logs.go。
【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考