4. 日志采集与集中管理

在 Databricks 平台上,日志并不是集中存放在一个文件里,而是按“谁产生、在哪产生、生命周期多长”分散在多个位置。要做好可观测性,第一步不是急着搭 ELK 或 Loki,而是先厘清五类日志的采集路径:集群日志、驱动日志、执行器日志、审计日志、作业日志。它们分别对应基础设施层、应用层、计算层、控制面与调度层,采集方式、时效性和结构化难度差异很大。

五类日志的采集路径

集群日志指集群生命周期事件,例如创建、启动、扩容、终止、配置变更。它们由控制面产生,主要通过 cluster_events 或云厂商的集群事件接口获取,属于低频、结构化程度高的元数据,适合用 API 拉取后写入日志平台。

驱动日志是 Driver 节点上 Spark 应用的主日志,包含作业提交、DAG 调度、任务失败堆栈、stdout/stderr 输出。默认路径在 DBFS 或云存储上,例如 dbfs:/cluster-logs/<cluster-id>/driver/,也可通过集群日志投递(Log Delivery)配置到 S3、ADLS 或 GCS。

执行器日志分布在每个 Executor 节点,记录任务级执行细节、GC 情况、shuffle 读写、OOM 等。路径通常为 .../executor/<executor-id>/stderr。执行器数量多、日志量大,是采集成本最高的部分,建议按需采集或只采集 ERROR/WARN 级别。

审计日志由 Databricks 审计系统统一产生,记录谁在何时对什么对象做了什么操作,例如登录、运行作业、修改权限、访问表。它通过审计日志投递到云存储,格式为 JSON,字段规范,是安全与合规分析的核心数据源。

作业日志是作业运行产生的输出,包括 notebook 输出、任务日志、运行结果。可通过 Jobs API 的 /runs/get-output 或运行详情接口获取,也可在作业配置中开启日志投递。

Databricks 五类日志采集路径与集中管理 集群日志 cluster_events / API 驱动日志 driver/stderr, stdout 执行器日志 executor/*/stderr 审计日志 audit JSON 投递 作业日志 Jobs API / 运行输出 采集层 Log Delivery 投递 云存储 (S3/ADLS/GCS) Filebeat / Fluent Bit API 拉取 (Jobs/Audit) 解析与结构化 Grok / JSON / 正则 字段提取 · 时间戳归一 ELK Stack Elasticsearch + Kibana 全文检索 · 聚合分析 Loki + Grafana 标签索引 · 低成本存储 LogQL 查询 · 告警 统一查询与告警 Kibana Discover / Grafana Explore · 阈值告警 · 关联分析 关键字段示例 timestamp cluster_id job_id / run_id executor_id log_level service_name user / action message exception_class duration_ms status

与 ELK / Loki 的集成方式

ELK 与 Loki 的定位不同,选择取决于查询模式与成本约束。ELK 基于 Elasticsearch 建立倒排索引,适合全文检索、复杂聚合和字段级分析,例如“查找过去 24 小时内所有包含 OutOfMemoryError 的执行器日志,并按集群聚合”。Loki 只索引标签,日志正文压缩存储,成本低、写入吞吐高,适合“按 cluster_id、job_id、log_level 快速过滤后查看原始日志”的场景,配合 Grafana 做告警。

集成路径通常有三条:

  • 云存储中转:Databricks 将日志投递到 S3/ADLS/GCS,再由 Filebeat 或 Fluent Bit 从对象存储读取并推送到 Elasticsearch 或 Loki。这是最通用的方式,适合驱动日志、执行器日志和审计日志。
  • API 拉取:用定时任务调用 Databricks REST API 获取集群事件、作业输出和审计日志,经处理后写入日志平台。适合低频、结构化数据。
  • 直接推送:在集群初始化脚本中安装日志代理,将 Driver 和 Executor 日志实时推送到远端。延迟最低,但需要管理代理生命周期和网络出口。

日志解析与结构化处理

原始日志大多是非结构化文本,直接入湖或入搜索引擎会导致查询效率低、字段无法聚合。结构化处理的目标是把每条日志转成带固定字段的 JSON 文档。以驱动日志中的一行典型输出为例:

25/06/12 14:32:07 INFO DAGScheduler: Job 3 finished: count at <unknown>:1, took 2.345 s

用 Grok 模式可以提取出时间、级别、组件、消息体:

%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:log_level} %{DATA:component}: %{GREEDYDATA:message}

解析后得到结构化文档:

{
  "timestamp": "2025-06-12T14:32:07",
  "log_level": "INFO",
  "component": "DAGScheduler",
  "message": "Job 3 finished: count at <unknown>:1, took 2.345 s",
  "cluster_id": "0612-143207-abc123",
  "job_id": 3,
  "duration_ms": 2345,
  "service_name": "spark-driver"
}

对于审计日志,本身已是 JSON,只需做字段映射和类型转换,例如把 timestamp 从毫秒转成 ISO8601,把 action_name 映射为统一枚举。对于执行器日志,建议先按 executor_id 和 log_level 做标签化,再决定是否保留完整正文。

解析阶段要统一时间戳时区。Databricks 日志默认使用 UTC,若日志平台按本地时区展示,需在采集或解析时显式转换,否则跨集群关联分析会出现时间错位。

采集策略与成本控制

执行器日志量可能是驱动日志的几十倍,全量采集成本很高。一个实用的策略是:驱动日志和审计日志全量采集;执行器日志只采集 WARN 和 ERROR 级别,或仅在作业失败时按 run_id 回溯拉取;集群日志和作业日志按 API 定时拉取,保留 30 到 90 天。在 Loki 中,可以通过 log_level 标签过滤,把 INFO 级别日志降采样或只保留最近 7 天。

不要把所有日志都塞进同一个索引或同一个 Loki 租户。建议按日志类型分索引或分标签流,例如 databricks-driver-*、databricks-audit-*、databricks-executor-*,否则查询时会扫描大量无关数据,既慢又贵。

一个可落地的采集配置示例

下面是一个 Filebeat 从 S3 读取 Databricks 驱动日志并推送到 Elasticsearch 的配置片段,用于说明关键字段:

filebeat.inputs:
  - type: s3
    bucket: my-databricks-logs
    prefix: cluster-logs/
    queue_url: https://sqs.us-east-1.amazonaws.com/123456789/databricks-logs
    processors:
      - add_fields:
          target: ''
          fields:
            service_name: spark-driver
            source_type: driver_log
      - dissect:
          tokenizer: "%{timestamp} %{log_level} %{component}: %{message}"
          field: "message"
          target_prefix: ""

output.elasticsearch:
  hosts: ["https://es.internal:9200"]
  index: "databricks-driver-%{+yyyy.MM.dd}"

这段配置做了三件事:从 S3 读取日志、用 dissect 做轻量结构化、按日期写入索引。实际生产中还需要处理多行堆栈合并、字段类型映射和索引生命周期管理。判断采集是否成功,可以看三个信号:Elasticsearch 或 Loki 中是否出现对应 service_name 的文档;timestamp 字段是否可排序且时区正确;按 cluster_id 过滤时能否在秒级返回结果。如果文档数量远小于预期,优先检查 SQS 队列权限和 S3 前缀是否匹配。

把五类日志的采集路径、解析规则和存储策略固定下来之后,后续的告警、根因分析和审计追溯才有可靠的数据基础。日志集中管理不是一次性任务,而是需要随集群规模、作业数量和合规要求持续调整的工程实践。