@atrim/instrument-web 0.5.0-11e06b7-20251119014031 → 0.5.0-c05e3a1-20251119131241

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atrim/instrument-web",
3
- "version": "0.5.0-11e06b7-20251119014031",
3
+ "version": "0.5.0-c05e3a1-20251119131241",
4
4
  "description": "OpenTelemetry instrumentation for browsers with centralized YAML configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,8 +1,8 @@
1
+ import { Effect, Layer } from 'effect';
1
2
  import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
2
- import { InstrumentationConfig, ConfigLoader } from '@atrim/instrument-core';
3
+ import { InstrumentationConfig, InitializationError, ConfigLoader } from '@atrim/instrument-core';
3
4
  export { ConfigError, ConfigFileError, ConfigUrlError, ConfigValidationError, ExportError, InitializationError, InstrumentationConfig, PatternConfig, PatternMatcher, ShutdownError, clearPatternMatcher, getPatternMatcher, initializePatternMatcher, shouldInstrumentSpan } from '@atrim/instrument-core';
4
5
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
- import { Layer } from 'effect';
6
6
  import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
7
7
  import { Context, Span } from '@opentelemetry/api';
8
8
 
@@ -70,31 +70,6 @@ interface SdkInitializationOptions {
70
70
  * @default true
71
71
  */
72
72
  enableXhr?: boolean;
73
- /**
74
- * Control trace context propagation for cross-origin requests
75
- *
76
- * Determines which cross-origin requests receive W3C Trace Context headers
77
- * (traceparent, tracestate). Note: Backends must allow these headers in CORS
78
- * configuration or requests will fail with CORS errors.
79
- *
80
- * - 'all': Propagate to all cross-origin requests (may cause CORS errors)
81
- * - 'none': Never propagate trace headers
82
- * - 'same-origin': Only propagate to same-origin requests (default)
83
- * - Array of URL patterns: Custom propagation patterns (regex strings)
84
- *
85
- * @default 'same-origin'
86
- *
87
- * @example Propagate to specific API domains
88
- * ```typescript
89
- * propagateTraceContext: ['^https://api\\.myapp\\.com', '^http://localhost:300[0-9]']
90
- * ```
91
- *
92
- * @example Disable propagation (for debugging CORS issues)
93
- * ```typescript
94
- * propagateTraceContext: 'none'
95
- * ```
96
- */
97
- propagateTraceContext?: 'all' | 'none' | 'same-origin' | string[];
98
73
  }
99
74
  /**
100
75
  * Get the current SDK instance
@@ -128,37 +103,47 @@ declare function resetSdk(): void;
128
103
  * Call this function once at application startup, before any other code runs.
129
104
  *
130
105
  * @param options - Initialization options
131
- * @returns WebTracerProvider instance
132
- * @throws {Error} If initialization fails
106
+ * @returns Effect that yields WebTracerProvider instance
133
107
  *
134
108
  * @example
135
109
  * ```typescript
110
+ * import { Effect } from 'effect'
136
111
  * import { initializeInstrumentation } from '@atrim/instrument-web'
137
112
  *
138
- * await initializeInstrumentation({
113
+ * const program = initializeInstrumentation({
139
114
  * serviceName: 'my-app',
140
115
  * otlpEndpoint: 'http://localhost:4318/v1/traces'
141
116
  * })
117
+ *
118
+ * await Effect.runPromise(program)
142
119
  * ```
143
120
  *
144
121
  * @example With pattern-based filtering
145
122
  * ```typescript
146
- * await initializeInstrumentation({
123
+ * const program = initializeInstrumentation({
147
124
  * serviceName: 'my-app',
148
125
  * configUrl: 'https://config.company.com/instrumentation.yaml'
149
126
  * })
127
+ *
128
+ * await Effect.runPromise(program)
150
129
  * ```
151
130
  *
152
- * @example Disable specific instrumentations
131
+ * @example With error handling
153
132
  * ```typescript
154
- * await initializeInstrumentation({
133
+ * const program = initializeInstrumentation({
155
134
  * serviceName: 'my-app',
156
- * enableUserInteraction: false, // Disable click tracking
157
- * enableXhr: false // Disable XMLHttpRequest tracking
158
- * })
135
+ * enableUserInteraction: false
136
+ * }).pipe(
137
+ * Effect.catchTag('InitializationError', (error) => {
138
+ * console.error('Failed to initialize:', error.reason)
139
+ * return Effect.die(error) // Re-throw or handle
140
+ * })
141
+ * )
142
+ *
143
+ * await Effect.runPromise(program)
159
144
  * ```
160
145
  */
161
- declare function initializeInstrumentation(options: SdkInitializationOptions): Promise<WebTracerProvider>;
146
+ declare const initializeInstrumentation: (options: SdkInitializationOptions) => Effect.Effect<WebTracerProvider, InitializationError>;
162
147
 
163
148
  /**
164
149
  * OTLP Exporter Factory for Browser
@@ -1,9 +1,9 @@
1
+ import { Data, Context, Effect, Layer, Deferred } from 'effect';
1
2
  import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
2
3
  import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
3
4
  import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
5
  import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
5
6
  import { ZoneContextManager } from '@opentelemetry/context-zone';
6
- import { Data, Context, Effect, Layer } from 'effect';
7
7
  import { FileSystem } from '@effect/platform/FileSystem';
8
8
  import * as HttpClient from '@effect/platform/HttpClient';
9
9
  import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
@@ -4539,92 +4539,34 @@ var PatternSpanProcessor = class {
4539
4539
 
4540
4540
  // src/core/sdk-initializer.ts
4541
4541
  var sdkInstance = null;
4542
- function buildPropagateTraceUrls(options, config) {
4543
- const apiOption = options.propagateTraceContext;
4544
- const yamlStrategy = config?.http?.propagate_trace_context?.strategy;
4545
- const yamlIncludeUrls = config?.http?.propagate_trace_context?.include_urls;
4546
- let effectiveStrategy;
4547
- let patterns = [];
4548
- if (apiOption !== void 0) {
4549
- if (typeof apiOption === "string") {
4550
- effectiveStrategy = apiOption;
4551
- } else {
4552
- effectiveStrategy = "patterns";
4553
- patterns = apiOption;
4554
- }
4555
- } else if (yamlStrategy) {
4556
- effectiveStrategy = yamlStrategy;
4557
- if (effectiveStrategy === "patterns" && yamlIncludeUrls) {
4558
- patterns = yamlIncludeUrls;
4559
- }
4560
- } else {
4561
- effectiveStrategy = "same-origin";
4562
- }
4563
- switch (effectiveStrategy) {
4564
- case "all":
4565
- return [/.*/];
4566
- case "none":
4567
- case "same-origin":
4568
- return [];
4569
- case "patterns": {
4570
- const regexes = [];
4571
- for (const pattern of patterns) {
4572
- try {
4573
- regexes.push(new RegExp(pattern));
4574
- } catch (error) {
4575
- console.warn(
4576
- `[@atrim/instrument-web] Invalid trace propagation pattern: "${pattern}"`,
4577
- error
4578
- );
4579
- }
4580
- }
4581
- return regexes;
4582
- }
4583
- default:
4584
- console.warn(
4585
- `[@atrim/instrument-web] Unknown trace propagation strategy: "${effectiveStrategy}". Defaulting to same-origin only.`
4586
- );
4587
- return [];
4588
- }
4589
- }
4590
- async function initializeSdk(options) {
4542
+ var initializationDeferred = null;
4543
+ var initializeSdkEffect = (options) => Effect.gen(function* () {
4591
4544
  if (sdkInstance) {
4592
4545
  return sdkInstance;
4593
4546
  }
4594
- try {
4595
- let config = null;
4596
- if (options.config) {
4597
- config = await loadConfigFromInline(options.config);
4598
- } else if (options.configPath || options.configUrl) {
4599
- const url = options.configUrl || options.configPath;
4600
- config = await loadConfig(url);
4601
- }
4602
- if (config) {
4603
- initializePatternMatcher(config);
4604
- }
4605
- const ignoreUrls = [
4606
- // Always ignore standard OTLP endpoints (prevents infinite loops)
4607
- /\/v1\/traces$/,
4608
- /\/v1\/metrics$/,
4609
- /\/v1\/logs$/
4610
- ];
4611
- if (config?.http?.ignore_outgoing_urls) {
4612
- for (const pattern of config.http.ignore_outgoing_urls) {
4613
- try {
4614
- ignoreUrls.push(new RegExp(pattern));
4615
- } catch (error) {
4616
- console.warn(
4617
- `[@atrim/instrument-web] Invalid ignore_outgoing_urls pattern: "${pattern}"`,
4618
- error
4619
- );
4620
- }
4621
- }
4622
- } else {
4623
- console.warn(
4624
- "[@atrim/instrument-web] Missing http.ignore_outgoing_urls in instrumentation.yaml. Using default OTLP endpoint patterns only. Consider adding http filtering to your config for better control."
4625
- );
4626
- }
4627
- const propagateTraceUrls = buildPropagateTraceUrls(options, config);
4547
+ if (initializationDeferred) {
4548
+ return yield* Deferred.await(initializationDeferred);
4549
+ }
4550
+ const deferred = yield* Deferred.make();
4551
+ initializationDeferred = deferred;
4552
+ const result = yield* performInitializationEffect(options).pipe(
4553
+ Effect.tap((provider) => Deferred.succeed(deferred, provider)),
4554
+ Effect.tapError((error) => Deferred.fail(deferred, error)),
4555
+ Effect.ensuring(
4556
+ Effect.sync(() => {
4557
+ initializationDeferred = null;
4558
+ })
4559
+ )
4560
+ );
4561
+ return result;
4562
+ });
4563
+ var performInitializationEffect = (options) => Effect.gen(function* () {
4564
+ const config = yield* loadConfigEffect(options);
4565
+ if (config) {
4566
+ initializePatternMatcher(config);
4567
+ }
4568
+ const ignoreUrls = buildIgnoreUrls(config);
4569
+ const exporter = yield* Effect.sync(() => {
4628
4570
  const exporterOptions = {};
4629
4571
  if (options.otlpEndpoint) {
4630
4572
  exporterOptions.endpoint = options.otlpEndpoint;
@@ -4632,18 +4574,23 @@ async function initializeSdk(options) {
4632
4574
  if (options.otlpHeaders) {
4633
4575
  exporterOptions.headers = options.otlpHeaders;
4634
4576
  }
4635
- const exporter = createOtlpExporter(exporterOptions);
4636
- const spanProcessors = [];
4637
- if (config) {
4638
- spanProcessors.push(new PatternSpanProcessor());
4639
- }
4640
- spanProcessors.push(new SimpleSpanProcessor(exporter));
4641
- const provider = new WebTracerProvider({
4577
+ return createOtlpExporter(exporterOptions);
4578
+ });
4579
+ const spanProcessors = [];
4580
+ if (config) {
4581
+ spanProcessors.push(new PatternSpanProcessor());
4582
+ }
4583
+ spanProcessors.push(new SimpleSpanProcessor(exporter));
4584
+ const provider = yield* Effect.sync(() => {
4585
+ const p = new WebTracerProvider({
4642
4586
  spanProcessors
4643
4587
  });
4644
- provider.register({
4588
+ p.register({
4645
4589
  contextManager: new ZoneContextManager()
4646
4590
  });
4591
+ return p;
4592
+ });
4593
+ yield* Effect.sync(() => {
4647
4594
  registerInstrumentations({
4648
4595
  instrumentations: [
4649
4596
  getWebAutoInstrumentations({
@@ -4656,25 +4603,69 @@ async function initializeSdk(options) {
4656
4603
  },
4657
4604
  "@opentelemetry/instrumentation-fetch": {
4658
4605
  enabled: options.enableFetch ?? true,
4659
- propagateTraceHeaderCorsUrls: propagateTraceUrls,
4660
- // Controlled by config/API
4606
+ propagateTraceHeaderCorsUrls: [/.*/],
4607
+ // Propagate to all origins
4661
4608
  clearTimingResources: true,
4662
4609
  ignoreUrls
4663
4610
  // Prevent self-instrumentation of OTLP exports
4664
4611
  },
4665
4612
  "@opentelemetry/instrumentation-xml-http-request": {
4666
4613
  enabled: options.enableXhr ?? true,
4667
- propagateTraceHeaderCorsUrls: propagateTraceUrls
4668
- // Controlled by config/API
4614
+ propagateTraceHeaderCorsUrls: [/.*/]
4669
4615
  }
4670
4616
  })
4671
4617
  ]
4672
4618
  });
4673
- sdkInstance = provider;
4674
- return provider;
4675
- } catch (error) {
4676
- throw new Error(`Failed to initialize OpenTelemetry SDK: ${error}`);
4619
+ });
4620
+ sdkInstance = provider;
4621
+ return provider;
4622
+ });
4623
+ var loadConfigEffect = (options) => Effect.gen(function* () {
4624
+ if (options.config) {
4625
+ return yield* Effect.tryPromise({
4626
+ try: () => loadConfigFromInline(options.config),
4627
+ catch: (error) => new InitializationError({
4628
+ reason: "Failed to load inline config",
4629
+ cause: error
4630
+ })
4631
+ });
4677
4632
  }
4633
+ if (options.configPath || options.configUrl) {
4634
+ const url = options.configUrl || options.configPath;
4635
+ return yield* Effect.tryPromise({
4636
+ try: () => loadConfig(url),
4637
+ catch: (error) => new InitializationError({
4638
+ reason: `Failed to load config from ${url}`,
4639
+ cause: error
4640
+ })
4641
+ });
4642
+ }
4643
+ return null;
4644
+ });
4645
+ function buildIgnoreUrls(config) {
4646
+ const ignoreUrls = [
4647
+ // Always ignore standard OTLP endpoints (prevents infinite loops)
4648
+ /\/v1\/traces$/,
4649
+ /\/v1\/metrics$/,
4650
+ /\/v1\/logs$/
4651
+ ];
4652
+ if (config?.http?.ignore_outgoing_urls) {
4653
+ for (const pattern of config.http.ignore_outgoing_urls) {
4654
+ try {
4655
+ ignoreUrls.push(new RegExp(pattern));
4656
+ } catch (error) {
4657
+ console.warn(
4658
+ `[@atrim/instrument-web] Invalid ignore_outgoing_urls pattern: "${pattern}"`,
4659
+ error
4660
+ );
4661
+ }
4662
+ }
4663
+ } else {
4664
+ console.warn(
4665
+ "[@atrim/instrument-web] Missing http.ignore_outgoing_urls in instrumentation.yaml. Using default OTLP endpoint patterns only. Consider adding http filtering to your config for better control."
4666
+ );
4667
+ }
4668
+ return ignoreUrls;
4678
4669
  }
4679
4670
  function getSdkInstance() {
4680
4671
  return sdkInstance;
@@ -4687,12 +4678,11 @@ async function shutdownSdk() {
4687
4678
  }
4688
4679
  function resetSdk() {
4689
4680
  sdkInstance = null;
4681
+ initializationDeferred = null;
4690
4682
  }
4691
4683
 
4692
4684
  // src/api.ts
4693
- async function initializeInstrumentation(options) {
4694
- return await initializeSdk(options);
4695
- }
4685
+ var initializeInstrumentation = (options) => initializeSdkEffect(options);
4696
4686
  function setSpanAttributes(span, attributes) {
4697
4687
  for (const [key, value] of Object.entries(attributes)) {
4698
4688
  span.setAttribute(key, value);