/** * @factory/observability — service resource attributes (TASK-0135). * * Every span/metric/log emitted by the four apps carries the SAME resource * so the collector can demux by `service.name`. The names are fixed by the * phase-15 spec (NOT the bare "api"/"worker" the NFR doc sketched — the spec * wins; recorded conflict, AGENTS rule 29): `factory-web`, `factory-api`, * `factory-worker`, `factory-realtime`. * * NOTE: OTel JS 2.x line (T-190b) — SDK core 2.12.0 + exporters 0.223.0 + * semantic-conventions 1.43.0 + `@prisma/instrumentation` 6.19.3 (Prisma 6.x * pairs with the 2.x train; the 1.x pairing predates it). In 2.x the * `Resource` class is GONE — resources are built with the * `resourceFromAttributes(...)` factory, and `Resource` is the TYPE (the old * `IResource` was renamed to `Resource`). */ import { resourceFromAttributes, type Resource } from "@opentelemetry/resources"; /** The four app service names (phase-15 spec, verbatim). */ export const SERVICE_NAMES = { web: "factory-web", api: "factory-api", worker: "factory-worker", realtime: "factory-realtime" } as const; export type ServiceName = (typeof SERVICE_NAMES)[keyof typeof SERVICE_NAMES]; export function isServiceName(name: string): name is ServiceName { return Object.values(SERVICE_NAMES).includes(name as ServiceName); } export interface BuildResourceOptions { /** `deployment.environment` attribute value (NODE_ENV). */ environment?: string; /** Service version — defaults to the package version (0.1.0). */ version?: string; /** Host name — defaults to the HOSTNAME/HOST env or "local". */ hostName?: string; } /** * Build the OTel `Resource` for one app. Stable attributes only (host name, * not IP — a pod IP churns on every reschedule and would fragment the * series). */ export function buildResource(serviceName: ServiceName, opts: BuildResourceOptions = {}): Resource { const { environment, version, hostName } = opts; const versionValue = version ?? "0.1.0"; return resourceFromAttributes({ "service.name": serviceName, "service.version": versionValue, "deployment.environment": environment ?? process.env.NODE_ENV ?? "development", "host.name": hostName ?? safeHostname() }); } function safeHostname(): string { return process.env.HOSTNAME || process.env.HOST || "local"; }