nestjs-otel 8.0.4-alpha.8 → 8.1.0

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/README.md CHANGED
@@ -310,7 +310,9 @@ export class BookService {
310
310
 
311
311
  Wide events (also known as canonical log lines) emit one context-rich event per request, with attributes accumulated across the whole request lifecycle. See [A Practitioner's Guide to Wide Events](https://jeremymorrell.dev/blog/a-practitioners-guide-to-wide-events/) for the pattern.
312
312
 
313
- This library implements the pattern on top of OpenTelemetry: the `WideEventInterceptor` opens an attribute bag per request and, when the request finishes, flushes everything onto the span that was active when the request entered the interceptor (usually the root HTTP span created by your instrumentation). The `WideEventService` lets any provider enrich that bag from anywhere in the request's async call chain — no request-scoped injection needed.
313
+ This library implements the pattern on top of OpenTelemetry: the `WideEventInterceptor` opens an attribute bag per request and, when the request finishes, flushes everything onto a single span — marking it with `nestjs_otel.wide_event = true` so you can filter wide-event spans in your backend. The `WideEventService` lets any provider enrich that bag from anywhere in the request's async call chain — no request-scoped injection needed.
314
+
315
+ By default the attributes land on the best-available recording span at flush time (the HTTP root on Express / plain Fastify, or the active handler span when an instrumentation such as `@opentelemetry/instrumentation-nestjs-core` or `@fastify/otel` nests a span per request phase). To guarantee they land on the **trace root span** regardless of instrumentation nesting, register the [`WideEventSpanProcessor`](#targeting-the-root-span-wideeventspanprocessor).
314
316
 
315
317
  1. Register the interceptor globally:
316
318
 
@@ -368,10 +370,11 @@ export class CheckoutService {
368
370
  }
369
371
  ```
370
372
 
371
- All accumulated attributes land on the root span as a single wide event:
373
+ All accumulated attributes land on a single span as one wide event:
372
374
 
373
375
  ```
374
- http_request
376
+ GET /checkout
377
+ ├── nestjs_otel.wide_event: true
375
378
  ├── code.function.name: CheckoutController.checkout
376
379
  ├── user.id: u-123
377
380
  ├── cart.items: 3
@@ -384,7 +387,8 @@ http_request
384
387
 
385
388
  Notes:
386
389
 
387
- - The interceptor seeds `code.function.name` (`<Controller>.<handler>`) automatically and records `error.type`/`error.message` when the handler throws.
390
+ - The flushed span is marked `nestjs_otel.wide_event = true`. This marker is set last, so a handler-provided field can never overwrite it.
391
+ - The interceptor seeds `code.function.name` (`<Controller>.<handler>`) automatically and records `error.type`/`error.message` (plus `error.stack` when present) when the handler throws.
388
392
  - `WideEventService` methods are safe no-ops outside a request handled by the interceptor.
389
393
  - An async-context-aware context manager is required (the default with `NodeSDK` / `AsyncLocalStorageContextManager`), same as for tracing in general.
390
394
 
@@ -425,6 +429,28 @@ export class BooksService {
425
429
  }
426
430
  ```
427
431
 
432
+ ### Targeting the root span (`WideEventSpanProcessor`)
433
+
434
+ Instrumentations like `@opentelemetry/instrumentation-nestjs-core` and `@fastify/otel` wrap each request phase (middleware, guard, handler) in its own span. As a result, the span active when the interceptor runs is a nested child, not the trace root — so by default the wide event lands on that child span (e.g. `AppController.handler`) rather than on the root `GET /route`.
435
+
436
+ Register the `WideEventSpanProcessor` on your `NodeSDK` to make the interceptor flush onto the **local-root span** (the span with no in-process parent — normally the HTTP server span) instead:
437
+
438
+ ```ts
439
+ import { NodeSDK } from '@opentelemetry/sdk-node';
440
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
441
+ import { WideEventSpanProcessor } from 'nestjs-otel';
442
+
443
+ export const otelSDK = new NodeSDK({
444
+ spanProcessors: [
445
+ new WideEventSpanProcessor(),
446
+ new BatchSpanProcessor(traceExporter),
447
+ ],
448
+ // ...instrumentations, contextManager, etc.
449
+ });
450
+ ```
451
+
452
+ The processor only tracks which span is the local root per trace; it never exports spans, so keep your existing exporting processor in the list. It is optional — without it, wide events still flush onto the best-available recording span (see above), just not necessarily the trace root.
453
+
428
454
  ## Metric Service
429
455
 
430
456
  [OpenTelemetry Metrics](https://www.npmjs.com/package/@opentelemetry/api) allow a user to collect data and export it to metrics backend like Prometheus.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-otel",
3
- "version": "8.0.4-alpha.8",
3
+ "version": "8.1.0",
4
4
  "description": "NestJS OpenTelemetry Library",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index",
@@ -1,24 +0,0 @@
1
- {
2
- "hooks": {
3
- "PreToolUse": [
4
- {
5
- "matcher": "Bash",
6
- "hooks": [
7
- {
8
- "type": "command",
9
- "command": "CMD=$(python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('tool_input',d).get('command',''))\" 2>/dev/null || true); case \"$CMD\" in *grep*|*rg\\ *|*ripgrep*|*find\\ *|*fd\\ *|*ack\\ *|*ag\\ *) [ -f graphify-out/graph.json ] && echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"MANDATORY: graphify-out/graph.json exists. You MUST run `graphify query \\\"<question>\\\"` before grepping raw files. Only grep after graphify has oriented you, or to modify/debug specific lines.\"}}' || true ;; esac"
10
- }
11
- ]
12
- },
13
- {
14
- "matcher": "Read|Glob",
15
- "hooks": [
16
- {
17
- "type": "command",
18
- "command": "HIT=$(python3 -c \"import json,sys;d=json.load(sys.stdin);t=d.get('tool_input',d);s=(str(t.get('file_path') or '')+' '+str(t.get('pattern') or '')+' '+str(t.get('path') or '')).lower().replace(chr(92),'/');exts=('.py','.js','.ts','.tsx','.jsx','.go','.rs','.java','.rb','.c','.h','.cpp','.hpp','.cc','.cs','.kt','.swift','.php','.scala','.lua','.sh','.md','.rst','.txt','.mdx');sys.stdout.write('1' if 'graphify-out/' not in s and any(e in s for e in exts) else '')\" 2>/dev/null || true); if [ \"$HIT\" = 1 ] && [ -f graphify-out/graph.json ]; then echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"additionalContext\":\"MANDATORY: graphify-out/graph.json exists. You MUST run graphify before reading source files. Use: `graphify query \\\"<question>\\\"` (scoped subgraph), `graphify explain \\\"<concept>\\\"`, or `graphify path \\\"<A>\\\" \\\"<B>\\\"`. Only read raw files after graphify has oriented you, or to modify/debug specific lines. This rule applies to subagents too \u2014 include it in every subagent prompt involving code exploration.\"}}'; fi || true"
19
- }
20
- ]
21
- }
22
- ]
23
- }
24
- }
@@ -1,20 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(cp tests/e2e/wide-events/wide-event.spec.ts /tmp/we.bak)",
5
- "Bash(cp /tmp/we.bak tests/e2e/wide-events/wide-event.spec.ts)",
6
- "Bash(graphify query *)",
7
- "Bash(rtk npm *)",
8
- "Bash(rtk jest *)",
9
- "WebFetch(domain:jeremymorrell.dev)",
10
- "WebFetch(domain:boristane.com)",
11
- "WebFetch(domain:isburmistrov.substack.com)",
12
- "Bash(rtk npx *)",
13
- "Bash(rtk ls *)",
14
- "Bash(rtk grep *)",
15
- "Bash(rtk read *)",
16
- "Bash(cd /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel-prom-grafana-tempo)",
17
- "Bash(rtk find *)"
18
- ]
19
- }
20
- }
package/.tmp_build.out DELETED
@@ -1,3 +0,0 @@
1
- go: cannot find main module, but found .git/config in /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel
2
- to create a module there, run:
3
- go mod init
@@ -1,12 +0,0 @@
1
- {
2
- "0": "Metric Decorators & Data",
3
- "1": "Span Decorator & Tests",
4
- "2": "Module Bootstrap & Tracing",
5
- "3": "Wide Events",
6
- "4": "Traceable Decorator",
7
- "5": "Counter Decorators & Metadata Utils",
8
- "6": "Test Service Fixtures",
9
- "7": "Metric Injection Tokens",
10
- "8": "Baggage Decorator",
11
- "9": "CurrentSpan Decorator"
12
- }
@@ -1 +0,0 @@
1
- /Users/pragmaticivan/.local/share/mise/installs/python/3.14.6/bin/python3.14
@@ -1 +0,0 @@
1
- /Users/pragmaticivan/Code/pragmaticivan/nestjs-otel
@@ -1,89 +0,0 @@
1
- # Graph Report - . (2026-06-15)
2
-
3
- ## Corpus Check
4
- - Corpus is ~6,192 words - fits in a single context window. You may not need a graph.
5
-
6
- ## Summary
7
- - 157 nodes · 286 edges · 10 communities (6 shown, 4 thin omitted)
8
- - Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
9
- - Token cost: 0 input · 0 output
10
-
11
- ## Community Hubs (Navigation)
12
- - [[_COMMUNITY_Metric Decorators & Data|Metric Decorators & Data]]
13
- - [[_COMMUNITY_Span Decorator & Tests|Span Decorator & Tests]]
14
- - [[_COMMUNITY_Module Bootstrap & Tracing|Module Bootstrap & Tracing]]
15
- - [[_COMMUNITY_Wide Events|Wide Events]]
16
- - [[_COMMUNITY_Traceable Decorator|Traceable Decorator]]
17
- - [[_COMMUNITY_Counter Decorators & Metadata Utils|Counter Decorators & Metadata Utils]]
18
- - [[_COMMUNITY_Test Service Fixtures|Test Service Fixtures]]
19
- - [[_COMMUNITY_Metric Injection Tokens|Metric Injection Tokens]]
20
- - [[_COMMUNITY_Baggage Decorator|Baggage Decorator]]
21
- - [[_COMMUNITY_CurrentSpan Decorator|CurrentSpan Decorator]]
22
-
23
- ## God Nodes (most connected - your core abstractions)
24
- 1. `TestSpan` - 17 edges
25
- 2. `OtelMetricOptions` - 12 edges
26
- 3. `OpenTelemetryModuleOptions` - 9 edges
27
- 4. `MetricService` - 9 edges
28
- 5. `getOrCreate()` - 8 edges
29
- 6. `OpenTelemetryCoreModule` - 8 edges
30
- 7. `WideEventService` - 8 edges
31
- 8. `OpenTelemetryModuleAsyncOptions` - 7 edges
32
- 9. `TestService` - 7 edges
33
- 10. `getWideEventBag()` - 7 edges
34
-
35
- ## Surprising Connections (you probably didn't know these)
36
- - `InjectMetric()` --calls--> `getToken()` [EXTRACTED]
37
- src/metrics/injector.ts → src/metrics/utils.ts
38
- - `record()` --calls--> `getWideEventBag()` [EXTRACTED]
39
- src/wide-events/wide-event-field.decorator.ts → src/wide-events/wide-event.context.ts
40
-
41
- ## Import Cycles
42
- - None detected.
43
-
44
- ## Communities (10 total, 4 thin omitted)
45
-
46
- ### Community 0 - "Metric Decorators & Data"
47
- Cohesion: 0.14
48
- Nodes (21): MetricParamDecorator, OtelCounter, OtelGauge, OtelHistogram, OtelObservableCounter, OtelObservableGauge, OtelObservableUpDownCounter, OtelUpDownCounter (+13 more)
49
-
50
- ### Community 1 - "Span Decorator & Tests"
51
- Cohesion: 0.08
52
- Nodes (10): ExtendedSpanOptions, handleOnResult(), recordException(), Span(), SpanDecoratorOptions, LazyThenable, makeFailingLazyThenable(), makeLazyThenable() (+2 more)
53
-
54
- ### Community 2 - "Module Bootstrap & Tracing"
55
- Cohesion: 0.15
56
- Nodes (8): OpenTelemetryMetrics, OpenTelemetryModuleAsyncOptions, OpenTelemetryModuleOptions, OpenTelemetryOptionsFactory, OpenTelemetryWideEvents, OpenTelemetryCoreModule, OpenTelemetryModule, TraceService
57
-
58
- ### Community 3 - "Wide Events"
59
- Cohesion: 0.13
60
- Nodes (10): getWideEventBag(), WIDE_EVENT_CONTEXT_KEY, WideEventBag, isAttributeValue(), record(), WideEventField(), CatsController, executionContext (+2 more)
61
-
62
- ### Community 4 - "Traceable Decorator"
63
- Cohesion: 0.22
64
- Nodes (3): TestTraceable, TestTraceableWithOptions, Traceable()
65
-
66
- ### Community 5 - "Counter Decorators & Metadata Utils"
67
- Cohesion: 0.27
68
- Nodes (4): OtelInstanceCounter(), OtelMethodCounter(), TestClass, copyMetadataFromFunctionToFunction()
69
-
70
- ## Knowledge Gaps
71
- - **20 isolated node(s):** `OpenTelemetryMetrics`, `OpenTelemetryWideEvents`, `MetricParamDecorator`, `OtelCounter`, `OtelUpDownCounter` (+15 more)
72
- These have ≤1 connection - possible missing edges or undocumented components.
73
- - **4 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
74
-
75
- ## Suggested Questions
76
- _Questions this graph is uniquely positioned to answer:_
77
-
78
- - **Why does `TestService` connect `Test Service Fixtures` to `Wide Events`?**
79
- _High betweenness centrality (0.076) - this node is a cross-community bridge._
80
- - **What connects `OpenTelemetryMetrics`, `OpenTelemetryWideEvents`, `MetricParamDecorator` to the rest of the system?**
81
- _20 weakly-connected nodes found - possible documentation gaps or missing edges._
82
- - **Should `Metric Decorators & Data` be split into smaller, more focused modules?**
83
- _Cohesion score 0.13636363636363635 - nodes in this community are weakly interconnected._
84
- - **Should `Span Decorator & Tests` be split into smaller, more focused modules?**
85
- _Cohesion score 0.08374384236453201 - nodes in this community are weakly interconnected._
86
- - **Should `Module Bootstrap & Tracing` be split into smaller, more focused modules?**
87
- _Cohesion score 0.14814814814814814 - nodes in this community are weakly interconnected._
88
- - **Should `Wide Events` be split into smaller, more focused modules?**
89
- _Cohesion score 0.1282051282051282 - nodes in this community are weakly interconnected._
@@ -1,12 +0,0 @@
1
- {
2
- "runs": [
3
- {
4
- "date": "2026-06-16T02:18:50.326220+00:00",
5
- "input_tokens": 0,
6
- "output_tokens": 0,
7
- "files": 33
8
- }
9
- ],
10
- "total_input_tokens": 0,
11
- "total_output_tokens": 0
12
- }