@atrim/instrument-web 0.4.1 → 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.4.1",
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",
@@ -68,7 +68,7 @@
68
68
  "tsup": "^8.0.1",
69
69
  "typescript": "^5.7.2",
70
70
  "vitest": "^4.0.8",
71
- "@atrim/instrument-core": "0.4.1"
71
+ "@atrim/instrument-core": "0.5.0"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsup",
@@ -82,10 +82,10 @@
82
82
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
83
83
  "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
84
84
  "clean": "rm -rf target",
85
- "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",
85
+ "publish:dev:version": "pnpm version $(node -p \"require('./package.json').version\")-$(git rev-parse --short HEAD)-$(date -u +%Y%m%d%H%M%S) --no-git-tag-version",
86
86
  "publish:dev:save": "node -p \"require('./package.json').version\" > .version",
87
87
  "publish:dev:publish": "pnpm build && pnpm publish --tag dev --access public --no-git-checks",
88
- "publish:dev:reset": "npm version 0.1.0 --no-git-tag-version",
88
+ "publish:dev:reset": "pnpm version 0.5.0 --no-git-tag-version",
89
89
  "publish:dev": "pnpm publish:dev:version && pnpm publish:dev:save && pnpm publish:dev:publish && pnpm publish:dev:reset"
90
90
  }
91
91
  }
@@ -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
 
@@ -103,37 +103,47 @@ declare function resetSdk(): void;
103
103
  * Call this function once at application startup, before any other code runs.
104
104
  *
105
105
  * @param options - Initialization options
106
- * @returns WebTracerProvider instance
107
- * @throws {Error} If initialization fails
106
+ * @returns Effect that yields WebTracerProvider instance
108
107
  *
109
108
  * @example
110
109
  * ```typescript
110
+ * import { Effect } from 'effect'
111
111
  * import { initializeInstrumentation } from '@atrim/instrument-web'
112
112
  *
113
- * await initializeInstrumentation({
113
+ * const program = initializeInstrumentation({
114
114
  * serviceName: 'my-app',
115
115
  * otlpEndpoint: 'http://localhost:4318/v1/traces'
116
116
  * })
117
+ *
118
+ * await Effect.runPromise(program)
117
119
  * ```
118
120
  *
119
121
  * @example With pattern-based filtering
120
122
  * ```typescript
121
- * await initializeInstrumentation({
123
+ * const program = initializeInstrumentation({
122
124
  * serviceName: 'my-app',
123
125
  * configUrl: 'https://config.company.com/instrumentation.yaml'
124
126
  * })
127
+ *
128
+ * await Effect.runPromise(program)
125
129
  * ```
126
130
  *
127
- * @example Disable specific instrumentations
131
+ * @example With error handling
128
132
  * ```typescript
129
- * await initializeInstrumentation({
133
+ * const program = initializeInstrumentation({
130
134
  * serviceName: 'my-app',
131
- * enableUserInteraction: false, // Disable click tracking
132
- * enableXhr: false // Disable XMLHttpRequest tracking
133
- * })
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)
134
144
  * ```
135
145
  */
136
- declare function initializeInstrumentation(options: SdkInitializationOptions): Promise<WebTracerProvider>;
146
+ declare const initializeInstrumentation: (options: SdkInitializationOptions) => Effect.Effect<WebTracerProvider, InitializationError>;
137
147
 
138
148
  /**
139
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';
@@ -4095,7 +4095,20 @@ var HttpFilteringConfigSchema = external_exports.object({
4095
4095
  // Patterns to ignore for incoming HTTP requests (string patterns only in YAML)
4096
4096
  ignore_incoming_paths: external_exports.array(external_exports.string()).optional(),
4097
4097
  // Require parent span for outgoing requests (prevents root spans for HTTP calls)
4098
- require_parent_for_outgoing_spans: external_exports.boolean().optional()
4098
+ require_parent_for_outgoing_spans: external_exports.boolean().optional(),
4099
+ // Trace context propagation configuration
4100
+ // Controls which cross-origin requests receive W3C Trace Context headers (traceparent, tracestate)
4101
+ propagate_trace_context: external_exports.object({
4102
+ // Strategy for trace propagation
4103
+ // - "all": Propagate to all cross-origin requests (may cause CORS errors)
4104
+ // - "none": Never propagate trace headers
4105
+ // - "same-origin": Only propagate to same-origin requests (default, safe)
4106
+ // - "patterns": Propagate based on include_urls patterns
4107
+ strategy: external_exports.enum(["all", "none", "same-origin", "patterns"]).default("same-origin"),
4108
+ // URL patterns to include when strategy is "patterns"
4109
+ // Supports regex patterns (e.g., "^https://api\\.myapp\\.com")
4110
+ include_urls: external_exports.array(external_exports.string()).optional()
4111
+ }).optional()
4099
4112
  });
4100
4113
  var InstrumentationConfigSchema = external_exports.object({
4101
4114
  version: external_exports.string(),
@@ -4526,43 +4539,34 @@ var PatternSpanProcessor = class {
4526
4539
 
4527
4540
  // src/core/sdk-initializer.ts
4528
4541
  var sdkInstance = null;
4529
- async function initializeSdk(options) {
4542
+ var initializationDeferred = null;
4543
+ var initializeSdkEffect = (options) => Effect.gen(function* () {
4530
4544
  if (sdkInstance) {
4531
4545
  return sdkInstance;
4532
4546
  }
4533
- try {
4534
- let config = null;
4535
- if (options.config) {
4536
- config = await loadConfigFromInline(options.config);
4537
- } else if (options.configPath || options.configUrl) {
4538
- const url = options.configUrl || options.configPath;
4539
- config = await loadConfig(url);
4540
- }
4541
- if (config) {
4542
- initializePatternMatcher(config);
4543
- }
4544
- const ignoreUrls = [
4545
- // Always ignore standard OTLP endpoints (prevents infinite loops)
4546
- /\/v1\/traces$/,
4547
- /\/v1\/metrics$/,
4548
- /\/v1\/logs$/
4549
- ];
4550
- if (config?.http?.ignore_outgoing_urls) {
4551
- for (const pattern of config.http.ignore_outgoing_urls) {
4552
- try {
4553
- ignoreUrls.push(new RegExp(pattern));
4554
- } catch (error) {
4555
- console.warn(
4556
- `[@atrim/instrument-web] Invalid ignore_outgoing_urls pattern: "${pattern}"`,
4557
- error
4558
- );
4559
- }
4560
- }
4561
- } else {
4562
- console.warn(
4563
- "[@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."
4564
- );
4565
- }
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(() => {
4566
4570
  const exporterOptions = {};
4567
4571
  if (options.otlpEndpoint) {
4568
4572
  exporterOptions.endpoint = options.otlpEndpoint;
@@ -4570,18 +4574,23 @@ async function initializeSdk(options) {
4570
4574
  if (options.otlpHeaders) {
4571
4575
  exporterOptions.headers = options.otlpHeaders;
4572
4576
  }
4573
- const exporter = createOtlpExporter(exporterOptions);
4574
- const spanProcessors = [];
4575
- if (config) {
4576
- spanProcessors.push(new PatternSpanProcessor());
4577
- }
4578
- spanProcessors.push(new SimpleSpanProcessor(exporter));
4579
- 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({
4580
4586
  spanProcessors
4581
4587
  });
4582
- provider.register({
4588
+ p.register({
4583
4589
  contextManager: new ZoneContextManager()
4584
4590
  });
4591
+ return p;
4592
+ });
4593
+ yield* Effect.sync(() => {
4585
4594
  registerInstrumentations({
4586
4595
  instrumentations: [
4587
4596
  getWebAutoInstrumentations({
@@ -4607,11 +4616,56 @@ async function initializeSdk(options) {
4607
4616
  })
4608
4617
  ]
4609
4618
  });
4610
- sdkInstance = provider;
4611
- return provider;
4612
- } catch (error) {
4613
- 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
+ });
4614
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;
4615
4669
  }
4616
4670
  function getSdkInstance() {
4617
4671
  return sdkInstance;
@@ -4624,12 +4678,11 @@ async function shutdownSdk() {
4624
4678
  }
4625
4679
  function resetSdk() {
4626
4680
  sdkInstance = null;
4681
+ initializationDeferred = null;
4627
4682
  }
4628
4683
 
4629
4684
  // src/api.ts
4630
- async function initializeInstrumentation(options) {
4631
- return await initializeSdk(options);
4632
- }
4685
+ var initializeInstrumentation = (options) => initializeSdkEffect(options);
4633
4686
  function setSpanAttributes(span, attributes) {
4634
4687
  for (const [key, value] of Object.entries(attributes)) {
4635
4688
  span.setAttribute(key, value);