Files
suaveplan/packages/commerce/contacts/macos-contacts/src/otel/instruments.md
2026-05-27 23:29:53 +01:00

3.8 KiB

instruments

(no submodules)

Purpose

Bundle of OpenTelemetry instruments emitted by @suaveplan/macos-contacts. The accessor getMacOSContactsInstruments() lazily binds against the current global MeterProvider, caches the bundle, and returns the same object on every subsequent call. Tests call resetMacOSContactsInstruments() after installing a fresh MeterProvider to force a rebind; production callers never need to touch the reset path.

Features

  • Six counters partitioned by outcome (success | permission_denied | access_failed):
    • macos_contacts.list_total — MacOSContactsBridge.getAllContacts
    • macos_contacts.search_total — searchContacts
    • macos_contacts.get_total — getContactById
    • macos_contacts.groups_list_total — getGroups
    • macos_contacts.group_members_total — getGroupMembers
    • macos_contacts.sync_total — sync
  • Two histograms (unit: ms):
    • macos_contacts.applescript_duration_ms — wraps every runner invocation
    • macos_contacts.sync_duration_ms — wraps sync end-to-end
  • Lazy + cached — the bundle is built on first access against the global MeterProvider. Subsequent calls return the same reference (===-equal).

Usage: Basic

import {
    getMacOSContactsInstruments,
} from "@suaveplan/macos-contacts";

const instruments = getMacOSContactsInstruments();
instruments.listTotal.add(1, { outcome: "success" });

Usage: Advanced

import { metrics } from "@opentelemetry/api";
import {
    InMemoryMetricExporter,
    MeterProvider,
    PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import {
    getMacOSContactsInstruments,
    resetMacOSContactsInstruments,
} from "@suaveplan/macos-contacts";

// Wire a real in-memory MeterProvider for tests.
const exporter = new InMemoryMetricExporter(0);
const reader = new PeriodicExportingMetricReader({
    exporter,
    exportIntervalMillis: 60_000,
});
const provider = new MeterProvider({ readers: [reader] });
metrics.disable();
metrics.setGlobalMeterProvider(provider);

// Force the bundle to rebind against the new provider.
resetMacOSContactsInstruments();
const instruments = getMacOSContactsInstruments();

API Reference

MacOSContactsInstruments

interface MacOSContactsInstruments {
    readonly listTotal: Counter;
    readonly searchTotal: Counter;
    readonly getTotal: Counter;
    readonly groupsListTotal: Counter;
    readonly groupMembersTotal: Counter;
    readonly syncTotal: Counter;
    readonly applescriptDurationMs: Histogram;
    readonly syncDurationMs: Histogram;
}

getMacOSContactsInstruments(): MacOSContactsInstruments

Lazily builds and caches the bundle. Returns the same object reference on every subsequent call until resetMacOSContactsInstruments() is called.

resetMacOSContactsInstruments(): void

Drops the cached bundle. The next getMacOSContactsInstruments() call rebuilds it against whatever global MeterProvider is in effect.

Implementation Notes

The bundle is built against the global MeterProvider rather than a parameter so consumers do not need to thread a meter through every layer. Tests reset the cache after installing a new provider so each test sees a fresh instrument set bound to its own in-memory exporter.

Counters partition by outcome so dashboards can distinguish revoked-permission incidents from transient osascript failures. Histograms record duration in milliseconds (unit: "ms"); the bridge wraps the runner call so the sample is recorded even when the runner rejects — observability survives platform-level failures.

Instrument names use snake_case per OpenTelemetry semantic conventions. The macos_contacts. prefix mirrors the package name so multi-package dashboards can filter by namespace.