UNPKG

@fiberplane/hono-otel

Version:

Hono middleware to forward OpenTelemetry traces to a local instance of @fiberplane/studio

88 lines (87 loc) 3.46 kB
import { ExportResultCode } from "@opentelemetry/core"; import { OTLPExporterError, } from "@opentelemetry/otlp-exporter-base"; import { createExportTraceServiceRequest } from "@opentelemetry/otlp-transformer"; import { isWrapped } from "./utils/index.js"; const defaultHeaders = { // INVESTIGATE - add user agent in the same way the OTLPTraceExporter does accept: "application/json", "content-type": "application/json", }; /** * @NOTE - We had to implement a custom SpanExporter because the OTLPTraceExporter * was not working for us in Cloudflare Workers. * * Specifically, the OTLPTraceExporter kept thinking that we were in the browser, * _but only when we attached a `headers` object to the request._ * * The lack of browser APIs (XHR, sendBeacon) led to runtime errors. * * Solution inspired by https://github.com/evanderkoogh/otel-cf-workers/blob/450435bc136d5b81aee655096aa4b8261f42d0c7/src/exporter.ts */ export class FPOTLPExporter { constructor(config, fetchFn, logger) { this.url = config.url; this.headers = Object.assign({}, defaultHeaders, config.headers); this.fetchFn = fetchFn; this.logger = logger; if (isWrapped(this.fetchFn)) { this.logger.error("FPOTLPExporter's fetch function is already instrumented, so we cannot send traces without causing an infinite loop"); throw new Error("FPOTLPExporter fetchFn cannot be instrumented"); } } export(items, resultCallback) { this._export(items) .then(() => { resultCallback({ code: ExportResultCode.SUCCESS }); }) .catch((error) => { resultCallback({ code: ExportResultCode.FAILED, error }); }); } _export(items) { return new Promise((resolve, reject) => { try { this.send(items, resolve, reject); } catch (e) { reject(e); } }); } send(items, onSuccess, onError) { const exportMessage = createExportTraceServiceRequest(items, /** useHex: */ true); // HACK - Convert timestamp fields to strings so that they are not parsed as numbers // This gives us better support in the Fiberplane Rust ingestor // const body = JSON.stringify(exportMessage, (key, value) => { // Convert timestamp fields to strings if (key === "startTimeUnixNano" || key === "endTimeUnixNano" || key === "timeUnixNano") { return String(value); } return value; }); const params = { method: "POST", headers: this.headers, body, }; // NOTE - You can log the payload to the console for debugging purposes // this.logger.debug(`Payload to send to OTLP (${body.length})\n`, body); this.fetchFn(this.url, params) .then(async (response) => { if (response.ok) { onSuccess(); } else { onError(new OTLPExporterError(`Exporter received a statusCode: ${response.status}`)); } }) .catch((error) => { onError(new OTLPExporterError(`Exception during export: ${error.toString()}`, error.code, error.stack)); }); } async shutdown() { } }