@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.
- package/README.md +400 -2
- package/dist/application/trace-application.d.ts +39 -0
- package/dist/application/trace-application.d.ts.map +1 -0
- package/dist/application/trace-application.js +31 -0
- package/dist/application/trace-application.js.map +1 -0
- package/dist/configuration/configure-tracing.d.ts +9 -0
- package/dist/configuration/configure-tracing.d.ts.map +1 -0
- package/dist/configuration/configure-tracing.js +21 -0
- package/dist/configuration/configure-tracing.js.map +1 -0
- package/dist/configuration/default-lifecycle-hooks.d.ts +2 -0
- package/dist/configuration/default-lifecycle-hooks.d.ts.map +1 -0
- package/dist/configuration/default-lifecycle-hooks.js +8 -0
- package/dist/configuration/default-lifecycle-hooks.js.map +1 -0
- package/dist/configuration/tracing-configuration.d.ts +16 -0
- package/dist/configuration/tracing-configuration.d.ts.map +1 -0
- package/dist/configuration/tracing-configuration.js +2 -0
- package/dist/configuration/tracing-configuration.js.map +1 -0
- package/dist/correlation/correlation-id-from-active-span.d.ts +6 -0
- package/dist/correlation/correlation-id-from-active-span.d.ts.map +1 -0
- package/dist/correlation/correlation-id-from-active-span.js +10 -0
- package/dist/correlation/correlation-id-from-active-span.js.map +1 -0
- package/dist/correlation/trace-log-fields.d.ts +13 -0
- package/dist/correlation/trace-log-fields.d.ts.map +1 -0
- package/dist/correlation/trace-log-fields.js +20 -0
- package/dist/correlation/trace-log-fields.js.map +1 -0
- package/dist/correlation/traced-error-reporter.d.ts +9 -0
- package/dist/correlation/traced-error-reporter.d.ts.map +1 -0
- package/dist/correlation/traced-error-reporter.js +23 -0
- package/dist/correlation/traced-error-reporter.js.map +1 -0
- package/dist/dependency/method.d.ts +2 -0
- package/dist/dependency/method.d.ts.map +1 -0
- package/dist/dependency/method.js +2 -0
- package/dist/dependency/method.js.map +1 -0
- package/dist/dependency/proxied.d.ts +8 -0
- package/dist/dependency/proxied.d.ts.map +1 -0
- package/dist/dependency/proxied.js +21 -0
- package/dist/dependency/proxied.js.map +1 -0
- package/dist/dependency/shadowed.d.ts +4 -0
- package/dist/dependency/shadowed.d.ts.map +1 -0
- package/dist/dependency/shadowed.js +14 -0
- package/dist/dependency/shadowed.js.map +1 -0
- package/dist/dependency/trace-bindings.d.ts +25 -0
- package/dist/dependency/trace-bindings.d.ts.map +1 -0
- package/dist/dependency/trace-bindings.js +35 -0
- package/dist/dependency/trace-bindings.js.map +1 -0
- package/dist/dependency/trace-dependency.d.ts +26 -0
- package/dist/dependency/trace-dependency.d.ts.map +1 -0
- package/dist/dependency/trace-dependency.js +56 -0
- package/dist/dependency/trace-dependency.js.map +1 -0
- package/dist/dependency/traceable-methods.d.ts +4 -0
- package/dist/dependency/traceable-methods.d.ts.map +1 -0
- package/dist/dependency/traceable-methods.js +46 -0
- package/dist/dependency/traceable-methods.js.map +1 -0
- package/dist/dependency/traced.d.ts +5 -0
- package/dist/dependency/traced.d.ts.map +1 -0
- package/dist/dependency/traced.js +56 -0
- package/dist/dependency/traced.js.map +1 -0
- package/dist/domain-events/traced-domain-events.d.ts +14 -0
- package/dist/domain-events/traced-domain-events.d.ts.map +1 -0
- package/dist/domain-events/traced-domain-events.js +49 -0
- package/dist/domain-events/traced-domain-events.js.map +1 -0
- package/dist/events/consumer-trace.d.ts +8 -0
- package/dist/events/consumer-trace.d.ts.map +1 -0
- package/dist/events/consumer-trace.js +2 -0
- package/dist/events/consumer-trace.js.map +1 -0
- package/dist/events/messaging-attributes.d.ts +12 -0
- package/dist/events/messaging-attributes.d.ts.map +1 -0
- package/dist/events/messaging-attributes.js +11 -0
- package/dist/events/messaging-attributes.js.map +1 -0
- package/dist/events/traced-consumer.d.ts +22 -0
- package/dist/events/traced-consumer.d.ts.map +1 -0
- package/dist/events/traced-consumer.js +65 -0
- package/dist/events/traced-consumer.js.map +1 -0
- package/dist/events/traced-publisher.d.ts +19 -0
- package/dist/events/traced-publisher.d.ts.map +1 -0
- package/dist/events/traced-publisher.js +69 -0
- package/dist/events/traced-publisher.js.map +1 -0
- package/dist/events/traced-subscriber.d.ts +15 -0
- package/dist/events/traced-subscriber.d.ts.map +1 -0
- package/dist/events/traced-subscriber.js +19 -0
- package/dist/events/traced-subscriber.js.map +1 -0
- package/dist/http/is-noise-path.d.ts +3 -0
- package/dist/http/is-noise-path.d.ts.map +1 -0
- package/dist/http/is-noise-path.js +7 -0
- package/dist/http/is-noise-path.js.map +1 -0
- package/dist/http/is-noise-route.d.ts +6 -0
- package/dist/http/is-noise-route.d.ts.map +1 -0
- package/dist/http/is-noise-route.js +7 -0
- package/dist/http/is-noise-route.js.map +1 -0
- package/dist/http/noise-paths.d.ts +3 -0
- package/dist/http/noise-paths.d.ts.map +1 -0
- package/dist/http/noise-paths.js +15 -0
- package/dist/http/noise-paths.js.map +1 -0
- package/dist/http/traced-routes.d.ts +28 -0
- package/dist/http/traced-routes.d.ts.map +1 -0
- package/dist/http/traced-routes.js +76 -0
- package/dist/http/traced-routes.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/traced-job.d.ts +16 -0
- package/dist/jobs/traced-job.d.ts.map +1 -0
- package/dist/jobs/traced-job.js +25 -0
- package/dist/jobs/traced-job.js.map +1 -0
- package/dist/local-root/admit-dependency-span.d.ts +7 -0
- package/dist/local-root/admit-dependency-span.d.ts.map +1 -0
- package/dist/local-root/admit-dependency-span.js +30 -0
- package/dist/local-root/admit-dependency-span.js.map +1 -0
- package/dist/local-root/correlate.d.ts +9 -0
- package/dist/local-root/correlate.d.ts.map +1 -0
- package/dist/local-root/correlate.js +23 -0
- package/dist/local-root/correlate.js.map +1 -0
- package/dist/local-root/local-root-tracker.d.ts +18 -0
- package/dist/local-root/local-root-tracker.d.ts.map +1 -0
- package/dist/local-root/local-root-tracker.js +26 -0
- package/dist/local-root/local-root-tracker.js.map +1 -0
- package/dist/local-root/local-root.d.ts +11 -0
- package/dist/local-root/local-root.d.ts.map +1 -0
- package/dist/local-root/local-root.js +2 -0
- package/dist/local-root/local-root.js.map +1 -0
- package/dist/local-root/local-roots.d.ts +4 -0
- package/dist/local-root/local-roots.d.ts.map +1 -0
- package/dist/local-root/local-roots.js +4 -0
- package/dist/local-root/local-roots.js.map +1 -0
- package/dist/local-root/work-attributes.d.ts +4 -0
- package/dist/local-root/work-attributes.d.ts.map +1 -0
- package/dist/local-root/work-attributes.js +11 -0
- package/dist/local-root/work-attributes.js.map +1 -0
- package/dist/node/default-instrumentations.d.ts +20 -0
- package/dist/node/default-instrumentations.d.ts.map +1 -0
- package/dist/node/default-instrumentations.js +55 -0
- package/dist/node/default-instrumentations.js.map +1 -0
- package/dist/node/init-tracing.d.ts +41 -0
- package/dist/node/init-tracing.d.ts.map +1 -0
- package/dist/node/init-tracing.js +71 -0
- package/dist/node/init-tracing.js.map +1 -0
- package/dist/node/instrumentations-off.d.ts +6 -0
- package/dist/node/instrumentations-off.d.ts.map +1 -0
- package/dist/node/instrumentations-off.js +39 -0
- package/dist/node/instrumentations-off.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/processors/attribute-allow-list.d.ts +25 -0
- package/dist/processors/attribute-allow-list.d.ts.map +1 -0
- package/dist/processors/attribute-allow-list.js +38 -0
- package/dist/processors/attribute-allow-list.js.map +1 -0
- package/dist/processors/default-allowed-attributes.d.ts +8 -0
- package/dist/processors/default-allowed-attributes.d.ts.map +1 -0
- package/dist/processors/default-allowed-attributes.js +48 -0
- package/dist/processors/default-allowed-attributes.js.map +1 -0
- package/dist/processors/ended-span.d.ts +19 -0
- package/dist/processors/ended-span.d.ts.map +1 -0
- package/dist/processors/ended-span.js +2 -0
- package/dist/processors/ended-span.js.map +1 -0
- package/dist/processors/error-message-scrubber.d.ts +13 -0
- package/dist/processors/error-message-scrubber.d.ts.map +1 -0
- package/dist/processors/error-message-scrubber.js +21 -0
- package/dist/processors/error-message-scrubber.js.map +1 -0
- package/dist/processors/url-secret-span-processor.d.ts +12 -0
- package/dist/processors/url-secret-span-processor.d.ts.map +1 -0
- package/dist/processors/url-secret-span-processor.js +47 -0
- package/dist/processors/url-secret-span-processor.js.map +1 -0
- package/dist/propagation/capture-traceparent.d.ts +3 -0
- package/dist/propagation/capture-traceparent.d.ts.map +1 -0
- package/dist/propagation/capture-traceparent.js +7 -0
- package/dist/propagation/capture-traceparent.js.map +1 -0
- package/dist/propagation/context-with-traceparent.d.ts +4 -0
- package/dist/propagation/context-with-traceparent.d.ts.map +1 -0
- package/dist/propagation/context-with-traceparent.js +8 -0
- package/dist/propagation/context-with-traceparent.js.map +1 -0
- package/dist/propagation/run-with-traceparent.d.ts +6 -0
- package/dist/propagation/run-with-traceparent.d.ts.map +1 -0
- package/dist/propagation/run-with-traceparent.js +10 -0
- package/dist/propagation/run-with-traceparent.js.map +1 -0
- package/dist/propagation/span-context-from-traceparent.d.ts +4 -0
- package/dist/propagation/span-context-from-traceparent.d.ts.map +1 -0
- package/dist/propagation/span-context-from-traceparent.js +23 -0
- package/dist/propagation/span-context-from-traceparent.js.map +1 -0
- package/dist/propagation/traceparent-from-context.d.ts +4 -0
- package/dist/propagation/traceparent-from-context.d.ts.map +1 -0
- package/dist/propagation/traceparent-from-context.js +11 -0
- package/dist/propagation/traceparent-from-context.js.map +1 -0
- package/dist/span/constructor-name.d.ts +3 -0
- package/dist/span/constructor-name.d.ts.map +1 -0
- package/dist/span/constructor-name.js +9 -0
- package/dist/span/constructor-name.js.map +1 -0
- package/dist/span/failed-traces.d.ts +2 -0
- package/dist/span/failed-traces.d.ts.map +1 -0
- package/dist/span/failed-traces.js +3 -0
- package/dist/span/failed-traces.js.map +1 -0
- package/dist/span/in-span.d.ts +12 -0
- package/dist/span/in-span.d.ts.map +1 -0
- package/dist/span/in-span.js +19 -0
- package/dist/span/in-span.js.map +1 -0
- package/dist/span/is-thenable.d.ts +2 -0
- package/dist/span/is-thenable.d.ts.map +1 -0
- package/dist/span/is-thenable.js +7 -0
- package/dist/span/is-thenable.js.map +1 -0
- package/dist/span/mark-span-failed.d.ts +4 -0
- package/dist/span/mark-span-failed.d.ts.map +1 -0
- package/dist/span/mark-span-failed.js +36 -0
- package/dist/span/mark-span-failed.js.map +1 -0
- package/dist/span/outcome-attribute.d.ts +3 -0
- package/dist/span/outcome-attribute.d.ts.map +1 -0
- package/dist/span/outcome-attribute.js +3 -0
- package/dist/span/outcome-attribute.js.map +1 -0
- package/dist/span/refusal-code.d.ts +4 -0
- package/dist/span/refusal-code.d.ts.map +1 -0
- package/dist/span/refusal-code.js +10 -0
- package/dist/span/refusal-code.js.map +1 -0
- package/dist/span/tracer.d.ts +4 -0
- package/dist/span/tracer.d.ts.map +1 -0
- package/dist/span/tracer.js +7 -0
- package/dist/span/tracer.js.map +1 -0
- package/dist/span/with-span.d.ts +9 -0
- package/dist/span/with-span.d.ts.map +1 -0
- package/dist/span/with-span.js +36 -0
- package/dist/span/with-span.js.map +1 -0
- package/dist/testing/expect-span-tree.d.ts +10 -0
- package/dist/testing/expect-span-tree.d.ts.map +1 -0
- package/dist/testing/expect-span-tree.js +29 -0
- package/dist/testing/expect-span-tree.js.map +1 -0
- package/dist/testing/expected-span.d.ts +17 -0
- package/dist/testing/expected-span.d.ts.map +1 -0
- package/dist/testing/expected-span.js +2 -0
- package/dist/testing/expected-span.js.map +1 -0
- package/dist/testing/install-test-tracing.d.ts +23 -0
- package/dist/testing/install-test-tracing.d.ts.map +1 -0
- package/dist/testing/install-test-tracing.js +48 -0
- package/dist/testing/install-test-tracing.js.map +1 -0
- package/dist/testing/render-span-tree.d.ts +8 -0
- package/dist/testing/render-span-tree.d.ts.map +1 -0
- package/dist/testing/render-span-tree.js +23 -0
- package/dist/testing/render-span-tree.js.map +1 -0
- package/dist/testing/span-mismatches.d.ts +5 -0
- package/dist/testing/span-mismatches.d.ts.map +1 -0
- package/dist/testing/span-mismatches.js +34 -0
- package/dist/testing/span-mismatches.js.map +1 -0
- package/dist/testing/span-node.d.ts +13 -0
- package/dist/testing/span-node.d.ts.map +1 -0
- package/dist/testing/span-node.js +2 -0
- package/dist/testing/span-node.js.map +1 -0
- package/dist/testing/span-tree-of.d.ts +8 -0
- package/dist/testing/span-tree-of.d.ts.map +1 -0
- package/dist/testing/span-tree-of.js +34 -0
- package/dist/testing/span-tree-of.js.map +1 -0
- package/dist/testing/start-order.d.ts +12 -0
- package/dist/testing/start-order.d.ts.map +1 -0
- package/dist/testing/start-order.js +17 -0
- package/dist/testing/start-order.js.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +4 -0
- package/dist/testing.js.map +1 -0
- package/dist/use-case/trace-use-cases.d.ts +10 -0
- package/dist/use-case/trace-use-cases.d.ts.map +1 -0
- package/dist/use-case/trace-use-cases.js +41 -0
- package/dist/use-case/trace-use-cases.js.map +1 -0
- package/package.json +75 -4
package/README.md
CHANGED
|
@@ -1,3 +1,401 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ddd-ts-kit/tracing
|
|
2
2
|
|
|
3
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|