@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.
- package/README.md +62 -304
- 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.
|
|
3
|
+
Traceway SDK for Node.js backends. Provides error tracking, distributed tracing, span management, and metrics collection.
|
|
4
4
|
|
|
5
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|
|
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.
|
|
26
|
+
"@tracewayapp/core": "0.3.0"
|
|
27
27
|
}
|
|
28
28
|
}
|