diff --git a/packages/framework/framework/package.json b/packages/framework/framework/package.json index ef37b1d380..fa3288ea5e 100644 --- a/packages/framework/framework/package.json +++ b/packages/framework/framework/package.json @@ -54,6 +54,7 @@ "@medusajs/modules-sdk": "^1.12.11", "@medusajs/utils": "^1.11.9", "@medusajs/workflows-sdk": "^0.1.6", + "@opentelemetry/api": "^1.9.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.18.2", diff --git a/packages/framework/framework/src/index.ts b/packages/framework/framework/src/index.ts index e3a8d090fe..66d9330d6b 100644 --- a/packages/framework/framework/src/index.ts +++ b/packages/framework/framework/src/index.ts @@ -9,6 +9,7 @@ export * from "./logger" export * from "./medusa-app-loader" export * from "./subscribers" export * from "./workflows" +export * from "./telemetry" export const MEDUSA_CLI_PATH = require.resolve("@medusajs/medusa-cli") diff --git a/packages/framework/framework/src/telemetry/index.ts b/packages/framework/framework/src/telemetry/index.ts new file mode 100644 index 0000000000..a6b623cc8b --- /dev/null +++ b/packages/framework/framework/src/telemetry/index.ts @@ -0,0 +1,79 @@ +import { + Tracer as OTTracer, + Span, + trace, + context, + propagation, +} from "@opentelemetry/api" + +/** + * Tracer creates an instrumentation scope within the application + * code. For example: You might create a tracer for the API + * requests, another one for the modules, one for workflows + * and so on. + * + * There is no need to create a Tracer instance per HTTP + * call. + */ +export class Tracer { + /** + * Reference to the underlying OpenTelemetry tracer + */ + #otTracer: OTTracer + constructor(public name: string, public version?: string) { + this.#otTracer = trace.getTracer(name, version) + } + + /** + * Returns the underlying tracer from open telemetry that + * could be used directly for certain advanced use-cases + */ + getOTTracer() { + return this.#otTracer + } + + /** + * Trace a function call. Using this method will create a + * child scope for the invocations within the callback. + */ + trace unknown>( + name: string, + callback: F + ): ReturnType { + return this.#otTracer.startActiveSpan(name, callback) + } + + /** + * Returns the active context + */ + getActiveContext() { + return context.active() + } + + /** + * Returns the propagation state from the current active + * context + */ + getPropagationState() { + let output = {} + propagation.inject(context.active(), output) + return output as { traceparent: string; tracestate?: string } + } + + /** + * Use the existing propogation state and trace an action. This + * will allow the newly traced action to be part of some + * existing trace + */ + withPropagationState(state: { traceparent: string; tracestate?: string }) { + return { + trace: unknown>( + name: string, + callback: F + ): ReturnType => { + const activeContext = propagation.extract(context.active(), state) + return this.#otTracer.startActiveSpan(name, {}, activeContext, callback) + }, + } + } +} diff --git a/yarn.lock b/yarn.lock index ed937b5bdd..266530145a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5900,6 +5900,7 @@ __metadata: "@medusajs/types": ^1.11.16 "@medusajs/utils": ^1.11.9 "@medusajs/workflows-sdk": ^0.1.6 + "@opentelemetry/api": ^1.9.0 "@types/express": ^4.17.17 "@types/jsonwebtoken": ^8.5.9 cookie-parser: ^1.4.6 @@ -7237,6 +7238,13 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api@npm:^1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + languageName: node + linkType: hard + "@peculiar/asn1-schema@npm:^2.3.8": version: 2.3.13 resolution: "@peculiar/asn1-schema@npm:2.3.13"