OtlpMetrics exports unit "1" for metrics with no declared unit, so Prometheus backends rename gauges to *_ratio

オープン
#10,372 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@effect-bot がすでに取り組んでいます。

2026年11月4日 から。

評価

この issue はまだ評価されていません。

説明

What version of Effect is running?

effect 4.1.2. We first hit this on 4.0.2.

What steps can reproduce the bug?
import { createServer } from 'node:http';
import { Effect, Layer, Metric } from 'effect';
import { OtlpMetrics, OtlpSerialization } from 'effect/observability';
import { FetchHttpClient } from 'effect/http';

const seen = new Map();
const server = createServer((req, res) => {
  let body = '';
  req.on('data', (chunk) => (body += chunk));
  req.on('end', () => {
    for (const rm of JSON.parse(body).resourceMetrics ?? [])
      for (const sm of rm.scopeMetrics ?? [])
        for (const m of sm.metrics ?? []) {
          const dp = (m.gauge ?? m.sum ?? m.histogram)?.dataPoints?.[0];
          seen.set(m.name, { unit: m.unit, attributes: dp?.attributes?.map((a) => a.key) });
        }
    res.setHeader('content-type', 'application/json');
    res.end('{}');
  });
});
await new Promise((r) => server.listen(4399, '127.0.0.1', r));

const telemetry = OtlpMetrics.layer({
  url: 'http://127.0.0.1:4399/v1/metrics',
  resource: { serviceName: 'probe' },
  exportInterval: '200 millis',
}).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));

const program = Effect.gen(function* () {
  yield* Metric.update(Metric.gauge('app_build_info'), 1);
  yield* Metric.update(Metric.counter('app_events'), 1);
  yield* Metric.update(Metric.gauge('app_workaround', { attributes: { unit: '' } }), 1);
  yield* Effect.sleep('1 second');
});

await Effect.runPromise(program.pipe(Effect.provide(telemetry)));
console.log(Object.fromEntries(seen));
process.exit(0);
What is the expected behavior?

A metric with no declared unit is exported with an empty unit. unit is optional in the OpenTelemetry metrics API, and "1" is a claim the author never made: it means dimensionless.

What do you see instead?
app_build_info: { unit: '1', attributes: [] }
app_events: { unit: '1', attributes: [] }
app_workaround: { unit: '', attributes: [ 'unit' ] }

The default is in toUcumUnit, packages/effect/src/observability/OtlpMetrics.ts:533:

const toUcumUnit = (unit: string | undefined): string => unit === undefined ? "1" : ucumUnits.get(unit) ?? unit
Additional information

prometheus/otlptranslator appends _ratio to gauges whose unit is "1" (metric_namer.go). So app_build_info is stored as app_build_info_ratio. We found this in production on Mimir: alert rules written from the source name read an absent series, and no test in the emitting repo could see it.

The only workaround we found is attributes: { unit: '' }, as in app_workaround above. It also adds unit to the data point attributes.

Changing the default renames stored series for anyone already querying the _ratio names, so it may deserve a release note.

主要言語
TypeScript
スター
16.7k
フォーク
808
平均マージ
11時間 29分
マージ済み PR(30日)
837

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Effect-TS/effect のほかの issue

Effect-TS/effect の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。