@moostjs/otel 0.6.50 → 0.6.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  let _opentelemetry_api = require("@opentelemetry/api");
3
3
  let moost = require("moost");
4
+ let _opentelemetry_core = require("@opentelemetry/core");
4
5
  let _moostjs_event_http = require("@moostjs/event-http");
6
+ let net = require("net");
5
7
  let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
6
8
 
7
9
  //#region packages/otel/src/context.ts
@@ -185,12 +187,39 @@ function readHandlerOtelMeta(controller, method) {
185
187
  return meta;
186
188
  }
187
189
  /** Root spans already owned by a Moost event — never attached to (renamed, re-attributed) twice. */ const claimedSpans = /* @__PURE__ */ new WeakSet();
190
+ const otelHostSpanKey = (0, moost.globalKey)("otel.hostSpan");
188
191
  /**
189
- * Whether the active span is the server span of the http instrumentation, not yet owned by a
190
- * Moost event. Anything else active (a lifecycle span of a running event, a client span) is a
191
- * parent, not the request's own span.
192
- */ function isInstrumentationServerSpan(span) {
193
- return !!span && !claimedSpans.has(span) && span.kind === _opentelemetry_api.SpanKind.SERVER;
192
+ * The server span of the http instrumentation (`@opentelemetry/instrumentation-http`) for this
193
+ * network request, when no Moost event owns it yet.
194
+ *
195
+ * The instrumentation publishes it as the request's RPC metadata, so it is found even when a host
196
+ * app's middleware span (Express instrumentation) is active, not the server span itself. Without
197
+ * RPC metadata only an active SERVER span qualifies — anything else active (a lifecycle span of a
198
+ * running event, a client span) is a parent, not the request's own span. An in-process request
199
+ * (`fetch()` / `request()` on the HTTP app) runs on a fake socket and never attaches: it is not
200
+ * the network request the server span stands for.
201
+ */ function getHostServerSpan(req) {
202
+ if (!(req?.socket instanceof net.Socket)) return;
203
+ const rpc = (0, _opentelemetry_core.getRPCMetadata)(_opentelemetry_api.context.active());
204
+ if (rpc?.type === _opentelemetry_core.RPCType.HTTP && rpc.span) return claimedSpans.has(rpc.span) ? void 0 : {
205
+ span: rpc.span,
206
+ rpc,
207
+ mount: getMountRoute(req, rpc)
208
+ };
209
+ const active = _opentelemetry_api.trace.getActiveSpan();
210
+ return active?.kind === _opentelemetry_api.SpanKind.SERVER && !claimedSpans.has(active) ? {
211
+ span: active,
212
+ mount: ""
213
+ } : void 0;
214
+ }
215
+ /**
216
+ * The route template a host app mounted Moost under, when it stripped that path from `req.url`
217
+ * (`app.use('/api', moostMiddleware)`): the route the host's own instrumentation reported
218
+ * for the request so far. Empty when Moost sees the whole path.
219
+ */ function getMountRoute(req, rpc) {
220
+ const path = (url) => url?.split("?")[0];
221
+ if (!req.originalUrl || path(req.originalUrl) === path(req.url) || !rpc.route) return "";
222
+ return rpc.route.replace(/\/+$/, "");
194
223
  }
195
224
  /**
196
225
  * The controller context of the current event, or `undefined` while no handler is resolved yet
@@ -239,16 +268,23 @@ function readHandlerOtelMeta(controller, method) {
239
268
  if (eventType === "init") return cb();
240
269
  const { registerSpan } = useOtelContext();
241
270
  const isHttp = eventType === _moostjs_event_http.httpKind.name;
271
+ const req = isHttp ? this.getRequest() : void 0;
242
272
  if (isHttp) this.patchRsponse();
243
- const active = isHttp ? _opentelemetry_api.trace.getActiveSpan() : void 0;
244
- const attached = isInstrumentationServerSpan(active) ? active : void 0;
245
- const span = attached ?? tracer.startSpan(`${eventType} Event`, isHttp && !active ? { kind: _opentelemetry_api.SpanKind.SERVER } : void 0);
273
+ const host = getHostServerSpan(req);
274
+ const active = _opentelemetry_api.trace.getActiveSpan();
275
+ const span = host?.span ?? tracer.startSpan(isHttp ? req?.method || "HTTP" : `${eventType} Event`, isHttp && !active ? { kind: _opentelemetry_api.SpanKind.SERVER } : void 0);
246
276
  claimedSpans.add(span);
247
277
  registerSpan(span);
248
- return this.withSpan(span, cb, {
278
+ if (host) (0, moost.current)().set(otelHostSpanKey, host);
279
+ const opts = {
249
280
  withMetrics: true,
250
- endSpan: !attached
251
- });
281
+ endSpan: !host
282
+ };
283
+ if (host && active && active !== host.span) {
284
+ const outer = _opentelemetry_api.context.active();
285
+ return this.withSpan(span, () => _opentelemetry_api.context.with(outer, cb), opts);
286
+ }
287
+ return this.withSpan(span, cb, opts);
252
288
  }
253
289
  getEventType() {
254
290
  const ctx = (0, moost.current)();
@@ -300,8 +336,15 @@ function readHandlerOtelMeta(controller, method) {
300
336
  const span = getSpan();
301
337
  if (span) {
302
338
  span.setAttributes(chm.attrs);
303
- if (chm.attrs["moost.event_type"] === _moostjs_event_http.httpKind.name) span.updateName(`${this.getRequest()?.method || ""} ${_route || "<unresolved>"}`);
304
- else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
339
+ if (chm.attrs["moost.event_type"] === _moostjs_event_http.httpKind.name) {
340
+ const host = ctx.has(otelHostSpanKey) ? ctx.get(otelHostSpanKey) : void 0;
341
+ const httpRoute = _route ? `${host?.mount || ""}${_route}` : void 0;
342
+ span.updateName(`${this.getRequest()?.method || ""} ${httpRoute || "<unresolved>"}`);
343
+ if (httpRoute) {
344
+ span.setAttribute("http.route", httpRoute);
345
+ if (host?.rpc) host.rpc.route = httpRoute;
346
+ }
347
+ } else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
305
348
  }
306
349
  }
307
350
  if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
@@ -328,6 +371,7 @@ function readHandlerOtelMeta(controller, method) {
328
371
  const ctx = (0, moost.current)();
329
372
  const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
330
373
  const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
374
+ if (startTime === void 0 && route === void 0 && a["moost.event_type"] === _moostjs_event_http.httpKind.name) return;
331
375
  const duration = Date.now() - (startTime || Date.now() - 1);
332
376
  const attrs = {
333
377
  ...ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {},
package/dist/index.d.ts CHANGED
@@ -4,8 +4,7 @@ import * as moost from 'moost';
4
4
  import { Mate, TMoostMetadata, TMateParamMeta, ContextInjector, TContextInjectorHook } from 'moost';
5
5
  import { EventContext } from '@wooksjs/event-core';
6
6
  import { BatchSpanProcessor, ReadableSpan, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
7
- import * as http from 'http';
8
- import { ServerResponse } from 'http';
7
+ import { IncomingMessage, ServerResponse } from 'http';
9
8
 
10
9
  /** Event context key storing the root OpenTelemetry span. */
11
10
  declare const otelSpanKey: moost.Key<Span | undefined>;
@@ -146,8 +145,8 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
146
145
  }): T;
147
146
  startEventMetrics(a: Record<string, string | number | boolean | undefined>, route?: string): void;
148
147
  endEventMetrics(a: Record<string, string | number | boolean | undefined>, error?: Error): void;
149
- getRequest(): http.IncomingMessage | undefined;
150
- getResponse(): (ServerResponse<http.IncomingMessage> & {
148
+ getRequest(): IncomingMessage | undefined;
149
+ getResponse(): (ServerResponse<IncomingMessage> & {
151
150
  _statusCode?: number;
152
151
  }) | undefined;
153
152
  }
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { SpanKind, SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
2
2
  import { ContextInjector, current, eventTypeKey, getConstructor, getMoostMate, globalKey, replaceContextInjector, useControllerContext } from "moost";
3
+ import { RPCType, getRPCMetadata } from "@opentelemetry/core";
3
4
  import { httpKind } from "@moostjs/event-http";
5
+ import { Socket } from "net";
4
6
  import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
5
7
 
6
8
  //#region packages/otel/src/context.ts
@@ -184,12 +186,39 @@ function readHandlerOtelMeta(controller, method) {
184
186
  return meta;
185
187
  }
186
188
  /** Root spans already owned by a Moost event — never attached to (renamed, re-attributed) twice. */ const claimedSpans = /* @__PURE__ */ new WeakSet();
189
+ const otelHostSpanKey = globalKey("otel.hostSpan");
187
190
  /**
188
- * Whether the active span is the server span of the http instrumentation, not yet owned by a
189
- * Moost event. Anything else active (a lifecycle span of a running event, a client span) is a
190
- * parent, not the request's own span.
191
- */ function isInstrumentationServerSpan(span) {
192
- return !!span && !claimedSpans.has(span) && span.kind === SpanKind.SERVER;
191
+ * The server span of the http instrumentation (`@opentelemetry/instrumentation-http`) for this
192
+ * network request, when no Moost event owns it yet.
193
+ *
194
+ * The instrumentation publishes it as the request's RPC metadata, so it is found even when a host
195
+ * app's middleware span (Express instrumentation) is active, not the server span itself. Without
196
+ * RPC metadata only an active SERVER span qualifies — anything else active (a lifecycle span of a
197
+ * running event, a client span) is a parent, not the request's own span. An in-process request
198
+ * (`fetch()` / `request()` on the HTTP app) runs on a fake socket and never attaches: it is not
199
+ * the network request the server span stands for.
200
+ */ function getHostServerSpan(req) {
201
+ if (!(req?.socket instanceof Socket)) return;
202
+ const rpc = getRPCMetadata(context.active());
203
+ if (rpc?.type === RPCType.HTTP && rpc.span) return claimedSpans.has(rpc.span) ? void 0 : {
204
+ span: rpc.span,
205
+ rpc,
206
+ mount: getMountRoute(req, rpc)
207
+ };
208
+ const active = trace.getActiveSpan();
209
+ return active?.kind === SpanKind.SERVER && !claimedSpans.has(active) ? {
210
+ span: active,
211
+ mount: ""
212
+ } : void 0;
213
+ }
214
+ /**
215
+ * The route template a host app mounted Moost under, when it stripped that path from `req.url`
216
+ * (`app.use('/api', moostMiddleware)`): the route the host's own instrumentation reported
217
+ * for the request so far. Empty when Moost sees the whole path.
218
+ */ function getMountRoute(req, rpc) {
219
+ const path = (url) => url?.split("?")[0];
220
+ if (!req.originalUrl || path(req.originalUrl) === path(req.url) || !rpc.route) return "";
221
+ return rpc.route.replace(/\/+$/, "");
193
222
  }
194
223
  /**
195
224
  * The controller context of the current event, or `undefined` while no handler is resolved yet
@@ -238,16 +267,23 @@ function readHandlerOtelMeta(controller, method) {
238
267
  if (eventType === "init") return cb();
239
268
  const { registerSpan } = useOtelContext();
240
269
  const isHttp = eventType === httpKind.name;
270
+ const req = isHttp ? this.getRequest() : void 0;
241
271
  if (isHttp) this.patchRsponse();
242
- const active = isHttp ? trace.getActiveSpan() : void 0;
243
- const attached = isInstrumentationServerSpan(active) ? active : void 0;
244
- const span = attached ?? tracer.startSpan(`${eventType} Event`, isHttp && !active ? { kind: SpanKind.SERVER } : void 0);
272
+ const host = getHostServerSpan(req);
273
+ const active = trace.getActiveSpan();
274
+ const span = host?.span ?? tracer.startSpan(isHttp ? req?.method || "HTTP" : `${eventType} Event`, isHttp && !active ? { kind: SpanKind.SERVER } : void 0);
245
275
  claimedSpans.add(span);
246
276
  registerSpan(span);
247
- return this.withSpan(span, cb, {
277
+ if (host) current().set(otelHostSpanKey, host);
278
+ const opts = {
248
279
  withMetrics: true,
249
- endSpan: !attached
250
- });
280
+ endSpan: !host
281
+ };
282
+ if (host && active && active !== host.span) {
283
+ const outer = context.active();
284
+ return this.withSpan(span, () => context.with(outer, cb), opts);
285
+ }
286
+ return this.withSpan(span, cb, opts);
251
287
  }
252
288
  getEventType() {
253
289
  const ctx = current();
@@ -299,8 +335,15 @@ function readHandlerOtelMeta(controller, method) {
299
335
  const span = getSpan();
300
336
  if (span) {
301
337
  span.setAttributes(chm.attrs);
302
- if (chm.attrs["moost.event_type"] === httpKind.name) span.updateName(`${this.getRequest()?.method || ""} ${_route || "<unresolved>"}`);
303
- else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
338
+ if (chm.attrs["moost.event_type"] === httpKind.name) {
339
+ const host = ctx.has(otelHostSpanKey) ? ctx.get(otelHostSpanKey) : void 0;
340
+ const httpRoute = _route ? `${host?.mount || ""}${_route}` : void 0;
341
+ span.updateName(`${this.getRequest()?.method || ""} ${httpRoute || "<unresolved>"}`);
342
+ if (httpRoute) {
343
+ span.setAttribute("http.route", httpRoute);
344
+ if (host?.rpc) host.rpc.route = httpRoute;
345
+ }
346
+ } else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
304
347
  }
305
348
  }
306
349
  if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
@@ -327,6 +370,7 @@ function readHandlerOtelMeta(controller, method) {
327
370
  const ctx = current();
328
371
  const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
329
372
  const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
373
+ if (startTime === void 0 && route === void 0 && a["moost.event_type"] === httpKind.name) return;
330
374
  const duration = Date.now() - (startTime || Date.now() - 1);
331
375
  const attrs = {
332
376
  ...ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/otel",
3
- "version": "0.6.50",
3
+ "version": "0.6.51",
4
4
  "description": "@moostjs/otel",
5
5
  "keywords": [
6
6
  "composables",
@@ -39,15 +39,16 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@opentelemetry/api": "^1.9.1",
42
+ "@opentelemetry/core": "^2.10.0",
42
43
  "@opentelemetry/sdk-trace-base": "^2.10.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "vitest": "3.2.7"
46
47
  },
47
48
  "peerDependencies": {
48
- "@wooksjs/event-core": "^0.7.28",
49
- "@moostjs/event-http": "^0.6.50",
50
- "moost": "^0.6.50"
49
+ "@wooksjs/event-core": "^0.7.29",
50
+ "@moostjs/event-http": "^0.6.51",
51
+ "moost": "^0.6.51"
51
52
  },
52
53
  "scripts": {
53
54
  "pub": "pnpm publish --access public",