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

Cerrado
#10,372 0 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@effect-bot ya está trabajando en esto.

Desde el 4/11/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

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.

Lenguaje dominante
TypeScript
Estrellas
16.7k
Forks
808
Merge medio
11 h 31 min
PR fusionados (30 d)
834

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de Effect-TS/effect

Todos los issues de Effect-TS/effect

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.