@arizeai/phoenix-otel 0.4.2 → 0.4.3
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/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/register.d.ts +21 -0
- package/dist/esm/register.d.ts.map +1 -1
- package/dist/esm/register.js +135 -2
- package/dist/esm/register.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/register.d.ts +21 -0
- package/dist/src/register.d.ts.map +1 -1
- package/dist/src/register.js +136 -1
- package/dist/src/register.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/manual-spans.mdx +99 -0
- package/docs/overview.mdx +248 -0
- package/package.json +7 -2
- package/src/index.ts +1 -0
- package/src/register.ts +229 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Manual Spans"
|
|
3
|
+
description: "Create manual spans with @arizeai/phoenix-otel"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The package re-exports `trace`, `context`, and `SpanStatusCode` from OpenTelemetry so you can create custom spans after registration without pulling them from another package.
|
|
7
|
+
|
|
8
|
+
For most use cases, prefer the `@arizeai/openinference-core` helpers (`withSpan`, `traceChain`, `traceAgent`, `traceTool`) over raw `startActiveSpan`. They automatically set the correct OpenInference span kind, handle errors, and close the span for you.
|
|
9
|
+
|
|
10
|
+
<section className="hidden" data-agent-context="relevant-source-files" aria-label="Relevant source files">
|
|
11
|
+
<h2>Relevant Source Files</h2>
|
|
12
|
+
<ul>
|
|
13
|
+
<li><code>src/index.ts</code></li>
|
|
14
|
+
</ul>
|
|
15
|
+
</section>
|
|
16
|
+
|
|
17
|
+
## Recommended — OpenInference Helpers
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @arizeai/openinference-core @arizeai/openinference-semantic-conventions
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { withSpan, setSession, setMetadata } from "@arizeai/openinference-core";
|
|
25
|
+
import { OpenInferenceSpanKind } from "@arizeai/openinference-semantic-conventions";
|
|
26
|
+
import { context } from "@opentelemetry/api";
|
|
27
|
+
import { register } from "@arizeai/phoenix-otel";
|
|
28
|
+
|
|
29
|
+
register({ projectName: "support-bot" });
|
|
30
|
+
|
|
31
|
+
const retrieveDocs = withSpan(
|
|
32
|
+
async (query: string) => {
|
|
33
|
+
const response = await fetch(`/api/search?q=${query}`);
|
|
34
|
+
return response.json();
|
|
35
|
+
},
|
|
36
|
+
{ name: "retrieve-docs", kind: OpenInferenceSpanKind.RETRIEVER }
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const generateAnswer = withSpan(
|
|
40
|
+
async (query: string, docs: string[]) => {
|
|
41
|
+
return `Answer based on ${docs.length} documents`;
|
|
42
|
+
},
|
|
43
|
+
{ name: "generate-answer", kind: OpenInferenceSpanKind.LLM }
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const ragPipeline = withSpan(
|
|
47
|
+
async (query: string) => {
|
|
48
|
+
const docs = await retrieveDocs(query);
|
|
49
|
+
return generateAnswer(query, docs);
|
|
50
|
+
},
|
|
51
|
+
{ name: "rag-pipeline", kind: OpenInferenceSpanKind.CHAIN }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Session and metadata propagate to all child spans
|
|
55
|
+
await context.with(
|
|
56
|
+
setMetadata(
|
|
57
|
+
setSession(context.active(), { sessionId: "session-abc-123" }),
|
|
58
|
+
{ environment: "production" }
|
|
59
|
+
),
|
|
60
|
+
() => ragPipeline("What is Phoenix?")
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Raw OpenTelemetry Spans
|
|
65
|
+
|
|
66
|
+
Use raw spans when you need full control over attributes and timing:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import {
|
|
70
|
+
SpanStatusCode,
|
|
71
|
+
register,
|
|
72
|
+
trace,
|
|
73
|
+
} from "@arizeai/phoenix-otel";
|
|
74
|
+
|
|
75
|
+
register({ projectName: "support-bot" });
|
|
76
|
+
|
|
77
|
+
const tracer = trace.getTracer("support-bot");
|
|
78
|
+
|
|
79
|
+
await tracer.startActiveSpan("lookup-customer", async (span) => {
|
|
80
|
+
try {
|
|
81
|
+
span.setAttribute("customer.id", "cust_123");
|
|
82
|
+
await Promise.resolve();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
span.recordException(error as Error);
|
|
85
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
86
|
+
throw error;
|
|
87
|
+
} finally {
|
|
88
|
+
span.end();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
<section className="hidden" data-agent-context="source-map" aria-label="Source map">
|
|
94
|
+
<h2>Source Map</h2>
|
|
95
|
+
<ul>
|
|
96
|
+
<li><code>src/index.ts</code></li>
|
|
97
|
+
<li><code>src/register.ts</code></li>
|
|
98
|
+
</ul>
|
|
99
|
+
</section>
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Overview"
|
|
3
|
+
description: "Bundled docs for @arizeai/phoenix-otel"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`@arizeai/phoenix-otel` is the Phoenix-focused OpenTelemetry package for Node.js. It wraps provider setup, span export, instrumentation registration, and a few convenience utilities for Phoenix-specific tracing workflows.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Install the package and any OpenTelemetry instrumentations you want to use with it.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @arizeai/phoenix-otel
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Recommended — Add OpenInference Core
|
|
17
|
+
|
|
18
|
+
`@arizeai/openinference-core` provides tracing helpers (`withSpan`, `traceChain`, `traceAgent`, `traceTool`), decorators (`@observe`), and context setters (`setSession`, `setUser`, `setMetadata`, `setTag`) that work on top of the provider registered by phoenix-otel. Install it alongside phoenix-otel for the best tracing experience.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @arizeai/phoenix-otel @arizeai/openinference-core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Add Instrumentations
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install \
|
|
28
|
+
@arizeai/phoenix-otel \
|
|
29
|
+
@opentelemetry/instrumentation-http \
|
|
30
|
+
@opentelemetry/instrumentation-express
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Typical Pairings
|
|
34
|
+
|
|
35
|
+
- `@arizeai/phoenix-otel` plus `@arizeai/openinference-core` for manual tracing with span-kind helpers and context propagation
|
|
36
|
+
- `@arizeai/phoenix-otel` plus OpenTelemetry instrumentations for automatic framework tracing
|
|
37
|
+
- `@arizeai/phoenix-otel` plus `@arizeai/phoenix-client` for experiment workflows
|
|
38
|
+
- `@arizeai/phoenix-otel` plus framework-specific OpenInference instrumentors (e.g. `@arizeai/openinference-instrumentation-openai`)
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { register } from "@arizeai/phoenix-otel";
|
|
44
|
+
import { withSpan } from "@arizeai/openinference-core";
|
|
45
|
+
import { OpenInferenceSpanKind } from "@arizeai/openinference-semantic-conventions";
|
|
46
|
+
|
|
47
|
+
const provider = register({ projectName: "support-bot" });
|
|
48
|
+
|
|
49
|
+
const handleQuery = withSpan(
|
|
50
|
+
async (query: string) => {
|
|
51
|
+
return `Handled: ${query}`;
|
|
52
|
+
},
|
|
53
|
+
{ name: "handle-query", kind: OpenInferenceSpanKind.CHAIN }
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
await handleQuery("Hello");
|
|
57
|
+
await provider.shutdown();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Docs And Source In `node_modules`
|
|
61
|
+
|
|
62
|
+
After install, a coding agent can inspect the installed package directly:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
node_modules/@arizeai/phoenix-otel/docs/
|
|
66
|
+
node_modules/@arizeai/phoenix-otel/src/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The bundled docs cover configuration, instrumentation, manual spans, and troubleshooting.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
`register()` can use explicit options, environment variables, or both. If you pass a Phoenix base URL, the package normalizes it to the OTLP collector endpoint at `/v1/traces`.
|
|
74
|
+
|
|
75
|
+
### Environment-Driven Setup
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export PHOENIX_COLLECTOR_ENDPOINT=http://localhost:6006
|
|
79
|
+
export PHOENIX_API_KEY=<your-api-key>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { register } from "@arizeai/phoenix-otel";
|
|
84
|
+
|
|
85
|
+
const provider = register({
|
|
86
|
+
projectName: "support-bot",
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Explicit Setup
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { DiagLogLevel, register } from "@arizeai/phoenix-otel";
|
|
94
|
+
|
|
95
|
+
const provider = register({
|
|
96
|
+
projectName: "support-bot",
|
|
97
|
+
url: "https://app.phoenix.arize.com",
|
|
98
|
+
apiKey: process.env.PHOENIX_API_KEY,
|
|
99
|
+
headers: {
|
|
100
|
+
"x-client-name": "support-bot-api",
|
|
101
|
+
"x-client-version": "1.4.2",
|
|
102
|
+
},
|
|
103
|
+
batch: true,
|
|
104
|
+
diagLogLevel: DiagLogLevel.INFO,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Key Options
|
|
109
|
+
|
|
110
|
+
| Option | Description |
|
|
111
|
+
|--------|-------------|
|
|
112
|
+
| `projectName` | Phoenix project name attached to exported spans. Defaults to `default`. |
|
|
113
|
+
| `url` | Phoenix base URL or full collector endpoint. The package appends `/v1/traces` when needed. |
|
|
114
|
+
| `apiKey` | API key sent as a bearer token. Falls back to `PHOENIX_API_KEY`. |
|
|
115
|
+
| `headers` | Additional OTLP headers merged with the auth header. |
|
|
116
|
+
| `batch` | `true` for batched export, `false` for immediate export during debugging. |
|
|
117
|
+
| `instrumentations` | OpenTelemetry instrumentations to register with the provider. |
|
|
118
|
+
| `spanProcessors` | Custom span processors. When provided, these replace the default Phoenix exporter setup. |
|
|
119
|
+
| `global` | Whether to attach the provider to the global OpenTelemetry APIs automatically. |
|
|
120
|
+
| `diagLogLevel` | OpenTelemetry diagnostic logging level. |
|
|
121
|
+
|
|
122
|
+
### Environment Variables
|
|
123
|
+
|
|
124
|
+
| Variable | Use |
|
|
125
|
+
|--------|-----|
|
|
126
|
+
| `PHOENIX_COLLECTOR_ENDPOINT` | Base Phoenix collector URL or full OTLP trace endpoint. |
|
|
127
|
+
| `PHOENIX_API_KEY` | API key used when you do not pass `apiKey` explicitly. |
|
|
128
|
+
|
|
129
|
+
## Instrumentation
|
|
130
|
+
|
|
131
|
+
Pass OpenTelemetry instrumentations to `register()` to capture framework and library activity automatically.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { register } from "@arizeai/phoenix-otel";
|
|
135
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
136
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
137
|
+
|
|
138
|
+
register({
|
|
139
|
+
projectName: "support-bot",
|
|
140
|
+
instrumentations: [
|
|
141
|
+
new HttpInstrumentation(),
|
|
142
|
+
new ExpressInstrumentation(),
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- the package also re-exports `registerInstrumentations`
|
|
148
|
+
- auto-instrumentation setups often work best when they are initialized very early in process startup
|
|
149
|
+
- for custom providers, you can pass your own `spanProcessors`
|
|
150
|
+
|
|
151
|
+
## Production
|
|
152
|
+
|
|
153
|
+
### Enable Batch Processing
|
|
154
|
+
|
|
155
|
+
Always set `batch: true` (the default) in production. The batch span processor groups spans before export, improving compression and reducing the number of outgoing network requests. Disable batching only during local debugging when you need spans to appear immediately.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const provider = register({
|
|
159
|
+
projectName: "support-bot",
|
|
160
|
+
url: "https://app.phoenix.arize.com",
|
|
161
|
+
apiKey: process.env.PHOENIX_API_KEY,
|
|
162
|
+
batch: true, // default — groups spans for efficient export
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Provider Lifecycle
|
|
167
|
+
|
|
168
|
+
In production you must flush and shut down the provider before your process exits, otherwise in-flight batches are lost.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
process.on("SIGTERM", async () => {
|
|
172
|
+
await provider.shutdown(); // flushes buffered spans, then tears down
|
|
173
|
+
process.exit(0);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
For short-lived processes (scripts, serverless functions, CLI tools) call `shutdown()` at the end of your work:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
await doWork();
|
|
181
|
+
await provider.shutdown(); // ensures the final batch is exported
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Custom Span Processors
|
|
185
|
+
|
|
186
|
+
If you need full control over export (for example, sending spans to a secondary backend), pass your own `spanProcessors`. This replaces the default Phoenix exporter setup entirely.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { register } from "@arizeai/phoenix-otel";
|
|
190
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
191
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
192
|
+
|
|
193
|
+
const exporter = new OTLPTraceExporter({
|
|
194
|
+
url: "https://app.phoenix.arize.com/v1/traces",
|
|
195
|
+
headers: { Authorization: `Bearer ${process.env.PHOENIX_API_KEY}` },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const provider = register({
|
|
199
|
+
projectName: "support-bot",
|
|
200
|
+
spanProcessors: [new BatchSpanProcessor(exporter)],
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Troubleshooting
|
|
205
|
+
|
|
206
|
+
When traces do not show up in Phoenix, check these common issues:
|
|
207
|
+
|
|
208
|
+
- confirm `projectName` is the project you are inspecting
|
|
209
|
+
- confirm `url` or `PHOENIX_COLLECTOR_ENDPOINT` points at the collector
|
|
210
|
+
- confirm `PHOENIX_API_KEY` is present for authenticated environments
|
|
211
|
+
- confirm registration happens before the instrumented work starts
|
|
212
|
+
- call `await provider.shutdown()` before process exit when using batched export
|
|
213
|
+
|
|
214
|
+
### Enable Diagnostic Logging
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { DiagLogLevel, register } from "@arizeai/phoenix-otel";
|
|
218
|
+
|
|
219
|
+
const provider = register({
|
|
220
|
+
projectName: "support-bot",
|
|
221
|
+
batch: false,
|
|
222
|
+
diagLogLevel: DiagLogLevel.DEBUG,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await provider.shutdown();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Package Surface
|
|
229
|
+
|
|
230
|
+
- `register()` creates a `NodeTracerProvider` and Phoenix exporter setup
|
|
231
|
+
- `trace`, `SpanStatusCode`, and other OpenTelemetry re-exports support manual tracing
|
|
232
|
+
- `registerInstrumentations()` wires automatic instrumentation into the same provider
|
|
233
|
+
- `objectAsAttributes()` converts JSON-like objects into OpenTelemetry attribute maps
|
|
234
|
+
|
|
235
|
+
## Where To Start
|
|
236
|
+
|
|
237
|
+
- [Manual spans](./manual-spans) for tracing with `@arizeai/openinference-core` helpers and raw OTel spans
|
|
238
|
+
|
|
239
|
+
<section className="hidden" data-agent-context="source-map" aria-label="Source map">
|
|
240
|
+
<h2>Source Map</h2>
|
|
241
|
+
<ul>
|
|
242
|
+
<li><code>src/index.ts</code></li>
|
|
243
|
+
<li><code>src/register.ts</code></li>
|
|
244
|
+
<li><code>src/config.ts</code></li>
|
|
245
|
+
<li><code>src/utils.ts</code></li>
|
|
246
|
+
<li><code>src/createNoOpProvider.ts</code></li>
|
|
247
|
+
</ul>
|
|
248
|
+
</section>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arizeai/phoenix-otel",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Otel registration and convenience methods",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arize",
|
|
@@ -19,8 +19,12 @@
|
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/Arize-ai/phoenix.git"
|
|
21
21
|
},
|
|
22
|
+
"directories": {
|
|
23
|
+
"doc": "./docs"
|
|
24
|
+
},
|
|
22
25
|
"files": [
|
|
23
26
|
"dist",
|
|
27
|
+
"docs",
|
|
24
28
|
"src",
|
|
25
29
|
"package.json"
|
|
26
30
|
],
|
|
@@ -37,6 +41,7 @@
|
|
|
37
41
|
"@arizeai/openinference-semantic-conventions": "^1.1.0",
|
|
38
42
|
"@arizeai/openinference-vercel": "^2.7.0",
|
|
39
43
|
"@opentelemetry/api": "^1.9.0",
|
|
44
|
+
"@opentelemetry/context-async-hooks": "^2.5.1",
|
|
40
45
|
"@opentelemetry/core": "^1.25.1",
|
|
41
46
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.205.0",
|
|
42
47
|
"@opentelemetry/instrumentation": "^0.57.2",
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
},
|
|
47
52
|
"devDependencies": {
|
|
48
53
|
"@types/node": "^24.9.1",
|
|
49
|
-
"vitest": "^4.0
|
|
54
|
+
"vitest": "^4.1.0"
|
|
50
55
|
},
|
|
51
56
|
"scripts": {
|
|
52
57
|
"build": "tsc --build tsconfig.json tsconfig.esm.json && tsc-alias -p tsconfig.esm.json",
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export {
|
|
|
5
5
|
type Tracer,
|
|
6
6
|
SpanStatusCode,
|
|
7
7
|
} from "@opentelemetry/api";
|
|
8
|
+
export { suppressTracing } from "@opentelemetry/core";
|
|
8
9
|
export { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
9
10
|
export { type Instrumentation } from "@opentelemetry/instrumentation";
|
|
10
11
|
export { type NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
package/src/register.ts
CHANGED
|
@@ -4,7 +4,22 @@ import {
|
|
|
4
4
|
OpenInferenceSimpleSpanProcessor,
|
|
5
5
|
} from "@arizeai/openinference-vercel";
|
|
6
6
|
import type { DiagLogLevel } from "@opentelemetry/api";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
context,
|
|
9
|
+
type ContextManager,
|
|
10
|
+
diag,
|
|
11
|
+
DiagConsoleLogger,
|
|
12
|
+
propagation,
|
|
13
|
+
type TextMapPropagator,
|
|
14
|
+
trace,
|
|
15
|
+
type TracerProvider,
|
|
16
|
+
} from "@opentelemetry/api";
|
|
17
|
+
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
18
|
+
import {
|
|
19
|
+
CompositePropagator,
|
|
20
|
+
W3CBaggagePropagator,
|
|
21
|
+
W3CTraceContextPropagator,
|
|
22
|
+
} from "@opentelemetry/core";
|
|
8
23
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
9
24
|
import type { Instrumentation } from "@opentelemetry/instrumentation";
|
|
10
25
|
import { registerInstrumentations } from "@opentelemetry/instrumentation";
|
|
@@ -153,6 +168,214 @@ export type RegisterParams = {
|
|
|
153
168
|
diagLogLevel?: DiagLogLevel;
|
|
154
169
|
};
|
|
155
170
|
|
|
171
|
+
export type GlobalTracerProviderRegistration = {
|
|
172
|
+
/**
|
|
173
|
+
* Detach the global tracer provider and reset global tracing APIs to no-op state.
|
|
174
|
+
*
|
|
175
|
+
* This clears the OpenTelemetry tracer provider, context manager, and propagator
|
|
176
|
+
* so a different provider can be attached later in the same process.
|
|
177
|
+
*/
|
|
178
|
+
detach: () => void;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
type GlobalTelemetrySnapshot = {
|
|
182
|
+
contextManager?: ContextManager;
|
|
183
|
+
propagator?: TextMapPropagator;
|
|
184
|
+
tracerProvider?: TracerProvider;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
type OpenTelemetryGlobalState = {
|
|
188
|
+
context?: ContextManager;
|
|
189
|
+
propagation?: TextMapPropagator;
|
|
190
|
+
trace?: TracerProvider;
|
|
191
|
+
version?: string;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type GlobalTracerProviderMount = {
|
|
195
|
+
id: number;
|
|
196
|
+
provider: NodeTracerProvider;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let nextGlobalTracerProviderMountId = 0;
|
|
200
|
+
let managedGlobalBaseSnapshot: GlobalTelemetrySnapshot | null = null;
|
|
201
|
+
const managedGlobalTracerProviderMounts: GlobalTracerProviderMount[] = [];
|
|
202
|
+
// OpenTelemetry v1 stores globals on this well-known symbol.
|
|
203
|
+
const OTEL_GLOBAL_SYMBOL = Symbol.for("opentelemetry.js.api.1");
|
|
204
|
+
|
|
205
|
+
function getOpenTelemetryGlobalState(): OpenTelemetryGlobalState | undefined {
|
|
206
|
+
return (
|
|
207
|
+
globalThis as typeof globalThis & {
|
|
208
|
+
[OTEL_GLOBAL_SYMBOL]?: OpenTelemetryGlobalState;
|
|
209
|
+
}
|
|
210
|
+
)[OTEL_GLOBAL_SYMBOL];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getGlobalTelemetrySnapshot(): GlobalTelemetrySnapshot {
|
|
214
|
+
const globalState = getOpenTelemetryGlobalState();
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
tracerProvider: globalState?.trace,
|
|
218
|
+
contextManager: globalState?.context,
|
|
219
|
+
propagator: globalState?.propagation,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clearGlobalTelemetry(): void {
|
|
224
|
+
trace.disable();
|
|
225
|
+
context.disable();
|
|
226
|
+
propagation.disable();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function restoreGlobalTelemetrySnapshot(
|
|
230
|
+
snapshot: GlobalTelemetrySnapshot | null
|
|
231
|
+
): void {
|
|
232
|
+
clearGlobalTelemetry();
|
|
233
|
+
|
|
234
|
+
if (!snapshot) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (snapshot.tracerProvider) {
|
|
239
|
+
trace.setGlobalTracerProvider(snapshot.tracerProvider);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (snapshot.contextManager) {
|
|
243
|
+
context.setGlobalContextManager(snapshot.contextManager);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (snapshot.propagator) {
|
|
247
|
+
propagation.setGlobalPropagator(snapshot.propagator);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Sets the given provider as the global OpenTelemetry provider using this
|
|
253
|
+
* module's own imports of `trace`, `context`, and `propagation`.
|
|
254
|
+
*
|
|
255
|
+
* We deliberately avoid calling `provider.register()` because the SDK's
|
|
256
|
+
* internal import of `@opentelemetry/api` may resolve to a different module
|
|
257
|
+
* instance in pnpm workspaces (different symlink paths → different module
|
|
258
|
+
* identity). By routing all global-state mutations through **our** copy of
|
|
259
|
+
* the OTEL API, snapshot/restore stays consistent.
|
|
260
|
+
*/
|
|
261
|
+
function setGlobalProvider(provider: NodeTracerProvider): void {
|
|
262
|
+
trace.setGlobalTracerProvider(provider);
|
|
263
|
+
|
|
264
|
+
const contextManager = new AsyncLocalStorageContextManager();
|
|
265
|
+
contextManager.enable();
|
|
266
|
+
context.setGlobalContextManager(contextManager);
|
|
267
|
+
|
|
268
|
+
propagation.setGlobalPropagator(
|
|
269
|
+
new CompositePropagator({
|
|
270
|
+
propagators: [
|
|
271
|
+
new W3CTraceContextPropagator(),
|
|
272
|
+
new W3CBaggagePropagator(),
|
|
273
|
+
],
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function restorePreviousManagedGlobalTracerProvider(): void {
|
|
279
|
+
const previousMount =
|
|
280
|
+
managedGlobalTracerProviderMounts[
|
|
281
|
+
managedGlobalTracerProviderMounts.length - 1
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
if (previousMount) {
|
|
285
|
+
clearGlobalTelemetry();
|
|
286
|
+
setGlobalProvider(previousMount.provider);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
restoreGlobalTelemetrySnapshot(managedGlobalBaseSnapshot);
|
|
291
|
+
managedGlobalBaseSnapshot = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function detachManagedGlobalTracerProvider(mountId?: number): void {
|
|
295
|
+
if (managedGlobalTracerProviderMounts.length === 0) {
|
|
296
|
+
clearGlobalTelemetry();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const mountIndex =
|
|
301
|
+
mountId == null
|
|
302
|
+
? managedGlobalTracerProviderMounts.length - 1
|
|
303
|
+
: managedGlobalTracerProviderMounts.findIndex(
|
|
304
|
+
(mount) => mount.id === mountId
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (mountIndex === -1) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const isTopMount =
|
|
312
|
+
mountIndex === managedGlobalTracerProviderMounts.length - 1;
|
|
313
|
+
managedGlobalTracerProviderMounts.splice(mountIndex, 1);
|
|
314
|
+
|
|
315
|
+
if (!isTopMount) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
restorePreviousManagedGlobalTracerProvider();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Detaches the current global OpenTelemetry tracer provider and resets the
|
|
324
|
+
* global trace, context, and propagation APIs.
|
|
325
|
+
*/
|
|
326
|
+
export function detachGlobalTracerProvider(): void {
|
|
327
|
+
detachManagedGlobalTracerProvider();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Attaches an existing tracer provider as the global OpenTelemetry provider.
|
|
332
|
+
*
|
|
333
|
+
* Returns a handle that can be used to detach it later so another provider can
|
|
334
|
+
* be attached in the same process.
|
|
335
|
+
*/
|
|
336
|
+
export function attachGlobalTracerProvider(
|
|
337
|
+
provider: NodeTracerProvider
|
|
338
|
+
): GlobalTracerProviderRegistration {
|
|
339
|
+
if (managedGlobalTracerProviderMounts.length === 0) {
|
|
340
|
+
managedGlobalBaseSnapshot = getGlobalTelemetrySnapshot();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const mountId = nextGlobalTracerProviderMountId++;
|
|
344
|
+
managedGlobalTracerProviderMounts.push({
|
|
345
|
+
id: mountId,
|
|
346
|
+
provider,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
clearGlobalTelemetry();
|
|
350
|
+
setGlobalProvider(provider);
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
detach: () => {
|
|
354
|
+
detachManagedGlobalTracerProvider(mountId);
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function bindGlobalTracerProviderRegistrationToShutdown({
|
|
360
|
+
provider,
|
|
361
|
+
registration,
|
|
362
|
+
}: {
|
|
363
|
+
provider: NodeTracerProvider;
|
|
364
|
+
registration: GlobalTracerProviderRegistration;
|
|
365
|
+
}): void {
|
|
366
|
+
const shutdown = provider.shutdown.bind(provider);
|
|
367
|
+
let hasDetachedRegistration = false;
|
|
368
|
+
|
|
369
|
+
provider.shutdown = async (): Promise<void> => {
|
|
370
|
+
if (!hasDetachedRegistration) {
|
|
371
|
+
hasDetachedRegistration = true;
|
|
372
|
+
registration.detach();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
await shutdown();
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
156
379
|
/**
|
|
157
380
|
* Registers Phoenix OpenTelemetry tracing with the specified configuration.
|
|
158
381
|
*
|
|
@@ -261,7 +484,11 @@ export function register(params: RegisterParams): NodeTracerProvider {
|
|
|
261
484
|
});
|
|
262
485
|
}
|
|
263
486
|
if (global) {
|
|
264
|
-
provider
|
|
487
|
+
const registration = attachGlobalTracerProvider(provider);
|
|
488
|
+
bindGlobalTracerProviderRegistrationToShutdown({
|
|
489
|
+
provider,
|
|
490
|
+
registration,
|
|
491
|
+
});
|
|
265
492
|
}
|
|
266
493
|
return provider;
|
|
267
494
|
}
|