// packages/core-shared/src/instrumentation/otel/otel-logger.ts import { logs, SeverityNumber } from "@opentelemetry/api-logs"; import { trace } from "@opentelemetry/api"; import { isReported, markReported } from "../reported-flag"; import type { ILogger, Breadcrumb, CaptureContext } from "../logger.interface"; export class OtelLogger implements ILogger { private readonly logger = logs.getLogger("@repo/core-shared", "1.0.0"); captureException(err: unknown, ctx?: CaptureContext): void { if (isReported(err)) return; markReported(err); const error = err instanceof Error ? err : new Error(String(err)); this.logger.emit({ severityNumber: SeverityNumber.ERROR, severityText: "ERROR", body: error.message, attributes: { "exception.type": error.name, "exception.message": error.message, "exception.stacktrace": error.stack ?? "", ...flattenTags(ctx?.tags), ...flattenExtras(ctx?.extras), ...(ctx?.fingerprint ? { "sentry.fingerprint": ctx.fingerprint.join("|") } : {}), }, }); } captureMessage(msg: string, level?: "info" | "warning" | "error", ctx?: CaptureContext): void { const severityNumber = level === "error" ? SeverityNumber.ERROR : level === "warning" ? SeverityNumber.WARN : SeverityNumber.INFO; const severityText = level === "error" ? "ERROR" : level === "warning" ? "WARNING" : "INFO"; this.logger.emit({ severityNumber, severityText, body: msg, attributes: { ...flattenTags(ctx?.tags), ...flattenExtras(ctx?.extras) }, }); } addBreadcrumb(b: Breadcrumb): void { const span = trace.getActiveSpan(); if (!span) return; span.addEvent(b.message, { "breadcrumb.category": b.category, "breadcrumb.level": b.level ?? "info", ...(b.data ? flattenExtras(b.data) : {}), }); } setUser(user: { id: string } | null): void { const span = trace.getActiveSpan(); if (!span) return; span.setAttribute("user.id", user?.id ?? ""); } } function flattenTags(tags?: Record): Record { if (!tags) return {}; return Object.fromEntries(Object.entries(tags).map(([k, v]) => [`tag.${k}`, v])); } function flattenExtras(extras?: Record): Record { if (!extras) return {}; return Object.fromEntries( Object.entries(extras).map(([k, v]) => [ `extra.${k}`, typeof v === "string" ? v : String(v), ]), ); }