UNPKG

@medusajs/medusa

Version:

Building blocks for digital commerce

261 lines • 9.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.instrumentHttpLayer = instrumentHttpLayer; exports.instrumentRemoteQuery = instrumentRemoteQuery; exports.instrumentWorkflows = instrumentWorkflows; exports.registerOtel = registerOtel; const lodash_1 = require("lodash"); const framework_1 = require("@medusajs/framework"); const http_1 = require("@medusajs/framework/http"); const telemetry_1 = require("@medusajs/framework/telemetry"); const orchestration_1 = require("@medusajs/framework/orchestration"); const EXCLUDED_RESOURCES = [".vite", "virtual:"]; function shouldExcludeResource(resource) { return EXCLUDED_RESOURCES.some((excludedResource) => resource.includes(excludedResource)); } /** * Instrument the first touch point of the HTTP layer to report traces to * OpenTelemetry */ function instrumentHttpLayer() { const startCommand = require("../commands/start"); const HTTPTracer = new telemetry_1.Tracer("@medusajs/http", "2.0.0"); const { SpanStatusCode } = require("@opentelemetry/api"); startCommand.traceRequestHandler = async (requestHandler, req, res, handlerPath) => { if (shouldExcludeResource(req.url)) { return await requestHandler(); } const traceName = handlerPath ?? `${req.method} ${req.url}`; await HTTPTracer.trace(traceName, async (span) => { span.setAttributes({ "http.route": handlerPath, "http.url": req.url, "http.method": req.method, ...req.headers, }); try { await requestHandler(); } finally { if (res.statusCode >= 500) { span.setStatus({ code: SpanStatusCode.ERROR, message: `Failed with ${res.statusMessage}`, }); } span.setAttributes({ "http.statusCode": res.statusCode }); span.end(); } }); }; /** * Instrumenting the route handler to report traces to * OpenTelemetry */ http_1.ApiLoader.traceRoute = (handler) => { return async (req, res) => { if (shouldExcludeResource(req.originalUrl)) { return await handler(req, res); } const label = req.route?.path ?? `${req.method} ${req.originalUrl}`; const traceName = `route handler: ${label}`; await HTTPTracer.trace(traceName, async (span) => { try { await handler(req, res); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error.message || "Failed", }); throw error; } finally { span.end(); } }); }; }; /** * Instrumenting the middleware handler to report traces to * OpenTelemetry */ http_1.ApiLoader.traceMiddleware = (handler) => { return async (req, res, next) => { if (shouldExcludeResource(req.originalUrl)) { return handler(req, res, next); } const traceName = `middleware: ${handler.name ? (0, lodash_1.snakeCase)(handler.name) : `anonymous`}`; await HTTPTracer.trace(traceName, async (span) => { try { await handler(req, res, next); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error.message || "Failed", }); throw error; } finally { span.end(); } }); }; }; } /** * Instrument the queries made using the remote query */ function instrumentRemoteQuery() { const QueryTracer = new telemetry_1.Tracer("@medusajs/query", "2.0.0"); const { SpanStatusCode } = require("@opentelemetry/api"); framework_1.Query.instrument.graphQuery(async function (queryFn, queryOptions) { return await QueryTracer.trace(`query.graph: ${queryOptions.entity}`, async (span) => { span.setAttributes({ "query.fields": queryOptions.fields, }); try { return await queryFn(); } catch (err) { span.setStatus({ code: SpanStatusCode.ERROR, message: err.message, }); throw err; } finally { span.end(); } }); }); framework_1.Query.instrument.remoteQuery(async function (queryFn, queryOptions) { const traceIdentifier = "entryPoint" in queryOptions ? queryOptions.entryPoint : "service" in queryOptions ? queryOptions.service : "__value" in queryOptions ? Object.keys(queryOptions.__value)[0] : "unknown source"; return await QueryTracer.trace(`remoteQuery: ${traceIdentifier}`, async (span) => { span.setAttributes({ "query.fields": "fields" in queryOptions ? queryOptions.fields : [], }); try { return await queryFn(); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error.message, }); throw error; } finally { span.end(); } }); }); framework_1.Query.instrument.remoteDataFetch(async function (fetchFn, serviceName, method, options) { return await QueryTracer.trace(`${(0, lodash_1.snakeCase)(serviceName)}.${(0, lodash_1.snakeCase)(method)}`, async (span) => { span.setAttributes({ "fetch.select": options.select, "fetch.relations": options.relations, }); try { return await fetchFn(); } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error.message, }); throw error; } finally { span.end(); } }); }); } /** * Instrument the workflows and steps execution */ function instrumentWorkflows() { const WorkflowsTracer = new telemetry_1.Tracer("@medusajs/framework/workflows-sdk", "2.0.0"); orchestration_1.TransactionOrchestrator.traceTransaction = async (transactionResumeFn, metadata) => { return await WorkflowsTracer.trace(`workflow:${(0, lodash_1.snakeCase)(metadata.model_id)}`, async function (span) { span.setAttribute("workflow.transaction_id", metadata.transaction_id); if (metadata.flow_metadata) { Object.entries(metadata.flow_metadata).forEach(([key, value]) => { span.setAttribute(`workflow.flow_metadata.${key}`, value); }); } return await transactionResumeFn().finally(() => span.end()); }); }; orchestration_1.TransactionOrchestrator.traceStep = async (stepHandler, metadata) => { return await WorkflowsTracer.trace(`step:${(0, lodash_1.snakeCase)(metadata.action)}:${metadata.type}`, async function (span) { Object.entries(metadata).forEach(([key, value]) => { span.setAttribute(`workflow.step.${key}`, value); }); // TODO: should we report error and re throw it? return await stepHandler().finally(() => span.end()); }); }; } /** * A helper function to configure the OpenTelemetry SDK with some defaults. * For better/more control, please configure the SDK manually. * * You will have to install the following packages within your app for * telemetry to work * * - @opentelemetry/sdk-node * - @opentelemetry/resources * - @opentelemetry/sdk-trace-node * - @opentelemetry/instrumentation-pg * - @opentelemetry/instrumentation */ function registerOtel(options) { const { exporter, serviceName, instrument, instrumentations, ...nodeSdkOptions } = { instrument: {}, instrumentations: [], ...options, }; const { Resource, resourceFromAttributes, } = require("@opentelemetry/resources"); const { NodeSDK } = require("@opentelemetry/sdk-node"); const { SimpleSpanProcessor } = require("@opentelemetry/sdk-trace-node"); if (instrument.db) { const { PgInstrumentation } = require("@opentelemetry/instrumentation-pg"); instrumentations.push(new PgInstrumentation()); } if (instrument.http) { instrumentHttpLayer(); } if (instrument.query) { instrumentRemoteQuery(); } if (instrument.workflows) { instrumentWorkflows(); } const sdk = new NodeSDK({ serviceName, /** * Older version of "@opentelemetry/resources" exports the "Resource" class. * Whereas, the new one exports the "resourceFromAttributes" method. */ resource: resourceFromAttributes ? resourceFromAttributes({ "service.name": serviceName, }) : new Resource({ "service.name": serviceName }), spanProcessor: new SimpleSpanProcessor(exporter), ...nodeSdkOptions, instrumentations: instrumentations, }); sdk.start(); return sdk; } //# sourceMappingURL=index.js.map