@ddd-ts-kit/tracing 0.0.0-stage → 0.1.1

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.
Files changed (262) hide show
  1. package/README.md +400 -2
  2. package/dist/application/trace-application.d.ts +39 -0
  3. package/dist/application/trace-application.d.ts.map +1 -0
  4. package/dist/application/trace-application.js +31 -0
  5. package/dist/application/trace-application.js.map +1 -0
  6. package/dist/configuration/configure-tracing.d.ts +9 -0
  7. package/dist/configuration/configure-tracing.d.ts.map +1 -0
  8. package/dist/configuration/configure-tracing.js +21 -0
  9. package/dist/configuration/configure-tracing.js.map +1 -0
  10. package/dist/configuration/default-lifecycle-hooks.d.ts +2 -0
  11. package/dist/configuration/default-lifecycle-hooks.d.ts.map +1 -0
  12. package/dist/configuration/default-lifecycle-hooks.js +8 -0
  13. package/dist/configuration/default-lifecycle-hooks.js.map +1 -0
  14. package/dist/configuration/tracing-configuration.d.ts +16 -0
  15. package/dist/configuration/tracing-configuration.d.ts.map +1 -0
  16. package/dist/configuration/tracing-configuration.js +2 -0
  17. package/dist/configuration/tracing-configuration.js.map +1 -0
  18. package/dist/correlation/correlation-id-from-active-span.d.ts +6 -0
  19. package/dist/correlation/correlation-id-from-active-span.d.ts.map +1 -0
  20. package/dist/correlation/correlation-id-from-active-span.js +10 -0
  21. package/dist/correlation/correlation-id-from-active-span.js.map +1 -0
  22. package/dist/correlation/trace-log-fields.d.ts +13 -0
  23. package/dist/correlation/trace-log-fields.d.ts.map +1 -0
  24. package/dist/correlation/trace-log-fields.js +20 -0
  25. package/dist/correlation/trace-log-fields.js.map +1 -0
  26. package/dist/correlation/traced-error-reporter.d.ts +9 -0
  27. package/dist/correlation/traced-error-reporter.d.ts.map +1 -0
  28. package/dist/correlation/traced-error-reporter.js +23 -0
  29. package/dist/correlation/traced-error-reporter.js.map +1 -0
  30. package/dist/dependency/method.d.ts +2 -0
  31. package/dist/dependency/method.d.ts.map +1 -0
  32. package/dist/dependency/method.js +2 -0
  33. package/dist/dependency/method.js.map +1 -0
  34. package/dist/dependency/proxied.d.ts +8 -0
  35. package/dist/dependency/proxied.d.ts.map +1 -0
  36. package/dist/dependency/proxied.js +21 -0
  37. package/dist/dependency/proxied.js.map +1 -0
  38. package/dist/dependency/shadowed.d.ts +4 -0
  39. package/dist/dependency/shadowed.d.ts.map +1 -0
  40. package/dist/dependency/shadowed.js +14 -0
  41. package/dist/dependency/shadowed.js.map +1 -0
  42. package/dist/dependency/trace-bindings.d.ts +25 -0
  43. package/dist/dependency/trace-bindings.d.ts.map +1 -0
  44. package/dist/dependency/trace-bindings.js +35 -0
  45. package/dist/dependency/trace-bindings.js.map +1 -0
  46. package/dist/dependency/trace-dependency.d.ts +26 -0
  47. package/dist/dependency/trace-dependency.d.ts.map +1 -0
  48. package/dist/dependency/trace-dependency.js +56 -0
  49. package/dist/dependency/trace-dependency.js.map +1 -0
  50. package/dist/dependency/traceable-methods.d.ts +4 -0
  51. package/dist/dependency/traceable-methods.d.ts.map +1 -0
  52. package/dist/dependency/traceable-methods.js +46 -0
  53. package/dist/dependency/traceable-methods.js.map +1 -0
  54. package/dist/dependency/traced.d.ts +5 -0
  55. package/dist/dependency/traced.d.ts.map +1 -0
  56. package/dist/dependency/traced.js +56 -0
  57. package/dist/dependency/traced.js.map +1 -0
  58. package/dist/domain-events/traced-domain-events.d.ts +14 -0
  59. package/dist/domain-events/traced-domain-events.d.ts.map +1 -0
  60. package/dist/domain-events/traced-domain-events.js +49 -0
  61. package/dist/domain-events/traced-domain-events.js.map +1 -0
  62. package/dist/events/consumer-trace.d.ts +8 -0
  63. package/dist/events/consumer-trace.d.ts.map +1 -0
  64. package/dist/events/consumer-trace.js +2 -0
  65. package/dist/events/consumer-trace.js.map +1 -0
  66. package/dist/events/messaging-attributes.d.ts +12 -0
  67. package/dist/events/messaging-attributes.d.ts.map +1 -0
  68. package/dist/events/messaging-attributes.js +11 -0
  69. package/dist/events/messaging-attributes.js.map +1 -0
  70. package/dist/events/traced-consumer.d.ts +22 -0
  71. package/dist/events/traced-consumer.d.ts.map +1 -0
  72. package/dist/events/traced-consumer.js +65 -0
  73. package/dist/events/traced-consumer.js.map +1 -0
  74. package/dist/events/traced-publisher.d.ts +19 -0
  75. package/dist/events/traced-publisher.d.ts.map +1 -0
  76. package/dist/events/traced-publisher.js +69 -0
  77. package/dist/events/traced-publisher.js.map +1 -0
  78. package/dist/events/traced-subscriber.d.ts +15 -0
  79. package/dist/events/traced-subscriber.d.ts.map +1 -0
  80. package/dist/events/traced-subscriber.js +19 -0
  81. package/dist/events/traced-subscriber.js.map +1 -0
  82. package/dist/http/is-noise-path.d.ts +3 -0
  83. package/dist/http/is-noise-path.d.ts.map +1 -0
  84. package/dist/http/is-noise-path.js +7 -0
  85. package/dist/http/is-noise-path.js.map +1 -0
  86. package/dist/http/is-noise-route.d.ts +6 -0
  87. package/dist/http/is-noise-route.d.ts.map +1 -0
  88. package/dist/http/is-noise-route.js +7 -0
  89. package/dist/http/is-noise-route.js.map +1 -0
  90. package/dist/http/noise-paths.d.ts +3 -0
  91. package/dist/http/noise-paths.d.ts.map +1 -0
  92. package/dist/http/noise-paths.js +15 -0
  93. package/dist/http/noise-paths.js.map +1 -0
  94. package/dist/http/traced-routes.d.ts +28 -0
  95. package/dist/http/traced-routes.d.ts.map +1 -0
  96. package/dist/http/traced-routes.js +76 -0
  97. package/dist/http/traced-routes.js.map +1 -0
  98. package/dist/index.d.ts +36 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +32 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/jobs/traced-job.d.ts +16 -0
  103. package/dist/jobs/traced-job.d.ts.map +1 -0
  104. package/dist/jobs/traced-job.js +25 -0
  105. package/dist/jobs/traced-job.js.map +1 -0
  106. package/dist/local-root/admit-dependency-span.d.ts +7 -0
  107. package/dist/local-root/admit-dependency-span.d.ts.map +1 -0
  108. package/dist/local-root/admit-dependency-span.js +30 -0
  109. package/dist/local-root/admit-dependency-span.js.map +1 -0
  110. package/dist/local-root/correlate.d.ts +9 -0
  111. package/dist/local-root/correlate.d.ts.map +1 -0
  112. package/dist/local-root/correlate.js +23 -0
  113. package/dist/local-root/correlate.js.map +1 -0
  114. package/dist/local-root/local-root-tracker.d.ts +18 -0
  115. package/dist/local-root/local-root-tracker.d.ts.map +1 -0
  116. package/dist/local-root/local-root-tracker.js +26 -0
  117. package/dist/local-root/local-root-tracker.js.map +1 -0
  118. package/dist/local-root/local-root.d.ts +11 -0
  119. package/dist/local-root/local-root.d.ts.map +1 -0
  120. package/dist/local-root/local-root.js +2 -0
  121. package/dist/local-root/local-root.js.map +1 -0
  122. package/dist/local-root/local-roots.d.ts +4 -0
  123. package/dist/local-root/local-roots.d.ts.map +1 -0
  124. package/dist/local-root/local-roots.js +4 -0
  125. package/dist/local-root/local-roots.js.map +1 -0
  126. package/dist/local-root/work-attributes.d.ts +4 -0
  127. package/dist/local-root/work-attributes.d.ts.map +1 -0
  128. package/dist/local-root/work-attributes.js +11 -0
  129. package/dist/local-root/work-attributes.js.map +1 -0
  130. package/dist/node/default-instrumentations.d.ts +20 -0
  131. package/dist/node/default-instrumentations.d.ts.map +1 -0
  132. package/dist/node/default-instrumentations.js +55 -0
  133. package/dist/node/default-instrumentations.js.map +1 -0
  134. package/dist/node/init-tracing.d.ts +41 -0
  135. package/dist/node/init-tracing.d.ts.map +1 -0
  136. package/dist/node/init-tracing.js +71 -0
  137. package/dist/node/init-tracing.js.map +1 -0
  138. package/dist/node/instrumentations-off.d.ts +6 -0
  139. package/dist/node/instrumentations-off.d.ts.map +1 -0
  140. package/dist/node/instrumentations-off.js +39 -0
  141. package/dist/node/instrumentations-off.js.map +1 -0
  142. package/dist/node.d.ts +3 -0
  143. package/dist/node.d.ts.map +1 -0
  144. package/dist/node.js +3 -0
  145. package/dist/node.js.map +1 -0
  146. package/dist/processors/attribute-allow-list.d.ts +25 -0
  147. package/dist/processors/attribute-allow-list.d.ts.map +1 -0
  148. package/dist/processors/attribute-allow-list.js +38 -0
  149. package/dist/processors/attribute-allow-list.js.map +1 -0
  150. package/dist/processors/default-allowed-attributes.d.ts +8 -0
  151. package/dist/processors/default-allowed-attributes.d.ts.map +1 -0
  152. package/dist/processors/default-allowed-attributes.js +48 -0
  153. package/dist/processors/default-allowed-attributes.js.map +1 -0
  154. package/dist/processors/ended-span.d.ts +19 -0
  155. package/dist/processors/ended-span.d.ts.map +1 -0
  156. package/dist/processors/ended-span.js +2 -0
  157. package/dist/processors/ended-span.js.map +1 -0
  158. package/dist/processors/error-message-scrubber.d.ts +13 -0
  159. package/dist/processors/error-message-scrubber.d.ts.map +1 -0
  160. package/dist/processors/error-message-scrubber.js +21 -0
  161. package/dist/processors/error-message-scrubber.js.map +1 -0
  162. package/dist/processors/url-secret-span-processor.d.ts +12 -0
  163. package/dist/processors/url-secret-span-processor.d.ts.map +1 -0
  164. package/dist/processors/url-secret-span-processor.js +47 -0
  165. package/dist/processors/url-secret-span-processor.js.map +1 -0
  166. package/dist/propagation/capture-traceparent.d.ts +3 -0
  167. package/dist/propagation/capture-traceparent.d.ts.map +1 -0
  168. package/dist/propagation/capture-traceparent.js +7 -0
  169. package/dist/propagation/capture-traceparent.js.map +1 -0
  170. package/dist/propagation/context-with-traceparent.d.ts +4 -0
  171. package/dist/propagation/context-with-traceparent.d.ts.map +1 -0
  172. package/dist/propagation/context-with-traceparent.js +8 -0
  173. package/dist/propagation/context-with-traceparent.js.map +1 -0
  174. package/dist/propagation/run-with-traceparent.d.ts +6 -0
  175. package/dist/propagation/run-with-traceparent.d.ts.map +1 -0
  176. package/dist/propagation/run-with-traceparent.js +10 -0
  177. package/dist/propagation/run-with-traceparent.js.map +1 -0
  178. package/dist/propagation/span-context-from-traceparent.d.ts +4 -0
  179. package/dist/propagation/span-context-from-traceparent.d.ts.map +1 -0
  180. package/dist/propagation/span-context-from-traceparent.js +23 -0
  181. package/dist/propagation/span-context-from-traceparent.js.map +1 -0
  182. package/dist/propagation/traceparent-from-context.d.ts +4 -0
  183. package/dist/propagation/traceparent-from-context.d.ts.map +1 -0
  184. package/dist/propagation/traceparent-from-context.js +11 -0
  185. package/dist/propagation/traceparent-from-context.js.map +1 -0
  186. package/dist/span/constructor-name.d.ts +3 -0
  187. package/dist/span/constructor-name.d.ts.map +1 -0
  188. package/dist/span/constructor-name.js +9 -0
  189. package/dist/span/constructor-name.js.map +1 -0
  190. package/dist/span/failed-traces.d.ts +2 -0
  191. package/dist/span/failed-traces.d.ts.map +1 -0
  192. package/dist/span/failed-traces.js +3 -0
  193. package/dist/span/failed-traces.js.map +1 -0
  194. package/dist/span/in-span.d.ts +12 -0
  195. package/dist/span/in-span.d.ts.map +1 -0
  196. package/dist/span/in-span.js +19 -0
  197. package/dist/span/in-span.js.map +1 -0
  198. package/dist/span/is-thenable.d.ts +2 -0
  199. package/dist/span/is-thenable.d.ts.map +1 -0
  200. package/dist/span/is-thenable.js +7 -0
  201. package/dist/span/is-thenable.js.map +1 -0
  202. package/dist/span/mark-span-failed.d.ts +4 -0
  203. package/dist/span/mark-span-failed.d.ts.map +1 -0
  204. package/dist/span/mark-span-failed.js +36 -0
  205. package/dist/span/mark-span-failed.js.map +1 -0
  206. package/dist/span/outcome-attribute.d.ts +3 -0
  207. package/dist/span/outcome-attribute.d.ts.map +1 -0
  208. package/dist/span/outcome-attribute.js +3 -0
  209. package/dist/span/outcome-attribute.js.map +1 -0
  210. package/dist/span/refusal-code.d.ts +4 -0
  211. package/dist/span/refusal-code.d.ts.map +1 -0
  212. package/dist/span/refusal-code.js +10 -0
  213. package/dist/span/refusal-code.js.map +1 -0
  214. package/dist/span/tracer.d.ts +4 -0
  215. package/dist/span/tracer.d.ts.map +1 -0
  216. package/dist/span/tracer.js +7 -0
  217. package/dist/span/tracer.js.map +1 -0
  218. package/dist/span/with-span.d.ts +9 -0
  219. package/dist/span/with-span.d.ts.map +1 -0
  220. package/dist/span/with-span.js +36 -0
  221. package/dist/span/with-span.js.map +1 -0
  222. package/dist/testing/expect-span-tree.d.ts +10 -0
  223. package/dist/testing/expect-span-tree.d.ts.map +1 -0
  224. package/dist/testing/expect-span-tree.js +29 -0
  225. package/dist/testing/expect-span-tree.js.map +1 -0
  226. package/dist/testing/expected-span.d.ts +17 -0
  227. package/dist/testing/expected-span.d.ts.map +1 -0
  228. package/dist/testing/expected-span.js +2 -0
  229. package/dist/testing/expected-span.js.map +1 -0
  230. package/dist/testing/install-test-tracing.d.ts +23 -0
  231. package/dist/testing/install-test-tracing.d.ts.map +1 -0
  232. package/dist/testing/install-test-tracing.js +48 -0
  233. package/dist/testing/install-test-tracing.js.map +1 -0
  234. package/dist/testing/render-span-tree.d.ts +8 -0
  235. package/dist/testing/render-span-tree.d.ts.map +1 -0
  236. package/dist/testing/render-span-tree.js +23 -0
  237. package/dist/testing/render-span-tree.js.map +1 -0
  238. package/dist/testing/span-mismatches.d.ts +5 -0
  239. package/dist/testing/span-mismatches.d.ts.map +1 -0
  240. package/dist/testing/span-mismatches.js +34 -0
  241. package/dist/testing/span-mismatches.js.map +1 -0
  242. package/dist/testing/span-node.d.ts +13 -0
  243. package/dist/testing/span-node.d.ts.map +1 -0
  244. package/dist/testing/span-node.js +2 -0
  245. package/dist/testing/span-node.js.map +1 -0
  246. package/dist/testing/span-tree-of.d.ts +8 -0
  247. package/dist/testing/span-tree-of.d.ts.map +1 -0
  248. package/dist/testing/span-tree-of.js +34 -0
  249. package/dist/testing/span-tree-of.js.map +1 -0
  250. package/dist/testing/start-order.d.ts +12 -0
  251. package/dist/testing/start-order.d.ts.map +1 -0
  252. package/dist/testing/start-order.js +17 -0
  253. package/dist/testing/start-order.js.map +1 -0
  254. package/dist/testing.d.ts +6 -0
  255. package/dist/testing.d.ts.map +1 -0
  256. package/dist/testing.js +4 -0
  257. package/dist/testing.js.map +1 -0
  258. package/dist/use-case/trace-use-cases.d.ts +10 -0
  259. package/dist/use-case/trace-use-cases.d.ts.map +1 -0
  260. package/dist/use-case/trace-use-cases.js +41 -0
  261. package/dist/use-case/trace-use-cases.js.map +1 -0
  262. package/package.json +75 -4
package/README.md CHANGED
@@ -1,3 +1,401 @@
1
- # Temporary Holding Version
1
+ # @ddd-ts-kit/tracing
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ OpenTelemetry tracing that the domain never sees. Nothing in `domain/` or `application/` imports
4
+ this package: use cases, adapters, events and jobs are wrapped where they are wired, in the
5
+ composition root, and one user action reads as one trace.
6
+
7
+ ```
8
+ npm i @ddd-ts-kit/tracing @opentelemetry/api
9
+ # for the Node bootstrap (@ddd-ts-kit/tracing/node)
10
+ npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http
11
+ # for the test harness (@ddd-ts-kit/tracing/testing)
12
+ npm i -D @opentelemetry/sdk-trace-base @opentelemetry/context-async-hooks
13
+ ```
14
+
15
+ ## Setup
16
+
17
+ ```ts
18
+ // platform/tracing.ts: imported before everything else
19
+ import { initTracing } from '@ddd-ts-kit/tracing/node';
20
+
21
+ export const tracing = initTracing({ serviceName: 'clinic-api' });
22
+ ```
23
+
24
+ ```ts
25
+ // main.ts
26
+ import { tracing } from '@/platform/tracing.js'; // first import, see "Load order"
27
+ import { createApplication, kernelModule } from '@ddd-ts-kit/di';
28
+ import { traceApplication, tracedPublisher, tracedRoutes } from '@ddd-ts-kit/tracing';
29
+
30
+ const app = createApplication({
31
+ modules: [kernelModule({ instrumentation: traceApplication(), eventPublisher: outbox }), schedulingModule, notificationModule],
32
+ });
33
+
34
+ mountExpress(server, routes, { executionScope: app.resolve(ExecutionScope), around: tracedRoutes() });
35
+ new OutboxRelay({ store, publisher: tracedPublisher(bus), clock }).start({ intervalMs: 1_000 });
36
+
37
+ await app.start();
38
+ process.on('SIGTERM', () => void app.stop().then(() => tracing.shutdown()));
39
+ ```
40
+
41
+ That is all: `traceApplication()` is the value `kernelModule` wraps the application with, and
42
+ no class knows about it. Three places stay outside of the kernel module and take one wrapper
43
+ each: the HTTP pipeline (`around: tracedRoutes()`), the publisher an outbox relay sends with
44
+ (`tracedPublisher(bus)`), and a project that builds its own tracer provider (see "Span
45
+ processors").
46
+
47
+ ### Jaeger, in five lines
48
+
49
+ Jaeger v2 receives OTLP natively, so there is nothing between the application and Jaeger:
50
+
51
+ ```yaml
52
+ services:
53
+ jaeger:
54
+ image: jaegertracing/jaeger:2.22.0
55
+ profiles: [observability]
56
+ ports: ['127.0.0.1:16686:16686', '127.0.0.1:4318:4318']
57
+ ```
58
+
59
+ ```
60
+ docker compose --profile observability up -d jaeger
61
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 node dist/main.js
62
+ ```
63
+
64
+ `initTracing({ serviceName })` needs nothing else: the endpoint comes from the standard
65
+ `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) variable, and without
66
+ either nothing is exported. Open `http://127.0.0.1:16686` and search the service.
67
+
68
+ ### Load order
69
+
70
+ Auto-instrumentation patches `http` and the database drivers when they are loaded, so
71
+ `initTracing` must have run before. In an ES module the static imports are evaluated before the
72
+ first statement of the file: keep the call in its own module and import that module first (as
73
+ above), or preload it with `node --import ./dist/platform/tracing.js`. Packages published as ES
74
+ modules also need the `@opentelemetry/instrumentation` loader hook.
75
+
76
+ ## What a trace looks like
77
+
78
+ One request that books an appointment, announces it to two other bounded contexts through the
79
+ outbox, and is published by the relay a moment later, from a timer and outside of any request:
80
+
81
+ ```
82
+ POST /appointments SERVER scheduling
83
+ command BookAppointment INTERNAL
84
+ repository AppointmentRepository.findOverlapping INTERNAL
85
+ repository AppointmentRepository.save INTERNAL
86
+ unit-of-work before-commit INTERNAL after every save, before the commit
87
+ domain-event scheduling.appointment.booked INTERNAL
88
+ handler AppointmentBookedPublisher INTERNAL
89
+ outbox append scheduling.appointment.booked INTERNAL (the response is sent once this is committed)
90
+ publish scheduling.appointment.booked PRODUCER by the relay, later, same trace
91
+ process notification.appointment-booked CONSUMER notification
92
+ command SendBookingConfirmation INTERNAL
93
+ external MailGateway.send INTERNAL
94
+ process analytics.appointment-booked CONSUMER analytics
95
+ command RecordBooking INTERNAL
96
+ repository BookingRepository.save INTERNAL
97
+ ```
98
+
99
+ This is the tree as produced, asserted span for span by
100
+ `test/scenarios/one-action-one-trace.spec.ts` and, against a real Jaeger, by
101
+ `test/jaeger/real-jaeger.spec.ts`. A scheduled job is a trace of its own:
102
+
103
+ ```
104
+ job scheduling.send-reminders INTERNAL (root)
105
+ command SendReminders INTERNAL
106
+ repository AppointmentRepository.findOverlapping INTERNAL
107
+ ```
108
+
109
+ ### The phases of a unit of work
110
+
111
+ The handlers of domain events are not under the save that recorded the event: the unit of work
112
+ dispatches them once the command handler has returned. Under the span of the command, in order:
113
+
114
+ ```
115
+ command SendParcels INTERNAL
116
+ repository ParcelRepository.save INTERNAL
117
+ repository ParcelRepository.save INTERNAL
118
+ unit-of-work before-commit INTERNAL the events of both saves
119
+ domain-event shipping.parcel.sent INTERNAL
120
+ repository ParcelRepository.save INTERNAL a handler saves another aggregate
121
+ domain-event shipping.parcel.sent INTERNAL
122
+ unit-of-work before-commit INTERNAL what the handlers recorded in turn
123
+ domain-event shipping.parcel.tracked INTERNAL
124
+ unit-of-work after-commit INTERNAL once committed, same trace
125
+ domain-event shipping.parcel.sent INTERNAL
126
+ domain-event shipping.parcel.sent INTERNAL
127
+ ```
128
+
129
+ - `unit-of-work before-commit` follows the save spans. What the handlers recorded in turn is
130
+ under a second span of the same name.
131
+ - `unit-of-work after-commit` is there when an event has a handler of that phase, and holds
132
+ only those events. It is a child of the command span, and stays in the trace when its
133
+ handler fails: the command span still says `succeeded`.
134
+ - A handler declared with a kind (`@DomainEventHandler`) is a `handler <ClassName>` span under
135
+ its `domain-event` span; a callback attached with `dispatcher.on` has none.
136
+ - Outside any unit of work (a repository called alone) the `domain-event` spans stay under the
137
+ save span that dispatched them, without a phase span.
138
+
139
+ This tree is asserted by `test/unit-of-work-phases.spec.ts`.
140
+
141
+ ### Span names, kinds and attributes
142
+
143
+ | Span | Kind | Made by | Attributes |
144
+ |---|---|---|---|
145
+ | `<METHOD> <route template>` | SERVER | `tracedRoutes()` (renames the span an HTTP instrumentation opened, or starts one) | `http.route`, `http.request.method`, `http.response.status_code`, `app.operation`, `app.correlation_id`, `app.origin`, `app.outcome`, `app.error.code` |
146
+ | `command <Name>`, `query <Name>` | INTERNAL | `traceUseCases()`, a bus middleware | `app.use_case.kind`, `app.use_case.name`, `app.context`, `app.outcome`, `app.error.code`, `error.type` |
147
+ | `<kind> <Port>.<method>`: `repository`, `gateway`, `external`, `publisher` | INTERNAL | `traceBindings()` | `code.namespace` (the adapter class), `code.function`, `app.dependency.kind`, `app.context` |
148
+ | `unit-of-work before-commit` | INTERNAL | `tracedDomainEvents()`, around each round of events dispatched at the end of a unit of work, before the commit | none |
149
+ | `unit-of-work after-commit` | INTERNAL | `tracedDomainEvents()`, around the events that have an `after-commit` handler, once the unit of work has committed | none |
150
+ | `domain-event <name>` | INTERNAL | `tracedDomainEvents()` | `app.domain_event.name` |
151
+ | `handler <ClassName>` | INTERNAL | `traceBindings()`, kind `domain-event-handler` | `code.namespace`, `code.function`, `app.dependency.kind`, `app.context` |
152
+ | `outbox append <type>` | INTERNAL | `tracedPublisher()` around a publisher that stores | `messaging.system` (`outbox`), `messaging.operation.name` (`append`), `messaging.operation.type` (`create`), `messaging.destination.name`, `messaging.message.id` |
153
+ | `publish <type>` | PRODUCER | `tracedPublisher()` around a bus | `messaging.system`, `messaging.operation.name` (`publish`), `messaging.operation.type` (`send`), `messaging.destination.name`, `messaging.message.id` |
154
+ | `process <consumer>` | CONSUMER | `tracedConsumer()`, one per delivery | `messaging.system`, `messaging.operation.*` (`process`), `messaging.destination.name`, `messaging.message.id`, `messaging.consumer.group.name`, `app.messaging.source`, `app.messaging.attempt`, `app.messaging.redelivered`, `app.context`, `app.correlation_id`, `app.origin`, `app.outcome` |
155
+ | `job <name>` | INTERNAL, always a root | `tracedJob()` | `app.job.name`, `app.job.scheduled_at`, `app.job.attempt`, `app.context`, `app.correlation_id`, `app.origin`, `app.outcome` |
156
+
157
+ - Names are stable and of low cardinality: a route template, never a URL; a class, a port, an
158
+ event type or a consumer name, never an identifier.
159
+ - `app.outcome` is `succeeded`, `refused` (a business answer), `failed`, and for a delivery
160
+ `dead-lettered`. A `DomainError` is an answer, not an incident: the use case span says
161
+ `refused` and `app.error.code`, and is not an error. Anything else sets the status ERROR with
162
+ `error.type` (the class name) and `app.error.code` (the `code`, when it is shaped like one).
163
+ - **A request refused by authorization has no use case span**: the middleware sits after the use
164
+ case guard, so the trace is the SERVER span alone, with the status answered and the code
165
+ (`FORBIDDEN`). A use case refused by its own rules has its span, `refused`.
166
+ - **One mechanism per span.** A use case is traced by the bus middleware, a consumer by
167
+ `process <consumer>`, a job by `job <name>`: `traceBindings()` leaves the bindings of kind
168
+ `use-case`, `consumer` and `job` alone.
169
+ - **Privacy**: class, method, port, context, codes and the identifiers of the kit (event id,
170
+ correlation id, job name). Never arguments, results, payloads, SQL parameter values, error
171
+ messages, aggregate ids, nor anything about the actor or the tenant.
172
+
173
+ ### Across the asynchronous boundary
174
+
175
+ The trace context is captured when the integration event is created, inside the request:
176
+ `outbox append <type>` stamps its own `traceparent` on the event, and the outbox stores it with
177
+ the record (every outbox store of the kit persists and restores it exactly: the contract suite
178
+ of `@ddd-ts-kit/testing` says so for the in-memory, Drizzle, Knex, Prisma and TypeORM stores).
179
+ The relay publishes later, from another call chain or another process: `tracedPublisher(bus)`
180
+ makes `publish <type>` a child of what the record names, never of the loop of the relay, and
181
+ stamps its own span on what goes out, so the consumers are its children. A tick that finds
182
+ nothing to publish makes no span, and there is no span per tick.
183
+
184
+ An event without `traceparent` (stored before tracing was configured) continues the active
185
+ span instead.
186
+
187
+ **Continue or link.** The messaging conventions of OpenTelemetry allow a consumer to continue
188
+ the trace of the producer or to start a new one that links to it. The default is to continue,
189
+ `traceApplication({ consumers: 'continue' })`: following one action end to end is then opening
190
+ one trace, and Jaeger shows a link only as a reference to click through, one consumer at a
191
+ time. Choose `consumers: 'link'` for a system where one event fans out to so many consumers,
192
+ or is retried for so long, that a trace would never end: each delivery is then the root of its
193
+ own trace, linked to the publication.
194
+
195
+ **Deliveries.** A provider tells each handler which delivery this is (`EventDelivery` of the
196
+ kernel: attempt, last attempt, transport, source, consumer), and each attempt is a span of its
197
+ own: `app.messaging.attempt` counts from 1, `app.messaging.redelivered` marks the ones after
198
+ the first, a failed attempt is an error with the code of what was thrown, and the attempt
199
+ after which the provider gives the event up says `app.outcome: dead-lettered`. RabbitMQ, Kafka,
200
+ NATS, Redis Streams and the in-memory bus all say it; their `traceparent` travels in the
201
+ envelope and in a real header (`traceparent`).
202
+
203
+ ## The pieces, without `kernelModule`
204
+
205
+ `traceApplication()` is these, one per member, for a composition written by hand:
206
+
207
+ ```ts
208
+ bus.use(traceUseCases()); // after authorization, outside the unit of work
209
+ traceDependency(new KnexOrderRepository(db), { kind: 'repository', context: 'ordering', port: 'OrderRepository' });
210
+ tracedDomainEvents(dispatcher);
211
+ tracedPublisher(new OutboxPublisher(store)); // outbox append <type>
212
+ tracedPublisher(bus); // publish <type>
213
+ tracedSubscriber(bus, { trace: 'continue' }); // process <type>
214
+ tracedConsumer(handler, { consumer: 'billing.invoice-on-order-placed', context: 'billing' });
215
+ tracedJob(run, { name: 'billing.send-reminders', context: 'billing' });
216
+ tracedErrorReporter(reporter);
217
+ createApplication({ modules, decorate: traceBindings() }); // bindings only
218
+ configureTracing({ executionScope }); // correlation id on spans and log fields
219
+ ```
220
+
221
+ The last two lines are for an application whose kernel module is given no `instrumentation`.
222
+ With it, `traceBindings()` is already the `binding` member the kernel module puts around every
223
+ binding and the execution scope is handed over by the module, so the `decorate` hook adds
224
+ nothing (wrapping an instance twice is a no-op). A project generated by the CLI has neither line: it calls
225
+ `traceApplication()` once in `src/platform/lay-foundations.ts`, `tracedRoutes()` in
226
+ `src/platform/auth/http-boundary.ts`, `tracedPublisher(...)` in
227
+ `src/platform/compose-application.ts`, and `initTracing` in `src/platform/tracing.ts`. The
228
+ rule `tracing/platform-only` keeps these imports in the platform directory, `main.ts` and
229
+ `apps/`.
230
+
231
+ A publisher names its transport itself (`messagingSystem`: `rabbitmq`, `kafka`, `nats`,
232
+ `redis`, `in-memory`, and `outbox` for the outbox publisher); `scopedPublisher` and
233
+ `validatingPublisher` pass it on, and `tracedPublisher(publisher, { system })` says it for a
234
+ publisher of your own.
235
+
236
+ For a custom transport: `captureTraceparent()` when storing a message,
237
+ `runWithTraceparent(traceparent, fn)` when handling it. `spanContextFromTraceparent` accepts
238
+ version `00` only and rejects all-zero ids. `withSpan(name, fn, attributes?)` exists for
239
+ platform code (a migration, a startup step); domain and application code never call it.
240
+
241
+ ## What a dependency span contains
242
+
243
+ `traceDependency` opens one span per call of an async method, named `<kind> <Port>.<method>`
244
+ after the port when it is given and after the class otherwise.
245
+
246
+ - Never arguments, return values or error messages; the error is rethrown untouched.
247
+ - Synchronous methods are not traced. A method declared `async` is traced from its first call; a
248
+ method that merely returns a promise is traced from its second call. A traced call returns a
249
+ promise, so do not wrap an object whose methods return a chainable query builder.
250
+ - Getters, symbols, generators and lifecycle hooks (`onModuleInit`, `onModuleDestroy`,
251
+ `onApplicationBootstrap`, `onApplicationShutdown`, `dispose`) are left alone.
252
+ - The instance is wrapped in place: same object, `instanceof` and `this` intact, wrapping twice
253
+ is a no-op. Nothing enumerable is added, so the instance spreads and compares as before.
254
+ - `private` is a compile-time keyword: at run time a private method is a method and a function
255
+ held in a private field is a method too. Both are traced when async, and a method that calls
256
+ another through `this` produces a child span. The aspects of `@ddd-ts-kit/aspects` differ on
257
+ purpose: they advise the call made from outside only.
258
+ - A frozen or sealed instance is fronted by a proxy instead. Its methods and getters run with
259
+ the real instance as `this`; the calls it makes on itself are then not traced.
260
+ - **Order of the wrappers of a repository** under `kernelModule({ instrumentation })`,
261
+ outermost first: the `decorate` hook of the application when there is one, the span, the
262
+ hand-over of domain events (`dispatchingOnSave` of the kernel), then the aspects of the
263
+ binding (`retry`, `timeout`). So `repository X.save` covers the retries, and a retried save
264
+ announces its events once. Inside a unit of work `repository X.save` contains no
265
+ `domain-event` span: the save only queues the events, and they are dispatched under
266
+ `unit-of-work before-commit`, a sibling that follows the saves. Outside any unit of work the
267
+ save dispatches before it resolves, and the `domain-event` spans are its children.
268
+
269
+ ## Noise
270
+
271
+ A trace tells the story of an action and nothing else. The defaults, and the option for each:
272
+
273
+ | What | Default | Option |
274
+ |---|---|---|
275
+ | Probes, metrics, documentation | no span for `/health`, `/healthz`, `/live`, `/livez`, `/ready`, `/readyz`, `/startup`, `/metrics`, `/docs`, `/openapi.json`, `/favicon.ico` and what is below them; no span for the documentation routes of `@ddd-ts-kit/http` wherever they are mounted | `initTracing({ ignoredPaths })`, `tracedRoutes({ ignore })` |
276
+ | Outbox relay | no span per tick, none when nothing is published; the queries of its polling make no span (see the drivers) | |
277
+ | Instrumentations on | `http` and `undici` and `grpc` (the two sides of a request), `pg`, `mysql2`, `mysql`, `mongodb`, `ioredis`, `redis` | `initTracing({ autoInstrumentations })`, or `instrumentations` to replace them all |
278
+ | Database drivers | the statement as written, with its placeholders, never the values bound to them; only inside a trace (`requireParentSpan`), so a poller or a pool makes no trace of its own; no connection or pool checkout span (`ignoreConnectSpans`) | `autoInstrumentations` |
279
+ | Instrumentations off | `fs`, `dns`, `net` (how a call was made, not what was done); `express`, `connect`, `router`, `koa`, `hapi`, `restify`, `nestjs-core` (a span per middleware, where the kit names the request by its route); `amqplib`, `kafkajs` (they would double `publish` and `process`); `knex`, `generic-pool`, `dataloader`, `lru-memoizer`, `mongoose` (they repeat the driver); logging bridges, runtime and host metrics | `autoInstrumentations: { '@opentelemetry/instrumentation-fs': { enabled: true } }` |
280
+ | Attributes | an allow-list (`DEFAULT_ALLOWED_ATTRIBUTES`): what the kit writes and what the instrumentations kept on say of a request, a query, a peer. Headers, bodies, parameter values, user and client identifiers are dropped before export | `initTracing({ allowedAttributes })`, `new AttributeAllowList({ allowed, alsoAllowed })` |
281
+ | Volume | under one local root (a request, a delivery, a job run), at most 25 dependency, domain event or publication spans share a name and 200 exist in total; beyond, the call runs untraced (an event published past the cap still carries the trace on) and `app.dependency_spans.dropped` counts on the root | `configureTracing({ maxSpansPerName, maxSpansPerRoot })` |
282
+ | Secrets | query string values and userinfo of recorded URLs become `REDACTED`; exception messages and stack traces are dropped | |
283
+
284
+ Use case, consumer, job and request spans are never capped: there is one per unit of work.
285
+
286
+ ### Sampling
287
+
288
+ `initTracing({ sampleRatio: 0.2 })` samples a share of the traces where they start, and a
289
+ sampled parent is always followed: a trace is kept or dropped whole, across the outbox and the
290
+ contexts, since the decision travels in the `traceparent` stored with the event. Without the
291
+ option the standard `OTEL_TRACES_SAMPLER` variables apply, and by default every trace is kept.
292
+
293
+ Keeping every trace that ends in error, whatever the ratio, cannot be decided when a trace
294
+ starts. That is tail sampling, and it belongs to a collector, which sees whole traces. Sample
295
+ everything in the application and decide there:
296
+
297
+ ```yaml
298
+ # OpenTelemetry Collector (contrib): between the applications and Jaeger
299
+ receivers:
300
+ otlp: { protocols: { http: { endpoint: 0.0.0.0:4318 } } }
301
+ processors:
302
+ tail_sampling:
303
+ decision_wait: 30s
304
+ policies:
305
+ - { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
306
+ - { name: dead-letters, type: string_attribute, string_attribute: { key: app.outcome, values: [dead-lettered] } }
307
+ - { name: the-rest, type: probabilistic, probabilistic: { sampling_percentage: 10 } }
308
+ exporters:
309
+ otlp_http/jaeger: { endpoint: http://jaeger:4318 }
310
+ service:
311
+ pipelines:
312
+ traces: { receivers: [otlp], processors: [tail_sampling], exporters: [otlp_http/jaeger] }
313
+ ```
314
+
315
+ `decision_wait` must cover the delay of the outbox relay and of the retries, or the late spans
316
+ of a trace are decided apart.
317
+
318
+ ## Span processors
319
+
320
+ For a provider you build yourself, add them before the exporting processor:
321
+
322
+ | Processor | Effect |
323
+ |---|---|
324
+ | `LocalRootTracker` | enables the per-root caps, names the correlation on the local root, and lets `tracedRoutes()` recognise the span of an HTTP instrumentation |
325
+ | `UrlSecretSpanProcessor` | `url.full`, `http.url`, `url.query`: query values and userinfo become `REDACTED`, parameter names stay |
326
+ | `ErrorMessageScrubber` | drops `exception.message`, `exception.stacktrace` and the status message recorded by instrumentations; keeps `exception.type` |
327
+ | `AttributeAllowList` | keeps the allowed attributes only |
328
+
329
+ `initTracing` installs the four, then `extraSpanProcessors`, then the exporter.
330
+
331
+ ## Pivoting: trace, logs, audit trail, error tracker
332
+
333
+ Every span that starts a unit of work (the SERVER span, `process <consumer>`, `job <name>`)
334
+ carries `app.correlation_id` and `app.origin`: the correlation id of the kit, the same in every
335
+ bounded context an action reaches. From there:
336
+
337
+ ```ts
338
+ const logger = pino({ mixin: () => traceLogFields() });
339
+ // inside a unit of work: { correlationId, origin, actorId, tenantId?, causationId?, trace_id, span_id }
340
+ ```
341
+
342
+ | From | To | By |
343
+ |---|---|---|
344
+ | a trace | its log lines | `trace_id` of the log fields, or `correlationId` |
345
+ | a trace | the audit entries of the action | `app.correlation_id` is the `correlationId` of the `AuditEntry` |
346
+ | an error report | its trace | `traceId` of the `ErrorReport`, filled by `tracedErrorReporter` (also for a failed job or a buried event, reported once their spans are over) |
347
+ | a log line, an audit entry | the trace | search the tag `app.correlation_id` in Jaeger |
348
+
349
+ `traceApplication()` gives the execution scope of the application to `configureTracing`; a
350
+ composition by hand calls `configureTracing({ executionScope })` itself, and
351
+ `traceLogFields(scope)` takes the scope as an argument for a logger built earlier.
352
+ `correlationIdFromActiveSpan()` answers the trace id, usable as a support reference when the
353
+ project has no execution scope. **Nothing personal becomes a span attribute**: the actor and
354
+ the tenant stay in the log fields, where retention is yours.
355
+
356
+ ## Asserting traces in a project
357
+
358
+ ```ts
359
+ import { expectSpanTree, installTestTracing } from '@ddd-ts-kit/tracing/testing';
360
+
361
+ const tracing = installTestTracing(); // a real provider that keeps the ended spans; uninstall() when done
362
+
363
+ it('books an appointment in one trace', async () => {
364
+ const trace = await tracing.traceOf(() => app.resolve(CommandBus).execute(new BookAppointment('09:00')));
365
+
366
+ expectSpanTree(trace, [
367
+ {
368
+ name: 'command BookAppointment',
369
+ attributes: { 'app.context': 'scheduling', 'app.outcome': 'succeeded' },
370
+ children: [
371
+ { name: 'repository AppointmentRepository.findOverlapping' },
372
+ { name: 'repository AppointmentRepository.save' },
373
+ { name: 'unit-of-work before-commit', children: [{ name: 'domain-event scheduling.appointment.booked' }] },
374
+ ],
375
+ },
376
+ ]);
377
+ });
378
+ ```
379
+
380
+ - `traceOf(action)` answers the spans that ended during the action as plain data (`SpanNode`:
381
+ name, kind, status, attributes, trace and span ids, links, children in the order they
382
+ started): one tree per root, so two traces are two trees.
383
+ - `expectSpanTree(actual, expected)` is exact on names and structure: a span that is not listed
384
+ fails, and so does a listed one that is missing. Kinds, statuses and attributes are checked
385
+ when given (`undefined` expects an attribute to be absent), and a span is expected not to be
386
+ an error unless `status: 'ERROR'` says so. On a mismatch the error lists what differs, then
387
+ shows both trees as indented text. It depends on no test runner.
388
+ - `installTestTracing({ processors, sampler })` takes the processors of the project and a
389
+ sampler, to assert scrubbing or sampling.
390
+
391
+ ## Tests
392
+
393
+ ```
394
+ npx vitest run packages/tracing
395
+ DDD_TS_KIT_TEST_JAEGER_IMAGE=jaegertracing/jaeger:2.22.0 npx vitest run packages/tracing/test/jaeger
396
+ ```
397
+
398
+ The second command needs Docker: it starts a Jaeger of its own (a container named
399
+ `ddd-ts-kit-test-trace-jaeger-<pid>`, on two free loopback ports), runs the scenario above with
400
+ `initTracing({ serviceName })` and the standard endpoint variable, reads the trace back through
401
+ the HTTP API of Jaeger and asserts the same tree, then removes the container.
@@ -0,0 +1,39 @@
1
+ import type { BusMiddleware, DomainEventDispatcher, ErrorReporter, ExecutionScope, IntegrationEventCallback, IntegrationEventPublisher, JobExecution } from '@ddd-ts-kit/kernel';
2
+ import { type TracedBinding } from '../dependency/trace-bindings.js';
3
+ import type { ConsumerTrace } from '../events/consumer-trace.js';
4
+ export interface TraceApplicationOptions {
5
+ /**
6
+ * How a consumed event relates to its publication. Default `continue`: the consumers are
7
+ * children of the publication, so one action is one trace. `link` starts a trace per
8
+ * delivery, linked to it.
9
+ */
10
+ readonly consumers?: ConsumerTrace;
11
+ }
12
+ type JobRun = (execution: JobExecution) => Promise<void>;
13
+ /** The wrappers `kernelModule({ instrumentation })` of `@ddd-ts-kit/di` applies, one per kind of span. */
14
+ export interface TracedApplication {
15
+ readonly binding: <T>(instance: T, binding: TracedBinding) => T;
16
+ readonly useCase: BusMiddleware;
17
+ readonly publisher: (publisher: IntegrationEventPublisher) => IntegrationEventPublisher;
18
+ readonly consumer: (handler: IntegrationEventCallback, consumer: {
19
+ readonly consumer: string;
20
+ readonly context: string;
21
+ }) => IntegrationEventCallback;
22
+ readonly domainEvents: (dispatcher: DomainEventDispatcher) => DomainEventDispatcher;
23
+ readonly job: (run: JobRun, job: {
24
+ readonly name: string;
25
+ readonly context: string;
26
+ }) => JobRun;
27
+ readonly errorReporter: (reporter: ErrorReporter) => ErrorReporter;
28
+ readonly executionScope: (scope: ExecutionScope) => void;
29
+ }
30
+ /**
31
+ * Everything that traces an application, in one value: `kernelModule({ instrumentation:
32
+ * traceApplication() })`. Use cases, repositories and adapters, the dispatch of domain events
33
+ * and their handlers, the outbox, the publications, the consumers and the jobs each get their
34
+ * span; error reports carry the trace id, and the execution scope of the application is given
35
+ * to `configureTracing`, so spans and log fields carry the correlation id.
36
+ */
37
+ export declare function traceApplication(options?: TraceApplicationOptions): TracedApplication;
38
+ export {};
39
+ //# sourceMappingURL=trace-application.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-application.d.ts","sourceRoot":"","sources":["../../src/application/trace-application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,wBAAwB,EACxB,yBAAyB,EACzB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;CACpC;AAED,KAAK,MAAM,GAAG,CAAC,SAAS,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzD,0GAA0G;AAC1G,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,KAAK,CAAC,CAAC;IAChE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,yBAAyB,KAAK,yBAAyB,CAAC;IACxF,QAAQ,CAAC,QAAQ,EAAE,CACjB,OAAO,EAAE,wBAAwB,EACjC,QAAQ,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAC9D,wBAAwB,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,qBAAqB,KAAK,qBAAqB,CAAC;IACpF,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IAChG,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,aAAa,CAAC;IACnE,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,iBAAiB,CAezF"}
@@ -0,0 +1,31 @@
1
+ import { configureTracing } from '../configuration/configure-tracing.js';
2
+ import { tracedErrorReporter } from '../correlation/traced-error-reporter.js';
3
+ import { traceBindings } from '../dependency/trace-bindings.js';
4
+ import { tracedDomainEvents } from '../domain-events/traced-domain-events.js';
5
+ import { tracedConsumer } from '../events/traced-consumer.js';
6
+ import { tracedPublisher } from '../events/traced-publisher.js';
7
+ import { tracedJob } from '../jobs/traced-job.js';
8
+ import { traceUseCases } from '../use-case/trace-use-cases.js';
9
+ /**
10
+ * Everything that traces an application, in one value: `kernelModule({ instrumentation:
11
+ * traceApplication() })`. Use cases, repositories and adapters, the dispatch of domain events
12
+ * and their handlers, the outbox, the publications, the consumers and the jobs each get their
13
+ * span; error reports carry the trace id, and the execution scope of the application is given
14
+ * to `configureTracing`, so spans and log fields carry the correlation id.
15
+ */
16
+ export function traceApplication(options = {}) {
17
+ const trace = options.consumers ?? 'continue';
18
+ return {
19
+ binding: traceBindings(),
20
+ useCase: traceUseCases(),
21
+ publisher: (publisher) => tracedPublisher(publisher),
22
+ consumer: (handler, { consumer, context }) => tracedConsumer(handler, { consumer, context, trace }),
23
+ domainEvents: tracedDomainEvents,
24
+ job: tracedJob,
25
+ errorReporter: tracedErrorReporter,
26
+ executionScope: (executionScope) => {
27
+ configureTracing({ executionScope });
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=trace-application.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-application.js","sourceRoot":"","sources":["../../src/application/trace-application.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAsB,MAAM,iCAAiC,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AA4B/D;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAO,GAA4B,EAAE;IACpE,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC;IAE9C,OAAO;QACL,OAAO,EAAE,aAAa,EAAE;QACxB,OAAO,EAAE,aAAa,EAAE;QACxB,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC;QACpD,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACnG,YAAY,EAAE,kBAAkB;QAChC,GAAG,EAAE,SAAS;QACd,aAAa,EAAE,mBAAmB;QAClC,cAAc,EAAE,CAAC,cAAc,EAAE,EAAE;YACjC,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { TracingConfiguration } from './tracing-configuration.js';
2
+ /**
3
+ * Overrides the given keys and keeps the others. Lifecycle hooks are read when an
4
+ * instance is wrapped, the caps on every call.
5
+ */
6
+ export declare function configureTracing(overrides: Partial<TracingConfiguration>): void;
7
+ export declare function resetTracingConfiguration(): void;
8
+ export declare function tracingConfiguration(): TracingConfiguration;
9
+ //# sourceMappingURL=configure-tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-tracing.d.ts","sourceRoot":"","sources":["../../src/configuration/configure-tracing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAUvE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAE/E;AAED,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED,wBAAgB,oBAAoB,IAAI,oBAAoB,CAE3D"}
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_LIFECYCLE_HOOKS } from './default-lifecycle-hooks.js';
2
+ const defaults = {
3
+ maxSpansPerName: 25,
4
+ maxSpansPerRoot: 200,
5
+ lifecycleHooks: DEFAULT_LIFECYCLE_HOOKS,
6
+ };
7
+ let current = defaults;
8
+ /**
9
+ * Overrides the given keys and keeps the others. Lifecycle hooks are read when an
10
+ * instance is wrapped, the caps on every call.
11
+ */
12
+ export function configureTracing(overrides) {
13
+ current = { ...current, ...overrides };
14
+ }
15
+ export function resetTracingConfiguration() {
16
+ current = defaults;
17
+ }
18
+ export function tracingConfiguration() {
19
+ return current;
20
+ }
21
+ //# sourceMappingURL=configure-tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure-tracing.js","sourceRoot":"","sources":["../../src/configuration/configure-tracing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,MAAM,QAAQ,GAAyB;IACrC,eAAe,EAAE,EAAE;IACnB,eAAe,EAAE,GAAG;IACpB,cAAc,EAAE,uBAAuB;CACxC,CAAC;AAEF,IAAI,OAAO,GAAyB,QAAQ,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAwC;IACvE,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO,GAAG,QAAQ,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const DEFAULT_LIFECYCLE_HOOKS: readonly string[];
2
+ //# sourceMappingURL=default-lifecycle-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-lifecycle-hooks.d.ts","sourceRoot":"","sources":["../../src/configuration/default-lifecycle-hooks.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAMpD,CAAC"}
@@ -0,0 +1,8 @@
1
+ export const DEFAULT_LIFECYCLE_HOOKS = [
2
+ 'onModuleInit',
3
+ 'onModuleDestroy',
4
+ 'onApplicationBootstrap',
5
+ 'onApplicationShutdown',
6
+ 'dispose',
7
+ ];
8
+ //# sourceMappingURL=default-lifecycle-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-lifecycle-hooks.js","sourceRoot":"","sources":["../../src/configuration/default-lifecycle-hooks.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,cAAc;IACd,iBAAiB;IACjB,wBAAwB;IACxB,uBAAuB;IACvB,SAAS;CACV,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ExecutionScope } from '@ddd-ts-kit/kernel';
2
+ export interface TracingConfiguration {
3
+ /** Dependency spans sharing one name allowed under one local root. */
4
+ readonly maxSpansPerName: number;
5
+ /** Dependency spans allowed in total under one local root. */
6
+ readonly maxSpansPerRoot: number;
7
+ /** Method names never wrapped: frameworks call them outside of any request. */
8
+ readonly lifecycleHooks: readonly string[];
9
+ /**
10
+ * When given, the local root span of each unit of work names its correlation id and its
11
+ * origin (`app.correlation_id`, `app.origin`), and `traceLogFields()` carries the log fields
12
+ * of the execution context. Never the actor, the tenant or the session.
13
+ */
14
+ readonly executionScope?: ExecutionScope;
15
+ }
16
+ //# sourceMappingURL=tracing-configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-configuration.d.ts","sourceRoot":"","sources":["../../src/configuration/tracing-configuration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;CAC1C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tracing-configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing-configuration.js","sourceRoot":"","sources":["../../src/configuration/tracing-configuration.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Trace id of the active span. Usable as the correlation id of a log line and as the
3
+ * support reference shown to an end user: it leads straight to the trace.
4
+ */
5
+ export declare function correlationIdFromActiveSpan(): string | undefined;
6
+ //# sourceMappingURL=correlation-id-from-active-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-id-from-active-span.d.ts","sourceRoot":"","sources":["../../src/correlation/correlation-id-from-active-span.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,SAAS,CAIhE"}
@@ -0,0 +1,10 @@
1
+ import { isSpanContextValid, trace } from '@opentelemetry/api';
2
+ /**
3
+ * Trace id of the active span. Usable as the correlation id of a log line and as the
4
+ * support reference shown to an end user: it leads straight to the trace.
5
+ */
6
+ export function correlationIdFromActiveSpan() {
7
+ const spanContext = trace.getActiveSpan()?.spanContext();
8
+ return spanContext !== undefined && isSpanContextValid(spanContext) ? spanContext.traceId : undefined;
9
+ }
10
+ //# sourceMappingURL=correlation-id-from-active-span.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation-id-from-active-span.js","sourceRoot":"","sources":["../../src/correlation/correlation-id-from-active-span.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC;IAEzD,OAAO,WAAW,KAAK,SAAS,IAAI,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACxG,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type ExecutionScope, type LogFields } from '@ddd-ts-kit/kernel';
2
+ export interface TraceLogFields extends LogFields {
3
+ trace_id?: string;
4
+ span_id?: string;
5
+ }
6
+ /**
7
+ * Fields for a logger mixin (pino `mixin`, winston format...): the trace and span ids inside a
8
+ * span, and, inside the execution scope given here or to `configureTracing`, the log fields of
9
+ * the execution context (`logFieldsOf`: correlation, causation, origin, actor and tenant ids).
10
+ * Empty outside of both.
11
+ */
12
+ export declare function traceLogFields(scope?: ExecutionScope): TraceLogFields;
13
+ //# sourceMappingURL=trace-log-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-log-fields.d.ts","sourceRoot":"","sources":["../../src/correlation/trace-log-fields.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGtF,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,cAAc,CAUrE"}