@atrim/instrument-web 0.4.0-08fae61-20251118193014

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,91 @@
1
+ {
2
+ "name": "@atrim/instrument-web",
3
+ "version": "0.4.0-08fae61-20251118193014",
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
+ "@effect/platform": "^0.93.0",
71
+ "effect": "^3.19.0",
72
+ "@opentelemetry/api": "^1.9.0",
73
+ "@opentelemetry/auto-instrumentations-web": "^0.54.0",
74
+ "@opentelemetry/context-zone": "^2.2.0",
75
+ "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
76
+ "@opentelemetry/instrumentation": "^0.208.0",
77
+ "@opentelemetry/resources": "^2.2.0",
78
+ "@opentelemetry/sdk-trace-base": "^2.2.0",
79
+ "@opentelemetry/sdk-trace-web": "^2.2.0",
80
+ "yaml": "^2.8.1"
81
+ },
82
+ "devDependencies": {
83
+ "@opentelemetry/semantic-conventions": "^1.38.0",
84
+ "@types/node": "^20.10.0",
85
+ "@vitest/coverage-v8": "^4.0.8",
86
+ "jsdom": "^24.0.0",
87
+ "tsup": "^8.0.1",
88
+ "typescript": "^5.7.2",
89
+ "vitest": "^4.0.8"
90
+ }
91
+ }
@@ -0,0 +1,413 @@
1
+ import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
2
+ import { InstrumentationConfig, ConfigLoader } from '@atrim/instrument-core';
3
+ export { ConfigError, ConfigFileError, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, InstrumentationConfig, PatternConfig, PatternMatcher, ShutdownError, clearPatternMatcher, getPatternMatcher, initializePatternMatcher, shouldInstrumentSpan } from '@atrim/instrument-core';
4
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
+ import { Layer } from 'effect';
6
+ import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
+ import { Context, Span } from '@opentelemetry/api';
8
+
9
+ /**
10
+ * SDK Initializer for Browser
11
+ *
12
+ * Initializes the OpenTelemetry WebTracerProvider with:
13
+ * - Auto-instrumentation (fetch, XHR, document load, user interactions)
14
+ * - Pattern-based span filtering
15
+ * - OTLP HTTP export
16
+ */
17
+
18
+ interface SdkInitializationOptions {
19
+ /**
20
+ * Service name (required for browser - no auto-detection)
21
+ * @example 'my-app-frontend'
22
+ */
23
+ serviceName: string;
24
+ /**
25
+ * Service version (optional)
26
+ * @example '1.0.0'
27
+ */
28
+ serviceVersion?: string;
29
+ /**
30
+ * Path to instrumentation.yaml file
31
+ * Note: Only works if file is publicly accessible
32
+ */
33
+ configPath?: string;
34
+ /**
35
+ * URL to remote instrumentation.yaml
36
+ * @example 'https://config.company.com/instrumentation.yaml'
37
+ */
38
+ configUrl?: string;
39
+ /**
40
+ * Inline configuration object (takes precedence over file/URL)
41
+ */
42
+ config?: InstrumentationConfig;
43
+ /**
44
+ * OTLP endpoint URL
45
+ * @default 'http://localhost:4318/v1/traces'
46
+ */
47
+ otlpEndpoint?: string;
48
+ /**
49
+ * OTLP HTTP headers
50
+ * @example { 'Authorization': 'Bearer token' }
51
+ */
52
+ otlpHeaders?: Record<string, string>;
53
+ /**
54
+ * Enable document load instrumentation
55
+ * @default true
56
+ */
57
+ enableDocumentLoad?: boolean;
58
+ /**
59
+ * Enable user interaction instrumentation
60
+ * @default true
61
+ */
62
+ enableUserInteraction?: boolean;
63
+ /**
64
+ * Enable fetch API instrumentation
65
+ * @default true
66
+ */
67
+ enableFetch?: boolean;
68
+ /**
69
+ * Enable XMLHttpRequest instrumentation
70
+ * @default true
71
+ */
72
+ enableXhr?: boolean;
73
+ }
74
+ /**
75
+ * Get the current SDK instance
76
+ *
77
+ * @returns WebTracerProvider instance or null if not initialized
78
+ */
79
+ declare function getSdkInstance(): WebTracerProvider | null;
80
+ /**
81
+ * Shutdown the SDK gracefully
82
+ *
83
+ * Flushes pending spans and releases resources
84
+ */
85
+ declare function shutdownSdk(): Promise<void>;
86
+ /**
87
+ * Reset the SDK instance (for testing)
88
+ *
89
+ * Does not shutdown - just clears the singleton
90
+ */
91
+ declare function resetSdk(): void;
92
+
93
+ /**
94
+ * Public API for @atrim/instrument-web
95
+ *
96
+ * Main initialization functions for browser instrumentation
97
+ */
98
+
99
+ /**
100
+ * Initialize OpenTelemetry instrumentation for browser
101
+ *
102
+ * This is the main entry point for setting up tracing in browser applications.
103
+ * Call this function once at application startup, before any other code runs.
104
+ *
105
+ * @param options - Initialization options
106
+ * @returns WebTracerProvider instance
107
+ * @throws {Error} If initialization fails
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * import { initializeInstrumentation } from '@atrim/instrument-web'
112
+ *
113
+ * await initializeInstrumentation({
114
+ * serviceName: 'my-app',
115
+ * otlpEndpoint: 'http://localhost:4318/v1/traces'
116
+ * })
117
+ * ```
118
+ *
119
+ * @example With pattern-based filtering
120
+ * ```typescript
121
+ * await initializeInstrumentation({
122
+ * serviceName: 'my-app',
123
+ * configUrl: 'https://config.company.com/instrumentation.yaml'
124
+ * })
125
+ * ```
126
+ *
127
+ * @example Disable specific instrumentations
128
+ * ```typescript
129
+ * await initializeInstrumentation({
130
+ * serviceName: 'my-app',
131
+ * enableUserInteraction: false, // Disable click tracking
132
+ * enableXhr: false // Disable XMLHttpRequest tracking
133
+ * })
134
+ * ```
135
+ */
136
+ declare function initializeInstrumentation(options: SdkInitializationOptions): Promise<WebTracerProvider>;
137
+
138
+ /**
139
+ * OTLP Exporter Factory for Browser
140
+ *
141
+ * Creates OTLP HTTP exporters for sending traces from the browser.
142
+ * Browser only supports HTTP/JSON protocol (no gRPC).
143
+ */
144
+
145
+ interface OtlpExporterOptions {
146
+ /**
147
+ * OTLP endpoint URL
148
+ * Must end in /v1/traces for browser exporter
149
+ * @default 'http://localhost:4318/v1/traces'
150
+ */
151
+ endpoint?: string;
152
+ /**
153
+ * Custom HTTP headers (e.g., for authentication)
154
+ * @example { 'Authorization': 'Bearer token' }
155
+ */
156
+ headers?: Record<string, string>;
157
+ /**
158
+ * Request timeout in milliseconds
159
+ * @default 10000
160
+ */
161
+ timeout?: number;
162
+ }
163
+ /**
164
+ * Create an OTLP HTTP trace exporter for browser
165
+ *
166
+ * @param options - Exporter configuration options
167
+ * @returns OTLPTraceExporter instance
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const exporter = createOtlpExporter({
172
+ * endpoint: 'http://localhost:4318/v1/traces',
173
+ * headers: { 'x-api-key': 'secret' }
174
+ * })
175
+ * ```
176
+ */
177
+ declare function createOtlpExporter(options?: OtlpExporterOptions): OTLPTraceExporter;
178
+ /**
179
+ * Get OTLP endpoint from environment or use default
180
+ *
181
+ * Checks in order:
182
+ * 1. import.meta.env.VITE_OTEL_EXPORTER_OTLP_ENDPOINT (Vite)
183
+ * 2. window.OTEL_EXPORTER_OTLP_ENDPOINT (runtime config)
184
+ * 3. Default: http://localhost:4318/v1/traces
185
+ *
186
+ * @returns OTLP endpoint URL
187
+ */
188
+ declare function getOtlpEndpoint(): string;
189
+
190
+ /**
191
+ * Browser configuration loader using Effect Platform
192
+ *
193
+ * Provides HttpClient layer for the core ConfigLoader service
194
+ * (No FileSystem in browser)
195
+ */
196
+
197
+ /**
198
+ * Complete ConfigLoader layer with browser platform dependencies
199
+ *
200
+ * Provides:
201
+ * - ConfigLoader service
202
+ * - HttpClient (from FetchHttpClient)
203
+ * - No FileSystem (browser doesn't have filesystem access)
204
+ */
205
+ declare const WebConfigLoaderLive: Layer.Layer<ConfigLoader, never, never>;
206
+ /**
207
+ * Load configuration from URI (Promise-based convenience API)
208
+ *
209
+ * Automatically provides browser platform layers (HttpClient only)
210
+ *
211
+ * @param uri - Configuration URI (http://, https://, or relative path)
212
+ * @returns Promise that resolves to validated configuration
213
+ */
214
+ declare function loadConfig(uri: string): Promise<InstrumentationConfig>;
215
+ /**
216
+ * Load configuration from inline content (Promise-based convenience API)
217
+ *
218
+ * @param content - YAML string, JSON string, or plain object
219
+ * @returns Promise that resolves to validated configuration
220
+ */
221
+ declare function loadConfigFromInline(content: string | unknown): Promise<InstrumentationConfig>;
222
+
223
+ /**
224
+ * Pattern-Based Span Processor for Browser
225
+ *
226
+ * Filters spans based on patterns defined in instrumentation.yaml
227
+ * Re-uses pattern matching logic from @atrim/instrument-core
228
+ */
229
+
230
+ /**
231
+ * Span processor that filters spans based on pattern matching
232
+ *
233
+ * This processor runs BEFORE export and drops spans that don't match
234
+ * the configured patterns in instrumentation.yaml.
235
+ *
236
+ * @example
237
+ * ```yaml
238
+ * # instrumentation.yaml
239
+ * instrumentation:
240
+ * instrument_patterns:
241
+ * - pattern: "^documentLoad"
242
+ * - pattern: "^HTTP (GET|POST)"
243
+ * ignore_patterns:
244
+ * - pattern: "^HTTP GET /health"
245
+ * ```
246
+ */
247
+ declare class PatternSpanProcessor implements SpanProcessor {
248
+ /**
249
+ * Called when a span is started
250
+ * No-op for this processor
251
+ */
252
+ onStart(_span: ReadableSpan, _parentContext: Context): void;
253
+ /**
254
+ * Called when a span ends
255
+ * Filters out spans that don't match patterns
256
+ */
257
+ onEnd(span: ReadableSpan): void;
258
+ /**
259
+ * Shutdown the processor
260
+ * No-op for this processor
261
+ */
262
+ shutdown(): Promise<void>;
263
+ /**
264
+ * Force flush pending spans
265
+ * No-op for this processor
266
+ */
267
+ forceFlush(): Promise<void>;
268
+ }
269
+
270
+ /**
271
+ * Browser Span Helpers
272
+ *
273
+ * Utility functions for adding metadata to spans in browser applications
274
+ */
275
+
276
+ /**
277
+ * Set multiple attributes on a span
278
+ *
279
+ * @param span - OpenTelemetry span
280
+ * @param attributes - Key-value pairs to set as attributes
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * setSpanAttributes(span, {
285
+ * 'user.id': '123',
286
+ * 'page.url': window.location.href
287
+ * })
288
+ * ```
289
+ */
290
+ declare function setSpanAttributes(span: Span, attributes: Record<string, string | number | boolean>): void;
291
+ /**
292
+ * Record an exception on a span
293
+ *
294
+ * @param span - OpenTelemetry span
295
+ * @param error - Error object to record
296
+ * @param context - Additional context attributes
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * try {
301
+ * // ...
302
+ * } catch (error) {
303
+ * recordException(span, error, { 'page.url': window.location.href })
304
+ * }
305
+ * ```
306
+ */
307
+ declare function recordException(span: Span, error: Error, context?: Record<string, string | number | boolean>): void;
308
+ /**
309
+ * Mark a span as successful
310
+ *
311
+ * Sets status to OK and adds optional attributes
312
+ *
313
+ * @param span - OpenTelemetry span
314
+ * @param attributes - Optional attributes to add
315
+ */
316
+ declare function markSpanSuccess(span: Span, attributes?: Record<string, string | number | boolean>): void;
317
+ /**
318
+ * Mark a span as failed
319
+ *
320
+ * Sets status to ERROR and adds optional error message/attributes
321
+ *
322
+ * @param span - OpenTelemetry span
323
+ * @param message - Error message
324
+ * @param attributes - Optional attributes to add
325
+ */
326
+ declare function markSpanError(span: Span, message?: string, attributes?: Record<string, string | number | boolean>): void;
327
+ /**
328
+ * Annotate span with HTTP request details
329
+ *
330
+ * @param span - OpenTelemetry span
331
+ * @param options - HTTP request details
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * annotateHttpRequest(span, {
336
+ * method: 'GET',
337
+ * url: 'https://api.example.com/users',
338
+ * statusCode: 200,
339
+ * responseTime: 150
340
+ * })
341
+ * ```
342
+ */
343
+ declare function annotateHttpRequest(span: Span, options: {
344
+ method?: string;
345
+ url?: string;
346
+ statusCode?: number;
347
+ responseTime?: number;
348
+ requestSize?: number;
349
+ responseSize?: number;
350
+ }): void;
351
+ /**
352
+ * Annotate span with cache operation details
353
+ *
354
+ * @param span - OpenTelemetry span
355
+ * @param options - Cache operation details
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * annotate CacheOperation(span, {
360
+ * operation: 'get',
361
+ * key: 'user:123',
362
+ * hit: true
363
+ * })
364
+ * ```
365
+ */
366
+ declare function annotateCacheOperation(span: Span, options: {
367
+ operation: 'get' | 'set' | 'delete' | 'clear';
368
+ key?: string;
369
+ hit?: boolean;
370
+ size?: number;
371
+ }): void;
372
+ /**
373
+ * Annotate span with user interaction details
374
+ *
375
+ * @param span - OpenTelemetry span
376
+ * @param options - User interaction details
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * annotateUserInteraction(span, {
381
+ * type: 'click',
382
+ * target: 'button#submit',
383
+ * page: '/checkout'
384
+ * })
385
+ * ```
386
+ */
387
+ declare function annotateUserInteraction(span: Span, options: {
388
+ type: 'click' | 'submit' | 'input' | 'navigation';
389
+ target?: string;
390
+ page?: string;
391
+ }): void;
392
+ /**
393
+ * Annotate span with page navigation details
394
+ *
395
+ * @param span - OpenTelemetry span
396
+ * @param options - Navigation details
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * annotateNavigation(span, {
401
+ * from: '/home',
402
+ * to: '/profile',
403
+ * type: 'client-side'
404
+ * })
405
+ * ```
406
+ */
407
+ declare function annotateNavigation(span: Span, options: {
408
+ from?: string;
409
+ to?: string;
410
+ type?: 'client-side' | 'server-side' | 'initial';
411
+ }): void;
412
+
413
+ export { type OtlpExporterOptions, PatternSpanProcessor, type SdkInitializationOptions, WebConfigLoaderLive, annotateCacheOperation, annotateHttpRequest, annotateNavigation, annotateUserInteraction, createOtlpExporter, getOtlpEndpoint, getSdkInstance, initializeInstrumentation, loadConfig, loadConfigFromInline, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk };
@@ -0,0 +1,231 @@
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 { ConfigLoaderLive, ConfigLoader, 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 { Layer, Effect } from 'effect';
9
+ import { FetchHttpClient } from '@effect/platform';
10
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
11
+ import { SpanStatusCode } from '@opentelemetry/api';
12
+
13
+ // src/core/sdk-initializer.ts
14
+ var WebConfigLoaderLive = ConfigLoaderLive.pipe(Layer.provide(FetchHttpClient.layer));
15
+ async function loadConfig(uri) {
16
+ const program = Effect.gen(function* () {
17
+ const loader = yield* ConfigLoader;
18
+ return yield* loader.loadFromUri(uri);
19
+ });
20
+ return Effect.runPromise(program.pipe(Effect.provide(WebConfigLoaderLive)));
21
+ }
22
+ async function loadConfigFromInline(content) {
23
+ const program = Effect.gen(function* () {
24
+ const loader = yield* ConfigLoader;
25
+ return yield* loader.loadFromInline(content);
26
+ });
27
+ return Effect.runPromise(program.pipe(Effect.provide(WebConfigLoaderLive)));
28
+ }
29
+ function createOtlpExporter(options = {}) {
30
+ const endpoint = options.endpoint || getOtlpEndpoint();
31
+ return new OTLPTraceExporter({
32
+ url: endpoint,
33
+ headers: options.headers || {},
34
+ timeoutMillis: options.timeout || 1e4
35
+ });
36
+ }
37
+ function getOtlpEndpoint() {
38
+ try {
39
+ if (typeof import.meta !== "undefined") {
40
+ const metaEnv = import.meta.env;
41
+ if (metaEnv && metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT) {
42
+ return String(metaEnv.VITE_OTEL_EXPORTER_OTLP_ENDPOINT);
43
+ }
44
+ }
45
+ } catch {
46
+ }
47
+ if (typeof window !== "undefined") {
48
+ const windowConfig = window.OTEL_EXPORTER_OTLP_ENDPOINT;
49
+ if (windowConfig) {
50
+ return String(windowConfig);
51
+ }
52
+ }
53
+ return "http://localhost:4318/v1/traces";
54
+ }
55
+ var PatternSpanProcessor = class {
56
+ /**
57
+ * Called when a span is started
58
+ * No-op for this processor
59
+ */
60
+ onStart(_span, _parentContext) {
61
+ }
62
+ /**
63
+ * Called when a span ends
64
+ * Filters out spans that don't match patterns
65
+ */
66
+ onEnd(span) {
67
+ const spanName = span.name;
68
+ if (!shouldInstrumentSpan(spanName)) {
69
+ return;
70
+ }
71
+ }
72
+ /**
73
+ * Shutdown the processor
74
+ * No-op for this processor
75
+ */
76
+ async shutdown() {
77
+ }
78
+ /**
79
+ * Force flush pending spans
80
+ * No-op for this processor
81
+ */
82
+ async forceFlush() {
83
+ }
84
+ };
85
+
86
+ // src/core/sdk-initializer.ts
87
+ var sdkInstance = null;
88
+ async function initializeSdk(options) {
89
+ if (sdkInstance) {
90
+ return sdkInstance;
91
+ }
92
+ try {
93
+ let config = null;
94
+ if (options.config) {
95
+ config = await loadConfigFromInline(options.config);
96
+ } else if (options.configPath || options.configUrl) {
97
+ const url = options.configUrl || options.configPath;
98
+ config = await loadConfig(url);
99
+ }
100
+ if (config) {
101
+ initializePatternMatcher(config);
102
+ }
103
+ const exporterOptions = {};
104
+ if (options.otlpEndpoint) {
105
+ exporterOptions.endpoint = options.otlpEndpoint;
106
+ }
107
+ if (options.otlpHeaders) {
108
+ exporterOptions.headers = options.otlpHeaders;
109
+ }
110
+ const exporter = createOtlpExporter(exporterOptions);
111
+ const spanProcessors = [];
112
+ if (config) {
113
+ spanProcessors.push(new PatternSpanProcessor());
114
+ }
115
+ spanProcessors.push(new SimpleSpanProcessor(exporter));
116
+ const provider = new WebTracerProvider({
117
+ spanProcessors
118
+ });
119
+ provider.register({
120
+ contextManager: new ZoneContextManager()
121
+ });
122
+ registerInstrumentations({
123
+ instrumentations: [
124
+ getWebAutoInstrumentations({
125
+ "@opentelemetry/instrumentation-document-load": {
126
+ enabled: options.enableDocumentLoad ?? true
127
+ },
128
+ "@opentelemetry/instrumentation-user-interaction": {
129
+ enabled: options.enableUserInteraction ?? true,
130
+ eventNames: ["click", "submit"]
131
+ },
132
+ "@opentelemetry/instrumentation-fetch": {
133
+ enabled: options.enableFetch ?? true,
134
+ propagateTraceHeaderCorsUrls: [/.*/],
135
+ // Propagate to all origins
136
+ clearTimingResources: true
137
+ },
138
+ "@opentelemetry/instrumentation-xml-http-request": {
139
+ enabled: options.enableXhr ?? true,
140
+ propagateTraceHeaderCorsUrls: [/.*/]
141
+ }
142
+ })
143
+ ]
144
+ });
145
+ sdkInstance = provider;
146
+ return provider;
147
+ } catch (error) {
148
+ throw new Error(`Failed to initialize OpenTelemetry SDK: ${error}`);
149
+ }
150
+ }
151
+ function getSdkInstance() {
152
+ return sdkInstance;
153
+ }
154
+ async function shutdownSdk() {
155
+ if (sdkInstance) {
156
+ await sdkInstance.shutdown();
157
+ sdkInstance = null;
158
+ }
159
+ }
160
+ function resetSdk() {
161
+ sdkInstance = null;
162
+ }
163
+
164
+ // src/api.ts
165
+ async function initializeInstrumentation(options) {
166
+ return await initializeSdk(options);
167
+ }
168
+ function setSpanAttributes(span, attributes) {
169
+ for (const [key, value] of Object.entries(attributes)) {
170
+ span.setAttribute(key, value);
171
+ }
172
+ }
173
+ function recordException(span, error, context) {
174
+ span.recordException(error);
175
+ if (context) {
176
+ setSpanAttributes(span, context);
177
+ }
178
+ }
179
+ function markSpanSuccess(span, attributes) {
180
+ span.setStatus({ code: SpanStatusCode.OK });
181
+ if (attributes) {
182
+ setSpanAttributes(span, attributes);
183
+ }
184
+ }
185
+ function markSpanError(span, message, attributes) {
186
+ span.setStatus({
187
+ code: SpanStatusCode.ERROR,
188
+ message: message || "Operation failed"
189
+ });
190
+ if (attributes) {
191
+ setSpanAttributes(span, attributes);
192
+ }
193
+ }
194
+ function annotateHttpRequest(span, options) {
195
+ const attrs = {};
196
+ if (options.method) attrs["http.method"] = options.method;
197
+ if (options.url) attrs["http.url"] = options.url;
198
+ if (options.statusCode) attrs["http.status_code"] = options.statusCode;
199
+ if (options.responseTime) attrs["http.response_time_ms"] = options.responseTime;
200
+ if (options.requestSize) attrs["http.request.body.size"] = options.requestSize;
201
+ if (options.responseSize) attrs["http.response.body.size"] = options.responseSize;
202
+ setSpanAttributes(span, attrs);
203
+ }
204
+ function annotateCacheOperation(span, options) {
205
+ const attrs = {
206
+ "cache.operation": options.operation
207
+ };
208
+ if (options.key) attrs["cache.key"] = options.key;
209
+ if (options.hit !== void 0) attrs["cache.hit"] = options.hit;
210
+ if (options.size) attrs["cache.size"] = options.size;
211
+ setSpanAttributes(span, attrs);
212
+ }
213
+ function annotateUserInteraction(span, options) {
214
+ const attrs = {
215
+ "user.interaction.type": options.type
216
+ };
217
+ if (options.target) attrs["user.interaction.target"] = options.target;
218
+ if (options.page) attrs["user.interaction.page"] = options.page;
219
+ setSpanAttributes(span, attrs);
220
+ }
221
+ function annotateNavigation(span, options) {
222
+ const attrs = {};
223
+ if (options.from) attrs["navigation.from"] = options.from;
224
+ if (options.to) attrs["navigation.to"] = options.to;
225
+ if (options.type) attrs["navigation.type"] = options.type;
226
+ setSpanAttributes(span, attrs);
227
+ }
228
+
229
+ export { PatternSpanProcessor, WebConfigLoaderLive, annotateCacheOperation, annotateHttpRequest, annotateNavigation, annotateUserInteraction, createOtlpExporter, getOtlpEndpoint, getSdkInstance, initializeInstrumentation, loadConfig, loadConfigFromInline, markSpanError, markSpanSuccess, recordException, resetSdk, setSpanAttributes, shutdownSdk };
230
+ //# sourceMappingURL=index.js.map
231
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/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":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Browser configuration loader using Effect Platform\n *\n * Provides HttpClient layer for the core ConfigLoader service\n * (No FileSystem in browser)\n */\n\nimport { Effect, Layer } from 'effect'\nimport { FetchHttpClient } from '@effect/platform'\nimport { ConfigLoader, ConfigLoaderLive, type InstrumentationConfig } from '@atrim/instrument-core'\n\n/**\n * Complete ConfigLoader layer with browser platform dependencies\n *\n * Provides:\n * - ConfigLoader service\n * - HttpClient (from FetchHttpClient)\n * - No FileSystem (browser doesn't have filesystem access)\n */\nexport const WebConfigLoaderLive = ConfigLoaderLive.pipe(Layer.provide(FetchHttpClient.layer))\n\n/**\n * Load configuration from URI (Promise-based convenience API)\n *\n * Automatically provides browser platform layers (HttpClient only)\n *\n * @param uri - Configuration URI (http://, https://, or relative path)\n * @returns Promise that resolves to validated configuration\n */\nexport async function loadConfig(uri: string): Promise<InstrumentationConfig> {\n const program = Effect.gen(function* () {\n const loader = yield* ConfigLoader\n return yield* loader.loadFromUri(uri)\n })\n\n return Effect.runPromise(program.pipe(Effect.provide(WebConfigLoaderLive)))\n}\n\n/**\n * Load configuration from inline content (Promise-based convenience API)\n *\n * @param content - YAML string, JSON string, or plain object\n * @returns Promise that resolves to validated configuration\n */\nexport async function loadConfigFromInline(\n content: string | unknown\n): Promise<InstrumentationConfig> {\n const program = Effect.gen(function* () {\n const loader = yield* ConfigLoader\n return yield* loader.loadFromInline(content)\n })\n\n return Effect.runPromise(program.pipe(Effect.provide(WebConfigLoaderLive)))\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, loadConfigFromInline } from '../services/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 = await loadConfigFromInline(options.config)\n } else if (options.configPath || options.configUrl) {\n // In browser, configPath is treated as a URL (can be relative like '/instrumentation.yaml')\n const url = options.configUrl || options.configPath!\n config = await loadConfig(url)\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"]}