@atrim/instrument-web 0.4.0-56522df-20251118051035

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 ADDED
@@ -0,0 +1,360 @@
1
+ # @atrim/instrument-web
2
+
3
+ OpenTelemetry instrumentation for browsers with centralized YAML configuration.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@atrim/instrument-web.svg)](https://www.npmjs.com/package/@atrim/instrument-web)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - ✅ **Zero-config OpenTelemetry setup** for browser applications
11
+ - ✅ **Auto-instrumentation** (fetch, XHR, document load, user interactions)
12
+ - ✅ **Pattern-based span filtering** via `instrumentation.yaml`
13
+ - ✅ **OTLP export over HTTP** (browser-compatible)
14
+ - ✅ **TypeScript support** with full type definitions
15
+ - ✅ **<50KB bundle size** (gzipped) - optimized for web
16
+ - ✅ **Modern browsers** (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @atrim/instrument-web
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { initializeInstrumentation } from '@atrim/instrument-web'
28
+
29
+ // Initialize once at application startup
30
+ await initializeInstrumentation({
31
+ serviceName: 'my-app',
32
+ otlpEndpoint: 'http://localhost:4318/v1/traces'
33
+ })
34
+
35
+ // That's it! Auto-instrumentation is now active.
36
+ ```
37
+
38
+ ## Auto-Instrumentations
39
+
40
+ The following are automatically instrumented when you initialize:
41
+
42
+ ### 1. Document Load
43
+ Captures page load timing metrics (LCP, FCP, TTFB).
44
+
45
+ ### 2. Fetch API
46
+ All `fetch()` calls are automatically traced with:
47
+ - HTTP method, URL, status code
48
+ - Request/response headers (configurable)
49
+ - Request/response timing
50
+
51
+ ### 3. XMLHttpRequest (XHR)
52
+ Legacy AJAX requests are traced automatically.
53
+
54
+ ### 4. User Interactions
55
+ Click events and form submissions are captured.
56
+
57
+ ## Manual Instrumentation
58
+
59
+ You can also create custom spans:
60
+
61
+ ```typescript
62
+ import { trace } from '@opentelemetry/api'
63
+
64
+ const tracer = trace.getTracer('my-app')
65
+
66
+ tracer.startActiveSpan('my-operation', (span) => {
67
+ try {
68
+ // Your code here
69
+ span.setAttribute('user.id', '123')
70
+ span.setAttribute('operation.type', 'checkout')
71
+
72
+ // ... do work ...
73
+
74
+ span.setStatus({ code: 1 }) // OK
75
+ } catch (error) {
76
+ span.recordException(error)
77
+ span.setStatus({ code: 2 }) // ERROR
78
+ throw error
79
+ } finally {
80
+ span.end()
81
+ }
82
+ })
83
+ ```
84
+
85
+ ## Span Helpers
86
+
87
+ We provide convenient helpers for common annotations:
88
+
89
+ ```typescript
90
+ import {
91
+ annotateHttpRequest,
92
+ annotateCacheOperation,
93
+ annotateUserInteraction,
94
+ annotateNavigation
95
+ } from '@atrim/instrument-web'
96
+
97
+ // HTTP request
98
+ annotateHttpRequest(span, {
99
+ method: 'GET',
100
+ url: 'https://api.example.com/users',
101
+ statusCode: 200,
102
+ responseTime: 150
103
+ })
104
+
105
+ // Cache operation
106
+ annotateCacheOperation(span, {
107
+ operation: 'get',
108
+ key: 'user:123',
109
+ hit: true
110
+ })
111
+
112
+ // User interaction
113
+ annotateUserInteraction(span, {
114
+ type: 'click',
115
+ target: 'button#submit',
116
+ page: '/checkout'
117
+ })
118
+
119
+ // Navigation
120
+ annotateNavigation(span, {
121
+ from: '/home',
122
+ to: '/profile',
123
+ type: 'client-side'
124
+ })
125
+ ```
126
+
127
+ ## Configuration
128
+
129
+ ### Basic Configuration
130
+
131
+ ```typescript
132
+ await initializeInstrumentation({
133
+ serviceName: 'my-app', // Required
134
+ serviceVersion: '1.0.0', // Optional
135
+ otlpEndpoint: 'http://localhost:4318/v1/traces', // Optional
136
+ otlpHeaders: { // Optional
137
+ 'Authorization': 'Bearer token'
138
+ }
139
+ })
140
+ ```
141
+
142
+ ### Pattern-Based Filtering
143
+
144
+ Create an `instrumentation.yaml` file:
145
+
146
+ ```yaml
147
+ version: "1.0"
148
+
149
+ instrumentation:
150
+ enabled: true
151
+
152
+ # Only instrument these patterns
153
+ instrument_patterns:
154
+ - pattern: "^documentLoad"
155
+ description: "Page load metrics"
156
+ - pattern: "^HTTP (GET|POST)"
157
+ description: "API requests"
158
+ - pattern: "^click"
159
+ description: "User clicks"
160
+
161
+ # Ignore these patterns
162
+ ignore_patterns:
163
+ - pattern: "^HTTP GET /health"
164
+ description: "Health check endpoints"
165
+ - pattern: "^HTTP.*analytics"
166
+ description: "Analytics requests"
167
+ ```
168
+
169
+ Then load it:
170
+
171
+ ```typescript
172
+ await initializeInstrumentation({
173
+ serviceName: 'my-app',
174
+ configPath: '/instrumentation.yaml' // Path relative to your app
175
+ })
176
+ ```
177
+
178
+ ### Remote Configuration
179
+
180
+ Load configuration from a remote URL:
181
+
182
+ ```typescript
183
+ await initializeInstrumentation({
184
+ serviceName: 'my-app',
185
+ configUrl: 'https://config.company.com/instrumentation.yaml'
186
+ })
187
+ ```
188
+
189
+ ### Disable Specific Instrumentations
190
+
191
+ ```typescript
192
+ await initializeInstrumentation({
193
+ serviceName: 'my-app',
194
+ enableDocumentLoad: true, // Page load metrics
195
+ enableUserInteraction: false, // Disable click tracking
196
+ enableFetch: true, // HTTP requests via fetch()
197
+ enableXhr: false // Disable XMLHttpRequest
198
+ })
199
+ ```
200
+
201
+ ## Environment Variables
202
+
203
+ Configure via Vite/Webpack environment variables:
204
+
205
+ ```bash
206
+ # .env
207
+ VITE_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
208
+ ```
209
+
210
+ Or set at runtime via window object:
211
+
212
+ ```typescript
213
+ window.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://custom:4318/v1/traces'
214
+ ```
215
+
216
+ ## Browser Considerations
217
+
218
+ ### CORS
219
+
220
+ Your OpenTelemetry collector must have CORS enabled:
221
+
222
+ ```yaml
223
+ # otel-collector-config.yaml
224
+ receivers:
225
+ otlp:
226
+ protocols:
227
+ http:
228
+ cors:
229
+ allowed_origins:
230
+ - "http://localhost:3000"
231
+ - "https://your-app.com"
232
+ allowed_headers:
233
+ - "*"
234
+ ```
235
+
236
+ ### Content Security Policy (CSP)
237
+
238
+ Add your OTLP endpoint to CSP:
239
+
240
+ ```html
241
+ <meta http-equiv="Content-Security-Policy"
242
+ content="connect-src 'self' http://localhost:4318">
243
+ ```
244
+
245
+ ### Bundle Size
246
+
247
+ The package is optimized for web with:
248
+ - ESM-only (no CJS overhead)
249
+ - Tree-shakeable exports
250
+ - Code splitting support
251
+ - Minification-friendly
252
+
253
+ **Estimated sizes:**
254
+ - Core SDK: ~15KB (gzipped)
255
+ - Auto-instrumentations: ~20KB (gzipped)
256
+ - OTLP exporter: ~10KB (gzipped)
257
+ - **Total: ~45KB (gzipped)**
258
+
259
+ ## Examples
260
+
261
+ See the [examples/web-vanilla](../../examples/web-vanilla) directory for a complete working example.
262
+
263
+ ## API Reference
264
+
265
+ ### `initializeInstrumentation(options)`
266
+
267
+ Initialize OpenTelemetry for the browser.
268
+
269
+ **Options:**
270
+ - `serviceName` (string, required) - Service name for telemetry
271
+ - `serviceVersion` (string, optional) - Service version
272
+ - `otlpEndpoint` (string, optional) - OTLP HTTP endpoint (default: `http://localhost:4318/v1/traces`)
273
+ - `otlpHeaders` (object, optional) - Custom HTTP headers for OTLP export
274
+ - `configPath` (string, optional) - Path to `instrumentation.yaml` file
275
+ - `configUrl` (string, optional) - URL to remote `instrumentation.yaml`
276
+ - `config` (object, optional) - Inline configuration object
277
+ - `enableDocumentLoad` (boolean, optional) - Enable document load instrumentation (default: true)
278
+ - `enableUserInteraction` (boolean, optional) - Enable user interaction instrumentation (default: true)
279
+ - `enableFetch` (boolean, optional) - Enable fetch instrumentation (default: true)
280
+ - `enableXhr` (boolean, optional) - Enable XMLHttpRequest instrumentation (default: true)
281
+
282
+ **Returns:** `Promise<WebTracerProvider>`
283
+
284
+ ### `getSdkInstance()`
285
+
286
+ Get the current SDK instance.
287
+
288
+ **Returns:** `WebTracerProvider | null`
289
+
290
+ ### `shutdownSdk()`
291
+
292
+ Shutdown the SDK gracefully.
293
+
294
+ **Returns:** `Promise<void>`
295
+
296
+ ## Comparison with Node.js Package
297
+
298
+ | Feature | @atrim/instrument-web | @atrim/instrument-node |
299
+ |---------|----------------------|------------------------|
300
+ | Platform | Browser | Node.js, Bun, Deno |
301
+ | Export Protocol | HTTP only | HTTP + gRPC |
302
+ | Auto-instrumentations | Fetch, XHR, DOM events | HTTP, gRPC, DB, etc. |
303
+ | Effect-TS | ❌ No | ✅ Yes |
304
+ | Bundle Size | <50KB | N/A |
305
+ | Service Detection | Manual | Automatic |
306
+
307
+ ## Requirements
308
+
309
+ - **Modern browsers:** Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
310
+ - **Build tool:** Vite, Webpack, or similar (for bundling)
311
+ - **OpenTelemetry Collector:** Running with HTTP endpoint
312
+
313
+ ## Troubleshooting
314
+
315
+ ### Traces not appearing
316
+
317
+ 1. **Check collector is running:**
318
+ ```bash
319
+ docker run -p 4318:4318 otel/opentelemetry-collector
320
+ ```
321
+
322
+ 2. **Check CORS configuration** on your collector
323
+
324
+ 3. **Check browser console** for errors
325
+
326
+ 4. **Verify endpoint:**
327
+ ```typescript
328
+ import { getOtlpEndpoint } from '@atrim/instrument-web'
329
+ console.log('OTLP endpoint:', getOtlpEndpoint())
330
+ ```
331
+
332
+ ### Performance issues
333
+
334
+ 1. **Disable unused instrumentations:**
335
+ ```typescript
336
+ await initializeInstrumentation({
337
+ serviceName: 'my-app',
338
+ enableUserInteraction: false, // Reduce overhead
339
+ enableDocumentLoad: false
340
+ })
341
+ ```
342
+
343
+ 2. **Use pattern filtering** to reduce span volume
344
+
345
+ 3. **Sample traces** at the collector level
346
+
347
+ ## Contributing
348
+
349
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup.
350
+
351
+ ## License
352
+
353
+ MIT License - see [LICENSE](../../LICENSE) for details.
354
+
355
+ ## Links
356
+
357
+ - [GitHub Repository](https://github.com/atrim-ai/instrumentation)
358
+ - [Issue Tracker](https://github.com/atrim-ai/instrumentation/issues)
359
+ - [OpenTelemetry JavaScript](https://opentelemetry.io/docs/languages/js/)
360
+ - [Atrim Platform](https://atrim.ai)
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@atrim/instrument-web",
3
+ "version": "0.4.0-56522df-20251118051035",
4
+ "description": "OpenTelemetry instrumentation for browsers with centralized YAML configuration",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Atrim AI",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/atrim-ai/instrumentation.git",
11
+ "directory": "packages/web"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/atrim-ai/instrumentation/issues"
15
+ },
16
+ "homepage": "https://github.com/atrim-ai/instrumentation#readme",
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "opentelemetry",
22
+ "instrumentation",
23
+ "tracing",
24
+ "observability",
25
+ "browser",
26
+ "web",
27
+ "frontend",
28
+ "spa",
29
+ "react",
30
+ "fetch",
31
+ "xhr"
32
+ ],
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "types": "./target/dist/index.d.ts",
39
+ "import": "./target/dist/index.js"
40
+ }
41
+ },
42
+ "main": "./target/dist/index.js",
43
+ "module": "./target/dist/index.js",
44
+ "types": "./target/dist/index.d.ts",
45
+ "files": [
46
+ "target/dist",
47
+ "README.md",
48
+ "LICENSE"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "dev": "tsup --watch",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "test:coverage": "vitest run --coverage",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "eslint src",
58
+ "lint:fix": "eslint src --fix",
59
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
60
+ "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
61
+ "clean": "rm -rf target",
62
+ "publish:dev:version": "npm version $(git describe --tags --abbrev=0 | sed 's/^.*@//' | sed 's/^v//')-$(git rev-parse --short HEAD)-$(date -u +%Y%m%d%H%M%S) --no-git-tag-version",
63
+ "publish:dev:save": "node -p \"require('./package.json').version\" > .version",
64
+ "publish:dev:publish": "pnpm build && npm publish --tag dev --access public",
65
+ "publish:dev:reset": "npm version 0.1.0 --no-git-tag-version",
66
+ "publish:dev": "pnpm publish:dev:version && pnpm publish:dev:save && pnpm publish:dev:publish && pnpm publish:dev:reset"
67
+ },
68
+ "dependencies": {
69
+ "@atrim/instrument-core": "workspace:*",
70
+ "@opentelemetry/api": "^1.9.0",
71
+ "@opentelemetry/auto-instrumentations-web": "^0.54.0",
72
+ "@opentelemetry/context-zone": "^2.2.0",
73
+ "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
74
+ "@opentelemetry/instrumentation": "^0.208.0",
75
+ "@opentelemetry/resources": "^2.2.0",
76
+ "@opentelemetry/sdk-trace-base": "^2.2.0",
77
+ "@opentelemetry/sdk-trace-web": "^2.2.0",
78
+ "yaml": "^2.8.1"
79
+ },
80
+ "devDependencies": {
81
+ "@opentelemetry/semantic-conventions": "^1.38.0",
82
+ "@types/node": "^20.10.0",
83
+ "@vitest/coverage-v8": "^4.0.8",
84
+ "jsdom": "^24.0.0",
85
+ "tsup": "^8.0.1",
86
+ "typescript": "^5.7.2",
87
+ "vitest": "^4.0.8"
88
+ }
89
+ }
@@ -0,0 +1,379 @@
1
+ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
2
+ import { InstrumentationConfig } from '@atrim/instrument-core';
3
+ export { ConfigError, ConfigFileError, ConfigLoaderOptions, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, InstrumentationConfig, PatternConfig, PatternMatcher, ShutdownError, clearConfigCache, clearPatternMatcher, getPatternMatcher, initializePatternMatcher, loadConfig, shouldInstrumentSpan } from '@atrim/instrument-core';
4
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
+ import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
6
+ import { Context, Span } from '@opentelemetry/api';
7
+
8
+ /**
9
+ * SDK Initializer for Browser
10
+ *
11
+ * Initializes the OpenTelemetry WebTracerProvider with:
12
+ * - Auto-instrumentation (fetch, XHR, document load, user interactions)
13
+ * - Pattern-based span filtering
14
+ * - OTLP HTTP export
15
+ */
16
+
17
+ interface SdkInitializationOptions {
18
+ /**
19
+ * Service name (required for browser - no auto-detection)
20
+ * @example 'my-app-frontend'
21
+ */
22
+ serviceName: string;
23
+ /**
24
+ * Service version (optional)
25
+ * @example '1.0.0'
26
+ */
27
+ serviceVersion?: string;
28
+ /**
29
+ * Path to instrumentation.yaml file
30
+ * Note: Only works if file is publicly accessible
31
+ */
32
+ configPath?: string;
33
+ /**
34
+ * URL to remote instrumentation.yaml
35
+ * @example 'https://config.company.com/instrumentation.yaml'
36
+ */
37
+ configUrl?: string;
38
+ /**
39
+ * Inline configuration object (takes precedence over file/URL)
40
+ */
41
+ config?: InstrumentationConfig;
42
+ /**
43
+ * OTLP endpoint URL
44
+ * @default 'http://localhost:4318/v1/traces'
45
+ */
46
+ otlpEndpoint?: string;
47
+ /**
48
+ * OTLP HTTP headers
49
+ * @example { 'Authorization': 'Bearer token' }
50
+ */
51
+ otlpHeaders?: Record<string, string>;
52
+ /**
53
+ * Enable document load instrumentation
54
+ * @default true
55
+ */
56
+ enableDocumentLoad?: boolean;
57
+ /**
58
+ * Enable user interaction instrumentation
59
+ * @default true
60
+ */
61
+ enableUserInteraction?: boolean;
62
+ /**
63
+ * Enable fetch API instrumentation
64
+ * @default true
65
+ */
66
+ enableFetch?: boolean;
67
+ /**
68
+ * Enable XMLHttpRequest instrumentation
69
+ * @default true
70
+ */
71
+ enableXhr?: boolean;
72
+ }
73
+ /**
74
+ * Get the current SDK instance
75
+ *
76
+ * @returns WebTracerProvider instance or null if not initialized
77
+ */
78
+ declare function getSdkInstance(): WebTracerProvider | null;
79
+ /**
80
+ * Shutdown the SDK gracefully
81
+ *
82
+ * Flushes pending spans and releases resources
83
+ */
84
+ declare function shutdownSdk(): Promise<void>;
85
+ /**
86
+ * Reset the SDK instance (for testing)
87
+ *
88
+ * Does not shutdown - just clears the singleton
89
+ */
90
+ declare function resetSdk(): void;
91
+
92
+ /**
93
+ * Public API for @atrim/instrument-web
94
+ *
95
+ * Main initialization functions for browser instrumentation
96
+ */
97
+
98
+ /**
99
+ * Initialize OpenTelemetry instrumentation for browser
100
+ *
101
+ * This is the main entry point for setting up tracing in browser applications.
102
+ * Call this function once at application startup, before any other code runs.
103
+ *
104
+ * @param options - Initialization options
105
+ * @returns WebTracerProvider instance
106
+ * @throws {Error} If initialization fails
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { initializeInstrumentation } from '@atrim/instrument-web'
111
+ *
112
+ * await initializeInstrumentation({
113
+ * serviceName: 'my-app',
114
+ * otlpEndpoint: 'http://localhost:4318/v1/traces'
115
+ * })
116
+ * ```
117
+ *
118
+ * @example With pattern-based filtering
119
+ * ```typescript
120
+ * await initializeInstrumentation({
121
+ * serviceName: 'my-app',
122
+ * configUrl: 'https://config.company.com/instrumentation.yaml'
123
+ * })
124
+ * ```
125
+ *
126
+ * @example Disable specific instrumentations
127
+ * ```typescript
128
+ * await initializeInstrumentation({
129
+ * serviceName: 'my-app',
130
+ * enableUserInteraction: false, // Disable click tracking
131
+ * enableXhr: false // Disable XMLHttpRequest tracking
132
+ * })
133
+ * ```
134
+ */
135
+ declare function initializeInstrumentation(options: SdkInitializationOptions): Promise<WebTracerProvider>;
136
+
137
+ /**
138
+ * OTLP Exporter Factory for Browser
139
+ *
140
+ * Creates OTLP HTTP exporters for sending traces from the browser.
141
+ * Browser only supports HTTP/JSON protocol (no gRPC).
142
+ */
143
+
144
+ interface OtlpExporterOptions {
145
+ /**
146
+ * OTLP endpoint URL
147
+ * Must end in /v1/traces for browser exporter
148
+ * @default 'http://localhost:4318/v1/traces'
149
+ */
150
+ endpoint?: string;
151
+ /**
152
+ * Custom HTTP headers (e.g., for authentication)
153
+ * @example { 'Authorization': 'Bearer token' }
154
+ */
155
+ headers?: Record<string, string>;
156
+ /**
157
+ * Request timeout in milliseconds
158
+ * @default 10000
159
+ */
160
+ timeout?: number;
161
+ }
162
+ /**
163
+ * Create an OTLP HTTP trace exporter for browser
164
+ *
165
+ * @param options - Exporter configuration options
166
+ * @returns OTLPTraceExporter instance
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const exporter = createOtlpExporter({
171
+ * endpoint: 'http://localhost:4318/v1/traces',
172
+ * headers: { 'x-api-key': 'secret' }
173
+ * })
174
+ * ```
175
+ */
176
+ declare function createOtlpExporter(options?: OtlpExporterOptions): OTLPTraceExporter;
177
+ /**
178
+ * Get OTLP endpoint from environment or use default
179
+ *
180
+ * Checks in order:
181
+ * 1. import.meta.env.VITE_OTEL_EXPORTER_OTLP_ENDPOINT (Vite)
182
+ * 2. window.OTEL_EXPORTER_OTLP_ENDPOINT (runtime config)
183
+ * 3. Default: http://localhost:4318/v1/traces
184
+ *
185
+ * @returns OTLP endpoint URL
186
+ */
187
+ declare function getOtlpEndpoint(): string;
188
+
189
+ /**
190
+ * Pattern-Based Span Processor for Browser
191
+ *
192
+ * Filters spans based on patterns defined in instrumentation.yaml
193
+ * Re-uses pattern matching logic from @atrim/instrument-core
194
+ */
195
+
196
+ /**
197
+ * Span processor that filters spans based on pattern matching
198
+ *
199
+ * This processor runs BEFORE export and drops spans that don't match
200
+ * the configured patterns in instrumentation.yaml.
201
+ *
202
+ * @example
203
+ * ```yaml
204
+ * # instrumentation.yaml
205
+ * instrumentation:
206
+ * instrument_patterns:
207
+ * - pattern: "^documentLoad"
208
+ * - pattern: "^HTTP (GET|POST)"
209
+ * ignore_patterns:
210
+ * - pattern: "^HTTP GET /health"
211
+ * ```
212
+ */
213
+ declare class PatternSpanProcessor implements SpanProcessor {
214
+ /**
215
+ * Called when a span is started
216
+ * No-op for this processor
217
+ */
218
+ onStart(_span: ReadableSpan, _parentContext: Context): void;
219
+ /**
220
+ * Called when a span ends
221
+ * Filters out spans that don't match patterns
222
+ */
223
+ onEnd(span: ReadableSpan): void;
224
+ /**
225
+ * Shutdown the processor
226
+ * No-op for this processor
227
+ */
228
+ shutdown(): Promise<void>;
229
+ /**
230
+ * Force flush pending spans
231
+ * No-op for this processor
232
+ */
233
+ forceFlush(): Promise<void>;
234
+ }
235
+
236
+ /**
237
+ * Browser Span Helpers
238
+ *
239
+ * Utility functions for adding metadata to spans in browser applications
240
+ */
241
+
242
+ /**
243
+ * Set multiple attributes on a span
244
+ *
245
+ * @param span - OpenTelemetry span
246
+ * @param attributes - Key-value pairs to set as attributes
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * setSpanAttributes(span, {
251
+ * 'user.id': '123',
252
+ * 'page.url': window.location.href
253
+ * })
254
+ * ```
255
+ */
256
+ declare function setSpanAttributes(span: Span, attributes: Record<string, string | number | boolean>): void;
257
+ /**
258
+ * Record an exception on a span
259
+ *
260
+ * @param span - OpenTelemetry span
261
+ * @param error - Error object to record
262
+ * @param context - Additional context attributes
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * try {
267
+ * // ...
268
+ * } catch (error) {
269
+ * recordException(span, error, { 'page.url': window.location.href })
270
+ * }
271
+ * ```
272
+ */
273
+ declare function recordException(span: Span, error: Error, context?: Record<string, string | number | boolean>): void;
274
+ /**
275
+ * Mark a span as successful
276
+ *
277
+ * Sets status to OK and adds optional attributes
278
+ *
279
+ * @param span - OpenTelemetry span
280
+ * @param attributes - Optional attributes to add
281
+ */
282
+ declare function markSpanSuccess(span: Span, attributes?: Record<string, string | number | boolean>): void;
283
+ /**
284
+ * Mark a span as failed
285
+ *
286
+ * Sets status to ERROR and adds optional error message/attributes
287
+ *
288
+ * @param span - OpenTelemetry span
289
+ * @param message - Error message
290
+ * @param attributes - Optional attributes to add
291
+ */
292
+ declare function markSpanError(span: Span, message?: string, attributes?: Record<string, string | number | boolean>): void;
293
+ /**
294
+ * Annotate span with HTTP request details
295
+ *
296
+ * @param span - OpenTelemetry span
297
+ * @param options - HTTP request details
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * annotateHttpRequest(span, {
302
+ * method: 'GET',
303
+ * url: 'https://api.example.com/users',
304
+ * statusCode: 200,
305
+ * responseTime: 150
306
+ * })
307
+ * ```
308
+ */
309
+ declare function annotateHttpRequest(span: Span, options: {
310
+ method?: string;
311
+ url?: string;
312
+ statusCode?: number;
313
+ responseTime?: number;
314
+ requestSize?: number;
315
+ responseSize?: number;
316
+ }): void;
317
+ /**
318
+ * Annotate span with cache operation details
319
+ *
320
+ * @param span - OpenTelemetry span
321
+ * @param options - Cache operation details
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * annotate CacheOperation(span, {
326
+ * operation: 'get',
327
+ * key: 'user:123',
328
+ * hit: true
329
+ * })
330
+ * ```
331
+ */
332
+ declare function annotateCacheOperation(span: Span, options: {
333
+ operation: 'get' | 'set' | 'delete' | 'clear';
334
+ key?: string;
335
+ hit?: boolean;
336
+ size?: number;
337
+ }): void;
338
+ /**
339
+ * Annotate span with user interaction details
340
+ *
341
+ * @param span - OpenTelemetry span
342
+ * @param options - User interaction details
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * annotateUserInteraction(span, {
347
+ * type: 'click',
348
+ * target: 'button#submit',
349
+ * page: '/checkout'
350
+ * })
351
+ * ```
352
+ */
353
+ declare function annotateUserInteraction(span: Span, options: {
354
+ type: 'click' | 'submit' | 'input' | 'navigation';
355
+ target?: string;
356
+ page?: string;
357
+ }): void;
358
+ /**
359
+ * Annotate span with page navigation details
360
+ *
361
+ * @param span - OpenTelemetry span
362
+ * @param options - Navigation details
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * annotateNavigation(span, {
367
+ * from: '/home',
368
+ * to: '/profile',
369
+ * type: 'client-side'
370
+ * })
371
+ * ```
372
+ */
373
+ declare function annotateNavigation(span: Span, options: {
374
+ from?: string;
375
+ to?: string;
376
+ type?: 'client-side' | 'server-side' | 'initial';
377
+ }): void;
378
+
379
+ export { type OtlpExporterOptions, PatternSpanProcessor, type SdkInitializationOptions, annotateCacheOperation, annotateHttpRequest, annotateNavigation, annotateUserInteraction, createOtlpExporter, getOtlpEndpoint, getSdkInstance, initializeInstrumentation, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk };
@@ -0,0 +1,255 @@
1
+ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
2
+ import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
3
+ import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
+ import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
5
+ import { ZoneContextManager } from '@opentelemetry/context-zone';
6
+ import { InstrumentationConfigSchema, shouldInstrumentSpan, initializePatternMatcher } from '@atrim/instrument-core';
7
+ export { ConfigError, ConfigFileError, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, PatternMatcher, ShutdownError, clearPatternMatcher, getPatternMatcher, initializePatternMatcher, shouldInstrumentSpan } from '@atrim/instrument-core';
8
+ import { parse } from 'yaml';
9
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
10
+ import { SpanStatusCode } from '@opentelemetry/api';
11
+
12
+ // src/core/sdk-initializer.ts
13
+ async function loadConfig(options) {
14
+ if (options.config) {
15
+ return validateConfig(options.config);
16
+ }
17
+ if (options.configUrl) {
18
+ return await loadConfigFromUrl(options.configUrl);
19
+ }
20
+ throw new Error("No configuration provided. Use config or configUrl option.");
21
+ }
22
+ async function loadConfigFromUrl(url) {
23
+ try {
24
+ const response = await fetch(url, {
25
+ method: "GET",
26
+ headers: {
27
+ Accept: "application/yaml, text/yaml, application/x-yaml"
28
+ },
29
+ // 5 second timeout
30
+ signal: AbortSignal.timeout(5e3)
31
+ });
32
+ if (!response.ok) {
33
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
34
+ }
35
+ const yamlText = await response.text();
36
+ if (yamlText.length > 1e6) {
37
+ throw new Error("Configuration file too large (>1MB)");
38
+ }
39
+ const config = parse(yamlText);
40
+ return validateConfig(config);
41
+ } catch (error) {
42
+ throw new Error(`Failed to load config from ${url}: ${error}`);
43
+ }
44
+ }
45
+ function validateConfig(config) {
46
+ try {
47
+ return InstrumentationConfigSchema.parse(config);
48
+ } catch (error) {
49
+ throw new Error(`Invalid configuration schema: ${error}`);
50
+ }
51
+ }
52
+ function createOtlpExporter(options = {}) {
53
+ const endpoint = options.endpoint || getOtlpEndpoint();
54
+ return new OTLPTraceExporter({
55
+ url: endpoint,
56
+ headers: options.headers || {},
57
+ timeoutMillis: options.timeout || 1e4
58
+ });
59
+ }
60
+ function getOtlpEndpoint() {
61
+ try {
62
+ if (typeof import.meta !== "undefined") {
63
+ const metaEnv = import.meta.env;
64
+ if (metaEnv && metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT) {
65
+ return String(metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT);
66
+ }
67
+ }
68
+ } catch {
69
+ }
70
+ if (typeof window !== "undefined") {
71
+ const windowConfig = window.OTEL_EXPORTER_OTLP_ENDPOINT;
72
+ if (windowConfig) {
73
+ return String(windowConfig);
74
+ }
75
+ }
76
+ return "http://localhost:4318/v1/traces";
77
+ }
78
+ var PatternSpanProcessor = class {
79
+ /**
80
+ * Called when a span is started
81
+ * No-op for this processor
82
+ */
83
+ onStart(_span, _parentContext) {
84
+ }
85
+ /**
86
+ * Called when a span ends
87
+ * Filters out spans that don't match patterns
88
+ */
89
+ onEnd(span) {
90
+ const spanName = span.name;
91
+ if (!shouldInstrumentSpan(spanName)) {
92
+ return;
93
+ }
94
+ }
95
+ /**
96
+ * Shutdown the processor
97
+ * No-op for this processor
98
+ */
99
+ async shutdown() {
100
+ }
101
+ /**
102
+ * Force flush pending spans
103
+ * No-op for this processor
104
+ */
105
+ async forceFlush() {
106
+ }
107
+ };
108
+
109
+ // src/core/sdk-initializer.ts
110
+ var sdkInstance = null;
111
+ async function initializeSdk(options) {
112
+ if (sdkInstance) {
113
+ return sdkInstance;
114
+ }
115
+ try {
116
+ let config = null;
117
+ if (options.config) {
118
+ config = options.config;
119
+ } else if (options.configPath) {
120
+ config = await loadConfig({ configPath: options.configPath });
121
+ } else if (options.configUrl) {
122
+ config = await loadConfig({ configUrl: options.configUrl });
123
+ }
124
+ if (config) {
125
+ initializePatternMatcher(config);
126
+ }
127
+ const exporterOptions = {};
128
+ if (options.otlpEndpoint) {
129
+ exporterOptions.endpoint = options.otlpEndpoint;
130
+ }
131
+ if (options.otlpHeaders) {
132
+ exporterOptions.headers = options.otlpHeaders;
133
+ }
134
+ const exporter = createOtlpExporter(exporterOptions);
135
+ const spanProcessors = [];
136
+ if (config) {
137
+ spanProcessors.push(new PatternSpanProcessor());
138
+ }
139
+ spanProcessors.push(new SimpleSpanProcessor(exporter));
140
+ const provider = new WebTracerProvider({
141
+ spanProcessors
142
+ });
143
+ provider.register({
144
+ contextManager: new ZoneContextManager()
145
+ });
146
+ registerInstrumentations({
147
+ instrumentations: [
148
+ getWebAutoInstrumentations({
149
+ "@opentelemetry/instrumentation-document-load": {
150
+ enabled: options.enableDocumentLoad ?? true
151
+ },
152
+ "@opentelemetry/instrumentation-user-interaction": {
153
+ enabled: options.enableUserInteraction ?? true,
154
+ eventNames: ["click", "submit"]
155
+ },
156
+ "@opentelemetry/instrumentation-fetch": {
157
+ enabled: options.enableFetch ?? true,
158
+ propagateTraceHeaderCorsUrls: [/.*/],
159
+ // Propagate to all origins
160
+ clearTimingResources: true
161
+ },
162
+ "@opentelemetry/instrumentation-xml-http-request": {
163
+ enabled: options.enableXhr ?? true,
164
+ propagateTraceHeaderCorsUrls: [/.*/]
165
+ }
166
+ })
167
+ ]
168
+ });
169
+ sdkInstance = provider;
170
+ return provider;
171
+ } catch (error) {
172
+ throw new Error(`Failed to initialize OpenTelemetry SDK: ${error}`);
173
+ }
174
+ }
175
+ function getSdkInstance() {
176
+ return sdkInstance;
177
+ }
178
+ async function shutdownSdk() {
179
+ if (sdkInstance) {
180
+ await sdkInstance.shutdown();
181
+ sdkInstance = null;
182
+ }
183
+ }
184
+ function resetSdk() {
185
+ sdkInstance = null;
186
+ }
187
+
188
+ // src/api.ts
189
+ async function initializeInstrumentation(options) {
190
+ return await initializeSdk(options);
191
+ }
192
+ function setSpanAttributes(span, attributes) {
193
+ for (const [key, value] of Object.entries(attributes)) {
194
+ span.setAttribute(key, value);
195
+ }
196
+ }
197
+ function recordException(span, error, context) {
198
+ span.recordException(error);
199
+ if (context) {
200
+ setSpanAttributes(span, context);
201
+ }
202
+ }
203
+ function markSpanSuccess(span, attributes) {
204
+ span.setStatus({ code: SpanStatusCode.OK });
205
+ if (attributes) {
206
+ setSpanAttributes(span, attributes);
207
+ }
208
+ }
209
+ function markSpanError(span, message, attributes) {
210
+ span.setStatus({
211
+ code: SpanStatusCode.ERROR,
212
+ message: message || "Operation failed"
213
+ });
214
+ if (attributes) {
215
+ setSpanAttributes(span, attributes);
216
+ }
217
+ }
218
+ function annotateHttpRequest(span, options) {
219
+ const attrs = {};
220
+ if (options.method) attrs["http.method"] = options.method;
221
+ if (options.url) attrs["http.url"] = options.url;
222
+ if (options.statusCode) attrs["http.status_code"] = options.statusCode;
223
+ if (options.responseTime) attrs["http.response_time_ms"] = options.responseTime;
224
+ if (options.requestSize) attrs["http.request.body.size"] = options.requestSize;
225
+ if (options.responseSize) attrs["http.response.body.size"] = options.responseSize;
226
+ setSpanAttributes(span, attrs);
227
+ }
228
+ function annotateCacheOperation(span, options) {
229
+ const attrs = {
230
+ "cache.operation": options.operation
231
+ };
232
+ if (options.key) attrs["cache.key"] = options.key;
233
+ if (options.hit !== void 0) attrs["cache.hit"] = options.hit;
234
+ if (options.size) attrs["cache.size"] = options.size;
235
+ setSpanAttributes(span, attrs);
236
+ }
237
+ function annotateUserInteraction(span, options) {
238
+ const attrs = {
239
+ "user.interaction.type": options.type
240
+ };
241
+ if (options.target) attrs["user.interaction.target"] = options.target;
242
+ if (options.page) attrs["user.interaction.page"] = options.page;
243
+ setSpanAttributes(span, attrs);
244
+ }
245
+ function annotateNavigation(span, options) {
246
+ const attrs = {};
247
+ if (options.from) attrs["navigation.from"] = options.from;
248
+ if (options.to) attrs["navigation.to"] = options.to;
249
+ if (options.type) attrs["navigation.type"] = options.type;
250
+ setSpanAttributes(span, attrs);
251
+ }
252
+
253
+ export { PatternSpanProcessor, annotateCacheOperation, annotateHttpRequest, annotateNavigation, annotateUserInteraction, createOtlpExporter, getOtlpEndpoint, getSdkInstance, initializeInstrumentation, loadConfig, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk };
254
+ //# sourceMappingURL=index.js.map
255
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/config-loader.ts","../../src/core/exporter-factory.ts","../../src/core/span-processor.ts","../../src/core/sdk-initializer.ts","../../src/api.ts","../../src/integrations/standard/span-helpers.ts"],"names":["parseYAML"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Browser-compatible configuration loader\n *\n * Only supports URL and inline config (no file system access in browser)\n */\n\nimport { parse as parseYAML } from 'yaml'\nimport type { InstrumentationConfig } from '@atrim/instrument-core'\nimport { InstrumentationConfigSchema } from '@atrim/instrument-core'\n\nexport interface ConfigLoaderOptions {\n configUrl?: string\n config?: InstrumentationConfig\n}\n\n/**\n * Load configuration for browser\n *\n * Supports:\n * 1. Inline config object (highest priority)\n * 2. Remote URL (fetch from server)\n *\n * Note: File system access is not available in browser\n *\n * @param options - Configuration options\n * @returns Parsed and validated configuration\n * @throws {Error} If config loading or validation fails\n */\nexport async function loadConfig(options: ConfigLoaderOptions): Promise<InstrumentationConfig> {\n // 1. Inline config (highest priority)\n if (options.config) {\n return validateConfig(options.config)\n }\n\n // 2. Remote URL\n if (options.configUrl) {\n return await loadConfigFromUrl(options.configUrl)\n }\n\n throw new Error('No configuration provided. Use config or configUrl option.')\n}\n\n/**\n * Load configuration from remote URL\n */\nasync function loadConfigFromUrl(url: string): Promise<InstrumentationConfig> {\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n Accept: 'application/yaml, text/yaml, application/x-yaml'\n },\n // 5 second timeout\n signal: AbortSignal.timeout(5000)\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const yamlText = await response.text()\n\n // Validate size (max 1MB)\n if (yamlText.length > 1_000_000) {\n throw new Error('Configuration file too large (>1MB)')\n }\n\n const config = parseYAML(yamlText)\n return validateConfig(config)\n } catch (error) {\n throw new Error(`Failed to load config from ${url}: ${error}`)\n }\n}\n\n/**\n * Validate configuration against schema\n */\nfunction validateConfig(config: unknown): InstrumentationConfig {\n try {\n return InstrumentationConfigSchema.parse(config)\n } catch (error) {\n throw new Error(`Invalid configuration schema: ${error}`)\n }\n}\n","/**\n * OTLP Exporter Factory for Browser\n *\n * Creates OTLP HTTP exporters for sending traces from the browser.\n * Browser only supports HTTP/JSON protocol (no gRPC).\n */\n\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n\nexport interface OtlpExporterOptions {\n /**\n * OTLP endpoint URL\n * Must end in /v1/traces for browser exporter\n * @default 'http://localhost:4318/v1/traces'\n */\n endpoint?: string\n\n /**\n * Custom HTTP headers (e.g., for authentication)\n * @example { 'Authorization': 'Bearer token' }\n */\n headers?: Record<string, string>\n\n /**\n * Request timeout in milliseconds\n * @default 10000\n */\n timeout?: number\n}\n\n/**\n * Create an OTLP HTTP trace exporter for browser\n *\n * @param options - Exporter configuration options\n * @returns OTLPTraceExporter instance\n *\n * @example\n * ```typescript\n * const exporter = createOtlpExporter({\n * endpoint: 'http://localhost:4318/v1/traces',\n * headers: { 'x-api-key': 'secret' }\n * })\n * ```\n */\nexport function createOtlpExporter(options: OtlpExporterOptions = {}): OTLPTraceExporter {\n const endpoint = options.endpoint || getOtlpEndpoint()\n\n return new OTLPTraceExporter({\n url: endpoint,\n headers: options.headers || {},\n timeoutMillis: options.timeout || 10000\n })\n}\n\n/**\n * Get OTLP endpoint from environment or use default\n *\n * Checks in order:\n * 1. import.meta.env.VITE_OTEL_EXPORTER_OTLP_ENDPOINT (Vite)\n * 2. window.OTEL_EXPORTER_OTLP_ENDPOINT (runtime config)\n * 3. Default: http://localhost:4318/v1/traces\n *\n * @returns OTLP endpoint URL\n */\nexport function getOtlpEndpoint(): string {\n // Check Vite environment variables\n try {\n if (typeof import.meta !== 'undefined') {\n const metaEnv = (import.meta as { env?: Record<string, unknown> }).env\n if (metaEnv && metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT) {\n return String(metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT)\n }\n }\n } catch {\n // import.meta may not be available in all environments\n }\n\n // Check window object (runtime config)\n if (typeof window !== 'undefined') {\n const windowConfig = (window as { OTEL_EXPORTER_OTLP_ENDPOINT?: unknown })\n .OTEL_EXPORTER_OTLP_ENDPOINT\n if (windowConfig) {\n return String(windowConfig)\n }\n }\n\n // Default endpoint\n return 'http://localhost:4318/v1/traces'\n}\n","/**\n * Pattern-Based Span Processor for Browser\n *\n * Filters spans based on patterns defined in instrumentation.yaml\n * Re-uses pattern matching logic from @atrim/instrument-core\n */\n\nimport { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { Context } from '@opentelemetry/api'\nimport { shouldInstrumentSpan } from '@atrim/instrument-core'\n\n/**\n * Span processor that filters spans based on pattern matching\n *\n * This processor runs BEFORE export and drops spans that don't match\n * the configured patterns in instrumentation.yaml.\n *\n * @example\n * ```yaml\n * # instrumentation.yaml\n * instrumentation:\n * instrument_patterns:\n * - pattern: \"^documentLoad\"\n * - pattern: \"^HTTP (GET|POST)\"\n * ignore_patterns:\n * - pattern: \"^HTTP GET /health\"\n * ```\n */\nexport class PatternSpanProcessor implements SpanProcessor {\n /**\n * Called when a span is started\n * No-op for this processor\n */\n onStart(_span: ReadableSpan, _parentContext: Context): void {\n // No-op\n }\n\n /**\n * Called when a span ends\n * Filters out spans that don't match patterns\n */\n onEnd(span: ReadableSpan): void {\n const spanName = span.name\n\n // Check if span should be instrumented\n if (!shouldInstrumentSpan(spanName)) {\n // Drop the span by not forwarding it\n // The span will not reach subsequent processors\n return\n }\n\n // Span passes filtering - will be processed by next processor\n }\n\n /**\n * Shutdown the processor\n * No-op for this processor\n */\n async shutdown(): Promise<void> {\n // No-op\n }\n\n /**\n * Force flush pending spans\n * No-op for this processor\n */\n async forceFlush(): Promise<void> {\n // No-op\n }\n}\n","/**\n * SDK Initializer for Browser\n *\n * Initializes the OpenTelemetry WebTracerProvider with:\n * - Auto-instrumentation (fetch, XHR, document load, user interactions)\n * - Pattern-based span filtering\n * - OTLP HTTP export\n */\n\nimport { WebTracerProvider } from '@opentelemetry/sdk-trace-web'\nimport { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'\nimport { registerInstrumentations } from '@opentelemetry/instrumentation'\nimport { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'\nimport { ZoneContextManager } from '@opentelemetry/context-zone'\nimport type { InstrumentationConfig } from '@atrim/instrument-core'\nimport { initializePatternMatcher } from '@atrim/instrument-core'\nimport { loadConfig } from './config-loader.js'\nimport { createOtlpExporter, type OtlpExporterOptions } from './exporter-factory.js'\nimport { PatternSpanProcessor } from './span-processor.js'\n\nexport interface SdkInitializationOptions {\n /**\n * Service name (required for browser - no auto-detection)\n * @example 'my-app-frontend'\n */\n serviceName: string\n\n /**\n * Service version (optional)\n * @example '1.0.0'\n */\n serviceVersion?: string\n\n /**\n * Path to instrumentation.yaml file\n * Note: Only works if file is publicly accessible\n */\n configPath?: string\n\n /**\n * URL to remote instrumentation.yaml\n * @example 'https://config.company.com/instrumentation.yaml'\n */\n configUrl?: string\n\n /**\n * Inline configuration object (takes precedence over file/URL)\n */\n config?: InstrumentationConfig\n\n /**\n * OTLP endpoint URL\n * @default 'http://localhost:4318/v1/traces'\n */\n otlpEndpoint?: string\n\n /**\n * OTLP HTTP headers\n * @example { 'Authorization': 'Bearer token' }\n */\n otlpHeaders?: Record<string, string>\n\n /**\n * Enable document load instrumentation\n * @default true\n */\n enableDocumentLoad?: boolean\n\n /**\n * Enable user interaction instrumentation\n * @default true\n */\n enableUserInteraction?: boolean\n\n /**\n * Enable fetch API instrumentation\n * @default true\n */\n enableFetch?: boolean\n\n /**\n * Enable XMLHttpRequest instrumentation\n * @default true\n */\n enableXhr?: boolean\n}\n\n// Singleton instance\nlet sdkInstance: WebTracerProvider | null = null\n\n/**\n * Initialize the OpenTelemetry SDK for browser\n *\n * @param options - SDK initialization options\n * @returns WebTracerProvider instance\n * @throws {Error} If initialization fails\n *\n * @example\n * ```typescript\n * const provider = await initializeSdk({\n * serviceName: 'my-app',\n * otlpEndpoint: 'http://localhost:4318/v1/traces'\n * })\n * ```\n */\nexport async function initializeSdk(options: SdkInitializationOptions): Promise<WebTracerProvider> {\n if (sdkInstance) {\n return sdkInstance\n }\n\n try {\n // Load configuration (if specified)\n let config: InstrumentationConfig | null = null\n\n if (options.config) {\n config = options.config\n } else if (options.configPath) {\n config = await loadConfig({ configPath: options.configPath })\n } else if (options.configUrl) {\n config = await loadConfig({ configUrl: options.configUrl })\n }\n\n // Initialize pattern matcher (if config available)\n if (config) {\n initializePatternMatcher(config)\n }\n\n // Create OTLP exporter\n const exporterOptions: OtlpExporterOptions = {}\n if (options.otlpEndpoint) {\n exporterOptions.endpoint = options.otlpEndpoint\n }\n if (options.otlpHeaders) {\n exporterOptions.headers = options.otlpHeaders\n }\n const exporter = createOtlpExporter(exporterOptions)\n\n // Build span processors array\n const spanProcessors = []\n\n // 1. Pattern-based filtering (if config available)\n if (config) {\n spanProcessors.push(new PatternSpanProcessor())\n }\n\n // 2. OTLP export (SimpleSpanProcessor for browser - no batching)\n spanProcessors.push(new SimpleSpanProcessor(exporter))\n\n // Create WebTracerProvider with processors\n // Note: Resource configuration will be handled via environment or programmatically\n const provider = new WebTracerProvider({\n spanProcessors\n })\n\n // Note: Resource attributes (service name, version) should be set via:\n // 1. OTEL_RESOURCE_ATTRIBUTES environment variable\n // 2. Custom Resource passed to WebTracerProvider\n // 3. Collector configuration\n // For now, serviceName is used primarily for logging/debugging\n\n // Register the provider\n provider.register({\n contextManager: new ZoneContextManager()\n })\n\n // Register auto-instrumentations\n registerInstrumentations({\n instrumentations: [\n getWebAutoInstrumentations({\n '@opentelemetry/instrumentation-document-load': {\n enabled: options.enableDocumentLoad ?? true\n },\n '@opentelemetry/instrumentation-user-interaction': {\n enabled: options.enableUserInteraction ?? true,\n eventNames: ['click', 'submit']\n },\n '@opentelemetry/instrumentation-fetch': {\n enabled: options.enableFetch ?? true,\n propagateTraceHeaderCorsUrls: [/.*/], // Propagate to all origins\n clearTimingResources: true\n },\n '@opentelemetry/instrumentation-xml-http-request': {\n enabled: options.enableXhr ?? true,\n propagateTraceHeaderCorsUrls: [/.*/]\n }\n })\n ]\n })\n\n sdkInstance = provider\n return provider\n } catch (error) {\n throw new Error(`Failed to initialize OpenTelemetry SDK: ${error}`)\n }\n}\n\n/**\n * Get the current SDK instance\n *\n * @returns WebTracerProvider instance or null if not initialized\n */\nexport function getSdkInstance(): WebTracerProvider | null {\n return sdkInstance\n}\n\n/**\n * Shutdown the SDK gracefully\n *\n * Flushes pending spans and releases resources\n */\nexport async function shutdownSdk(): Promise<void> {\n if (sdkInstance) {\n await sdkInstance.shutdown()\n sdkInstance = null\n }\n}\n\n/**\n * Reset the SDK instance (for testing)\n *\n * Does not shutdown - just clears the singleton\n */\nexport function resetSdk(): void {\n sdkInstance = null\n}\n","/**\n * Public API for @atrim/instrument-web\n *\n * Main initialization functions for browser instrumentation\n */\n\nimport type { WebTracerProvider } from '@opentelemetry/sdk-trace-web'\nimport { initializeSdk, type SdkInitializationOptions } from './core/sdk-initializer.js'\n\n/**\n * Initialize OpenTelemetry instrumentation for browser\n *\n * This is the main entry point for setting up tracing in browser applications.\n * Call this function once at application startup, before any other code runs.\n *\n * @param options - Initialization options\n * @returns WebTracerProvider instance\n * @throws {Error} If initialization fails\n *\n * @example\n * ```typescript\n * import { initializeInstrumentation } from '@atrim/instrument-web'\n *\n * await initializeInstrumentation({\n * serviceName: 'my-app',\n * otlpEndpoint: 'http://localhost:4318/v1/traces'\n * })\n * ```\n *\n * @example With pattern-based filtering\n * ```typescript\n * await initializeInstrumentation({\n * serviceName: 'my-app',\n * configUrl: 'https://config.company.com/instrumentation.yaml'\n * })\n * ```\n *\n * @example Disable specific instrumentations\n * ```typescript\n * await initializeInstrumentation({\n * serviceName: 'my-app',\n * enableUserInteraction: false, // Disable click tracking\n * enableXhr: false // Disable XMLHttpRequest tracking\n * })\n * ```\n */\nexport async function initializeInstrumentation(\n options: SdkInitializationOptions\n): Promise<WebTracerProvider> {\n return await initializeSdk(options)\n}\n","/**\n * Browser Span Helpers\n *\n * Utility functions for adding metadata to spans in browser applications\n */\n\nimport { Span, SpanStatusCode } from '@opentelemetry/api'\n\n/**\n * Set multiple attributes on a span\n *\n * @param span - OpenTelemetry span\n * @param attributes - Key-value pairs to set as attributes\n *\n * @example\n * ```typescript\n * setSpanAttributes(span, {\n * 'user.id': '123',\n * 'page.url': window.location.href\n * })\n * ```\n */\nexport function setSpanAttributes(\n span: Span,\n attributes: Record<string, string | number | boolean>\n): void {\n for (const [key, value] of Object.entries(attributes)) {\n span.setAttribute(key, value)\n }\n}\n\n/**\n * Record an exception on a span\n *\n * @param span - OpenTelemetry span\n * @param error - Error object to record\n * @param context - Additional context attributes\n *\n * @example\n * ```typescript\n * try {\n * // ...\n * } catch (error) {\n * recordException(span, error, { 'page.url': window.location.href })\n * }\n * ```\n */\nexport function recordException(\n span: Span,\n error: Error,\n context?: Record<string, string | number | boolean>\n): void {\n span.recordException(error)\n\n if (context) {\n setSpanAttributes(span, context)\n }\n}\n\n/**\n * Mark a span as successful\n *\n * Sets status to OK and adds optional attributes\n *\n * @param span - OpenTelemetry span\n * @param attributes - Optional attributes to add\n */\nexport function markSpanSuccess(\n span: Span,\n attributes?: Record<string, string | number | boolean>\n): void {\n span.setStatus({ code: SpanStatusCode.OK })\n\n if (attributes) {\n setSpanAttributes(span, attributes)\n }\n}\n\n/**\n * Mark a span as failed\n *\n * Sets status to ERROR and adds optional error message/attributes\n *\n * @param span - OpenTelemetry span\n * @param message - Error message\n * @param attributes - Optional attributes to add\n */\nexport function markSpanError(\n span: Span,\n message?: string,\n attributes?: Record<string, string | number | boolean>\n): void {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: message || 'Operation failed'\n })\n\n if (attributes) {\n setSpanAttributes(span, attributes)\n }\n}\n\n/**\n * Annotate span with HTTP request details\n *\n * @param span - OpenTelemetry span\n * @param options - HTTP request details\n *\n * @example\n * ```typescript\n * annotateHttpRequest(span, {\n * method: 'GET',\n * url: 'https://api.example.com/users',\n * statusCode: 200,\n * responseTime: 150\n * })\n * ```\n */\nexport function annotateHttpRequest(\n span: Span,\n options: {\n method?: string\n url?: string\n statusCode?: number\n responseTime?: number\n requestSize?: number\n responseSize?: number\n }\n): void {\n const attrs: Record<string, string | number> = {}\n\n if (options.method) attrs['http.method'] = options.method\n if (options.url) attrs['http.url'] = options.url\n if (options.statusCode) attrs['http.status_code'] = options.statusCode\n if (options.responseTime) attrs['http.response_time_ms'] = options.responseTime\n if (options.requestSize) attrs['http.request.body.size'] = options.requestSize\n if (options.responseSize) attrs['http.response.body.size'] = options.responseSize\n\n setSpanAttributes(span, attrs)\n}\n\n/**\n * Annotate span with cache operation details\n *\n * @param span - OpenTelemetry span\n * @param options - Cache operation details\n *\n * @example\n * ```typescript\n * annotate CacheOperation(span, {\n * operation: 'get',\n * key: 'user:123',\n * hit: true\n * })\n * ```\n */\nexport function annotateCacheOperation(\n span: Span,\n options: {\n operation: 'get' | 'set' | 'delete' | 'clear'\n key?: string\n hit?: boolean\n size?: number\n }\n): void {\n const attrs: Record<string, string | number | boolean> = {\n 'cache.operation': options.operation\n }\n\n if (options.key) attrs['cache.key'] = options.key\n if (options.hit !== undefined) attrs['cache.hit'] = options.hit\n if (options.size) attrs['cache.size'] = options.size\n\n setSpanAttributes(span, attrs)\n}\n\n/**\n * Annotate span with user interaction details\n *\n * @param span - OpenTelemetry span\n * @param options - User interaction details\n *\n * @example\n * ```typescript\n * annotateUserInteraction(span, {\n * type: 'click',\n * target: 'button#submit',\n * page: '/checkout'\n * })\n * ```\n */\nexport function annotateUserInteraction(\n span: Span,\n options: {\n type: 'click' | 'submit' | 'input' | 'navigation'\n target?: string\n page?: string\n }\n): void {\n const attrs: Record<string, string> = {\n 'user.interaction.type': options.type\n }\n\n if (options.target) attrs['user.interaction.target'] = options.target\n if (options.page) attrs['user.interaction.page'] = options.page\n\n setSpanAttributes(span, attrs)\n}\n\n/**\n * Annotate span with page navigation details\n *\n * @param span - OpenTelemetry span\n * @param options - Navigation details\n *\n * @example\n * ```typescript\n * annotateNavigation(span, {\n * from: '/home',\n * to: '/profile',\n * type: 'client-side'\n * })\n * ```\n */\nexport function annotateNavigation(\n span: Span,\n options: {\n from?: string\n to?: string\n type?: 'client-side' | 'server-side' | 'initial'\n }\n): void {\n const attrs: Record<string, string> = {}\n\n if (options.from) attrs['navigation.from'] = options.from\n if (options.to) attrs['navigation.to'] = options.to\n if (options.type) attrs['navigation.type'] = options.type\n\n setSpanAttributes(span, attrs)\n}\n"]}