GreptimeDB 为可观测性数据的长期存储和查询而设计,日志是其中的核心场景。关于与 Grafana Loki 的详细性能对比,可参考《超越 Loki!GreptimeDB 日志场景性能报告》;这条迁移路线在生产环境的大规模实践,可参考《从 Loki 到 GreptimeDB:OceanBase Cloud 300TB 多云日志实践》。
GreptimeDB 提供 Loki 兼容的写入端点,现有的 Loki 写入端只需少量配置改动,就能把新日志同时发送到 GreptimeDB。本文将完成以下四步:
- 运行一个 Docker Compose 场景,向 Loki 发送模拟的 syslog 消息;
- 配置 Grafana Alloy,把每条日志同时写入 Loki 和 GreptimeDB;
- 添加一个 GreptimeDB Pipeline,把每条消息解析成结构化字段;
- 在 GreptimeDB 内置的 Dashboard 中查询结构化日志。
本文只覆盖双写和新日志写入链路的切换,不会复制已经存储在 Loki 中的数据。如需迁移历史日志,请从原始数据源、归档或导出记录中回放。完整的迁移方案参见官方迁移指南。
前置条件
需要安装 Docker 和 Docker Compose,并确保主机上的 514、3000、3100、4000、12345、51893、51898 端口可用。
1. 启动 Loki 场景
我们从 Grafana Alloy syslog 场景开始:
git clone https://github.com/grafana/alloy-scenarios.git
cd alloy-scenarios/syslog
docker compose up -d所有服务启动后,打开 Grafana Explore(http://localhost:3000/explore),选择 Loki 数据源,执行以下 LogQL 查询:
{component="loki.source.syslog"}模拟器每 3~8 秒发送一条新消息,很快就能看到日志进来。
要停止该场景,执行:
docker compose down2. 把日志镜像写入 GreptimeDB
首先,在 docker-compose.yml 中添加一个 GreptimeDB standalone 服务:
greptimedb:
image: greptime/greptimedb:${GREPTIMEDB_VERSION:-v1.1.2}
ports:
- "4000:4000"
command:
- standalone
- start
- --http-addr
- 0.0.0.0:4000该场景默认使用 GreptimeDB v1.1.2,可通过 GREPTIMEDB_VERSION 环境变量覆盖。端口 4000 暴露 HTTP API 和内置 Dashboard。
接下来,修改 config.alloy,让 syslog 数据源把每条日志转发到两个 loki.write 接收器:
livedebugging {
enabled = true
}
loki.source.syslog "local" {
listener {
address = "0.0.0.0:51893"
labels = { component = "loki.source.syslog", protocol = "tcp" }
}
listener {
address = "0.0.0.0:51898"
protocol = "udp"
labels = { component = "loki.source.syslog", protocol = "udp" }
}
forward_to = [
loki.write.local.receiver,
loki.write.greptimedb.receiver,
]
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
loki.write "greptimedb" {
endpoint {
url = "http://greptimedb:4000/v1/loki/api/v1/push"
headers = {
"X-Greptime-DB-Name" = "public",
"X-Greptime-Log-Table-Name" = "loki_syslog_raw",
"X-Greptime-Pipeline-Name" = "greptime_identity",
}
}
}原有的 local 接收器继续写入 Loki,新增的 greptimedb 接收器把同样的日志发送到 GreptimeDB 的 Loki 兼容端点。
这几个 header 分别指定了 public 数据库、loki_syslog_raw 表和内置的 greptime_identity Pipeline。对于 Loki 写入请求,GreptimeDB 传给 Pipeline 的输入对象包含:原始时间戳 greptime_timestamp、原始日志行 loki_line,以及每个 stream 标签对应的 loki_label_<name>。这个内置 Pipeline 会根据这些字段自动推断表结构并直接写入,不解析原始日志行。
启动更新后的服务:
docker compose up -d打开 http://localhost:4000/dashboard,选择 Logs Query,再选择 public 数据库和 loki_syslog_raw 表。此时日志已同时存在于 Loki 和 GreptimeDB 中,但消息体仍以非结构化的 loki_line 形式存储。下一步,我们让日志级别等字段可以直接检索。

在 Logs Query 中查看 loki_syslog_raw 表
3. 用 Pipeline 解析 syslog 消息
模拟器发出的消息格式如下:
CRITICAL: Configuration loaded创建 syslog_pipeline.yaml,写入以下 Pipeline 定义:
version: 2
processors:
- regex:
fields:
- loki_line, log
patterns:
- '^\s*(?<level>[A-Z]+):\s*(?<message>.*)$'
ignore_missing: true
transform:
- field: greptime_timestamp
type: time
index: timestamp
- field: log_level
type: string
index: inverted
tag: true
- field: log_message
type: string
index: fulltext
- fields:
- loki_label_component
- loki_label_protocol
type: string
index: inverted
tag: true这个 Pipeline 做了四件事:
version: 2表示先执行显式声明的 transform,再把 Pipeline 上下文中剩余的字段一并写入表中,因此原始的loki_line依然保留;regex处理器读取loki_line,以log作为输出前缀,两个命名捕获组分别生成log_level和log_message;greptime_timestamp保留 Loki 日志条目的原始时间戳,作为表必需的时间索引;- 对用于等值过滤的字段添加倒排索引,并为
log_message添加全文索引以支持文本检索。
关键列如下:
| 列名 | 来源 | 存储与索引角色 |
|---|---|---|
greptime_timestamp | Loki 日志条目时间戳 | 时间索引 |
log_level | level 正则捕获组 | 带倒排索引的字符串 Tag |
log_message | message 正则捕获组 | 带全文索引的字符串字段 |
loki_label_component | Alloy stream 标签 | 带倒排索引的字符串 Tag |
loki_label_protocol | Alloy stream 标签 | 带倒排索引的字符串 Tag |
4. 在 Alloy 启动前上传 Pipeline
添加一个一次性的 greptimedb-pipeline 服务负责上传 YAML 文件,并让 Alloy 等它成功结束后再启动。完整的 docker-compose.yml 如下:
services:
rsyslog:
image: rsyslog/syslog_appliance_alpine:latest@sha256:c0dd7cad9ff3234967ff59879590175b7590e8a5f5621ec49a85aff546b44a3b
container_name: rsyslog
ports:
- "514:514/udp"
- "514:514/tcp"
volumes:
- ./rsyslog.conf:/etc/rsyslog.conf
depends_on:
- alloy
syslog-simulator:
image: python:${PYTHON_VERSION:-3.11-slim}
container_name: syslog-simulator
volumes:
- ./syslog_simulator.py:/syslog_simulator.py
environment:
- SYSLOG_HOST=rsyslog
- SYSLOG_PORT=514
depends_on:
- rsyslog
command: ["python3", "/syslog_simulator.py"]
alloy:
image: grafana/alloy:${GRAFANA_ALLOY_VERSION:-v1.17.1}
ports:
- "12345:12345"
- "51893:51893"
- "51898:51898"
volumes:
- ./config.alloy:/etc/alloy/config.alloy
- ./logs:/tmp/app-logs/
command: run --server.http.listen-addr=0.0.0.0:12345 --stability.level=experimental --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
depends_on:
loki:
condition: service_started
greptimedb-pipeline:
condition: service_completed_successfully
loki:
image: grafana/loki:${GRAFANA_LOKI_VERSION:-3.7.3}
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
command: -config.file=/etc/loki/local-config.yaml
greptimedb:
image: greptime/greptimedb:${GREPTIMEDB_VERSION:-v1.1.2}
ports:
- "4000:4000"
command:
- standalone
- start
- --http-addr
- 0.0.0.0:4000
greptimedb-pipeline:
image: curlimages/curl:${CURL_VERSION:-8.21.0}
volumes:
- ./syslog_pipeline.yaml:/syslog_pipeline.yaml
depends_on:
- greptimedb
command:
- --fail
- --silent
- --show-error
- --retry
- "60"
- --retry-delay
- "1"
- --retry-all-errors
- -X
- POST
- http://greptimedb:4000/v1/pipelines/syslog_logs
- -F
- file=@/syslog_pipeline.yaml
grafana:
image: grafana/grafana:${GRAFANA_VERSION:-13.1.0}
environment:
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_BASIC_ENABLED=false
ports:
- "3000:3000/tcp"
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: false
version: 1
editable: false
EOF
/run.sh这个辅助服务会在 GreptimeDB 启动期间不断重试上传;在你按下文把 Alloy 的 header 切换到自定义 Pipeline 后,Alloy 会等上传成功退出才启动,因此第一条日志就能用上自定义 Pipeline。
最后,把 config.alloy 中的表名和 Pipeline 两个 header 改为:
"X-Greptime-Log-Table-Name" = "loki_syslog_logs",
"X-Greptime-Pipeline-Name" = "syslog_logs",自定义 Pipeline 写入一张新表,这样 GreptimeDB 建表时就能应用 Tag 和索引定义。之前用于验证的原始数据仍保留在 loki_syslog_raw 表中。
再次启动:
docker compose up -d打开 http://localhost:4000/dashboard,进入 Logs Query,选择 public.loki_syslog_logs,可以看到 log_level 和 log_message 已经与原始 Loki 字段并列出现。

在 Logs Query 中查看 loki_syslog_logs 表的结构化列
在查询构建器中把 log_level 过滤为 CRITICAL,或切换到 Code 模式执行以下 SQL:
SELECT greptime_timestamp, log_level, log_message
FROM loki_syslog_logs
WHERE log_level = 'CRITICAL'
ORDER BY greptime_timestamp DESC
LIMIT 100;log_level 上的等值过滤会命中倒排索引,消息体内的检索则使用 log_message 上的全文索引。

在查询构建器中把 log_level 过滤为 CRITICAL
总结
这套迁移方案让 Loki 链路保持运行,同时用同样的实时流量验证 GreptimeDB。一个小小的自定义 Pipeline,就能把每条原始 syslog 日志变成带索引、可查询的列,而且不丢弃原始消息和 Loki 标签。
在生产环境移除 Loki 写入之前,建议对比相同时间窗口内的数据、确认数据保留要求,并迁移仍依赖 LogQL 的看板和告警。如果历史数据也需要可检索,请在最终切换前单独回放。
更多信息请参考官方文档:从 Loki 迁移、Pipeline 配置和 GreptimeDB Dashboard。


