@arizeai/phoenix-otel 0.3.0 → 0.3.1

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": "@arizeai/phoenix-otel",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Otel registration and convenience methods",
5
5
  "main": "dist/src/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^24.9.1",
41
- "vitest": "^2.1.9"
41
+ "vitest": "^4.0.10"
42
42
  },
43
43
  "scripts": {
44
44
  "clean": "rimraf dist",
@@ -46,6 +46,7 @@
46
46
  "build": "tsc --build tsconfig.json tsconfig.esm.json && tsc-alias -p tsconfig.esm.json",
47
47
  "postbuild": "echo '{\"type\": \"module\"}' > ./dist/esm/package.json",
48
48
  "type:check": "tsc --noEmit",
49
- "test": "vitest --typecheck"
49
+ "test": "vitest run",
50
+ "test:watch": "vitest watch"
50
51
  }
51
52
  }
package/src/register.ts CHANGED
@@ -18,93 +18,326 @@ import {
18
18
  SpanProcessor,
19
19
  } from "@opentelemetry/sdk-trace-node";
20
20
 
21
+ /**
22
+ * Type definition for HTTP headers used in OTLP communication
23
+ * @example { "custom-header": "value", "x-api-version": "1.0" }
24
+ */
21
25
  export type Headers = Record<string, string>;
22
26
 
23
27
  /**
24
- * Configuration parameters for registering Phoenix OpenTelemetry tracing
28
+ * Configuration parameters for registering Phoenix OpenTelemetry tracing.
29
+ *
30
+ * This interface defines all the available options for configuring the Phoenix
31
+ * OpenTelemetry integration, including connection details, processing options,
32
+ * and instrumentation settings.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const config: RegisterParams = {
37
+ * projectName: "my-application",
38
+ * url: "https://app.phoenix.arize.com",
39
+ * apiKey: "your-api-key",
40
+ * batch: true,
41
+ * global: true
42
+ * };
43
+ * ```
25
44
  */
26
45
  export type RegisterParams = {
27
46
  /**
28
- * The project the spans should be associated to
47
+ * The project name that spans will be associated with in Phoenix.
48
+ * This helps organize and filter traces in the Phoenix UI.
49
+ *
29
50
  * @default "default"
51
+ * @example "my-web-app"
52
+ * @example "api-service"
30
53
  */
31
54
  projectName?: string;
55
+
32
56
  /**
33
- * The URL to the phoenix server. Can be postfixed with tracing path.
34
- * If not provided, environment variables are checked.
57
+ * The URL to the Phoenix server. Can be postfixed with the tracing path.
58
+ * If not provided, the system will check the PHOENIX_COLLECTOR_URL environment variable.
59
+ *
60
+ * The URL will be automatically normalized to include the `/v1/traces` endpoint if not present.
61
+ *
35
62
  * @example "https://app.phoenix.arize.com"
63
+ * @example "https://app.phoenix.arize.com/v1/traces"
64
+ * @example "http://localhost:6006"
36
65
  */
37
66
  url?: string;
67
+
38
68
  /**
39
- * The API key for the phoenix instance.
40
- * If not provided, environment variables are checked.
69
+ * The API key for authenticating with the Phoenix instance.
70
+ * If not provided, the system will check the PHOENIX_API_KEY environment variable.
71
+ *
72
+ * The API key will be automatically added to the Authorization header as a Bearer token.
73
+ *
74
+ * @example "phx_1234567890abcdef"
41
75
  */
42
76
  apiKey?: string;
77
+
43
78
  /**
44
- * Headers to be used when communicating with the OTLP collector
79
+ * Additional headers to be included when communicating with the OTLP collector.
80
+ * These headers will be merged with any automatically generated headers (like Authorization).
81
+ *
82
+ * @example { "x-custom-header": "value", "x-api-version": "1.0" }
45
83
  */
46
84
  headers?: Headers;
85
+
47
86
  /**
48
87
  * Whether to use batching for span processing.
49
- * It is recommended to use batching in production environments
88
+ *
89
+ * - `true` (default): Uses OpenInferenceBatchSpanProcessor for better performance in production
90
+ * - `false`: Uses OpenInferenceSimpleSpanProcessor for immediate span export (useful for debugging)
91
+ *
92
+ * Batching is recommended for production environments as it reduces network overhead
93
+ * and improves performance by sending multiple spans in a single request.
94
+ *
50
95
  * @default true
51
96
  */
52
97
  batch?: boolean;
98
+
53
99
  /**
54
- * A list of instrumentation to register.
55
- * Note: this may only work with commonjs projects. ESM projects will require manually applying instrumentation.
100
+ * A list of OpenTelemetry instrumentations to automatically register.
101
+ *
102
+ * **Note**: This feature may only work with CommonJS projects. ESM projects
103
+ * may require manual instrumentation registration.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
108
+ * import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
109
+ *
110
+ * const instrumentations = [
111
+ * new HttpInstrumentation(),
112
+ * new ExpressInstrumentation()
113
+ * ];
114
+ * ```
56
115
  */
57
116
  instrumentations?: Instrumentation[];
117
+
58
118
  /**
59
- * Whether to set the tracer as a global provider.
119
+ * Custom span processors to add to the tracer provider.
120
+ *
121
+ * **Important**: When provided, this will override the default span processor
122
+ * created from the `url`, `apiKey`, `headers`, and `batch` parameters.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
127
+ *
128
+ * const customProcessors = [
129
+ * new BatchSpanProcessor(exporter),
130
+ * new MyCustomSpanProcessor()
131
+ * ];
132
+ * ```
133
+ */
134
+ spanProcessors?: SpanProcessor[];
135
+
136
+ /**
137
+ * Whether to register the tracer provider as the global provider.
138
+ *
139
+ * When `true` (default), the provider will be registered globally and can be
140
+ * accessed throughout the application. Set to `false` if you want to manage
141
+ * the provider lifecycle manually or use multiple providers.
142
+ *
60
143
  * @default true
61
144
  */
62
145
  global?: boolean;
146
+
63
147
  /**
64
- * The diag log level to set for the built in DiagConsoleLogger instance.
65
- * Omit to disable built in logging.
148
+ * The diagnostic log level for the built-in DiagConsoleLogger.
149
+ *
150
+ * This controls the verbosity of OpenTelemetry's internal logging.
151
+ * Omit this parameter to disable built-in logging entirely.
152
+ *
153
+ * @example DiagLogLevel.INFO
154
+ * @example DiagLogLevel.DEBUG
155
+ * @example DiagLogLevel.ERROR
66
156
  */
67
157
  diagLogLevel?: DiagLogLevel;
68
158
  };
69
159
 
70
160
  /**
71
- * Registers Phoenix OpenTelemetry tracing with the specified configuration
161
+ * Registers Phoenix OpenTelemetry tracing with the specified configuration.
162
+ *
163
+ * This function sets up a complete OpenTelemetry tracing pipeline configured
164
+ * to send traces to a Phoenix instance. It creates a NodeTracerProvider with
165
+ * appropriate span processors, resource attributes, and optional instrumentations.
166
+ *
167
+ * The function handles:
168
+ * - Creating and configuring a NodeTracerProvider
169
+ * - Setting up OTLP trace export to Phoenix
170
+ * - Configuring span processors (batch or simple)
171
+ * - Registering instrumentations (if provided)
172
+ * - Setting up resource attributes for project identification
173
+ * - Optional global provider registration
72
174
  *
73
175
  * @param params - Configuration parameters for Phoenix tracing
74
- * @param params.url - The URL to the phoenix server. Can be postfixed with tracing path
75
- * @param params.apiKey - The API key for the phoenix instance
76
- * @param params.projectName - The project the spans should be associated to
77
- * @param params.instrumentations - A list of instrumentation to register
78
- * @param params.batch - Whether to use batching for span processing
79
- * @param params.global - Whether to set the tracer as a global provider
80
- * @param params.diagLogLevel - the diagnostics log level
81
- * @returns {NodeTracerProvider} The configured NodeTracerProvider instance
176
+ * @returns The configured NodeTracerProvider instance that can be used to create traces
177
+ *
178
+ * @example
179
+ * Basic usage with minimal configuration:
180
+ * ```typescript
181
+ * import { register } from '@arizeai/phoenix-otel';
182
+ *
183
+ * // Uses environment variables for URL and API key
184
+ * const provider = register({
185
+ * projectName: 'my-application'
186
+ * });
187
+ * ```
188
+ *
189
+ * @example
190
+ * Full configuration with custom settings:
191
+ * ```typescript
192
+ * import { register } from '@arizeai/phoenix-otel';
193
+ * import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
194
+ * import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
195
+ *
196
+ * const provider = register({
197
+ * projectName: 'my-web-app',
198
+ * url: 'https://app.phoenix.arize.com',
199
+ * apiKey: 'phx_1234567890abcdef',
200
+ * batch: true,
201
+ * global: true,
202
+ * instrumentations: [
203
+ * new HttpInstrumentation(),
204
+ * new ExpressInstrumentation()
205
+ * ],
206
+ * diagLogLevel: DiagLogLevel.INFO
207
+ * });
208
+ * ```
82
209
  *
83
210
  * @example
211
+ * Custom span processors:
84
212
  * ```typescript
85
213
  * import { register } from '@arizeai/phoenix-otel';
214
+ * import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
215
+ * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
216
+ *
217
+ * const exporter = new OTLPTraceExporter({
218
+ * url: 'https://app.phoenix.arize.com/v1/traces',
219
+ * headers: { 'Authorization': 'Bearer your-api-key' }
220
+ * });
86
221
  *
87
222
  * const provider = register({
88
223
  * projectName: 'my-app',
224
+ * spanProcessors: [new BatchSpanProcessor(exporter)],
225
+ * global: false // Manual provider management
226
+ * });
227
+ * ```
228
+ *
229
+ * @example
230
+ * Debugging configuration:
231
+ * ```typescript
232
+ * const provider = register({
233
+ * projectName: 'debug-app',
234
+ * url: 'http://localhost:6006',
235
+ * batch: false, // Immediate span export for debugging
236
+ * diagLogLevel: DiagLogLevel.DEBUG
237
+ * });
238
+ * ```
239
+ */
240
+ export function register(params: RegisterParams): NodeTracerProvider {
241
+ const {
242
+ projectName = "default",
243
+ instrumentations,
244
+ global = true,
245
+ diagLogLevel,
246
+ spanProcessors,
247
+ } = params;
248
+
249
+ if (diagLogLevel) {
250
+ diag.setLogger(new DiagConsoleLogger(), diagLogLevel);
251
+ }
252
+ const provider = new NodeTracerProvider({
253
+ resource: resourceFromAttributes({
254
+ [SEMRESATTRS_PROJECT_NAME]: projectName,
255
+ }),
256
+ spanProcessors: spanProcessors || [getDefaultSpanProcessor(params)],
257
+ });
258
+
259
+ if (instrumentations) {
260
+ registerInstrumentations({
261
+ instrumentations,
262
+ tracerProvider: provider,
263
+ });
264
+ }
265
+ if (global) {
266
+ provider.register();
267
+ }
268
+ return provider;
269
+ }
270
+ /**
271
+ * Creates a default span processor configured for Phoenix OpenTelemetry tracing.
272
+ *
273
+ * This function creates an appropriate span processor (batch or simple) based on
274
+ * the provided configuration parameters. It handles URL normalization, header
275
+ * configuration, and API key authentication automatically.
276
+ *
277
+ * The function will:
278
+ * - Normalize the collector URL to include the `/v1/traces` endpoint if needed
279
+ * - Configure authentication headers using the provided API key
280
+ * - Merge any additional custom headers
281
+ * - Create either a batch or simple span processor based on the `batch` parameter
282
+ *
283
+ * @param params - Configuration parameters for the span processor
284
+ * @param params.url - The URL to the Phoenix server (will be normalized to include `/v1/traces`)
285
+ * @param params.apiKey - The API key for authenticating with the Phoenix instance
286
+ * @param params.headers - Additional headers to include in OTLP requests
287
+ * @param params.batch - Whether to use batching for span processing (recommended for production)
288
+ * @returns A configured SpanProcessor instance ready for use with a NodeTracerProvider
289
+ *
290
+ * @example
291
+ * Basic usage with environment variables:
292
+ * ```typescript
293
+ * const processor = getDefaultSpanProcessor({
294
+ * batch: true
295
+ * });
296
+ * ```
297
+ *
298
+ * @example
299
+ * Full configuration with custom settings:
300
+ * ```typescript
301
+ * const processor = getDefaultSpanProcessor({
302
+ * url: 'https://app.phoenix.arize.com',
303
+ * apiKey: 'phx_1234567890abcdef',
304
+ * headers: { 'x-custom-header': 'value' },
305
+ * batch: true
306
+ * });
307
+ * ```
308
+ *
309
+ * @example
310
+ * Debugging configuration with immediate export:
311
+ * ```typescript
312
+ * const processor = getDefaultSpanProcessor({
313
+ * url: 'http://localhost:6006',
314
+ * batch: false // Immediate span export for debugging
315
+ * });
316
+ * ```
317
+ *
318
+ * @example
319
+ * Using with custom headers:
320
+ * ```typescript
321
+ * const processor = getDefaultSpanProcessor({
89
322
  * url: 'https://app.phoenix.arize.com',
90
323
  * apiKey: 'your-api-key',
324
+ * headers: {
325
+ * 'x-api-version': '1.0',
326
+ * 'x-client-name': 'my-app'
327
+ * },
91
328
  * batch: true
92
329
  * });
93
330
  * ```
94
331
  */
95
- export function register({
332
+ export function getDefaultSpanProcessor({
96
333
  url: paramsUrl,
97
334
  apiKey: paramsApiKey,
98
335
  headers: paramsHeaders = {},
99
- projectName = "default",
100
- instrumentations,
101
336
  batch = true,
102
- global = true,
103
- diagLogLevel,
104
- }: RegisterParams): NodeTracerProvider {
105
- if (diagLogLevel) {
106
- diag.setLogger(new DiagConsoleLogger(), diagLogLevel);
107
- }
337
+ }: Pick<
338
+ RegisterParams,
339
+ "url" | "apiKey" | "batch" | "headers"
340
+ >): SpanProcessor {
108
341
  const url = ensureCollectorEndpoint(
109
342
  paramsUrl || getEnvCollectorURL() || "http://localhost:6006"
110
343
  );
@@ -126,35 +359,70 @@ export function register({
126
359
  } else {
127
360
  spanProcessor = new OpenInferenceSimpleSpanProcessor({ exporter });
128
361
  }
129
- const provider = new NodeTracerProvider({
130
- resource: resourceFromAttributes({
131
- [SEMRESATTRS_PROJECT_NAME]: projectName,
132
- }),
133
- spanProcessors: [spanProcessor],
134
- });
135
-
136
- if (instrumentations) {
137
- registerInstrumentations({
138
- instrumentations,
139
- tracerProvider: provider,
140
- });
141
- }
142
- if (global) {
143
- provider.register();
144
- }
145
- return provider;
362
+ return spanProcessor;
146
363
  }
147
-
148
364
  /**
149
- * A utility method to normalize the URL to be HTTP compatible.
150
- * Assumes we are using HTTP over gRPC and ensures the URL ends with the correct traces endpoint.
365
+ * Normalizes a Phoenix server URL to ensure it includes the correct OTLP traces endpoint.
366
+ *
367
+ * This utility function ensures that any Phoenix server URL is properly formatted
368
+ * to include the `/v1/traces` endpoint required for OTLP trace export. It handles
369
+ * various URL formats and automatically appends the endpoint if missing.
370
+ *
371
+ * The function:
372
+ * - Checks if the URL already contains `/v1/traces`
373
+ * - If missing, appends `/v1/traces` to the base URL
374
+ * - Returns a properly formatted URL string
375
+ * - Assumes HTTP over gRPC protocol for OTLP communication
151
376
  *
152
- * @param url - The URL to the phoenix server. May contain a slug
153
- * @returns {string} The normalized URL with the '/v1/traces' endpoint
377
+ * @param url - The base URL to the Phoenix server (may or may not include the traces endpoint)
378
+ * @returns A normalized URL string that includes the `/v1/traces` endpoint
379
+ *
380
+ * @example
381
+ * URL without traces endpoint:
382
+ * ```typescript
383
+ * const normalized = ensureCollectorEndpoint('https://app.phoenix.arize.com');
384
+ * // Returns: 'https://app.phoenix.arize.com/v1/traces'
385
+ * ```
386
+ *
387
+ * @example
388
+ * URL that already includes traces endpoint:
389
+ * ```typescript
390
+ * const normalized = ensureCollectorEndpoint('https://app.phoenix.arize.com/v1/traces');
391
+ * // Returns: 'https://app.phoenix.arize.com/v1/traces'
392
+ * ```
393
+ *
394
+ * @example
395
+ * Local development URL:
396
+ * ```typescript
397
+ * const normalized = ensureCollectorEndpoint('http://localhost:6006');
398
+ * // Returns: 'http://localhost:6006/v1/traces'
399
+ * ```
400
+ *
401
+ * @example
402
+ * URL with custom port and path:
403
+ * ```typescript
404
+ * const normalized = ensureCollectorEndpoint('https://phoenix.example.com:8080');
405
+ * // Returns: 'https://phoenix.example.com:8080/v1/traces'
406
+ * ```
407
+ *
408
+ * @example
409
+ * URL with existing path (edge case):
410
+ * ```typescript
411
+ * const normalized = ensureCollectorEndpoint('https://app.phoenix.arize.com/api');
412
+ * // Returns: 'https://app.phoenix.arize.com/api/v1/traces'
413
+ * ```
154
414
  */
155
415
  export function ensureCollectorEndpoint(url: string): string {
156
416
  if (!url.includes("/v1/traces")) {
157
- return new URL("/v1/traces", url).toString();
417
+ // Ensure the base URL has a trailing slash for proper path concatenation
418
+ // Without this, the URL constructor treats the last segment as a file and replaces it
419
+ const baseUrl = new URL(url);
420
+ if (!baseUrl.pathname.endsWith("/")) {
421
+ baseUrl.pathname += "/";
422
+ }
423
+ // Append v1/traces to the pathname (without leading slash to append, not replace)
424
+ baseUrl.pathname += "v1/traces";
425
+ return baseUrl.toString();
158
426
  }
159
427
  return new URL(url).toString();
160
428
  }