@virtu3d/event-manager 0.2.7
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/LICENSE +21 -0
- package/README.md +624 -0
- package/dist/browser/index.d.mts +631 -0
- package/dist/browser/index.d.ts +631 -0
- package/dist/browser/index.js +1140 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/index.mjs +1104 -0
- package/dist/browser/index.mjs.map +1 -0
- package/dist/context/index.d.mts +111 -0
- package/dist/context/index.d.ts +111 -0
- package/dist/context/index.js +186 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/index.mjs +176 -0
- package/dist/context/index.mjs.map +1 -0
- package/dist/index.d.mts +568 -0
- package/dist/index.d.ts +568 -0
- package/dist/index.js +1044 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +999 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +118 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Virtu3D
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
# @fountaneengineering/event-manager
|
|
2
|
+
|
|
3
|
+
[](https://gitlab.com/fountaneEngineering/consutling-projects/virtu/event-manager)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Lightweight event router for analytics and observability with browser support and distributed tracing.
|
|
7
|
+
|
|
8
|
+
## Latest Release: v0.2.6
|
|
9
|
+
|
|
10
|
+
### What's New
|
|
11
|
+
- **Fixed**: Mixpanel/PostHog connectors now lazy-loaded - no Node.js dependencies bundled in browser builds
|
|
12
|
+
- **Fixed**: Console.log statements removed from production trace interceptors
|
|
13
|
+
- **Fixed**: Peer dependencies updated to require OTEL SDK v2.x (prevents silent span loss)
|
|
14
|
+
- **Fixed**: Circular self-dependency removed from package.json
|
|
15
|
+
- **Improved**: Resource fallback for older OTEL SDK versions
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### 1. Configure npm registry
|
|
20
|
+
|
|
21
|
+
Create or update `.npmrc` in your project root:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
@fountaneengineering:registry=https://gitlab.com/api/v4/projects/77867432/packages/npm/
|
|
25
|
+
//gitlab.com/api/v4/projects/77867432/packages/npm/:_authToken=${GITLAB_TOKEN}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Install the package
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @fountaneengineering/event-manager
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Install peer dependencies (as needed)
|
|
35
|
+
|
|
36
|
+
**Browser environments:**
|
|
37
|
+
```bash
|
|
38
|
+
# For OpenTelemetry distributed tracing (browser)
|
|
39
|
+
npm install @opentelemetry/api@^1.9.0 \
|
|
40
|
+
@opentelemetry/sdk-trace-web@^2.0.0 \
|
|
41
|
+
@opentelemetry/sdk-trace-base@^2.0.0 \
|
|
42
|
+
@opentelemetry/resources@^2.0.0 \
|
|
43
|
+
@opentelemetry/context-zone@^2.0.0 \
|
|
44
|
+
@opentelemetry/exporter-trace-otlp-http@^0.200.0 \
|
|
45
|
+
@opentelemetry/semantic-conventions@^1.0.0
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Node.js environments:**
|
|
49
|
+
```bash
|
|
50
|
+
# For OpenTelemetry distributed tracing (Node.js)
|
|
51
|
+
npm install @opentelemetry/api@^1.9.0 \
|
|
52
|
+
@opentelemetry/sdk-node@^0.200.0
|
|
53
|
+
|
|
54
|
+
# For Mixpanel analytics (Node.js only - lazy loaded)
|
|
55
|
+
npm install mixpanel@^0.18.0
|
|
56
|
+
|
|
57
|
+
# For PostHog analytics (Node.js only - lazy loaded)
|
|
58
|
+
npm install posthog-node@^3.0.0
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> **Note**: Mixpanel and PostHog connectors are **lazy-loaded** and only work in Node.js environments. They will not be bundled in browser builds.
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { TelemetryRouter, installFetchInterceptor } from '@fountaneengineering/event-manager/browser';
|
|
67
|
+
|
|
68
|
+
// Initialize telemetry
|
|
69
|
+
const telemetry = new TelemetryRouter({
|
|
70
|
+
debug: true,
|
|
71
|
+
providers: [
|
|
72
|
+
{
|
|
73
|
+
name: 'console',
|
|
74
|
+
type: 'console',
|
|
75
|
+
config: { pretty: true },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'otel',
|
|
79
|
+
type: 'otel-browser',
|
|
80
|
+
config: {
|
|
81
|
+
serviceName: 'my-app',
|
|
82
|
+
collectorUrl: 'http://localhost:4318/v1/traces',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Install fetch interceptor for automatic trace propagation
|
|
89
|
+
installFetchInterceptor({
|
|
90
|
+
propagateToUrls: [/api\.example\.com/, /localhost:3000/],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Track events
|
|
94
|
+
telemetry.track('button_click', { button_id: 'login' });
|
|
95
|
+
|
|
96
|
+
// Identify users
|
|
97
|
+
telemetry.identify('user-123', { email: 'user@example.com', plan: 'pro' });
|
|
98
|
+
|
|
99
|
+
// Track page views
|
|
100
|
+
telemetry.page('Dashboard', { section: 'analytics' });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Features
|
|
104
|
+
|
|
105
|
+
- **Multi-provider routing** - Send events to multiple analytics/observability providers simultaneously
|
|
106
|
+
- **Browser-optimized** - Lightweight bundle with tree-shaking support
|
|
107
|
+
- **Distributed tracing** - W3C Trace Context propagation across services
|
|
108
|
+
- **Fetch interceptor** - Automatic trace header injection for fetch/XHR/Axios
|
|
109
|
+
- **Type-safe** - Full TypeScript support with comprehensive types
|
|
110
|
+
- **Plugin system** - Extend functionality with custom plugins
|
|
111
|
+
- **Zero required dependencies** - All providers are optional peer dependencies
|
|
112
|
+
|
|
113
|
+
## Examples
|
|
114
|
+
|
|
115
|
+
### Analytics Example
|
|
116
|
+
|
|
117
|
+
Track user behavior and product analytics with Mixpanel or PostHog:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager/browser';
|
|
121
|
+
|
|
122
|
+
const analytics = new TelemetryRouter({
|
|
123
|
+
providers: [
|
|
124
|
+
{
|
|
125
|
+
name: 'mixpanel',
|
|
126
|
+
type: 'mixpanel',
|
|
127
|
+
config: { token: 'your-mixpanel-token' },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'posthog',
|
|
131
|
+
type: 'posthog',
|
|
132
|
+
config: {
|
|
133
|
+
apiKey: 'your-posthog-key',
|
|
134
|
+
host: 'https://app.posthog.com',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Identify user on login
|
|
141
|
+
analytics.identify('user-123', {
|
|
142
|
+
email: 'user@example.com',
|
|
143
|
+
plan: 'premium',
|
|
144
|
+
signupDate: '2024-01-15',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Track feature usage
|
|
148
|
+
analytics.track('feature_used', {
|
|
149
|
+
feature: 'export_report',
|
|
150
|
+
format: 'pdf',
|
|
151
|
+
itemCount: 50,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Track conversions
|
|
155
|
+
analytics.track('purchase_completed', {
|
|
156
|
+
orderId: 'ORD-456',
|
|
157
|
+
amount: 99.99,
|
|
158
|
+
currency: 'USD',
|
|
159
|
+
items: ['product-a', 'product-b'],
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Track page views
|
|
163
|
+
analytics.page('Pricing', {
|
|
164
|
+
referrer: 'homepage',
|
|
165
|
+
variant: 'annual-discount',
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Observability Example
|
|
170
|
+
|
|
171
|
+
Distributed tracing with OpenTelemetry for debugging and performance monitoring:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import {
|
|
175
|
+
TelemetryRouter,
|
|
176
|
+
installFetchInterceptor,
|
|
177
|
+
startSpan,
|
|
178
|
+
getTraceHeaders,
|
|
179
|
+
} from '@fountaneengineering/event-manager/browser';
|
|
180
|
+
|
|
181
|
+
// Initialize with OTEL collector
|
|
182
|
+
const observability = new TelemetryRouter({
|
|
183
|
+
providers: [
|
|
184
|
+
{
|
|
185
|
+
name: 'otel',
|
|
186
|
+
type: 'otel-browser',
|
|
187
|
+
config: {
|
|
188
|
+
serviceName: 'frontend-app',
|
|
189
|
+
collectorUrl: 'https://otel-collector.example.com/v1/traces',
|
|
190
|
+
resourceAttributes: {
|
|
191
|
+
'deployment.environment': 'production',
|
|
192
|
+
'service.version': '1.2.3',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Auto-inject trace headers into API calls
|
|
200
|
+
installFetchInterceptor({
|
|
201
|
+
propagateToUrls: [/api\.example\.com/, /backend\.example\.com/],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Create custom spans for complex operations
|
|
205
|
+
async function checkout(cartId: string) {
|
|
206
|
+
const span = await startSpan('checkout-flow', 'checkout-service');
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
span.setAttribute('cart.id', cartId);
|
|
210
|
+
|
|
211
|
+
// Validate cart
|
|
212
|
+
const cart = await fetch(`/api/cart/${cartId}`);
|
|
213
|
+
span.addEvent('cart_validated');
|
|
214
|
+
|
|
215
|
+
// Process payment
|
|
216
|
+
const payment = await processPayment(cart);
|
|
217
|
+
span.setAttribute('payment.id', payment.id);
|
|
218
|
+
span.addEvent('payment_processed');
|
|
219
|
+
|
|
220
|
+
// Create order
|
|
221
|
+
const order = await createOrder(cart, payment);
|
|
222
|
+
span.setAttribute('order.id', order.id);
|
|
223
|
+
|
|
224
|
+
span.setStatus({ code: 1 }); // OK
|
|
225
|
+
return order;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
span.setStatus({ code: 2, message: error.message }); // ERROR
|
|
228
|
+
span.recordException(error);
|
|
229
|
+
throw error;
|
|
230
|
+
} finally {
|
|
231
|
+
span.end();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Track errors with trace context
|
|
236
|
+
window.onerror = (message, source, line, col, error) => {
|
|
237
|
+
observability.track('error_occurred', {
|
|
238
|
+
message,
|
|
239
|
+
source,
|
|
240
|
+
line,
|
|
241
|
+
column: col,
|
|
242
|
+
stack: error?.stack,
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Metrics Example
|
|
248
|
+
|
|
249
|
+
Track business and performance metrics:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager/browser';
|
|
253
|
+
|
|
254
|
+
const metrics = new TelemetryRouter({
|
|
255
|
+
providers: [
|
|
256
|
+
{
|
|
257
|
+
name: 'console',
|
|
258
|
+
type: 'console',
|
|
259
|
+
config: { pretty: true },
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'otel',
|
|
263
|
+
type: 'otel-browser',
|
|
264
|
+
config: {
|
|
265
|
+
serviceName: 'metrics-app',
|
|
266
|
+
collectorUrl: 'https://otel-collector.example.com/v1/traces',
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Performance metrics
|
|
273
|
+
metrics.track('page_load_time', {
|
|
274
|
+
metric: 'timing',
|
|
275
|
+
value: performance.timing.loadEventEnd - performance.timing.navigationStart,
|
|
276
|
+
unit: 'ms',
|
|
277
|
+
page: window.location.pathname,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Core Web Vitals
|
|
281
|
+
new PerformanceObserver((list) => {
|
|
282
|
+
for (const entry of list.getEntries()) {
|
|
283
|
+
metrics.track('web_vital', {
|
|
284
|
+
metric: entry.name,
|
|
285
|
+
value: entry.value,
|
|
286
|
+
rating: entry.rating,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}).observe({ type: 'largest-contentful-paint', buffered: true });
|
|
290
|
+
|
|
291
|
+
// Business metrics
|
|
292
|
+
metrics.track('revenue', {
|
|
293
|
+
metric: 'counter',
|
|
294
|
+
value: 149.99,
|
|
295
|
+
currency: 'USD',
|
|
296
|
+
source: 'subscription',
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
metrics.track('active_users', {
|
|
300
|
+
metric: 'gauge',
|
|
301
|
+
value: 1,
|
|
302
|
+
segment: 'premium',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// API performance
|
|
306
|
+
async function trackedFetch(url: string) {
|
|
307
|
+
const start = performance.now();
|
|
308
|
+
try {
|
|
309
|
+
const response = await fetch(url);
|
|
310
|
+
metrics.track('api_request', {
|
|
311
|
+
metric: 'histogram',
|
|
312
|
+
url,
|
|
313
|
+
duration: performance.now() - start,
|
|
314
|
+
status: response.status,
|
|
315
|
+
success: response.ok,
|
|
316
|
+
});
|
|
317
|
+
return response;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
metrics.track('api_request', {
|
|
320
|
+
metric: 'histogram',
|
|
321
|
+
url,
|
|
322
|
+
duration: performance.now() - start,
|
|
323
|
+
success: false,
|
|
324
|
+
error: error.message,
|
|
325
|
+
});
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Entry Points
|
|
332
|
+
|
|
333
|
+
The package provides three entry points:
|
|
334
|
+
|
|
335
|
+
| Entry Point | Use Case |
|
|
336
|
+
|-------------|----------|
|
|
337
|
+
| `@fountaneengineering/event-manager` | Node.js / Server-side |
|
|
338
|
+
| `@fountaneengineering/event-manager/browser` | Browser / Frontend apps |
|
|
339
|
+
| `@fountaneengineering/event-manager/context` | Context utilities only |
|
|
340
|
+
|
|
341
|
+
## Connectors
|
|
342
|
+
|
|
343
|
+
### Console Connector
|
|
344
|
+
|
|
345
|
+
Logs events to the console. Useful for development and debugging.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager/browser';
|
|
349
|
+
|
|
350
|
+
const telemetry = new TelemetryRouter({
|
|
351
|
+
providers: [
|
|
352
|
+
{
|
|
353
|
+
name: 'console',
|
|
354
|
+
type: 'console',
|
|
355
|
+
config: {
|
|
356
|
+
pretty: true, // Pretty print JSON
|
|
357
|
+
logLevel: 'debug', // 'debug' | 'info' | 'warn' | 'error'
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Browser OpenTelemetry Connector
|
|
365
|
+
|
|
366
|
+
Sends traces to an OpenTelemetry collector.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager/browser';
|
|
370
|
+
|
|
371
|
+
const telemetry = new TelemetryRouter({
|
|
372
|
+
providers: [
|
|
373
|
+
{
|
|
374
|
+
name: 'otel',
|
|
375
|
+
type: 'otel-browser',
|
|
376
|
+
config: {
|
|
377
|
+
serviceName: 'my-frontend',
|
|
378
|
+
collectorUrl: 'https://otel-collector.example.com/v1/traces',
|
|
379
|
+
resourceAttributes: {
|
|
380
|
+
'deployment.environment': 'production',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Mixpanel Connector
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager';
|
|
392
|
+
|
|
393
|
+
const telemetry = new TelemetryRouter({
|
|
394
|
+
providers: [
|
|
395
|
+
{
|
|
396
|
+
name: 'mixpanel',
|
|
397
|
+
type: 'mixpanel',
|
|
398
|
+
config: {
|
|
399
|
+
token: 'your-mixpanel-token',
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### PostHog Connector
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { TelemetryRouter } from '@fountaneengineering/event-manager';
|
|
410
|
+
|
|
411
|
+
const telemetry = new TelemetryRouter({
|
|
412
|
+
providers: [
|
|
413
|
+
{
|
|
414
|
+
name: 'posthog',
|
|
415
|
+
type: 'posthog',
|
|
416
|
+
config: {
|
|
417
|
+
apiKey: 'your-posthog-key',
|
|
418
|
+
host: 'https://app.posthog.com', // or your self-hosted instance
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Fetch Interceptor
|
|
426
|
+
|
|
427
|
+
Automatically inject trace headers into outgoing HTTP requests.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { installFetchInterceptor, uninstallFetchInterceptor } from '@fountaneengineering/event-manager/browser';
|
|
431
|
+
|
|
432
|
+
// Install interceptor
|
|
433
|
+
installFetchInterceptor({
|
|
434
|
+
propagateToUrls: [
|
|
435
|
+
/api\.example\.com/,
|
|
436
|
+
/localhost:3000/,
|
|
437
|
+
'https://specific-url.com/api',
|
|
438
|
+
],
|
|
439
|
+
excludeUrls: [/analytics\.google\.com/],
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// All fetch calls to matching URLs now include trace headers
|
|
443
|
+
fetch('https://api.example.com/users'); // traceparent header automatically added
|
|
444
|
+
|
|
445
|
+
// Uninstall when done
|
|
446
|
+
uninstallFetchInterceptor();
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### XHR Interceptor
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
import { installXHRInterceptor } from '@fountaneengineering/event-manager/browser';
|
|
453
|
+
|
|
454
|
+
installXHRInterceptor({
|
|
455
|
+
propagateToUrls: [/api\.example\.com/],
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## React Integration
|
|
460
|
+
|
|
461
|
+
Create a custom hook:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
// useTelemetry.ts
|
|
465
|
+
import { useEffect, useRef } from 'react';
|
|
466
|
+
import { TelemetryRouter, installFetchInterceptor } from '@fountaneengineering/event-manager/browser';
|
|
467
|
+
|
|
468
|
+
let telemetryInstance: TelemetryRouter | null = null;
|
|
469
|
+
|
|
470
|
+
function getTelemetry(): TelemetryRouter {
|
|
471
|
+
if (!telemetryInstance) {
|
|
472
|
+
telemetryInstance = new TelemetryRouter({
|
|
473
|
+
debug: process.env.NODE_ENV === 'development',
|
|
474
|
+
providers: [
|
|
475
|
+
{
|
|
476
|
+
name: 'otel',
|
|
477
|
+
type: 'otel-browser',
|
|
478
|
+
config: {
|
|
479
|
+
serviceName: 'react-app',
|
|
480
|
+
collectorUrl: process.env.REACT_APP_OTEL_URL,
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
installFetchInterceptor({
|
|
487
|
+
propagateToUrls: [process.env.REACT_APP_API_URL!],
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return telemetryInstance;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function useTelemetry() {
|
|
494
|
+
const telemetry = getTelemetry();
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
track: (event: string, properties?: Record<string, unknown>) =>
|
|
498
|
+
telemetry.track(event, properties),
|
|
499
|
+
identify: (userId: string, traits?: Record<string, unknown>) =>
|
|
500
|
+
telemetry.identify(userId, traits),
|
|
501
|
+
page: (name: string, properties?: Record<string, unknown>) =>
|
|
502
|
+
telemetry.page(name, properties),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Usage:
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
function LoginButton() {
|
|
511
|
+
const { track, identify } = useTelemetry();
|
|
512
|
+
|
|
513
|
+
const handleLogin = async () => {
|
|
514
|
+
const user = await login();
|
|
515
|
+
identify(user.id, { email: user.email });
|
|
516
|
+
track('user_login', { method: 'oauth' });
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
return <button onClick={handleLogin}>Login</button>;
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Axios Integration
|
|
524
|
+
|
|
525
|
+
For Axios, use the trace interceptor:
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
import axios from 'axios';
|
|
529
|
+
import { createAxiosTraceInterceptor } from '@fountaneengineering/event-manager/browser';
|
|
530
|
+
|
|
531
|
+
const api = axios.create({
|
|
532
|
+
baseURL: 'https://api.example.com',
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// Add trace interceptor
|
|
536
|
+
api.interceptors.request.use(
|
|
537
|
+
createAxiosTraceInterceptor({
|
|
538
|
+
propagateToUrls: [/api\.example\.com/],
|
|
539
|
+
})
|
|
540
|
+
);
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Manual Header Injection
|
|
544
|
+
|
|
545
|
+
If you need manual control:
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
import { getTraceHeaders } from '@fountaneengineering/event-manager/browser';
|
|
549
|
+
|
|
550
|
+
async function fetchData() {
|
|
551
|
+
const headers = await getTraceHeaders();
|
|
552
|
+
|
|
553
|
+
const response = await fetch('/api/data', {
|
|
554
|
+
headers: {
|
|
555
|
+
'Content-Type': 'application/json',
|
|
556
|
+
...headers, // { traceparent: '00-...', tracestate: '...' }
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
return response.json();
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Custom Spans
|
|
565
|
+
|
|
566
|
+
Create custom spans for detailed tracing:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
import { startSpan } from '@fountaneengineering/event-manager/browser';
|
|
570
|
+
|
|
571
|
+
async function processOrder(orderId: string) {
|
|
572
|
+
const span = await startSpan('process-order', 'order-service');
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
span.setAttribute('order.id', orderId);
|
|
576
|
+
|
|
577
|
+
const result = await submitOrder(orderId);
|
|
578
|
+
|
|
579
|
+
span.setStatus({ code: 1 }); // SpanStatusCode.OK
|
|
580
|
+
return result;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
span.setStatus({ code: 2, message: error.message }); // SpanStatusCode.ERROR
|
|
583
|
+
throw error;
|
|
584
|
+
} finally {
|
|
585
|
+
span.end();
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## API Reference
|
|
591
|
+
|
|
592
|
+
### TelemetryRouter
|
|
593
|
+
|
|
594
|
+
| Method | Description |
|
|
595
|
+
|--------|-------------|
|
|
596
|
+
| `track(event, payload?)` | Track a custom event |
|
|
597
|
+
| `identify(userId, traits?)` | Identify a user with optional traits |
|
|
598
|
+
| `page(name?, properties?)` | Track a page view |
|
|
599
|
+
| `registerConnector(connector)` | Register a connector instance |
|
|
600
|
+
| `unregisterConnector(name)` | Remove a connector |
|
|
601
|
+
| `addPlugin(plugin)` | Add a router plugin |
|
|
602
|
+
| `getConnectors()` | Get registered connector names |
|
|
603
|
+
| `shutdown()` | Gracefully shutdown all connectors |
|
|
604
|
+
|
|
605
|
+
### Context Utilities
|
|
606
|
+
|
|
607
|
+
| Function | Description |
|
|
608
|
+
|----------|-------------|
|
|
609
|
+
| `getTraceHeaders()` | Get current trace headers (async) |
|
|
610
|
+
| `getTraceHeadersSync()` | Get trace headers synchronously |
|
|
611
|
+
| `extractTraceContext(headers)` | Extract trace context from headers |
|
|
612
|
+
| `withTraceContext(headers, fn)` | Run function within trace context |
|
|
613
|
+
| `startSpan(name, serviceName?)` | Create a new span |
|
|
614
|
+
| `generateTraceId()` | Generate a random trace ID |
|
|
615
|
+
| `generateSpanId()` | Generate a random span ID |
|
|
616
|
+
|
|
617
|
+
### Interceptors
|
|
618
|
+
|
|
619
|
+
| Function | Description |
|
|
620
|
+
|----------|-------------|
|
|
621
|
+
| `installFetchInterceptor(config)` | Install global fetch interceptor |
|
|
622
|
+
| `uninstallFetchInterceptor()` | Remove fetch interceptor |
|
|
623
|
+
| `installXHRInterceptor(config)` | Install XHR interceptor |
|
|
624
|
+
| `createAxiosTraceInterceptor(config)` | Create Axios request interceptor |
|