@twin.org/tracing-processors 0.9.2-next.3 → 0.9.2-next.6

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.
@@ -64,6 +64,12 @@ export class TracingRouteProcessor {
64
64
  if (Is.empty(this._tracing)) {
65
65
  return;
66
66
  }
67
+ // Don't want to run tracing on routes without auth as we have no
68
+ // tenancy context to record the span against, and it will just create noise in the traces.
69
+ // this is usually endpoints like /health, /metrics, etc. that are not part of the business logic of the service.
70
+ if (route?.skipAuth ?? false) {
71
+ return;
72
+ }
67
73
  if (Is.stringValue(route?.path) && this._excludePaths.includes(route.path)) {
68
74
  return;
69
75
  }
@@ -72,13 +78,24 @@ export class TracingRouteProcessor {
72
78
  return;
73
79
  }
74
80
  const parentContext = TraceparentHelper.parse(Coerce.string(request.headers?.[TraceparentHelper.HEADER_NAME]));
75
- const span = await this._tracing.startSpan(this.spanName(request, route), {
76
- kind: SpanKind.Server,
77
- parentContext,
78
- attributes: this.requestAttributes(request, route)
79
- });
80
- processorState[TracingRouteProcessor._STATE_KEY] = span;
81
- Object.assign(contextIds, TracingHelper.spanContextToContextIds(span.context));
81
+ try {
82
+ const span = await this._tracing.startSpan(this.spanName(request, route), {
83
+ kind: SpanKind.Server,
84
+ parentContext,
85
+ attributes: this.requestAttributes(request, route)
86
+ });
87
+ processorState[TracingRouteProcessor._STATE_KEY] = span;
88
+ Object.assign(contextIds, TracingHelper.spanContextToContextIds(span.context));
89
+ }
90
+ catch (err) {
91
+ await this._logging?.log({
92
+ level: "error",
93
+ source: TracingRouteProcessor.CLASS_NAME,
94
+ ts: Date.now(),
95
+ message: "startSpanFailed",
96
+ error: BaseError.fromError(err)
97
+ });
98
+ }
82
99
  }
83
100
  /**
84
101
  * Post process the REST request for the specified route, ending the span for the request.
@@ -1 +1 @@
1
- {"version":3,"file":"tracingRouteProcessor.js","sourceRoot":"","sources":["../../src/tracingRouteProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIBaseRoute,\n\tIBaseRouteProcessor,\n\tIHttpResponse,\n\tIHttpServerRequest\n} from \"@twin.org/api-models\";\nimport type { IContextIds } from \"@twin.org/context\";\nimport { BaseError, Coerce, ComponentFactory, Is } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tSpanKind,\n\tSpanStatus,\n\tTraceparentHelper,\n\tTracingHelper,\n\ttype ISpan,\n\ttype ITracingComponent\n} from \"@twin.org/tracing-models\";\nimport { HttpStatusCode } from \"@twin.org/web\";\nimport type { ITracingRouteProcessorConstructorOptions } from \"./models/ITracingRouteProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and record it as a span.\n */\nexport class TracingRouteProcessor implements IBaseRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TracingRouteProcessor>();\n\n\t/**\n\t * The key the span is stored under while it is handed between the processor phases.\n\t * @internal\n\t */\n\tprivate static readonly _STATE_KEY: string = \"tracingProcessorSpan\";\n\n\t/**\n\t * The component for recording the spans.\n\t * @internal\n\t */\n\tprivate readonly _tracing?: ITracingComponent;\n\n\t/**\n\t * The component for logging failures.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * Request URL path prefixes to skip tracing, defaults to [\"/tracing\"].\n\t * @internal\n\t */\n\tprivate readonly _excludePaths: string[];\n\n\t/**\n\t * Route operation IDs to skip tracing.\n\t * @internal\n\t */\n\tprivate readonly _excludeOperationIds: string[];\n\n\t/**\n\t * Create a new instance of TracingRouteProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ITracingRouteProcessorConstructorOptions) {\n\t\tthis._tracing = ComponentFactory.getIfExists(options?.tracingComponentType);\n\t\tthis._logging = ComponentFactory.getIfExists(options?.loggingComponentType);\n\t\tthis._excludePaths = options?.config?.excludePaths ?? [\"/tracing\"];\n\t\tthis._excludeOperationIds = options?.config?.excludeOperationIds ?? [];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn TracingRouteProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route, opening the span for the request.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the span has been started.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (Is.empty(this._tracing)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (Is.stringValue(route?.path) && this._excludePaths.includes(route.path)) {\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tIs.stringValue(route?.operationId) &&\n\t\t\tthis._excludeOperationIds.includes(route.operationId)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst parentContext = TraceparentHelper.parse(\n\t\t\tCoerce.string(request.headers?.[TraceparentHelper.HEADER_NAME])\n\t\t);\n\n\t\tconst span = await this._tracing.startSpan(this.spanName(request, route), {\n\t\t\tkind: SpanKind.Server,\n\t\t\tparentContext,\n\t\t\tattributes: this.requestAttributes(request, route)\n\t\t});\n\n\t\tprocessorState[TracingRouteProcessor._STATE_KEY] = span;\n\n\t\tObject.assign(contextIds, TracingHelper.spanContextToContextIds(span.context));\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route, ending the span for the request.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the span has been ended.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst span = processorState[TracingRouteProcessor._STATE_KEY];\n\n\t\tif (Is.empty(this._tracing) || !Is.object<ISpan>(span)) {\n\t\t\treturn;\n\t\t}\n\n\t\tdelete processorState[TracingRouteProcessor._STATE_KEY];\n\n\t\ttry {\n\t\t\tconst statusCode = Is.number(response.statusCode) ? response.statusCode : HttpStatusCode.ok;\n\n\t\t\tspan.attributes = { ...span.attributes, \"http.status_code\": statusCode };\n\n\t\t\tawait this._tracing.endSpan(\n\t\t\t\tspan,\n\t\t\t\tstatusCode >= HttpStatusCode.badRequest ? SpanStatus.Error : SpanStatus.Ok\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: TracingRouteProcessor.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"endSpanFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Build the name for the span.\n\t * @param request The incoming request.\n\t * @param route The route to process.\n\t * @returns The span name.\n\t * @internal\n\t */\n\tprivate spanName(request: IHttpServerRequest, route: IBaseRoute | undefined): string {\n\t\tconst routeName = route?.operationId ?? route?.path;\n\n\t\treturn Is.stringValue(routeName)\n\t\t\t? routeName\n\t\t\t: `${request.method ?? \"UNKNOWN\"} ${TracingRouteProcessor.CLASS_NAME}`;\n\t}\n\n\t/**\n\t * Build the attributes describing the request.\n\t * @param request The incoming request.\n\t * @param route The route to process.\n\t * @returns The attributes.\n\t * @internal\n\t */\n\tprivate requestAttributes(\n\t\trequest: IHttpServerRequest,\n\t\troute: IBaseRoute | undefined\n\t): { [key: string]: unknown } {\n\t\tconst attributes: { [key: string]: unknown } = {};\n\n\t\tif (Is.stringValue(request.method)) {\n\t\t\tattributes[\"http.method\"] = request.method;\n\t\t}\n\t\tif (Is.stringValue(route?.path)) {\n\t\t\tattributes[\"http.route\"] = route.path;\n\t\t}\n\n\t\treturn attributes;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tracingRouteProcessor.js","sourceRoot":"","sources":["../../src/tracingRouteProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type {\n\tIBaseRoute,\n\tIBaseRouteProcessor,\n\tIHttpResponse,\n\tIHttpServerRequest\n} from \"@twin.org/api-models\";\nimport type { IContextIds } from \"@twin.org/context\";\nimport { BaseError, Coerce, ComponentFactory, Is } from \"@twin.org/core\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport {\n\tSpanKind,\n\tSpanStatus,\n\tTraceparentHelper,\n\tTracingHelper,\n\ttype ISpan,\n\ttype ITracingComponent\n} from \"@twin.org/tracing-models\";\nimport { HttpStatusCode } from \"@twin.org/web\";\nimport type { ITracingRouteProcessorConstructorOptions } from \"./models/ITracingRouteProcessorConstructorOptions.js\";\n\n/**\n * Process the REST request and record it as a span.\n */\nexport class TracingRouteProcessor implements IBaseRouteProcessor {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<TracingRouteProcessor>();\n\n\t/**\n\t * The key the span is stored under while it is handed between the processor phases.\n\t * @internal\n\t */\n\tprivate static readonly _STATE_KEY: string = \"tracingProcessorSpan\";\n\n\t/**\n\t * The component for recording the spans.\n\t * @internal\n\t */\n\tprivate readonly _tracing?: ITracingComponent;\n\n\t/**\n\t * The component for logging failures.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * Request URL path prefixes to skip tracing, defaults to [\"/tracing\"].\n\t * @internal\n\t */\n\tprivate readonly _excludePaths: string[];\n\n\t/**\n\t * Route operation IDs to skip tracing.\n\t * @internal\n\t */\n\tprivate readonly _excludeOperationIds: string[];\n\n\t/**\n\t * Create a new instance of TracingRouteProcessor.\n\t * @param options Options for the processor.\n\t */\n\tconstructor(options?: ITracingRouteProcessorConstructorOptions) {\n\t\tthis._tracing = ComponentFactory.getIfExists(options?.tracingComponentType);\n\t\tthis._logging = ComponentFactory.getIfExists(options?.loggingComponentType);\n\t\tthis._excludePaths = options?.config?.excludePaths ?? [\"/tracing\"];\n\t\tthis._excludeOperationIds = options?.config?.excludeOperationIds ?? [];\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn TracingRouteProcessor.CLASS_NAME;\n\t}\n\n\t/**\n\t * Pre process the REST request for the specified route, opening the span for the request.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the span has been started.\n\t */\n\tpublic async pre(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tif (Is.empty(this._tracing)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Don't want to run tracing on routes without auth as we have no\n\t\t// tenancy context to record the span against, and it will just create noise in the traces.\n\t\t// this is usually endpoints like /health, /metrics, etc. that are not part of the business logic of the service.\n\t\tif (route?.skipAuth ?? false) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (Is.stringValue(route?.path) && this._excludePaths.includes(route.path)) {\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tIs.stringValue(route?.operationId) &&\n\t\t\tthis._excludeOperationIds.includes(route.operationId)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst parentContext = TraceparentHelper.parse(\n\t\t\tCoerce.string(request.headers?.[TraceparentHelper.HEADER_NAME])\n\t\t);\n\n\t\ttry {\n\t\t\tconst span = await this._tracing.startSpan(this.spanName(request, route), {\n\t\t\t\tkind: SpanKind.Server,\n\t\t\t\tparentContext,\n\t\t\t\tattributes: this.requestAttributes(request, route)\n\t\t\t});\n\n\t\t\tprocessorState[TracingRouteProcessor._STATE_KEY] = span;\n\n\t\t\tObject.assign(contextIds, TracingHelper.spanContextToContextIds(span.context));\n\t\t} catch (err) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: TracingRouteProcessor.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"startSpanFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Post process the REST request for the specified route, ending the span for the request.\n\t * @param request The incoming request.\n\t * @param response The outgoing response.\n\t * @param route The route to process.\n\t * @param contextIds The context IDs of the request.\n\t * @param processorState The state handed through the processors.\n\t * @returns A promise that resolves when the span has been ended.\n\t */\n\tpublic async post(\n\t\trequest: IHttpServerRequest,\n\t\tresponse: IHttpResponse,\n\t\troute: IBaseRoute | undefined,\n\t\tcontextIds: IContextIds,\n\t\tprocessorState: { [id: string]: unknown }\n\t): Promise<void> {\n\t\tconst span = processorState[TracingRouteProcessor._STATE_KEY];\n\n\t\tif (Is.empty(this._tracing) || !Is.object<ISpan>(span)) {\n\t\t\treturn;\n\t\t}\n\n\t\tdelete processorState[TracingRouteProcessor._STATE_KEY];\n\n\t\ttry {\n\t\t\tconst statusCode = Is.number(response.statusCode) ? response.statusCode : HttpStatusCode.ok;\n\n\t\t\tspan.attributes = { ...span.attributes, \"http.status_code\": statusCode };\n\n\t\t\tawait this._tracing.endSpan(\n\t\t\t\tspan,\n\t\t\t\tstatusCode >= HttpStatusCode.badRequest ? SpanStatus.Error : SpanStatus.Ok\n\t\t\t);\n\t\t} catch (err) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: TracingRouteProcessor.CLASS_NAME,\n\t\t\t\tts: Date.now(),\n\t\t\t\tmessage: \"endSpanFailed\",\n\t\t\t\terror: BaseError.fromError(err)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Build the name for the span.\n\t * @param request The incoming request.\n\t * @param route The route to process.\n\t * @returns The span name.\n\t * @internal\n\t */\n\tprivate spanName(request: IHttpServerRequest, route: IBaseRoute | undefined): string {\n\t\tconst routeName = route?.operationId ?? route?.path;\n\n\t\treturn Is.stringValue(routeName)\n\t\t\t? routeName\n\t\t\t: `${request.method ?? \"UNKNOWN\"} ${TracingRouteProcessor.CLASS_NAME}`;\n\t}\n\n\t/**\n\t * Build the attributes describing the request.\n\t * @param request The incoming request.\n\t * @param route The route to process.\n\t * @returns The attributes.\n\t * @internal\n\t */\n\tprivate requestAttributes(\n\t\trequest: IHttpServerRequest,\n\t\troute: IBaseRoute | undefined\n\t): { [key: string]: unknown } {\n\t\tconst attributes: { [key: string]: unknown } = {};\n\n\t\tif (Is.stringValue(request.method)) {\n\t\t\tattributes[\"http.method\"] = request.method;\n\t\t}\n\t\tif (Is.stringValue(route?.path)) {\n\t\t\tattributes[\"http.route\"] = route.path;\n\t\t}\n\n\t\treturn attributes;\n\t}\n}\n"]}
package/docs/changelog.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2-next.6](https://github.com/iotaledger/twin-tracing/compare/tracing-processors-v0.9.2-next.5...tracing-processors-v0.9.2-next.6) (2026-08-19)
4
+
5
+
6
+ ### Features
7
+
8
+ * tenant aware entity storage ([#17](https://github.com/iotaledger/twin-tracing/issues/17)) ([ae11c56](https://github.com/iotaledger/twin-tracing/commit/ae11c56edf472bc1842997600238c2309ef7147d))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/tracing-models bumped from 0.9.2-next.5 to 0.9.2-next.6
16
+
17
+ ## [0.9.2-next.5](https://github.com/iotaledger/twin-tracing/compare/tracing-processors-v0.9.2-next.4...tracing-processors-v0.9.2-next.5) (2026-08-18)
18
+
19
+
20
+ ### Features
21
+
22
+ * missing locale ([9353f8f](https://github.com/iotaledger/twin-tracing/commit/9353f8fb5a25564299616942c21c31715ea23ad3))
23
+ * rename SpanEntity to Span ([5dcca6c](https://github.com/iotaledger/twin-tracing/commit/5dcca6cdb7d24eb047eb9334bd0691b6069df82b))
24
+ * tracing processors ([#9](https://github.com/iotaledger/twin-tracing/issues/9)) ([e5f5d16](https://github.com/iotaledger/twin-tracing/commit/e5f5d1616992440d067830965372d32fc34a939d))
25
+
26
+
27
+ ### Dependencies
28
+
29
+ * The following workspace dependencies were updated
30
+ * dependencies
31
+ * @twin.org/tracing-models bumped from 0.9.2-next.4 to 0.9.2-next.5
32
+
33
+ ## [0.9.2-next.4](https://github.com/iotaledger/twin-tracing/compare/tracing-processors-v0.9.2-next.3...tracing-processors-v0.9.2-next.4) (2026-08-18)
34
+
35
+
36
+ ### Features
37
+
38
+ * rename SpanEntity to Span ([5dcca6c](https://github.com/iotaledger/twin-tracing/commit/5dcca6cdb7d24eb047eb9334bd0691b6069df82b))
39
+
40
+
41
+ ### Dependencies
42
+
43
+ * The following workspace dependencies were updated
44
+ * dependencies
45
+ * @twin.org/tracing-models bumped from 0.9.2-next.3 to 0.9.2-next.4
46
+
3
47
  ## [0.9.2-next.3](https://github.com/iotaledger/twin-tracing/compare/tracing-processors-v0.9.2-next.2...tracing-processors-v0.9.2-next.3) (2026-08-17)
4
48
 
5
49
 
package/locales/en.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "error": {
3
3
  "tracingRouteProcessor": {
4
+ "startSpanFailed": "The span for the request could not be started",
4
5
  "endSpanFailed": "The span for the request could not be ended"
5
6
  }
6
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/tracing-processors",
3
- "version": "0.9.2-next.3",
3
+ "version": "0.9.2-next.6",
4
4
  "description": "Processors which record spans for inbound and outbound REST requests.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "@twin.org/core": "next",
20
20
  "@twin.org/logging-models": "next",
21
21
  "@twin.org/nameof": "next",
22
- "@twin.org/tracing-models": "0.9.2-next.3",
22
+ "@twin.org/tracing-models": "0.9.2-next.6",
23
23
  "@twin.org/web": "next"
24
24
  },
25
25
  "main": "./dist/es/index.js",