OpenLineage
OpenLineage 是面向数据血缘采集的开放标准。本指南说明如何将 OpenLineage RunEvent、JobEvent 和 DatasetEvent 接入观测云,适用于 Airflow、Spark、dbt 以及直接使用 OpenLineage 客户端的应用。
前置条件¶
开始前,请准备:
- 当前站点的数据接入地址,例如
https://openway.guance.com;其他站点或私有部署请使用实际地址。 - 目标工作空间的 Token。请将 Token 作为密钥管理,不要写入源码或提交到版本库。
- 支持 OpenLineage HTTP transport 的数据任务或客户端。
- 从任务运行环境到数据接入地址的 HTTPS 网络连通性。
下文使用以下占位符:
| 占位符 | 说明 |
|---|---|
<OPENLINEAGE_BASE_URL> |
不带路径和 query 参数的数据接入地址 |
<WORKSPACE_TOKEN> |
目标工作空间的 Token |
接入协议¶
| 项目 | 要求 |
|---|---|
| 方法 | POST |
| 路径 | /v1/write/openlineage |
| Content-Type | application/json,允许携带 charset 参数 |
| Content-Encoding | 可省略;压缩时使用 gzip |
| 请求体 | 单个 OpenLineage RunEvent、JobEvent 或 DatasetEvent JSON 对象 |
| 默认请求体上限 | 64 MiB;gzip 请求按收到的压缩后字节数计算,实际部署可调整;解压后的单条事件不能超过 8 MiB |
完整 URL 为:
每次请求只发送一个事件,不支持 JSON 数组或 OpenLineage batch 接口。建议保持单条事件远小于上限,避免超大的 Facet 影响发送和重试。
认证¶
支持以下三种认证方式:
| 方式 | 示例 | 建议 |
|---|---|---|
| Bearer | Authorization: Bearer <WORKSPACE_TOKEN> |
推荐,兼容 OpenLineage HTTP transport 的 api_key 认证 |
| Header | X-Token: <WORKSPACE_TOKEN> |
适合能够设置自定义 Header 的客户端 |
| Query | ?token=<WORKSPACE_TOKEN> |
仅用于兼容;URL 可能被代理或访问日志记录 |
如果请求同时提供多种 Token,优先级为 query token、X-Token、Bearer。不要在一个请求中配置多个不同的 Token。
事件要求¶
事件必须符合 OpenLineage 规范。三种事件都必须包含 eventTime(ISO 8601 时间)、producer(生产者 URI)和 schemaURL(规范版本地址);观测云 根据顶层的 run、job、dataset 识别类型:
| 事件类型 | 必填的身份字段 | 用途 |
|---|---|---|
RunEvent |
run.runId、job.namespace、job.name、eventType |
记录一次运行及其输入、输出;eventType 支持 START、RUNNING、COMPLETE、ABORT、FAIL、OTHER |
JobEvent |
job.namespace、job.name;不包含 run 或 dataset |
描述 Job 元数据,可包含 inputs、outputs |
DatasetEvent |
dataset.namespace、dataset.name;不包含 run 或 job |
描述独立 Dataset 元数据及 Schema/字段血缘 Facet |
RunEvent 和 JobEvent 有数据输入或输出时填写 inputs、outputs,每个 Dataset 应有 namespace 和 name。同一次运行的 RunEvent 必须使用相同的 runId、job.namespace 和 job.name;通常先发送 START,结束时再发送 COMPLETE、FAIL 或 ABORT。
事件分类与记录类型¶
接入后,source 表示原始 OpenLineage 事件的类型,lineage_record_type 表示 观测云 从该事件投影出的记录类型。它们不是同一个维度:同一个 RunEvent 可以生成多条记录,但这些记录的 source 都是 run_event。
source |
原始事件 | 可能生成的 lineage_record_type |
|---|---|---|
run_event |
RunEvent:一次运行的状态与输入、输出 |
event、dataset_edge;有相关 Facet 时还可有 schema_version、field_edge |
job_event |
JobEvent:Job 的静态或变更元数据 |
event、job、dataset_edge;有相关 Facet 时还可有 schema_version、field_edge |
dataset_event |
DatasetEvent:独立 Dataset 的元数据 |
event、dataset;有相关 Facet 时还可有 schema_version、field_edge |
event 保存经过敏感信息脱敏的完整事件 JSON(lineage_message);job、dataset 是实体记录;dataset_edge 表示 Job/Run 的输入或输出 Dataset;schema_version 是 Schema 摘要;field_edge 是字段级血缘。无相关输入、输出或 Facet 时,不会生成对应的派生记录。无效事件的审计记录可能出现 source=unknown_event、lineage_record_type=ingest_error;这不是第四种可发送的事件类型。
source 是 观测云 保留的分类字段。如果原始 JSON 顶层另有字符串 source,投影记录会将其保存在 src_source,不会覆盖 观测云 的 source;原始事件的该字段仍可在 event 记录的 lineage_message 中查看。
查询字段清单¶
下表是 观测云 提取到一级字段的字段清单,并非 OpenLineage 原始 JSON 的全部字段。字段按事件类型、记录类型及 Facet 是否存在而出现;某个字段未出现,不等于其值为空。需要完整事件时查看 lineage_record_type=event 的 lineage_message(敏感值可能已脱敏)。同一事件的多条投影记录通过 event_id 关联;run_id、job_id、dataset_id 分别用于关联运行、Job 和 Dataset。
通用字段与事件原文¶
| 字段 | 含义 |
|---|---|
__namespace |
固定为 lineage;对应 DQL 的 LN 命名空间 |
source |
事件分类:run_event、job_event、dataset_event;接入错误记录为 unknown_event |
src_source |
原始 JSON 顶层的字符串 source(如存在) |
lineage_record_type |
投影记录类型:event、job、dataset、dataset_edge、schema_version、field_edge、ingest_error |
event_id |
工作空间内按原始事件内容生成的事件标识;同一事件的投影记录共用 |
date、date_ns |
原始 eventTime,分别为毫秒、纳秒 Unix 时间戳;查询时间以此为准 |
create_time |
观测云 收到事件的毫秒时间戳 |
__docid |
存储层的单条记录 ID,不用于实体去重 |
projection_version、normalization_version |
观测云 投影格式和 Dataset 身份归一化版本 |
lineage_message |
脱敏后的完整事件 JSON;只在 event 记录上 |
producer、schema_url |
原始 producer 与 schemaURL;主要在 event,也在 job、dataset 记录上 |
ingest_delay_ms |
接收时间与 eventTime 的差值(毫秒);只在 event 上 |
facet_names |
事件携带的 Facet 名称;只在 event 上 |
redaction_policy_version、redacted_count |
原文脱敏策略版本和脱敏数量;只在 event 上 |
ingest_warning |
可选 Facet 解析失败或投影限额等降级警告代码;只在 event 上 |
Run 与 Job 字段¶
身份字段会随同一事件的派生记录传递;Job 名称、状态及 Facet 提取字段主要位于 event,JobEvent 的 Job 元数据还位于 job 记录。
| 字段 | 含义 |
|---|---|
event_type、run_id |
RunEvent 的状态和原始 run.runId |
job_id |
观测云 根据工作空间、job.namespace 和 job.name 生成的稳定 Job 标识 |
job_namespace、job_name |
原始 Job 身份;只在 event、job 记录上 |
parent_run_id、root_run_id |
parent Run Facet 中的父运行、根运行 ID |
parent_job_id、parent_job_namespace、parent_job_name |
父 Job 的标识和名称 |
root_job_id、root_job_namespace、root_job_name |
根 Job 的标识和名称 |
is_terminal、is_success、has_error |
是否终态、是否 COMPLETE、是否失败/中止或含错误 Facet |
error_type |
错误 Facet 中可识别的明确错误类型;不是完整错误消息 |
error_message、error_language、stacktrace_hash |
脱敏后的错误消息、编程语言及堆栈摘要 |
input_count、output_count |
原始输入、输出 Dataset 个数 |
integration、job_type、processing_type |
Job 的 jobType Facet |
engine_name、engine_version、openlineage_adapter_version |
Run 的 processing_engine Facet |
nominal_start_time、nominal_end_time |
Run 的计划起止时间(毫秒时间戳) |
has_sql、sql_hash |
是否有 SQL Facet 及其内容摘要;不暴露完整 SQL |
has_config、config_hash |
是否有配置 Facet 及其内容摘要 |
application_id、spark_master、deploy_mode、driver_host、spark_user |
Spark 应用信息(存在相应 Facet 时) |
trace_ids、trace_links |
可选的 Trace 关联 ID 列表和关联详情 |
Dataset、Schema 与血缘边字段¶
dataset 记录只由 DatasetEvent 生成;dataset_edge 由 RunEvent 或 JobEvent 的输入、输出生成。schema_version 只保留 Schema 摘要,完整 Schema 仍在 lineage_message 的原始 Facet 中。
| 字段 | 含义 |
|---|---|
dataset_id |
观测云 根据工作空间与归一化 Dataset 身份生成的稳定标识 |
dataset_namespace、dataset_name |
原始 Dataset 的 namespace、name |
dataset_key |
归一化后的 Dataset 身份键 |
dataset_type、data_platform |
归一化出的 Dataset 类型、数据平台 |
account_name、database_name、schema_name、table_name |
从 Dataset 身份解析出的层级名称;能解析时才有 |
data_source_name、data_source_uri |
Dataset 的 dataSource Facet;描述具体数据源,不等同于 data_platform |
lifecycle_state_change |
Dataset 的生命周期变化状态 |
io_type |
dataset_edge 或相关 schema_version 的方向:INPUT / OUTPUT |
row_count、size_bytes、file_count |
输入/输出统计 Facet 中的行数、字节数、文件数 |
schema_hash、schema_field_count |
Schema 字段列表摘要和字段数;可出现在 Dataset、边及 Schema 版本记录 |
run_dataset_edge_id、job_dataset_edge_id |
Run/Job 与 Dataset 输入输出关系的稳定标识 |
observed_at |
job、dataset、schema_version 的观察时间(毫秒) |
input_dataset_id、input_field |
field_edge 的上游 Dataset 和字段 |
output_dataset_id、output_field |
field_edge 的下游 Dataset 和字段 |
field_edge_id |
字段级血缘关系的稳定标识 |
transformation_type、transformation_subtype、transformation_hash |
字段转换类型、子类型及内容摘要 |
is_masking |
字段转换是否为脱敏处理 |
ingest_error 记录另有 ingest_error_reason_code(拒收原因)、ingest_error_payload_hash(载荷摘要)和 ingest_error_payload_size(载荷字节数);为避免泄漏,不保存原始错误载荷。
快速验证¶
将以下内容保存为 event.json,并为每次测试生成新的 runId:
{
"eventType": "START",
"eventTime": "2026-10-21T08:00:00Z",
"run": {
"runId": "4a8f90a7-79c0-4f80-89e7-6b6c5d4fc79a"
},
"job": {
"namespace": "example.scheduler",
"name": "daily_orders"
},
"inputs": [
{
"namespace": "postgresql://warehouse.example:5432",
"name": "sales.orders"
}
],
"outputs": [
{
"namespace": "postgresql://warehouse.example:5432",
"name": "analytics.daily_orders"
}
],
"producer": "https://example.com/openlineage/integration",
"schemaURL": "https://openlineage.io/spec/2-0-2/OpenLineage.json#/$defs/RunEvent"
}
使用 Bearer Token 发送:
curl --fail-with-body --silent --show-error \
-X POST '<OPENLINEAGE_BASE_URL>/v1/write/openlineage' \
-H 'Authorization: Bearer <WORKSPACE_TOKEN>' \
-H 'Content-Type: application/json' \
--data-binary @event.json
使用 gzip 压缩发送:
gzip -c event.json | curl --fail-with-body --silent --show-error \
-X POST '<OPENLINEAGE_BASE_URL>/v1/write/openlineage' \
-H 'Authorization: Bearer <WORKSPACE_TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Content-Encoding: gzip' \
--data-binary @-
请求成功后,再使用相同的 runId 和 Job 标识发送 COMPLETE 事件,验证一次完整的运行生命周期。
客户端配置¶
OpenLineage Python Client¶
OpenLineage Python Client 的 api_key 认证会生成 Bearer Header。以下配置同时启用 gzip 和对临时错误的重试:
export OPENLINEAGE__TRANSPORT__TYPE=http
export OPENLINEAGE__TRANSPORT__URL='<OPENLINEAGE_BASE_URL>'
export OPENLINEAGE__TRANSPORT__ENDPOINT='/v1/write/openlineage'
export OPENLINEAGE__TRANSPORT__AUTH__TYPE=api_key
export OPENLINEAGE__TRANSPORT__AUTH__APIKEY='<WORKSPACE_TOKEN>'
export OPENLINEAGE__TRANSPORT__COMPRESSION=gzip
export OPENLINEAGE__TRANSPORT__RETRY='{"total":5,"connect":5,"read":5,"backoff_factor":0.3,"status_forcelist":[429,500,502,503,504],"allowed_methods":["POST"]}'
不同客户端版本支持的配置项可能不同,升级前请核对 Python Client 配置说明。
Apache Airflow¶
安装与当前 Airflow 版本兼容的 OpenLineage Provider,然后配置 HTTP transport:
pip install apache-airflow-providers-openlineage
export AIRFLOW__OPENLINEAGE__NAMESPACE='production-airflow'
export AIRFLOW__OPENLINEAGE__TRANSPORT='{"type":"http","url":"<OPENLINEAGE_BASE_URL>","endpoint":"/v1/write/openlineage","auth":{"type":"api_key","apiKey":"<WORKSPACE_TOKEN>"},"compression":"gzip"}'
Provider 与 Airflow 有版本兼容要求。安装或升级时请查看 Airflow OpenLineage Provider 文档。
Apache Spark¶
加载与 Spark 版本兼容的 OpenLineage Spark 集成包,并设置以下属性:
spark.extraListeners=io.openlineage.spark.agent.OpenLineageSparkListener
spark.openlineage.transport.type=http
spark.openlineage.transport.url=<OPENLINEAGE_BASE_URL>
spark.openlineage.transport.endpoint=/v1/write/openlineage
spark.openlineage.transport.auth.type=api_key
spark.openlineage.transport.auth.apiKey=<WORKSPACE_TOKEN>
spark.openlineage.transport.compression=gzip
spark.openlineage.namespace=production-spark
集成包的加载方式与版本兼容关系,请参考 OpenLineage Spark 安装文档。
dbt¶
安装 openlineage-dbt,复用 Python Client 的 transport 配置,并使用 dbt-ol 替代 dbt:
pip install openlineage-dbt
export OPENLINEAGE__TRANSPORT__TYPE=http
export OPENLINEAGE__TRANSPORT__URL='<OPENLINEAGE_BASE_URL>'
export OPENLINEAGE__TRANSPORT__ENDPOINT='/v1/write/openlineage'
export OPENLINEAGE__TRANSPORT__AUTH__TYPE=api_key
export OPENLINEAGE__TRANSPORT__AUTH__APIKEY='<WORKSPACE_TOKEN>'
export OPENLINEAGE__TRANSPORT__COMPRESSION=gzip
export OPENLINEAGE_NAMESPACE='production-dbt'
dbt-ol run
不要只设置 OPENLINEAGE_URL:客户端默认路径通常是 /api/v1/lineage,本接入要求显式配置 /v1/write/openlineage。更多运行模式和适配器支持情况见 OpenLineage dbt 集成文档。
响应与重试¶
| 响应 | 含义 | 处理建议 |
|---|---|---|
2xx |
请求已接受 | 不重试 |
200 且 error_code 为 dataway.cached |
请求已接受并等待后续投递,暂不代表数据已经可查询 | 不要立即重试;稍后检查数据是否可见 |
400 或 422 |
JSON 或 OpenLineage 事件不合法 | 修正事件后再发送 |
401 或 403 |
Token 缺失、无效或无权限 | 修正认证后再发送 |
413 |
请求体超过限制 | 缩小 Facet 或单条事件大小 |
415 |
Content-Type 或 Content-Encoding 不支持 | 修正请求头或压缩方式 |
429、500、502、503、504 |
临时不可用、限流或服务错误 | 按 Retry-After 重试;未提供时使用带随机抖动的指数退避 |
发送方必须允许重复投递,并为同一次运行保持稳定的 runId。不要重试确定性的 4xx 错误,否则无效事件可能持续占用发送队列。
验证与排障¶
完成配置后,建议按以下顺序验证:
- 使用 curl 发送最小
START事件,确认返回2xx。 - 使用相同的 Run 和 Job 标识发送终态事件。
- 在观测云中按 Job namespace、Job name 或 runId 查找对应记录。
- 检查生产者日志,确认没有持续重试、认证错误或事件序列化错误。
常见问题:
| 现象 | 检查项 |
|---|---|
401 或 403 |
Token 是否属于目标工作空间;Bearer 是否以 Bearer 加一个空格开头;环境变量是否被正确注入 |
404 |
endpoint 是否明确配置为 /v1/write/openlineage,而不是客户端默认的 /api/v1/lineage |
413 |
事件或 Facet 是否过大;gzip 只能降低传输体积,仍建议缩小单条事件 |
415 |
Content-Type 是否为 JSON;压缩请求是否同时设置 Content-Encoding: gzip |
429 或 503 |
是否遵循 Retry-After;降低并发并增加带抖动的指数退避 |
| 返回成功但暂时查不到数据 | 是否收到 dataway.cached;Job namespace/name 和 runId 是否与查询条件一致;等待后再次检查 |
| 只有 START,没有结束状态 | 生产者是否在成功、失败和取消路径分别发送 COMPLETE、FAIL 或 ABORT |
安全建议¶
- 生产环境只使用 HTTPS,并保持证书校验开启。
- 优先使用 Bearer 或
X-Token,避免在 URL 中携带 Token。 - 通过 Secret、密钥管理服务或运行环境注入 Token;不要写入镜像、配置模板、命令历史或日志。
- 定期轮换 Token,并在泄漏后立即撤销旧 Token。
- Facet 可能包含 SQL、Schema 或业务元数据;发送前评估敏感信息,并按最小必要原则采集。