@nxgt/telemetry 0.2.0 → 0.2.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 (2) hide show
  1. package/README.md +129 -0
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -24,6 +24,135 @@ vocabulary exactly: the same severities, span kinds, statuses, `traceparent`
24
24
  rules, sampling rule and attribute names. A trace started in one and continued
25
25
  in the other is one trace.
26
26
 
27
+ ## Usage
28
+
29
+ The whole life of a service, in the order you write it. Each step links to the
30
+ section that explains it.
31
+
32
+ **1. Install one telemetry at startup**, before anything logs.
33
+ [The root](#the-root)
34
+
35
+ ```ts
36
+ // telemetry.ts
37
+ import { consoleExporter, createTelemetry } from '@nxgt/telemetry';
38
+
39
+ export const telemetry = createTelemetry('checkout', {
40
+ version: '1.4.0',
41
+ environment: 'production',
42
+ exporters: [consoleExporter()],
43
+ }).install();
44
+ ```
45
+
46
+ **2. Give each part of the code a logger.** A logger is cheap and has no
47
+ state; create it at module level. [Logging](#logging)
48
+
49
+ ```ts
50
+ import { createLogger } from '@nxgt/telemetry';
51
+
52
+ const log = createLogger('CheckoutService');
53
+
54
+ log.info('checkout started', { cartSize: 3 });
55
+ ```
56
+
57
+ **3. Declare the events that matter**, so a log line carries only the fields
58
+ you chose. [Declared event](#declared-event)
59
+
60
+ ```ts
61
+ import { event } from '@nxgt/telemetry';
62
+ import { z } from 'zod';
63
+
64
+ const Charged = event('checkout.charged', z.object({ orderId: z.string(), amount: z.number() }));
65
+
66
+ log.info(Charged({ orderId: 'o-1', amount: 42 }));
67
+ ```
68
+
69
+ **4. Wrap units of work in spans.** Everything inside a span, logs included,
70
+ carries its `traceId`. [Spans](#spans)
71
+
72
+ ```ts
73
+ import { span } from '@nxgt/telemetry';
74
+
75
+ export async function checkout(orderId: string) {
76
+ return span('checkout', { attributes: { orderId } }, async (scope) => {
77
+ const order = await span('order.load', () => orders.find(orderId));
78
+ scope.attribute('order.total', order.total);
79
+ await span('payment.charge', { kind: 'client' }, () => payments.charge(order));
80
+ log.info(Charged({ orderId, amount: order.total })); // carries the trace
81
+ return order;
82
+ });
83
+ }
84
+ ```
85
+
86
+ **5. Carry the trace across services.** Continue the caller's `traceparent` on
87
+ the way in and send the current one on the way out.
88
+ [Propagation](#propagation-and-traceparent)
89
+
90
+ ```ts
91
+ import { continuing, currentTraceparent } from '@nxgt/telemetry';
92
+
93
+ // in: an HTTP handler
94
+ await continuing(request.headers.get('traceparent'), 'POST /checkout', { kind: 'server' }, () =>
95
+ checkout(orderId),
96
+ );
97
+
98
+ // out: an HTTP call made inside a span
99
+ await fetch('https://stock.internal/reserve', {
100
+ method: 'POST',
101
+ headers: { traceparent: currentTraceparent() ?? '' },
102
+ });
103
+ ```
104
+
105
+ With Hono and httpyz, the two integrations do both halves for you:
106
+ [`@nxgt/telemetry-hono`](https://www.npmjs.com/package/@nxgt/telemetry-hono)
107
+ and [`@nxgt/telemetry-httpyz`](https://www.npmjs.com/package/@nxgt/telemetry-httpyz).
108
+
109
+ **6. Put request-wide facts in scope once**, instead of passing them to every
110
+ log call. [Context](#context)
111
+
112
+ ```ts
113
+ import { withAttributes } from '@nxgt/telemetry';
114
+
115
+ await withAttributes({ tenant: 'acme' }, () => checkout(orderId));
116
+ ```
117
+
118
+ **7. Ship the signals somewhere.** Swap the console for a collector, a file or
119
+ MongoDB. The code from steps 2 to 6 does not change. [Exporters](#exporters)
120
+
121
+ ```ts
122
+ import { otlpExporter } from '@nxgt/telemetry-otlp';
123
+
124
+ createTelemetry('checkout', {
125
+ sampler: ratioSampler(0.1), // keep one trace in ten; logs are never sampled
126
+ exporters: [otlpExporter({ endpoint: 'http://localhost:4318' })],
127
+ }).install();
128
+ ```
129
+
130
+ **8. Close it on shutdown, and await it**, or the last batch is lost.
131
+ [The root](#the-root)
132
+
133
+ ```ts
134
+ process.on('SIGTERM', async () => {
135
+ await telemetry.close();
136
+ process.exit(0);
137
+ });
138
+ ```
139
+
140
+ **In a test**, give each suite its own telemetry and an exporter that keeps
141
+ what it receives, instead of installing one globally.
142
+ [Context](#context)
143
+
144
+ ```ts
145
+ import type { Exporter, Signal } from '@nxgt/telemetry';
146
+ import { createTelemetry, withTelemetry } from '@nxgt/telemetry';
147
+
148
+ const received: Signal[] = [];
149
+ const collect: Exporter = { export: (_resource, batch) => void received.push(...batch) };
150
+ const telemetry = createTelemetry('test', { exporters: [collect] });
151
+
152
+ await withTelemetry(telemetry, () => checkout('o-1'));
153
+ await telemetry.close(); // flushes: `received` now holds the spans and logs
154
+ ```
155
+
27
156
  ## Concepts
28
157
 
29
158
  Every word this library uses, once, with the smallest example that shows it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/telemetry",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Logs and traces for a TypeScript service, with no OpenTelemetry SDK: spans over AsyncLocalStorage, W3C traceparent propagation, declared events, and a pipeline that never blocks the caller",
5
5
  "license": "MIT",
6
6
  "type": "module",