@tracewayapp/backend 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +62 -304
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,225 +1,67 @@
1
1
  # @tracewayapp/backend
2
2
 
3
- Traceway SDK for Node.js backends. Core telemetry collection library.
3
+ Traceway SDK for Node.js backends. Provides error tracking, distributed tracing, span management, and metrics collection.
4
4
 
5
- ## Quick Start
6
-
7
- ```ts
8
- import * as traceway from "@tracewayapp/backend";
9
-
10
- traceway.init("your-token@https://your-traceway-server.com/api/report", {
11
- version: "1.0.0",
12
- debug: true,
13
- });
14
-
15
- // Capture an error
16
- traceway.captureException(new Error("something broke"));
17
-
18
- // Capture a message
19
- traceway.captureMessage("Deployment completed");
20
-
21
- // Capture a custom metric
22
- traceway.captureMetric("queue.length", 42);
23
-
24
- // Graceful shutdown
25
- await traceway.shutdown();
26
- ```
27
-
28
- ## Architecture
29
-
30
- ### Two Modes of Operation
5
+ ## Installation
31
6
 
32
- The SDK supports both **manual** and **automatic** context propagation:
33
-
34
- ```
35
- ┌─────────────────────────────────────────────────────────────────────┐
36
- │ Your Application │
37
- ├─────────────────────────────────────────────────────────────────────┤
38
- │ │
39
- │ ┌─────────────────────────────────────────────────────────────┐ │
40
- │ │ AsyncLocalStorage │ │
41
- │ │ ┌─────────────────────────────────────────────────────┐ │ │
42
- │ │ │ TraceContext (per request/task) │ │ │
43
- │ │ │ - traceId: string │ │ │
44
- │ │ │ - spans: Span[] │ │ │
45
- │ │ │ - attributes: Record<string, string> │ │ │
46
- │ │ │ - startedAt: Date │ │ │
47
- │ │ │ - statusCode, bodySize, clientIP, endpoint │ │ │
48
- │ │ └─────────────────────────────────────────────────────┘ │ │
49
- │ └─────────────────────────────────────────────────────────────┘ │
50
- │ │ │
51
- │ withTraceContext() ───────┘ │
52
- │ │
53
- │ captureException() ──────► auto-detects context ──────┐ │
54
- │ captureMessage() ────────► auto-detects context ──────┤ │
55
- │ endSpan() ───────────────► auto-adds to context ──────┤ │
56
- │ captureCurrentTrace() ───► reads from context ────────┤ │
57
- │ ▼ │
58
- │ ┌────────────────────┐ │
59
- │ │ CollectionFrameStore│ │
60
- │ │ (global singleton) │ │
61
- │ └────────────────────┘ │
62
- │ │ │
63
- │ gzip + POST │
64
- │ ▼ │
65
- │ Traceway API │
66
- └─────────────────────────────────────────────────────────────────────┘
7
+ ```bash
8
+ npm install @tracewayapp/backend
67
9
  ```
68
10
 
69
- ### Automatic Context Propagation (Recommended)
70
-
71
- The SDK uses Node.js `AsyncLocalStorage` to automatically propagate trace context through async operations:
72
-
73
- ```ts
74
- import * as traceway from "@tracewayapp/backend";
75
-
76
- traceway.init("token@https://api.traceway.io/api/report");
11
+ ## Quick Start
77
12
 
78
- // Wrap your request handler
79
- app.get("/api/users", (req, res) => {
80
- traceway.withTraceContext(
81
- { endpoint: `${req.method} ${req.path}`, clientIP: req.ip },
13
+ ```typescript
14
+ import {
15
+ init,
16
+ captureException,
17
+ withTraceContext,
18
+ startSpan,
19
+ endSpan,
20
+ captureCurrentTrace,
21
+ shutdown,
22
+ } from "@tracewayapp/backend";
23
+
24
+ // Initialize once at startup
25
+ init("your-token@https://traceway.example.com/api/report");
26
+
27
+ // Use trace context for HTTP requests
28
+ async function handleRequest(req, res) {
29
+ await withTraceContext(
30
+ {
31
+ endpoint: `${req.method} ${req.path}`,
32
+ clientIP: req.ip,
33
+ },
82
34
  async () => {
83
- // All operations inside automatically belong to this trace
84
-
85
- const span = traceway.startSpan("db-query");
86
- const users = await db.query("SELECT * FROM users");
87
- traceway.endSpan(span); // Auto-added to trace context
88
-
89
- // Exceptions auto-link to trace
90
- if (!users.length) {
91
- traceway.captureMessage("No users found");
35
+ try {
36
+ const dbSpan = startSpan("database-query");
37
+ const users = await db.query("SELECT * FROM users");
38
+ endSpan(dbSpan);
39
+
40
+ res.json(users);
41
+ } catch (error) {
42
+ captureException(error);
43
+ res.status(500).json({ error: "Internal error" });
44
+ } finally {
45
+ captureCurrentTrace();
92
46
  }
93
-
94
- res.json(users);
95
-
96
- // Capture the trace at the end
97
- traceway.setTraceResponseInfo(200, JSON.stringify(users).length);
98
- traceway.captureCurrentTrace();
99
47
  }
100
48
  );
101
- });
102
- ```
103
-
104
- ### Context API
105
-
106
- | Function | Description |
107
- |----------|-------------|
108
- | `withTraceContext(options, fn)` | Run function within a new trace context |
109
- | `getTraceContext()` | Get current context (or undefined) |
110
- | `getTraceId()` | Get current trace ID (or undefined) |
111
- | `hasTraceContext()` | Check if inside a trace context |
112
- | `setTraceAttribute(key, value)` | Set attribute on current trace |
113
- | `setTraceAttributes(attrs)` | Set multiple attributes |
114
- | `setTraceResponseInfo(status, size?)` | Set HTTP response info |
115
- | `addSpanToContext(span)` | Manually add a span |
116
- | `getTraceSpans()` | Get all spans in current trace |
117
- | `getTraceDuration()` | Get elapsed time in ms |
118
- | `forkTraceContext(fn)` | Fork context for parallel ops |
119
- | `captureCurrentTrace()` | Capture trace from context |
120
-
121
- ### Building Framework Middleware
122
-
123
- The context API makes it easy to build framework-specific middleware:
124
-
125
- ```ts
126
- // Express middleware
127
- import * as traceway from "@tracewayapp/backend";
128
-
129
- export function tracewayMiddleware() {
130
- return (req, res, next) => {
131
- traceway.withTraceContext(
132
- {
133
- endpoint: `${req.method} ${req.path}`,
134
- clientIP: req.ip,
135
- attributes: { userAgent: req.get("User-Agent") || "" },
136
- },
137
- () => {
138
- // Intercept response to capture trace
139
- const originalEnd = res.end;
140
- res.end = function (...args) {
141
- traceway.setTraceResponseInfo(
142
- res.statusCode,
143
- res.get("Content-Length") ? parseInt(res.get("Content-Length")) : 0
144
- );
145
- traceway.captureCurrentTrace();
146
- return originalEnd.apply(this, args);
147
- };
148
-
149
- next();
150
- }
151
- );
152
- };
153
49
  }
154
50
 
155
- // Usage
156
- app.use(tracewayMiddleware());
157
- app.get("/api/users", async (req, res) => {
158
- // Context is already set up - just use the SDK
159
- const span = traceway.startSpan("fetch-users");
160
- try {
161
- const users = await getUsers();
162
- traceway.endSpan(span);
163
- res.json(users);
164
- } catch (err) {
165
- traceway.endSpan(span);
166
- traceway.captureException(err); // Auto-linked to request trace
167
- res.status(500).json({ error: "Internal error" });
168
- }
51
+ // Graceful shutdown
52
+ process.on("SIGTERM", async () => {
53
+ await shutdown();
54
+ process.exit(0);
169
55
  });
170
56
  ```
171
57
 
172
- ```ts
173
- // Fastify plugin
174
- import * as traceway from "@tracewayapp/backend";
175
-
176
- export const tracewayPlugin = (fastify, opts, done) => {
177
- fastify.addHook("onRequest", (request, reply, done) => {
178
- traceway.withTraceContext(
179
- {
180
- endpoint: `${request.method} ${request.url}`,
181
- clientIP: request.ip,
182
- },
183
- () => done()
184
- );
185
- });
186
-
187
- fastify.addHook("onResponse", (request, reply, done) => {
188
- traceway.setTraceResponseInfo(reply.statusCode);
189
- traceway.captureCurrentTrace();
190
- done();
191
- });
192
-
193
- done();
194
- };
195
- ```
196
-
197
- ### Manual Mode (Legacy)
198
-
199
- You can still use explicit parameter passing if preferred:
200
-
201
- ```ts
202
- import * as traceway from "@tracewayapp/backend";
203
- import { generateUUID } from "@tracewayapp/core";
204
-
205
- const traceId = generateUUID();
206
- const spans: Span[] = [];
207
-
208
- const span = traceway.startSpan("operation");
209
- // ... do work ...
210
- spans.push(traceway.endSpan(span, false)); // false = don't auto-add to context
211
-
212
- traceway.captureTrace(traceId, "GET /api", 150, new Date(), 200, 1024, "127.0.0.1", {}, spans);
213
- traceway.captureExceptionWithAttributes(err, {}, traceId);
214
- ```
215
-
216
- ## Public API
58
+ ## API
217
59
 
218
60
  ### Core Functions
219
61
 
220
62
  | Function | Description |
221
63
  |----------|-------------|
222
- | `init(connectionString, options?)` | Initialize the SDK (throws if already initialized) |
64
+ | `init(connectionString, options?)` | Initialize the SDK |
223
65
  | `shutdown()` | Stop timers, flush remaining data, and await final upload |
224
66
 
225
67
  ### Capture Functions
@@ -241,6 +83,23 @@ traceway.captureExceptionWithAttributes(err, {}, traceId);
241
83
  | `startSpan(name)` | Start a span (returns `SpanHandle`) |
242
84
  | `endSpan(span, addToContext?)` | End span (auto-adds to context by default) |
243
85
 
86
+ ### Context API
87
+
88
+ | Function | Description |
89
+ |----------|-------------|
90
+ | `withTraceContext(options, fn)` | Run function within a new trace context |
91
+ | `runWithTraceContext(options, fn)` | Run function within a new trace context (alternative) |
92
+ | `getTraceContext()` | Get current context (or `undefined`) |
93
+ | `getTraceId()` | Get current trace ID (or `undefined`) |
94
+ | `hasTraceContext()` | Check if inside a trace context |
95
+ | `setTraceAttribute(key, value)` | Set attribute on current trace |
96
+ | `setTraceAttributes(attrs)` | Set multiple attributes |
97
+ | `setTraceResponseInfo(status, size?)` | Set HTTP response info |
98
+ | `addSpanToContext(span)` | Manually add a span |
99
+ | `getTraceSpans()` | Get all spans in current trace |
100
+ | `getTraceDuration()` | Get elapsed time in ms |
101
+ | `forkTraceContext(fn)` | Fork context for parallel ops |
102
+
244
103
  ### Utility Functions
245
104
 
246
105
  | Function | Description |
@@ -248,28 +107,6 @@ traceway.captureExceptionWithAttributes(err, {}, traceId);
248
107
  | `shouldSample(isError)` | Check if trace should be recorded |
249
108
  | `measureTask(title, fn)` | Execute function as auto-captured task |
250
109
 
251
- ### Span Handle vs Span Object
252
-
253
- `startSpan()` returns a **span handle** for tracking:
254
- ```ts
255
- interface SpanHandle {
256
- id: string; // UUID
257
- name: string; // Operation name
258
- startTime: string; // ISO timestamp
259
- startedAt: number; // Unix ms (for duration calc)
260
- }
261
- ```
262
-
263
- `endSpan()` returns a **Span object** for the trace:
264
- ```ts
265
- interface Span {
266
- id: string; // Same UUID
267
- name: string; // Operation name
268
- startTime: string; // ISO timestamp
269
- duration: number; // Nanoseconds
270
- }
271
- ```
272
-
273
110
  ## Configuration Options
274
111
 
275
112
  | Option | Type | Default | Description |
@@ -284,85 +121,6 @@ interface Span {
284
121
  | `sampleRate` | `number` | `1.0` | Normal trace sampling rate (0.0-1.0) |
285
122
  | `errorSampleRate` | `number` | `1.0` | Error trace sampling rate (0.0-1.0) |
286
123
 
287
- ## Collection Frame Lifecycle
288
-
289
- ```
290
- ┌──────────────────────────────────────────────────────────────────┐
291
- │ 1. COLLECT │
292
- │ captureException/Trace/Metric ──► Current CollectionFrame │
293
- │ │
294
- │ 2. ROTATE (every collectionInterval ms) │
295
- │ Current frame ──► Send Queue (ring buffer, max 12 frames) │
296
- │ │
297
- │ 3. UPLOAD (respects uploadThrottle) │
298
- │ Send Queue ──► gzip ──► POST /api/report │
299
- │ Headers: Authorization: Bearer {token} │
300
- │ Content-Type: application/json │
301
- │ Content-Encoding: gzip │
302
- │ │
303
- │ 4. CLEANUP │
304
- │ 200 OK ──► Remove frames from queue │
305
- │ Failure ──► Retain frames for retry │
306
- │ │
307
- │ 5. SHUTDOWN │
308
- │ shutdown() ──► Rotate current ──► Upload all ──► Stop timers │
309
- └──────────────────────────────────────────────────────────────────┘
310
- ```
311
-
312
- ## Auto-Collected Metrics
313
-
314
- Every `metricsInterval` ms (default: 30s), these metrics are automatically captured:
315
-
316
- | Name | Description | Unit |
317
- |------|-------------|------|
318
- | `mem.used` | Process RSS memory | MB |
319
- | `mem.total` | Total system memory | MB |
320
- | `cpu.used_pcnt` | CPU usage (delta-based) | % (0-100) |
321
-
322
- ## Sampling
323
-
324
- Use sampling to reduce data volume in high-traffic applications:
325
-
326
- ```ts
327
- traceway.init(connectionString, {
328
- sampleRate: 0.1, // Record 10% of normal traces
329
- errorSampleRate: 1.0, // Record 100% of error traces
330
- });
331
-
332
- // In your code, check before capturing:
333
- if (traceway.shouldSample(isError)) {
334
- traceway.captureTrace(...);
335
- }
336
- ```
337
-
338
- The `measureTask()` function automatically respects sampling rates.
339
-
340
- ## Comparison with Go Client
341
-
342
- | Go Client Feature | JS Backend Equivalent |
343
- |-------------------|----------------------|
344
- | `Init(connectionString, opts...)` | `init(connectionString, options)` |
345
- | `StartTrace(ctx) context.Context` | `withTraceContext(options, fn)` |
346
- | `GetTraceFromContext(ctx)` | `getTraceContext()` |
347
- | `GetTraceIdFromContext(ctx)` | `getTraceId()` |
348
- | `GetAttributesFromContext(ctx)` | `getTraceContext()?.attributes` |
349
- | `WithAttributes(ctx, fn)` | `setTraceAttribute()` / `setTraceAttributes()` |
350
- | `StartSpan(ctx, name)` | `startSpan(name)` (auto-adds on `endSpan()`) |
351
- | `CaptureException(err)` | `captureException(err)` (auto-detects context) |
352
- | `CaptureExceptionWithContext(ctx, err)` | `captureException(err)` inside `withTraceContext()` |
353
- | `CaptureTrace(tc, ...)` | `captureCurrentTrace()` or `captureTrace(...)` |
354
- | `CaptureTask(tc, ...)` | `captureCurrentTrace()` with `isTask: true` |
355
- | `MeasureTask(title, fn)` | `measureTask(title, fn)` |
356
- | `Recover()` / `RecoverWithContext(ctx)` | Use try/catch with `captureException()` |
357
- | `tracewayhttp.New(...)` | Build with `withTraceContext()` (see examples) |
358
- | `tracewaygin.New(...)` | Build with `withTraceContext()` (see examples) |
359
- | `tracewaydb.NewTwDB(...)` | Wrap DB calls with `startSpan()`/`endSpan()` |
360
-
361
- ## Thread Safety
124
+ ## Requirements
362
125
 
363
- The SDK is designed for concurrent use in Node.js:
364
- - `AsyncLocalStorage` provides request isolation automatically
365
- - `CollectionFrameStore` uses synchronous operations (Node.js event loop)
366
- - Timer callbacks are non-blocking via `unref()`
367
- - `shutdown()` is async and awaits the final upload
368
- - Parallel requests each get their own isolated trace context
126
+ - Node.js >= 18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tracewayapp/backend",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Traceway SDK for Node.js backends",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -23,6 +23,6 @@
23
23
  "dev": "tsup --watch"
24
24
  },
25
25
  "dependencies": {
26
- "@tracewayapp/core": "0.2.0"
26
+ "@tracewayapp/core": "0.3.0"
27
27
  }
28
28
  }