首页
/ Immich 监控实践:基于 Prometheus、Grafana 与结构化日志的性能观测体系

Immich 监控实践:基于 Prometheus、Grafana 与结构化日志的性能观测体系

2026-09-04 09:16:06作者:邵娇湘

Immich 通过 Prometheus 指标与 OpenTelemetry 埋点提供内置的性能监控能力,并支持将日志切换为结构化 JSON 格式以便接入 Loki、ELK 等日志聚合系统。本文基于 Immich 官方监控文档,结合服务端源码中的遥测实现,完整讲解如何开启指标端点、配置 Prometheus 与 Grafana,以及如何启用 JSON 结构化日志。

Prometheus 采集原理

Prometheus 采用"拉取"(pull)策略:它会周期性地向你配置的各个数据源发起请求来获取指标,数据源在被请求之前不会主动推送任何数据。这意味着被监控的一方——即 Immich 服务端——必须暴露一个 HTTP 端点供 Prometheus 抓取。Immich 的指标默认不暴露,需要显式开启(详见下文配置)。

Immich 的指标采集构建在 OpenTelemetry 之上。从 telemetry.repository.ts 中的 bootstrapTelemetry 可以看到,Immich 使用 NodeSDK 启动遥测,并通过 PrometheusExporter({ port }) 作为指标读取器监听指定端口;同时注册了 HttpInstrumentationIORedisInstrumentationNestInstrumentationPgInstrumentation 四类插桩,分别覆盖 HTTP 请求、Redis 调用、NestJS 路由和 PostgreSQL 查询。由于采用了 OpenTelemetry 插桩,除了导出 Prometheus 指标外,导出分布式追踪(traces)也是可行的。

需要说明的是,监控是选择性开启(opt-in)的功能:Immich 只会采集你明确配置的指标,这些数据不会发送到 Immich 之外的任何地方(除非你自己配置了相应的导出目标)。

指标类型与指标分组

Prometheus 指标有三种基本形式,Immich 文档中对此有明确说明:

  • Counter(计数器):只能单调递增。例如某个 API 端点被调用的次数;
  • Gauge(仪表盘值):可以在一定范围内上下波动。例如 CPU 使用率;
  • Histogram(直方图):每个观测值被归入若干个"桶"(bucket),例如响应时间以毫秒为桶边界。其特殊之处在于桶是累积的:一个观测值不仅落入包含它的最小桶,还会同时计入所有更大的桶。例如直方图有 1ms、5ms、10ms 三个桶时,一次 3ms 的观测会同时计入 5ms 与 10ms 两个桶。

在源码层面,这三类形式对应 telemetry.repository.tsMetricGroupRepository 的三个方法:addToCounteraddToGauge(底层是 OpenTelemetry 的 UpDownCounter)、addToHistogram。所有以毫秒为单位计时的直方图会应用统一的分桶边界,见 aggregationBoundaries

const aggregationBoundaries = [
  0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10_000,
];

Immich 的指标按用途分组为 API(端点调用次数与响应时间)、host(内存与 CPU 使用率)和 IO(内部数据库查询、图像处理等),每组指标可以独立开启或关闭。源码中遥测分组由 ImmichTelemetry 枚举定义,共五个取值:

export enum ImmichTelemetry {
  Host = 'host',
  Api = 'api',
  Io = 'io',
  Repo = 'repo',
  Job = 'job',
}

repo 对应数据库仓库层的查询指标,job 对应后台任务指标。TelemetryRepositorytelemetry.repository.ts)内部按 apihostjobsrepo 等字段分别持有一个 MetricGroupRepository,在构造时根据环境变量中的遥测配置决定各组是否启用。

开启指标端点

Immich 默认不暴露任何指标端点。要开启它,需要在 .env 文件中添加环境变量:

IMMICH_TELEMETRY_INCLUDE=all

更细粒度的配置方式:

  • IMMICH_TELEMETRY_INCLUDE:以逗号分隔的列表枚举要采集的分组,例如 IMMICH_TELEMETRY_INCLUDE=repo,api
  • IMMICH_TELEMETRY_EXCLUDE:排除特定分组,例如只排除任务指标 IMMICH_TELEMETRY_EXCLUDE=job

可用的分组取值与默认行为,可在环境变量文档的 Prometheus 一节查到的参数表中确认(两个变量均供 server 容器使用,对应 api、microservices 两个 worker)。

从源码看,解析逻辑位于 config.repository.ts:当 IMMICH_TELEMETRY_INCLUDEall 时展开为全部遥测分组,否则按逗号拆分(空白字符会被忽略);最终生效的分组是 INCLUDE 集合减去 EXCLUDE 集合(setDifference),并会校验每个取值是否属于合法的 ImmichTelemetry,遇到非法取值会直接抛出 Invalid telemetry found 错误使服务启动失败——因此拼写错误会立即暴露,而不是被静默忽略。

关于端点端口:API 指标默认监听 8081,微服务(machine-learning 等)指标默认监听 8082,可分别通过 IMMICH_API_METRICS_PORTIMMICH_MICROSERVICES_METRICS_PORT 覆盖,默认值见 config.repository.ts。如果需要直接查看原始指标数据,可以为 immich_server 容器额外发布 8081:80818082:8082 端口,访问各自的 /metrics 端点即可看到与 Prometheus 采集一致的原始数据。

将 Prometheus 接入 Docker Compose

下一步是配置一个 Prometheus 实例来抓取上述端点。以下假设你尚无现成的 Prometheus 实例,已有实例的配置步骤类似。

在 Compose 文件中定义 Prometheus 服务(可直接参考仓库中的示例 docker-compose.prod.yml 的组织方式):

immich-prometheus:
  container_name: immich_prometheus
  ports:
    # this exposes the default port for Prometheus so you can interact with it
    - 9090:9090
  image: prom/prometheus
  volumes:
    # the Prometheus configuration file - a barebones one is provided to get started
    - ./prometheus.yml:/etc/prometheus/prometheus.yml
    # a named volume defined in the bottom of the Compose file; it can also be a mounted folder
    - prometheus-data:/prometheus

并在全局 volumes 列表中登记命名卷:

volumes:
  model-cache:
  prometheus-data:

最后一块拼图是 Prometheus 的配置文件。仓库内提供了一个开箱即用的起步版本 docker/prometheus.yml

global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: immich_api
    static_configs:
      - targets: ['immich-server:8081']

  - job_name: immich_microservices
    static_configs:
      - targets: ['immich-server:8082']

该文件定义了抓取间隔(每 15 秒一次)与两个抓取目标:immich_api 指向服务端容器的 8081 端口(API 指标),immich_microservices 指向 8082 端口(微服务指标)。注意这里使用的是 Docker 内部服务名 immich-server——将这份配置放到与 Compose 文件相同的目录后,无需为 immich 容器额外发布任何新端口,所有通信都在内部 Docker 网络中完成。

执行 docker compose downdocker compose up -d 重启栈后,Prometheus 实例即开始采集 Immich 服务端与微服务的指标。仓库提供的配置只是起点,Prometheus 的配置方式非常多,你可以在此基础上自由扩展(如添加告警规则、其他数据源等)。

查看指标

最简单的方式是直接使用 Prometheus 自带的 Web UI:访问 http://<host>:9090 后可以在界面上搜索并可视化指标,也可以查看各数据源(target)的抓取状态。

用 Grafana 做可视化展示

如果需要一个专门的、展示效果更好的工具,可以在 Prometheus 之上接入 Grafana。它连接 Prometheus(以及其他数据源)提供精细的数据可视化。

Grafana 的接入方式与 Prometheus 类似。添加一个服务:

immich-grafana:
  container_name: immich_grafana
  command: ['./run.sh', '-disable-reporting'] # this is to disable Grafana's telemetry
  ports:
    - 3000:3000
  image: grafana/grafana
  volumes:
    # stores your pretty dashboards and panels
    - grafana-data:/var/lib/grafana

再在 volumes 中登记 grafana-data

volumes:
  model-cache:
  prometheus-data:
  grafana-data:

-disable-reporting 参数用于关闭 Grafana 自身的遥测上报。重启服务栈后访问 http://<host>:3000,首次登录用户名和密码均为 admin,登录后需要修改密码。随后进入设置(Settings),添加一个数据源,地址填写 http://immich-prometheus:9090,让 Grafana 指向你的 Prometheus 实例。

开始搭建第一个仪表盘吧:新建 Panel 时选择 Prometheus 作为数据源即可。注意频繁保存仪表盘,否则进度会丢失。

结构化 JSON 日志

除 Prometheus 指标外,Immich 还支持结构化 JSON 日志输出,非常适合接入 Grafana Loki、ELK Stack、Datadog、Splunk 等日志聚合系统。

配置

默认情况下 Immich 输出人类可读的彩色控制台日志。启用 JSON 日志只需设置环境变量:

IMMICH_LOG_FORMAT=json

默认的 IMMICH_LOG_FORMAT=console 面向开发场景的人类可读输出;生产部署若配合日志聚合系统,建议改用 json。更多说明可参考环境变量文档中的 General 一节。

JSON 日志格式

启用后,日志以结构化 JSON 逐行输出:

{"level":"log","pid":36,"timestamp":1766533331507,"message":"Initialized websocket server","context":"WebsocketRepository"}
{"level":"warn","pid":48,"timestamp":1766533331629,"message":"Unable to open /build/www/index.html, skipping SSR.","context":"ApiService"}
{"level":"error","pid":36,"timestamp":1766533331690,"message":"Failed to load plugin immich-core:","context":"Error"}

字段含义:

字段 说明
level 日志级别(log、warn、error 等)
pid 进程 ID
timestamp Unix 时间戳(毫秒)
message 日志消息
context 产生日志的服务或组件名

源码中的日志实现

格式切换的实现位于 logging.repository.ts。Immich 继承 NestJS 的 ConsoleLogger 封装了 MyConsoleLogger,其构造参数中的 json 标志直接来自 IMMICH_LOG_FORMATlogFormat === LogFormat.Json 时启用 JSON 序列化,并自动关闭 ANSI 彩色输出(isColorEnabled 仅在非 JSON 模式下为真),保证日志聚合器拿到的是干净的结构化文本。日志级别支持 verbosedebuglogwarnerrorfatal 六级,且级别过滤由 LOG_LEVELS 前缀切片控制(例如设为 warn 时,仅 warn 及以上级别会被输出)。

一个值得注意的实现细节:formatContext 方法(logging.repository.ts)会从 nestjs-cls 的 Correlation ID 中读取请求关联 ID 并拼接进上下文前缀。从源码结构看,这意味着开启 JSON 日志后,同一请求链路产生的日志可通过 correlation ID 串联起来,便于在日志聚合系统中做请求级追踪——这与前文提到的 OpenTelemetry 上下文机制(AsyncLocalStorageContextManager)在思路上是一致的。

小结

Immich 的监控体系由三层组成:基于 OpenTelemetry + PrometheusExporter 的指标采集(按 host/api/io/repo/job 分组、可独立开关)、通过 Docker 内部网络抓取的 Prometheus + Grafana 可视化链路,以及可通过 IMMICH_LOG_FORMAT=json 启用的结构化日志。三者均以环境变量驱动、默认关闭,你可以根据运维需求选择开启其中的任意一部分。

登录后查看全文
热门项目推荐
相关项目推荐