@defjs/opentelemetry-server 0.0.1-beta.0 → 0.0.2
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 +61 -248
- package/index.d.ts +103 -18
- package/index.js +238 -109
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,290 +1,103 @@
|
|
|
1
1
|
# @defjs/opentelemetry-server
|
|
2
2
|
|
|
3
|
-
Server-side outbound OpenTelemetry
|
|
3
|
+
Server-side outbound OpenTelemetry for `@defjs/core` HTTP, SSE, and WebSocket clients.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It is a middleware plugin — the plugin form of an interceptor chain. Your application keeps its OpenTelemetry SDK, providers, exporters, and shutdown; the imported SDK definitions stay free of telemetry configuration. This package is outbound client instrumentation for server applications, not inbound server instrumentation.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Server environment** — Designed for server-side applications with an OpenTelemetry SDK, not browser-only telemetry setup.
|
|
9
|
-
- **Does not initialize the SDK** — This package does not initialize an OpenTelemetry SDK. Register a `TracerProvider` (`setGlobalTracerProvider`, or your SDK's `start()`) then pass that tracer (and optional meter) into `withOpenTelemetryServer(...)`. `trace.getTracer(...)` with no provider is a silent no-op.
|
|
10
|
-
- **Per-transport separation** — HTTP, SSE, and WebSocket each have independent interceptors, span lifecycles, and metric dimensions.
|
|
11
|
-
|
|
12
|
-
## Installation
|
|
13
|
-
|
|
14
|
-
Install `@defjs/opentelemetry-server` with the matching `@defjs/core` release line and its OpenTelemetry peers:
|
|
7
|
+
## Install
|
|
15
8
|
|
|
16
9
|
```sh
|
|
17
10
|
bun add @defjs/core @defjs/opentelemetry-server @opentelemetry/api @opentelemetry/core
|
|
18
11
|
```
|
|
19
12
|
|
|
20
|
-
The package is ESM and requires `@defjs/core` as a peer in the `^0.
|
|
13
|
+
The package is ESM and requires `@defjs/core` as a peer in the `^0.5.0` range.
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
## Quick start
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
This package does not install a `TracerProvider`. `trace.getTracer('my-service')` without `trace.setGlobalTracerProvider(...)` (your SDK's `start()` usually does this) returns a no-op tracer: interceptors still wrap requests, but spans are silently discarded. Pass a recording tracer from your SDK, or register the provider first:
|
|
17
|
+
It never initializes a provider or looks one up implicitly: pass the `tracer`, `meter`, and `logger` you want, and each signal works independently. Without a `tracer` no spans are created — interceptors still wrap requests, but `trace.getTracer()` returns a no-op until your SDK registers a provider.
|
|
27
18
|
|
|
28
19
|
```typescript
|
|
29
|
-
import { createClient, withEndpoint } from
|
|
30
|
-
import { withOpenTelemetryServer } from
|
|
31
|
-
import { trace } from
|
|
20
|
+
import { createClient, withEndpoint } from "@defjs/core";
|
|
21
|
+
import { withOpenTelemetryServer } from "@defjs/opentelemetry-server";
|
|
22
|
+
import { trace } from "@opentelemetry/api";
|
|
32
23
|
|
|
33
24
|
// After your OpenTelemetry SDK has called setGlobalTracerProvider:
|
|
34
|
-
const tracer = trace.getTracer(
|
|
25
|
+
const tracer = trace.getTracer("my-service");
|
|
35
26
|
|
|
36
27
|
const client = createClient(
|
|
37
|
-
withEndpoint(
|
|
38
|
-
withOpenTelemetryServer({
|
|
39
|
-
|
|
40
|
-
}),
|
|
41
|
-
)
|
|
28
|
+
withEndpoint("https://api.example.com"),
|
|
29
|
+
withOpenTelemetryServer({ tracer }),
|
|
30
|
+
);
|
|
42
31
|
```
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Set the optional `operation` field statically on `defineRequest(...)`, `defineEventStream(...)`, or `defineWebSocket(...)`:
|
|
33
|
+
Omit `tracer` to record metrics only; the plugin then never calls `trace.getTracer(...)` or the span hooks. `logger` accepts the native `Logger` from `@opentelemetry/api-logs`, not a Winston or Pino business logger.
|
|
47
34
|
|
|
48
35
|
```typescript
|
|
49
|
-
|
|
50
|
-
method: 'GET',
|
|
51
|
-
operation: 'orders.read',
|
|
52
|
-
path: '/orders/:id',
|
|
53
|
-
// input and output omitted
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
An explicit operation is the low-cardinality transport identity. HTTP spans use names such as `GET orders.read`; SSE and WebSocket spans use `SSE orders.watch` and `WebSocket orders.connect`. Spans and metrics also receive `defjs.operation`.
|
|
36
|
+
import { metrics } from "@opentelemetry/api";
|
|
58
37
|
|
|
59
|
-
|
|
38
|
+
const meter = metrics.getMeter("my-service");
|
|
60
39
|
|
|
61
|
-
## Full Configuration
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
40
|
const client = createClient(
|
|
65
|
-
withEndpoint(
|
|
66
|
-
withOpenTelemetryServer({
|
|
67
|
-
|
|
68
|
-
meter, // Optional, metrics only collected when provided
|
|
69
|
-
propagator, // Optional, default W3C TraceContext + Baggage
|
|
70
|
-
requireParentSpan: false,
|
|
71
|
-
http: {
|
|
72
|
-
enabled: true,
|
|
73
|
-
startSpanHook(req) {
|
|
74
|
-
return { 'app.operation': req.operation ?? 'unclassified' }
|
|
75
|
-
},
|
|
76
|
-
responseHook(span, res, req) {
|
|
77
|
-
span.setAttribute('defjs.response.status_text', res.statusText)
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
sse: {
|
|
81
|
-
enabled: true,
|
|
82
|
-
},
|
|
83
|
-
webSocket: {
|
|
84
|
-
enabled: true,
|
|
85
|
-
queryPropagation: false, // Safe default; set true only after reviewing URL propagation
|
|
86
|
-
},
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
41
|
+
withEndpoint("https://api.example.com"),
|
|
42
|
+
withOpenTelemetryServer({ meter }),
|
|
43
|
+
);
|
|
89
44
|
```
|
|
90
45
|
|
|
91
|
-
##
|
|
46
|
+
## Operation identity
|
|
92
47
|
|
|
93
|
-
`
|
|
48
|
+
Set `operation` statically on the definition. It is the low-cardinality transport identity: spans are named `GET orders.read`, `SSE orders.watch`, `WebSocket orders.connect`, and `defjs.operation` is recorded on spans and metrics.
|
|
94
49
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
queryPropagation: true,
|
|
102
|
-
},
|
|
103
|
-
})
|
|
50
|
+
```typescript
|
|
51
|
+
const readOrder = defineRequest({
|
|
52
|
+
method: "GET",
|
|
53
|
+
operation: "orders.read",
|
|
54
|
+
path: "/orders/:id",
|
|
55
|
+
});
|
|
104
56
|
```
|
|
105
57
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
With the safe default, trace context does not ride on the WebSocket URL. Use another reviewed correlation mechanism at the application layer, such as a protocol-reviewed first frame or a short-lived connection ticket, if your server still needs to link the connection to a trace.
|
|
109
|
-
|
|
110
|
-
### Configuration Options
|
|
111
|
-
|
|
112
|
-
| Option | Type | Default | Description |
|
|
113
|
-
| ------------------- | ------------------------------------- | -------------------------- | ------------------------------------------------------------ |
|
|
114
|
-
| `tracer` | `Tracer` | **Required** | External OpenTelemetry tracer |
|
|
115
|
-
| `meter` | `Meter` | `undefined` | External OpenTelemetry meter, omitting disables metrics |
|
|
116
|
-
| `propagator` | `TextMapPropagator` | W3C TraceContext + Baggage | Custom context propagator |
|
|
117
|
-
| `requireParentSpan` | `boolean` | `false` | Only create outbound spans when an active parent span exists |
|
|
118
|
-
| `http` | `OpenTelemetryServerHttpOptions` | `{}` | HTTP transport trace/metric options |
|
|
119
|
-
| `sse` | `OpenTelemetryServerSSEOptions` | `{}` | SSE transport trace/metric options |
|
|
120
|
-
| `webSocket` | `OpenTelemetryServerWebSocketOptions` | `{}` | WebSocket transport trace/metric options |
|
|
121
|
-
|
|
122
|
-
### HTTP Options
|
|
123
|
-
|
|
124
|
-
| Option | Type | Default | Description |
|
|
125
|
-
| --------------- | ------------------------------------------- | ----------- | ------------------------------------------------------------------------------ |
|
|
126
|
-
| `enabled` | `boolean` | `true` | Enable HTTP tracing |
|
|
127
|
-
| `startSpanHook` | `(req) => Attributes` | `undefined` | Add or override synchronous initial attributes before the HTTP span is created |
|
|
128
|
-
| `requestHook` | `(span, req) => void \| Promise<void>` | `undefined` | Customize HTTP span before request, `req` is `HttpRequest` |
|
|
129
|
-
| `responseHook` | `(span, res, req) => void \| Promise<void>` | `undefined` | Customize HTTP span after response; `req` is the original `HttpRequest` |
|
|
130
|
-
|
|
131
|
-
### SSE Options
|
|
132
|
-
|
|
133
|
-
| Option | Type | Default | Description |
|
|
134
|
-
| --------------- | ---------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
|
|
135
|
-
| `enabled` | `boolean` | `true` | Enable SSE tracing |
|
|
136
|
-
| `startSpanHook` | `(req) => Attributes` | `undefined` | Add or override synchronous initial attributes before the SSE span is created |
|
|
137
|
-
| `requestHook` | `(span, req) => void \| Promise<void>` | `undefined` | Customize SSE span before stream request |
|
|
138
|
-
| `responseHook` | `(span, stream, req) => void \| Promise<void>` | `undefined` | Customize SSE span after stream handle returned; `req` is the original `HttpRequest` |
|
|
139
|
-
|
|
140
|
-
### WebSocket Options
|
|
141
|
-
|
|
142
|
-
| Option | Type | Default | Description |
|
|
143
|
-
| ------------------ | ----------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
-
| `enabled` | `boolean` | `true` | Enable WebSocket tracing |
|
|
145
|
-
| `queryPropagation` | `boolean` | `false` | Inject trace context and baggage into the WebSocket URL query string. Enable only after reviewing URL logging and disclosure risks. |
|
|
146
|
-
| `startSpanHook` | `(req) => Attributes` | `undefined` | Add or override synchronous initial attributes before the WebSocket span is created |
|
|
147
|
-
| `requestHook` | `(span, req) => void \| Promise<void>` | `undefined` | Customize WebSocket span before connection request |
|
|
148
|
-
| `responseHook` | `(span, session, req) => void \| Promise<void>` | `undefined` | Customize WebSocket span after session returned; `req` is the original `HttpRequest` |
|
|
149
|
-
|
|
150
|
-
> **Hook timing and failures**: `startSpanHook(request)` is synchronous and runs before span creation. Its attributes are applied after built-in attributes. If it throws, Defjs creates the span with built-in attributes, records `defjs.otel.hook.error`, and the client request/stream/session continues normally. `requestHook` and `responseHook` run after creation and may return `void` or `Promise<void>`; their throws/rejections are recorded without blocking the operation.
|
|
151
|
-
>
|
|
152
|
-
> Each `responseHook` receives the original transport `HttpRequest` as its third argument. Read its explicit `operation`; do not reconstruct identity from its endpoint or a resolved URL.
|
|
153
|
-
>
|
|
154
|
-
> **Attribute hygiene**: This package has no built-in redactor or sensitive-key policy. Prefer explicit allowlists, application-owned redaction, and stable low-cardinality attributes in hooks. Do not attach resolved URLs, raw query strings, request or response bodies, full headers, baggage values, or message payloads unless your application has already reviewed privacy, cardinality, retention, and redaction requirements.
|
|
155
|
-
|
|
156
|
-
## HTTP Semantic Conventions and Metrics
|
|
157
|
-
|
|
158
|
-
HTTP tracing uses stable OpenTelemetry HTTP client attribute and metric names. This is not a claim of complete semantic-convention compliance. With an explicit operation, the interceptor creates a `SpanKind.CLIENT` span named `${method} ${operation}` and records `defjs.operation`. Without one, it preserves the previous method-only span name and omits that attribute. It also records:
|
|
159
|
-
|
|
160
|
-
- `http.request.method`
|
|
161
|
-
- `url.full`
|
|
162
|
-
- `server.address`
|
|
163
|
-
- `server.port`
|
|
164
|
-
- `http.response.status_code` only when a response status was received
|
|
165
|
-
|
|
166
|
-
HTTP span status and `error.type` follow these rules:
|
|
167
|
-
|
|
168
|
-
- status `100` through `399` leaves span status unset and does not set `error.type`;
|
|
169
|
-
- status `400` and above marks the client span `ERROR` and sets `error.type` to the status code string;
|
|
170
|
-
- a Defjs status-0 transport result does not set `http.response.status_code`; caller cancellation leaves status unset, timeout uses `ERROR` / `TIMEOUT`, and other transport failures use `ERROR` / `NETWORK_ERROR`;
|
|
171
|
-
- an error thrown through the interceptor marks the span `ERROR`, records the exception, and uses its `Error.name` or another low-cardinality type fallback as `error.type`.
|
|
58
|
+
Without it, HTTP spans use the method alone and SSE/WebSocket use their transport name. Never derive an operation from a resolved URL or a path containing identifiers.
|
|
172
59
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
import { createResolvedRequestUrl } from '@defjs/core'
|
|
60
|
+
## Configuration
|
|
177
61
|
|
|
62
|
+
```typescript
|
|
178
63
|
withOpenTelemetryServer({
|
|
179
|
-
tracer,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
},
|
|
189
|
-
})
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Resolved paths can contain sensitive or high-cardinality identifiers. Keep operation names static and apply application or SDK/exporter redaction before exporting URL attributes.
|
|
193
|
-
|
|
194
|
-
When `meter` is provided, the following stable metrics are collected:
|
|
195
|
-
|
|
196
|
-
| Metric | Unit | Attributes |
|
|
197
|
-
| ------------------------------ | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
-
| `http.client.request.duration` | `s` | `http.request.method`, optional `defjs.operation`, `server.address`, `server.port`, optional `http.response.status_code`, optional `error.type` |
|
|
199
|
-
|
|
200
|
-
The metric applies the same response-status and `error.type` classification as the HTTP span.
|
|
201
|
-
|
|
202
|
-
### Transport span boundary
|
|
203
|
-
|
|
204
|
-
The HTTP span ends when the interceptor receives the Defjs `HttpResponse`. Exact output-status dispatch and Struct decoding happen afterward, so a later `RESPONSE_VALIDATION_FAILED` or `UNDECLARED_STATUS` cannot update that transport span.
|
|
205
|
-
|
|
206
|
-
When telemetry must represent the final command outcome, create an application span around `client.execute(...)` and classify the returned tuple:
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
import { SpanStatusCode } from '@opentelemetry/api'
|
|
210
|
-
|
|
211
|
-
const outcome = await tracer.startActiveSpan('defjs.command', async (span) => {
|
|
212
|
-
try {
|
|
213
|
-
const outcome = await client.execute(command)
|
|
214
|
-
const [error] = outcome
|
|
215
|
-
if (error) {
|
|
216
|
-
span.setAttribute('error.type', error.code)
|
|
217
|
-
span.setStatus({ code: SpanStatusCode.ERROR })
|
|
218
|
-
}
|
|
219
|
-
return outcome
|
|
220
|
-
} catch (error) {
|
|
221
|
-
span.setAttribute('error.type', error instanceof Error ? error.name : typeof error)
|
|
222
|
-
span.setStatus({ code: SpanStatusCode.ERROR })
|
|
223
|
-
throw error
|
|
224
|
-
} finally {
|
|
225
|
-
span.end()
|
|
226
|
-
}
|
|
227
|
-
})
|
|
64
|
+
tracer, // optional: local spans and span hooks
|
|
65
|
+
meter, // optional: metrics
|
|
66
|
+
logger, // optional: failure diagnostics only
|
|
67
|
+
propagator, // optional: defaults to W3C TraceContext + Baggage
|
|
68
|
+
requireParentSpan: false,
|
|
69
|
+
http: { enabled: true, startSpanHook, requestHook, responseHook },
|
|
70
|
+
sse: { enabled: true, startSpanHook, requestHook, responseHook },
|
|
71
|
+
webSocket: { enabled: true, queryPropagation: false },
|
|
72
|
+
});
|
|
228
73
|
```
|
|
229
74
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
### Span Lifecycle
|
|
241
|
-
|
|
242
|
-
After startup succeeds, the SSE interceptor attaches to `stream.closed` and keeps the span open until the stream reaches its normal, fatal, or aborted terminal state. It records:
|
|
243
|
-
|
|
244
|
-
- `sse.connected` — Stream successfully established
|
|
245
|
-
- `sse.closed` — Stream normal end (server EOF)
|
|
246
|
-
- `sse.aborted` — Active close via `stream.close()`
|
|
247
|
-
- `sse.error` — Connection error or reconnect exhaustion
|
|
248
|
-
|
|
249
|
-
### Custom Metrics
|
|
250
|
-
|
|
251
|
-
When `meter` is provided, the following defjs custom metrics are collected (non-official OpenTelemetry stable semantic conventions):
|
|
252
|
-
|
|
253
|
-
| Metric | Unit | Meaning |
|
|
254
|
-
| -------------------------------------- | ---------- | ------------------------------------------------------- |
|
|
255
|
-
| `defjs.client.sse.connect.duration` | `s` | Time to establish stream connection |
|
|
256
|
-
| `defjs.client.sse.connection.duration` | `s` | Total duration from stream establishment to close/error |
|
|
257
|
-
| `defjs.client.sse.active_streams` | `{stream}` | Current active SSE stream count |
|
|
258
|
-
|
|
259
|
-
By default, **per-event spans are not created**, and **event payloads, event IDs, `Last-Event-ID`, delivery latency, lost events, or reconnect queues are not collected**. These are application-level semantics that may produce high-cardinality or sensitive telemetry. Implement them at the application layer if needed.
|
|
260
|
-
|
|
261
|
-
## WebSocket Connection-Level Tracing and Custom Metrics
|
|
262
|
-
|
|
263
|
-
WebSocket starts with an HTTP Upgrade handshake, but production environments care more about the post-handshake connection lifecycle: active connections, connection duration, close/error behavior, and connection failure rate. Since OpenTelemetry WebSocket semantic conventions are not yet stable, this package uses connection-level custom metrics.
|
|
264
|
-
|
|
265
|
-
### Span Lifecycle
|
|
266
|
-
|
|
267
|
-
The WebSocket span stays open until `session.closed` resolves, recording the following lifecycle events:
|
|
75
|
+
| Option | Type | Default | Description |
|
|
76
|
+
| ------------------- | --------------------------------------- | -------------------------- | --------------------------------------------------------------------------- |
|
|
77
|
+
| `tracer` | `Tracer` | `undefined` | External tracer; omitting disables local spans and hooks |
|
|
78
|
+
| `meter` | `Meter` | `undefined` | External meter; omitting disables metrics |
|
|
79
|
+
| `logger` | `Logger` from `@opentelemetry/api-logs` | `undefined` | External native Logger; omitting disables diagnostic LogRecords |
|
|
80
|
+
| `propagator` | `TextMapPropagator` | W3C TraceContext + Baggage | Custom context propagator |
|
|
81
|
+
| `requireParentSpan` | `boolean` | `false` | With a tracer, bypass all instrumentation when no active parent span exists |
|
|
82
|
+
| `http` | `OpenTelemetryServerHttpOptions` | `{}` | HTTP transport options |
|
|
83
|
+
| `sse` | `OpenTelemetryServerSSEOptions` | `{}` | SSE transport options |
|
|
84
|
+
| `webSocket` | `OpenTelemetryServerWebSocketOptions` | `{}` | WebSocket transport options |
|
|
268
85
|
|
|
269
|
-
- `
|
|
270
|
-
- `websocket.closed` — Connection normal close
|
|
271
|
-
- `websocket.error` — Connection error
|
|
86
|
+
Span hooks run only with a `tracer`. `startSpanHook(request)` is synchronous and runs before span creation; a throw leaves the built-in attributes and the operation continues. `requestHook` and `responseHook` are not awaited, and their failures never block or fail the operation. Configure `diag.setLogger(...)` to see hook diagnostics.
|
|
272
87
|
|
|
273
|
-
|
|
88
|
+
## Privacy defaults
|
|
274
89
|
|
|
275
|
-
|
|
90
|
+
- `webSocket.queryPropagation` defaults to `false`: trace context and baggage do not ride on the WebSocket URL. Enable it only after reviewing how your proxies, browsers, and access logs record query strings.
|
|
91
|
+
- No bodies, full headers, baggage values, or message payloads are added as telemetry fields, and no built-in redactor exists. Prefer allowlists and low-cardinality attributes in hooks.
|
|
92
|
+
- Diagnostics carry a fixed message, phase, operation, server address/port, and exception type — not raw URLs, headers, bodies, or stacks.
|
|
93
|
+
- SSE and WebSocket are traced per connection, not per event or message.
|
|
276
94
|
|
|
277
|
-
|
|
278
|
-
| -------------------------------------------- | -------------- | -------------------------------------------------------- |
|
|
279
|
-
| `defjs.client.websocket.connect.duration` | `s` | Time to establish WebSocket session |
|
|
280
|
-
| `defjs.client.websocket.connection.duration` | `s` | Total duration from session establishment to close/error |
|
|
281
|
-
| `defjs.client.websocket.active_connections` | `{connection}` | Current active WebSocket connection count |
|
|
95
|
+
## Documentation
|
|
282
96
|
|
|
283
|
-
|
|
97
|
+
- [OpenTelemetry guide](https://defjs.lyz.cloud/docs/integrations/opentelemetry-server) — full option tables, log events, span lifecycles, metrics, and third-party logging
|
|
98
|
+
- [OpenTelemetry API](https://defjs.lyz.cloud/api/opentelemetry-server)
|
|
99
|
+
- [Client](https://defjs.lyz.cloud/docs/client) · [SSE](https://defjs.lyz.cloud/docs/sse) · [WebSocket](https://defjs.lyz.cloud/docs/web-socket)
|
|
284
100
|
|
|
285
|
-
##
|
|
101
|
+
## License
|
|
286
102
|
|
|
287
|
-
|
|
288
|
-
- [Client](https://defjs.org/core/client) — `createClient` and full transport configuration
|
|
289
|
-
- [SSE](https://defjs.org/core/sse) — `defineEventStream` and streaming event consumption
|
|
290
|
-
- [WebSocket](https://defjs.org/core/web-socket) — `defineWebSocket` and real-time communication
|
|
103
|
+
MIT.
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { ClientOption, EventStreamHandle, HttpRequest, HttpResponse, WebSocketSessionLike } from "@defjs/core";
|
|
2
2
|
import { Attributes, Meter, Span, TextMapPropagator, Tracer } from "@opentelemetry/api";
|
|
3
|
-
|
|
4
|
-
/**
|
|
3
|
+
import { Logger } from "@opentelemetry/api-logs";
|
|
4
|
+
/**
|
|
5
|
+
* Shared per-transport OpenTelemetry client options: whether the transport is enabled and its span hooks, generic over the response type.
|
|
6
|
+
*
|
|
7
|
+
* `TResponse` is what `responseHook` receives: `HttpResponse<unknown>` for HTTP, `EventStreamHandle<unknown>` for SSE, `WebSocketSessionLike` for WebSocket. See {@link OpenTelemetryServerHttpOptions}, {@link OpenTelemetryServerSSEOptions} and {@link OpenTelemetryServerWebSocketOptions}.
|
|
8
|
+
*
|
|
9
|
+
* `enabled` defaults to `true`; only `enabled: false` turns the transport off. The old boolean switch (`http: false`) and top-level hooks are rejected by the TypeScript types only. There is no runtime check, so at runtime `http: false` leaves the transport enabled and a top-level hook is silently ignored.
|
|
10
|
+
*
|
|
11
|
+
* Span hooks run only when a `tracer` is supplied. `startSpanHook` runs synchronously before span creation for its own transport. Its attributes are applied after built-in attributes, so the application may override `url.full` or another initial value. If it throws, Defjs creates the span with built-in attributes and continues the request. `requestHook` runs after span creation and before the request is sent; `responseHook` runs after the transport result is returned. Both may return a promise, but neither is awaited: a throw or rejection never blocks or fails the operation. `responseHook` receives the original request as its third argument.
|
|
12
|
+
*
|
|
13
|
+
* While `span.isRecording()` is true, hook failures record `defjs.otel.hook.error` and `exception` events. For non-recording spans (ended or unsampled), failures go to the OpenTelemetry diagnostic logger through `diag.error`. Applications must configure `diag.setLogger(...)` from `@opentelemetry/api` to receive them; there is no automatic output by default. The package does not initialize the SDK, and this diagnostic is not a separate SDK `LogRecord`.
|
|
14
|
+
*
|
|
15
|
+
* By default, `url.full` resolves only `request.endpoint` against optional `request.baseEndpoint`; an independent `request.queryString` is not appended. That boundary is not redaction, and the package has no built-in redactor or sensitive-key policy. Construct and, if needed, redact an application-owned URL explicitly.
|
|
16
|
+
*
|
|
17
|
+
* @group opentelemetry-server
|
|
18
|
+
* @see {@link OpenTelemetryServerHttpOptions}
|
|
19
|
+
* @see {@link OpenTelemetryServerSSEOptions}
|
|
20
|
+
* @see {@link OpenTelemetryServerWebSocketOptions}
|
|
21
|
+
* @exampleFile examples/api/opentelemetry-server-transport-options.ts#example
|
|
22
|
+
*/
|
|
5
23
|
interface OpenTelemetryServerTransportOptions<TResponse> {
|
|
6
24
|
/** Whether this transport's instrumentation is enabled. Defaults to `true`. */
|
|
7
25
|
enabled?: boolean;
|
|
@@ -12,8 +30,54 @@ interface OpenTelemetryServerTransportOptions<TResponse> {
|
|
|
12
30
|
/** Customize the client span after the transport result is returned. */
|
|
13
31
|
responseHook?: (span: Span, res: TResponse, req: HttpRequest) => Promise<void> | void;
|
|
14
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for the HTTP transport — the shared options with an HttpResponse in responseHook.
|
|
35
|
+
*
|
|
36
|
+
* Set on `http` in `OpenTelemetryServerOptions`. It has no fields of its own: `enabled`, `startSpanHook`, `requestHook` and `responseHook` come from `OpenTelemetryServerTransportOptions`.
|
|
37
|
+
*
|
|
38
|
+
* - `responseHook(span, res, req)` receives the `HttpResponse` that came back through the interceptor, before the client decodes a declared body, together with the original `HttpRequest`. Read `req.operation` for the request identity; do not rebuild it from the endpoint or a resolved URL.
|
|
39
|
+
*
|
|
40
|
+
* - The HTTP span ends as soon as `responseHook` returns; the hook is not awaited, so only its synchronous part can still change the span. A status of 400 or above ends it `ERROR` with `error.type` set to the status string; 100–399 leaves its status unset. An asynchronous hook rejection after end is reported through `diag.error`; configure `diag.setLogger(...)` from `@opentelemetry/api` to receive it. It cannot append events to an ended span.
|
|
41
|
+
*
|
|
42
|
+
* - A rejection through the interceptor has no response status and does not call `responseHook`. That includes a network failure, caller cancellation, an abort whose reason is `TimeoutError`, any declared-body read failure (`BodyFailure`, including text, blob, and bytes, not only JSON), and a progress observer failure (`EXT_OBSERVER_FAILED`). The span ends `ERROR` with `error.type` set to the thrown class name, such as `TypeError`, `Error` or `BodyFailure`. Caller cancellation is `Error` whatever the abort reason. There is no `timeout` option; a deadline is `abort`.
|
|
43
|
+
*
|
|
44
|
+
* Pass `{ enabled: false }` to skip HTTP instrumentation. Span hooks run only when a `tracer` is supplied.
|
|
45
|
+
*
|
|
46
|
+
* @group opentelemetry-server
|
|
47
|
+
* @see {@link OpenTelemetryServerTransportOptions}
|
|
48
|
+
*/
|
|
15
49
|
type OpenTelemetryServerHttpOptions = OpenTelemetryServerTransportOptions<HttpResponse<unknown>>;
|
|
50
|
+
/**
|
|
51
|
+
* Options for the SSE transport — the shared options with the stream handle in responseHook.
|
|
52
|
+
*
|
|
53
|
+
* Set on `sse` in `OpenTelemetryServerOptions`. It has no fields of its own: `enabled`, `startSpanHook`, `requestHook` and `responseHook` come from `OpenTelemetryServerTransportOptions`.
|
|
54
|
+
*
|
|
55
|
+
* - `responseHook(span, stream, req)` runs once the stream has opened, before the span records `sse.connected`. `stream` is the `EventStreamHandle` the transport returned; `req` is the original `HttpRequest`.
|
|
56
|
+
*
|
|
57
|
+
* - SSE is traced at connection level. After startup the span stays open until `stream.closed` settles, and records `sse.connected`, then one of `sse.closed` (server EOF), `sse.aborted` (`stream.close()` or a caller abort of the open stream whose reason is not `TimeoutError`; the span still ends without error), or `sse.error` (including an abort whose reason is `TimeoutError`, which ends the span `ERROR`). No per-event spans are created.
|
|
58
|
+
*
|
|
59
|
+
* - A non-2xx handshake is observed as a connection failure: the span ends `ERROR` and, with a `logger`, one `WARN` is emitted whatever the response body.
|
|
60
|
+
*
|
|
61
|
+
* Pass `{ enabled: false }` to skip SSE instrumentation. Span hooks run only when a `tracer` is supplied.
|
|
62
|
+
*
|
|
63
|
+
* @group opentelemetry-server
|
|
64
|
+
* @see {@link OpenTelemetryServerTransportOptions}
|
|
65
|
+
*/
|
|
16
66
|
type OpenTelemetryServerSSEOptions = OpenTelemetryServerTransportOptions<EventStreamHandle<unknown>>;
|
|
67
|
+
/**
|
|
68
|
+
* Options for the WebSocket transport — the shared options plus queryPropagation.
|
|
69
|
+
*
|
|
70
|
+
* Set on `webSocket` in `OpenTelemetryServerOptions`. `enabled`, `startSpanHook`, `requestHook` and `responseHook` come from `OpenTelemetryServerTransportOptions`; `responseHook(span, session, req)` receives the opened `WebSocketSessionLike` and the original `HttpRequest`.
|
|
71
|
+
*
|
|
72
|
+
* `queryPropagation` defaults to `false`: the WebSocket query string is left unchanged and no trace context or baggage is written into the URL. Browser WebSocket clients usually cannot set headers, so enabling it writes the propagated context to the end of the query string, replacing any parameter of the same name. Query strings can be recorded by proxies, access logs and APM tools, and baggage values can be sensitive. Turn it on only after you accept that exposure.
|
|
73
|
+
*
|
|
74
|
+
* WebSocket is traced at connection level. The span stays open until `session.closed` settles. It records `websocket.connected`, then `websocket.closed` for a normal close or `websocket.error`. A close frame from the server counts as normal with any code, including 1011. An abrupt drop without a close frame (1006) is normal only where the runtime reports no socket `error` before `close` (Bun); where it does (Node.js), the session ends in error and logs once. A caller abort of an open session is also `websocket.error`: the span ends `ERROR` and no log is emitted. `error.type` follows the abort reason: `AbortError` for a default `abort()`, the `Error.name` of an `Error` reason, `string` for a string reason. An abort while the WebSocket is still connecting ends the span `ERROR` with `error.type` `DefjsFault`. No per-message spans are created.
|
|
75
|
+
*
|
|
76
|
+
* Pass `{ enabled: false }` to skip WebSocket instrumentation. Span hooks run only when a `tracer` is supplied.
|
|
77
|
+
*
|
|
78
|
+
* @group opentelemetry-server
|
|
79
|
+
* @see {@link OpenTelemetryServerTransportOptions}
|
|
80
|
+
*/
|
|
17
81
|
interface OpenTelemetryServerWebSocketOptions extends OpenTelemetryServerTransportOptions<WebSocketSessionLike> {
|
|
18
82
|
/**
|
|
19
83
|
* Inject trace context into the WebSocket URL query string.
|
|
@@ -22,19 +86,39 @@ interface OpenTelemetryServerWebSocketOptions extends OpenTelemetryServerTranspo
|
|
|
22
86
|
queryPropagation?: boolean;
|
|
23
87
|
}
|
|
24
88
|
/**
|
|
25
|
-
* Options for {@link withOpenTelemetryServer}.
|
|
89
|
+
* Options for {@link withOpenTelemetryServer}: tracer, meter, logger, propagator, and per-transport settings.
|
|
26
90
|
*
|
|
27
|
-
* Pass
|
|
91
|
+
* Pass application-owned OpenTelemetry `Tracer`, `Meter`, and/or `Logger` handles.
|
|
28
92
|
* This package does not initialize the OpenTelemetry SDK.
|
|
93
|
+
*
|
|
94
|
+
* - `tracer` — Optional. Without it there are no local spans and no span hooks.
|
|
95
|
+
*
|
|
96
|
+
* - `meter` — Optional. Without it no package metrics are recorded.
|
|
97
|
+
*
|
|
98
|
+
* - `logger` — Optional. A `Logger` from `@opentelemetry/api-logs`, not a Winston/Pino Logger. Without it no diagnostic LogRecords are emitted. Defjs never obtains a global logger or owns its provider.
|
|
99
|
+
*
|
|
100
|
+
* - `propagator` — Defaults to W3C Trace Context + Baggage.
|
|
101
|
+
*
|
|
102
|
+
* - `requireParentSpan` — Defaults to `false`. With a `tracer`, `true` bypasses all instrumentation, including logs, when no active parent span exists. Without a `tracer` it blocks neither metrics nor logs.
|
|
103
|
+
*
|
|
104
|
+
* - `http`, `sse`, `webSocket` — Per-transport options. Each defaults to `{}`; a transport is disabled only by `{ enabled: false }`.
|
|
105
|
+
*
|
|
106
|
+
* @group opentelemetry-server
|
|
107
|
+
* @see {@link OpenTelemetryServerHttpOptions}
|
|
108
|
+
* @see {@link OpenTelemetryServerSSEOptions}
|
|
109
|
+
* @see {@link OpenTelemetryServerWebSocketOptions}
|
|
110
|
+
* @see {@link OpenTelemetryServerTransportOptions}
|
|
29
111
|
*/
|
|
30
112
|
interface OpenTelemetryServerOptions {
|
|
31
|
-
/**
|
|
32
|
-
tracer
|
|
113
|
+
/** Optional tracer; omitting it disables outbound spans and span hooks. */
|
|
114
|
+
tracer?: Tracer;
|
|
33
115
|
/** Optional meter; when set, per-transport client metrics are recorded. */
|
|
34
116
|
meter?: Meter;
|
|
117
|
+
/** Optional logger; emits WARN diagnostics for failed outbound requests and connections. */
|
|
118
|
+
logger?: Logger;
|
|
35
119
|
/** Context propagator. Defaults to W3C Trace Context + Baggage. */
|
|
36
120
|
propagator?: TextMapPropagator;
|
|
37
|
-
/**
|
|
121
|
+
/** With a tracer, bypass instrumentation when no active parent span exists. Ignored without a tracer. */
|
|
38
122
|
requireParentSpan?: boolean;
|
|
39
123
|
/** HTTP transport instrumentation options. */
|
|
40
124
|
http?: OpenTelemetryServerHttpOptions;
|
|
@@ -46,20 +130,21 @@ interface OpenTelemetryServerOptions {
|
|
|
46
130
|
/**
|
|
47
131
|
* Client option that installs outbound OpenTelemetry interceptors for HTTP, SSE, and WebSocket.
|
|
48
132
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
133
|
+
* Appends one interceptor per enabled transport, in the order HTTP, SSE, WebSocket. Apply it at `createClient` time. It does not initialize the OpenTelemetry SDK or acquire global tracer, meter, or logger handles: pass the ones your application owns. Each transport can be toggled or customized via `http`, `sse`, and `webSocket`.
|
|
134
|
+
*
|
|
135
|
+
* `options` is required. It carries `tracer`, `meter`, `logger`, `propagator`, `requireParentSpan`, and the `http` / `sse` / `webSocket` transport options. Pass `{}` to install the interceptors with propagation only.
|
|
136
|
+
*
|
|
137
|
+
* Each of `tracer`, `meter` and `logger` is optional and works without the others. Without `tracer`, the plugin creates no local spans, never calls `trace.getTracer(...)`, and skips `startSpanHook`, `requestHook` and `responseHook`. Propagation still forwards the extracted parent context.
|
|
138
|
+
*
|
|
139
|
+
* HTTP, SSE, and WebSocket are on by default. Pass `{ enabled: false }` on a transport to skip it.
|
|
51
140
|
*
|
|
52
|
-
* @param options -
|
|
141
|
+
* @param options - Required settings object. Each of `tracer`, `meter`, `logger`, and `propagator` may be omitted; pass `{}` for propagation only.
|
|
53
142
|
* @returns A `ClientOption` for `createClient(...)`.
|
|
54
143
|
*
|
|
55
|
-
* @
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* withOpenTelemetryServer({ tracer }),
|
|
60
|
-
* )
|
|
61
|
-
* ```
|
|
144
|
+
* @group opentelemetry-server
|
|
145
|
+
* @see {@link ClientOption}
|
|
146
|
+
* @see {@link OpenTelemetryServerOptions}
|
|
147
|
+
* @exampleFile examples/api/with-opentelemetry-server.ts#example
|
|
62
148
|
*/
|
|
63
149
|
export declare function withOpenTelemetryServer(options: OpenTelemetryServerOptions): ClientOption;
|
|
64
|
-
//#endregion
|
|
65
150
|
export type { OpenTelemetryServerHttpOptions, OpenTelemetryServerOptions, OpenTelemetryServerSSEOptions, OpenTelemetryServerTransportOptions, OpenTelemetryServerWebSocketOptions };
|
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
2
|
-
import { ERR_ABORTED, createHttpInterceptor, createResolvedRequestUrl, createSSEInterceptor,
|
|
3
|
-
import { SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
4
|
-
|
|
2
|
+
import { ERR_ABORTED, createHttpInterceptor, createResolvedRequestUrl, createSSEInterceptor, createWebSocketInterceptor, isAbortCause, isTimeoutCause } from "@defjs/core";
|
|
3
|
+
import { SpanKind, SpanStatusCode, context, diag, trace } from "@opentelemetry/api";
|
|
4
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
5
|
+
// src/propagation/carrier.ts
|
|
5
6
|
const headersSetter = { set(carrier, key, value) {
|
|
6
7
|
if (carrier && key && value !== void 0) carrier.set(key, String(value));
|
|
7
8
|
} };
|
|
@@ -16,10 +17,9 @@ const headersGetter = {
|
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
19
|
const queryStringSetter = { set(carrier, key, value) {
|
|
19
|
-
if (carrier && key && value !== void 0) carrier.params.
|
|
20
|
+
if (carrier && key && value !== void 0) carrier.params.set(key, String(value));
|
|
20
21
|
} };
|
|
21
|
-
|
|
22
|
-
//#region src/telemetry/trace.ts
|
|
22
|
+
// src/telemetry/trace.ts
|
|
23
23
|
const spanStatusSet = /* @__PURE__ */ new WeakMap();
|
|
24
24
|
function markSpanStatusSet(span) {
|
|
25
25
|
spanStatusSet.set(span, true);
|
|
@@ -69,9 +69,9 @@ function createWebSocketSpan(tracer, url, parentCtx, operation, startAttributes)
|
|
|
69
69
|
}
|
|
70
70
|
}, parentCtx);
|
|
71
71
|
}
|
|
72
|
-
function setSpanHttpResponse(span, status
|
|
72
|
+
function setSpanHttpResponse(span, status) {
|
|
73
73
|
if (status !== 0) span.setAttribute("http.response.status_code", status);
|
|
74
|
-
const errorType = getHttpResponseErrorType(status
|
|
74
|
+
const errorType = getHttpResponseErrorType(status);
|
|
75
75
|
if (errorType) {
|
|
76
76
|
span.setAttribute("error.type", errorType);
|
|
77
77
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
@@ -100,6 +100,18 @@ function runSpanHook(span, hookName, hook) {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
function recordSpanHookError(span, hookName, error) {
|
|
103
|
+
if (!span.isRecording()) {
|
|
104
|
+
try {
|
|
105
|
+
const { traceId, spanId } = span.spanContext();
|
|
106
|
+
diag.error("@defjs/opentelemetry-server: span hook failed on a non-recording span", {
|
|
107
|
+
"hook.name": hookName,
|
|
108
|
+
"error.type": getErrorType(error),
|
|
109
|
+
traceId,
|
|
110
|
+
spanId
|
|
111
|
+
});
|
|
112
|
+
} catch {}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
103
115
|
try {
|
|
104
116
|
span.addEvent("defjs.otel.hook.error", {
|
|
105
117
|
"hook.name": hookName,
|
|
@@ -120,11 +132,7 @@ function addSpanEvent(span, eventName, attributes) {
|
|
|
120
132
|
function toError(error) {
|
|
121
133
|
return error instanceof Error ? error : new Error(String(error));
|
|
122
134
|
}
|
|
123
|
-
function getHttpResponseErrorType(status
|
|
124
|
-
if (status === 0) {
|
|
125
|
-
const errorCode = createTransportError(error).code;
|
|
126
|
-
return errorCode === "ABORTED" ? void 0 : errorCode;
|
|
127
|
-
}
|
|
135
|
+
function getHttpResponseErrorType(status) {
|
|
128
136
|
if (status >= 400) return String(status);
|
|
129
137
|
}
|
|
130
138
|
function getErrorType(error) {
|
|
@@ -132,8 +140,7 @@ function getErrorType(error) {
|
|
|
132
140
|
if (error === void 0 || error === null) return "_OTHER";
|
|
133
141
|
return typeof error;
|
|
134
142
|
}
|
|
135
|
-
|
|
136
|
-
//#region src/telemetry/url.ts
|
|
143
|
+
// src/telemetry/url.ts
|
|
137
144
|
function resolveUrl(endpoint, baseEndpoint) {
|
|
138
145
|
if (!baseEndpoint) return endpoint;
|
|
139
146
|
try {
|
|
@@ -155,8 +162,7 @@ function resolveHttpUrl(endpoint, baseEndpoint) {
|
|
|
155
162
|
return { url: endpoint };
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
|
-
|
|
159
|
-
//#region src/telemetry/metrics.ts
|
|
165
|
+
// src/telemetry/metrics.ts
|
|
160
166
|
const DURATION_BUCKETS = [
|
|
161
167
|
.005,
|
|
162
168
|
.01,
|
|
@@ -224,7 +230,7 @@ function createHttpMetricAttributes(req, response, error) {
|
|
|
224
230
|
...createServerMetricAttributes(req),
|
|
225
231
|
"http.request.method": req.method
|
|
226
232
|
};
|
|
227
|
-
const errorType = response ? getHttpResponseErrorType(response.status
|
|
233
|
+
const errorType = response ? getHttpResponseErrorType(response.status) : createErrorMetricAttributes(error)["error.type"];
|
|
228
234
|
if (errorType) attributes["error.type"] = errorType;
|
|
229
235
|
if (response && response.status !== 0) attributes["http.response.status_code"] = response.status;
|
|
230
236
|
return attributes;
|
|
@@ -248,26 +254,85 @@ function createErrorMetricAttributes(error) {
|
|
|
248
254
|
if (error === void 0 || error === null) return {};
|
|
249
255
|
return { "error.type": getErrorType(error) };
|
|
250
256
|
}
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
// src/telemetry/logs.ts
|
|
258
|
+
const CLIENT_EXCEPTIONS = {
|
|
259
|
+
http: {
|
|
260
|
+
eventName: "http.client.request.exception",
|
|
261
|
+
body: "HTTP client request failed"
|
|
262
|
+
},
|
|
263
|
+
sse: {
|
|
264
|
+
eventName: "defjs.client.sse.exception",
|
|
265
|
+
body: "SSE client connection failed"
|
|
266
|
+
},
|
|
267
|
+
websocket: {
|
|
268
|
+
eventName: "defjs.client.websocket.exception",
|
|
269
|
+
body: "WebSocket client connection failed"
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Caller cancellation produces no log. An abort whose reason is `TimeoutError` or `ERR_TIMEOUT` still logs.
|
|
274
|
+
*
|
|
275
|
+
* `request.abort` alone is not cancellation: WebSocket `finish()` aborts that same signal
|
|
276
|
+
* when a connect fails. Cancellation is an abort sentinel from any `@defjs/core` copy, or an
|
|
277
|
+
* `AbortError`, on the rejection or on `request.abort.reason`. There is no `timeout` option.
|
|
278
|
+
*/
|
|
279
|
+
function isCallerCancellation(request, error) {
|
|
280
|
+
const reason = request.abort?.reason;
|
|
281
|
+
if (isTimeoutCause(error) || isTimeoutCause(reason)) return false;
|
|
282
|
+
return isAbortCause(error) || isAbortCause(reason);
|
|
283
|
+
}
|
|
284
|
+
function emitClientException(logger, details) {
|
|
285
|
+
if (!logger) return;
|
|
286
|
+
try {
|
|
287
|
+
const { error, transport, request } = details;
|
|
288
|
+
if (isCallerCancellation(request, error)) return;
|
|
289
|
+
if (error instanceof Error && "code" in error) {
|
|
290
|
+
if (error.code === "NET_ABORTED" || transport === "http" && error.code === "HTTP_STATUS") return;
|
|
291
|
+
}
|
|
292
|
+
const attributes = {
|
|
293
|
+
...createServerMetricAttributes(request),
|
|
294
|
+
"defjs.phase": details.phase,
|
|
295
|
+
"exception.type": getErrorType(error)
|
|
296
|
+
};
|
|
297
|
+
if (transport === "http") attributes["http.request.method"] = request.method;
|
|
298
|
+
const closeCode = details.closeInfo?.code;
|
|
299
|
+
if (transport === "sse" && typeof closeCode === "string") attributes["defjs.sse.fault.code"] = closeCode;
|
|
300
|
+
if (transport === "websocket" && typeof closeCode === "number") attributes["websocket.close.code"] = closeCode;
|
|
301
|
+
logger.emit({
|
|
302
|
+
...CLIENT_EXCEPTIONS[transport],
|
|
303
|
+
context: details.context,
|
|
304
|
+
severityNumber: SeverityNumber.WARN,
|
|
305
|
+
severityText: "WARN",
|
|
306
|
+
attributes
|
|
307
|
+
});
|
|
308
|
+
} catch {
|
|
309
|
+
try {
|
|
310
|
+
diag.error("@defjs/opentelemetry-server: failed to emit client diagnostic log");
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// src/interceptor/http.ts
|
|
253
315
|
function createOpenTelemetryHttpInterceptor(options) {
|
|
254
316
|
return createHttpInterceptor(async (req, next) => {
|
|
255
|
-
const { tracer, propagator, metrics, requireParentSpan, startSpanHook, requestHook, responseHook } = options;
|
|
256
|
-
if (requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
317
|
+
const { tracer, logger, propagator, metrics, requireParentSpan, startSpanHook, requestHook, responseHook } = options;
|
|
318
|
+
if (tracer && requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
257
319
|
const parentCtx = propagator.extract(context.active(), req.headers ?? new Headers(), headersGetter);
|
|
258
320
|
const { url, serverAddress, serverPort } = resolveHttpUrl(req.endpoint, req.baseEndpoint);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
321
|
+
let span;
|
|
322
|
+
if (tracer) {
|
|
323
|
+
const startSpanHookResult = resolveStartSpanHook(startSpanHook, req);
|
|
324
|
+
span = createHttpSpan(tracer, req.method, url, parentCtx, req.operation, {
|
|
325
|
+
...serverAddress ? { "server.address": serverAddress } : {},
|
|
326
|
+
...serverPort ? { "server.port": serverPort } : {},
|
|
327
|
+
...startSpanHookResult.ok ? startSpanHookResult.attributes : {}
|
|
328
|
+
});
|
|
329
|
+
if (!startSpanHookResult.ok) runSpanHook(span, "startSpanHook", () => {
|
|
330
|
+
throw startSpanHookResult.error;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (span) runSpanHook(span, "requestHook", () => requestHook?.(span, req));
|
|
334
|
+
const spanCtx = span ? trace.setSpan(parentCtx, span) : parentCtx;
|
|
335
|
+
const headers = new Headers(req.headers);
|
|
271
336
|
propagator.inject(spanCtx, headers, headersSetter);
|
|
272
337
|
const startTime = performance.now();
|
|
273
338
|
try {
|
|
@@ -276,35 +341,46 @@ function createOpenTelemetryHttpInterceptor(options) {
|
|
|
276
341
|
headers
|
|
277
342
|
});
|
|
278
343
|
const durationS = durationSeconds(startTime);
|
|
279
|
-
|
|
280
|
-
|
|
344
|
+
if (span) {
|
|
345
|
+
runSpanHook(span, "responseHook", () => responseHook?.(span, response, req));
|
|
346
|
+
setSpanHttpResponse(span, response.status);
|
|
347
|
+
}
|
|
281
348
|
metrics?.requestDuration.record(durationS, createHttpMetricAttributes(req, response));
|
|
282
349
|
return response;
|
|
283
350
|
} catch (error) {
|
|
284
351
|
const durationS = durationSeconds(startTime);
|
|
285
|
-
|
|
352
|
+
emitClientException(logger, {
|
|
353
|
+
request: req,
|
|
354
|
+
context: spanCtx,
|
|
355
|
+
error,
|
|
356
|
+
transport: "http",
|
|
357
|
+
phase: "request"
|
|
358
|
+
});
|
|
359
|
+
if (span) setSpanError(span, error);
|
|
286
360
|
metrics?.requestDuration.record(durationS, createHttpMetricAttributes(req, void 0, error));
|
|
287
361
|
throw error;
|
|
288
362
|
}
|
|
289
363
|
});
|
|
290
364
|
}
|
|
291
|
-
|
|
292
|
-
//#region src/interceptor/sse.ts
|
|
365
|
+
// src/interceptor/sse.ts
|
|
293
366
|
function createOpenTelemetrySSEInterceptor(options) {
|
|
294
367
|
return createSSEInterceptor(async (req, next) => {
|
|
295
|
-
const { tracer, propagator, metrics, requireParentSpan, startSpanHook, requestHook, responseHook } = options;
|
|
296
|
-
if (requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
368
|
+
const { tracer, logger, propagator, metrics, requireParentSpan, startSpanHook, requestHook, responseHook } = options;
|
|
369
|
+
if (tracer && requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
297
370
|
const parentCtx = propagator.extract(context.active(), req.headers ?? new Headers(), headersGetter);
|
|
298
371
|
const url = resolveUrl(req.endpoint, req.baseEndpoint);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
372
|
+
let span;
|
|
373
|
+
if (tracer) {
|
|
374
|
+
const startSpanHookResult = resolveStartSpanHook(startSpanHook, req);
|
|
375
|
+
span = createSSESpan(tracer, url, parentCtx, req.operation, startSpanHookResult.ok ? startSpanHookResult.attributes : void 0);
|
|
376
|
+
if (!startSpanHookResult.ok) runSpanHook(span, "startSpanHook", () => {
|
|
377
|
+
throw startSpanHookResult.error;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const spanCtx = span ? trace.setSpan(parentCtx, span) : parentCtx;
|
|
381
|
+
if (span) runSpanHook(span, "requestHook", () => requestHook?.(span, req));
|
|
382
|
+
const headers = new Headers(req.headers);
|
|
306
383
|
propagator.inject(spanCtx, headers, headersSetter);
|
|
307
|
-
runSpanHook(span, "requestHook", () => requestHook?.(span, req));
|
|
308
384
|
const connectStartMs = performance.now();
|
|
309
385
|
try {
|
|
310
386
|
const stream = await next({
|
|
@@ -313,8 +389,10 @@ function createOpenTelemetrySSEInterceptor(options) {
|
|
|
313
389
|
});
|
|
314
390
|
const connectedAtMs = performance.now();
|
|
315
391
|
const activeAttributes = createServerMetricAttributes(req);
|
|
316
|
-
|
|
317
|
-
|
|
392
|
+
if (span) {
|
|
393
|
+
runSpanHook(span, "responseHook", () => responseHook?.(span, stream, req));
|
|
394
|
+
addSpanEvent(span, "sse.connected");
|
|
395
|
+
}
|
|
318
396
|
metrics?.connectDuration.record(durationSeconds(connectStartMs, connectedAtMs), createConnectionMetricAttributes(req, "success"));
|
|
319
397
|
metrics?.activeStreams.add(1, activeAttributes);
|
|
320
398
|
const closeActiveStream = () => {
|
|
@@ -323,59 +401,82 @@ function createOpenTelemetrySSEInterceptor(options) {
|
|
|
323
401
|
stream.closed.then((closeInfo) => {
|
|
324
402
|
closeActiveStream();
|
|
325
403
|
const closeAttributes = {
|
|
326
|
-
"sse.close.
|
|
327
|
-
...closeInfo.
|
|
404
|
+
"sse.close.kind": closeInfo.kind,
|
|
405
|
+
...closeInfo.kind === "error" ? { "defjs.sse.fault.code": closeInfo.code } : {}
|
|
328
406
|
};
|
|
329
|
-
if (closeInfo.
|
|
330
|
-
|
|
407
|
+
if (closeInfo.kind === "error") {
|
|
408
|
+
emitClientException(logger, {
|
|
409
|
+
request: req,
|
|
410
|
+
context: spanCtx,
|
|
411
|
+
error: closeInfo.cause,
|
|
412
|
+
transport: "sse",
|
|
413
|
+
phase: "connection",
|
|
414
|
+
closeInfo
|
|
415
|
+
});
|
|
416
|
+
if (span) addSpanEvent(span, "sse.error", closeAttributes);
|
|
331
417
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "error", {
|
|
332
418
|
...closeAttributes,
|
|
333
419
|
...createErrorMetricAttributes(closeInfo.cause)
|
|
334
420
|
}));
|
|
335
|
-
setSpanError(span, closeInfo.cause);
|
|
421
|
+
if (span) setSpanError(span, closeInfo.cause);
|
|
336
422
|
return;
|
|
337
423
|
}
|
|
338
|
-
addSpanEvent(span, closeInfo.
|
|
424
|
+
if (span) addSpanEvent(span, closeInfo.kind === "aborted" ? "sse.aborted" : "sse.closed", closeAttributes);
|
|
339
425
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "success", closeAttributes));
|
|
340
|
-
endSpan(span);
|
|
426
|
+
if (span) endSpan(span);
|
|
341
427
|
}, (error) => {
|
|
342
428
|
closeActiveStream();
|
|
343
|
-
|
|
429
|
+
emitClientException(logger, {
|
|
430
|
+
request: req,
|
|
431
|
+
context: spanCtx,
|
|
432
|
+
error,
|
|
433
|
+
transport: "sse",
|
|
434
|
+
phase: "connection"
|
|
435
|
+
});
|
|
436
|
+
if (span) addSpanEvent(span, "sse.error", createErrorMetricAttributes(error));
|
|
344
437
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "error", createErrorMetricAttributes(error)));
|
|
345
|
-
setSpanError(span, error);
|
|
438
|
+
if (span) setSpanError(span, error);
|
|
346
439
|
});
|
|
347
440
|
return stream;
|
|
348
441
|
} catch (error) {
|
|
442
|
+
emitClientException(logger, {
|
|
443
|
+
request: req,
|
|
444
|
+
context: spanCtx,
|
|
445
|
+
error,
|
|
446
|
+
transport: "sse",
|
|
447
|
+
phase: "connect"
|
|
448
|
+
});
|
|
349
449
|
metrics?.connectDuration.record(durationSeconds(connectStartMs), createConnectionMetricAttributes(req, "error", createErrorMetricAttributes(error)));
|
|
350
|
-
setSpanError(span, error);
|
|
450
|
+
if (span) setSpanError(span, error);
|
|
351
451
|
throw error;
|
|
352
452
|
}
|
|
353
453
|
});
|
|
354
454
|
}
|
|
355
|
-
|
|
356
|
-
//#region src/interceptor/web_socket.ts
|
|
455
|
+
// src/interceptor/web_socket.ts
|
|
357
456
|
function createOpenTelemetryWebSocketInterceptor(options) {
|
|
358
457
|
return createWebSocketInterceptor(async (req, next) => {
|
|
359
|
-
const { tracer, propagator, metrics, requireParentSpan, queryPropagation = false, startSpanHook, requestHook, responseHook } = options;
|
|
360
|
-
if (requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
458
|
+
const { tracer, logger, propagator, metrics, requireParentSpan, queryPropagation = false, startSpanHook, requestHook, responseHook } = options;
|
|
459
|
+
if (tracer && requireParentSpan && !trace.getActiveSpan()) return next(req);
|
|
361
460
|
const url = resolveUrl(req.endpoint, req.baseEndpoint);
|
|
362
461
|
const parentCtx = propagator.extract(context.active(), req.headers ?? new Headers(), headersGetter);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
462
|
+
let span;
|
|
463
|
+
if (tracer) {
|
|
464
|
+
const startSpanHookResult = resolveStartSpanHook(startSpanHook, req);
|
|
465
|
+
span = createWebSocketSpan(tracer, url, parentCtx, req.operation, startSpanHookResult.ok ? startSpanHookResult.attributes : void 0);
|
|
466
|
+
if (!startSpanHookResult.ok) runSpanHook(span, "startSpanHook", () => {
|
|
467
|
+
throw startSpanHookResult.error;
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const spanCtx = span ? trace.setSpan(parentCtx, span) : parentCtx;
|
|
369
471
|
let queryParams = req.queryParams;
|
|
370
472
|
let queryString = req.queryString;
|
|
371
473
|
if (queryPropagation) {
|
|
372
|
-
const
|
|
373
|
-
const carrier = { params: new URLSearchParams(queryParams) };
|
|
474
|
+
const carrier = { params: new URLSearchParams() };
|
|
374
475
|
propagator.inject(spanCtx, carrier, queryStringSetter);
|
|
375
|
-
|
|
376
|
-
|
|
476
|
+
queryString = replaceInjectedPairs(queryString ?? new URLSearchParams(queryParams).toString(), carrier.params);
|
|
477
|
+
queryParams = new URLSearchParams(queryString);
|
|
377
478
|
}
|
|
378
|
-
runSpanHook(span, "requestHook", () => requestHook?.(span, req));
|
|
479
|
+
if (span) runSpanHook(span, "requestHook", () => requestHook?.(span, req));
|
|
379
480
|
const connectStartMs = performance.now();
|
|
380
481
|
try {
|
|
381
482
|
const session = await next({
|
|
@@ -385,8 +486,10 @@ function createOpenTelemetryWebSocketInterceptor(options) {
|
|
|
385
486
|
});
|
|
386
487
|
const connectedAtMs = performance.now();
|
|
387
488
|
const activeAttributes = createServerMetricAttributes(req);
|
|
388
|
-
|
|
389
|
-
|
|
489
|
+
if (span) {
|
|
490
|
+
runSpanHook(span, "responseHook", () => responseHook?.(span, session, req));
|
|
491
|
+
addSpanEvent(span, "websocket.connected");
|
|
492
|
+
}
|
|
390
493
|
metrics?.connectDuration.record(durationSeconds(connectStartMs, connectedAtMs), createConnectionMetricAttributes(req, "success"));
|
|
391
494
|
metrics?.activeConnections.add(1, activeAttributes);
|
|
392
495
|
const closeActiveConnection = () => {
|
|
@@ -399,63 +502,88 @@ function createOpenTelemetryWebSocketInterceptor(options) {
|
|
|
399
502
|
const closeCause = runtimeCloseInfo.cause;
|
|
400
503
|
if (typeof closeKind === "string" ? closeKind !== "closed" : typeof closeCause !== "undefined") {
|
|
401
504
|
const error = typeof closeKind === "string" ? closeKind === "error" ? closeCause : closeCause ?? ERR_ABORTED : closeCause;
|
|
402
|
-
|
|
505
|
+
if (closeKind !== "aborted") emitClientException(logger, {
|
|
506
|
+
request: req,
|
|
507
|
+
context: spanCtx,
|
|
508
|
+
error,
|
|
509
|
+
transport: "websocket",
|
|
510
|
+
phase: "connection",
|
|
511
|
+
closeInfo: runtimeCloseInfo
|
|
512
|
+
});
|
|
513
|
+
if (span) addSpanEvent(span, "websocket.error", createErrorMetricAttributes(error));
|
|
403
514
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "error", createErrorMetricAttributes(error)));
|
|
404
|
-
setSpanError(span, error);
|
|
515
|
+
if (span) setSpanError(span, error);
|
|
405
516
|
return;
|
|
406
517
|
}
|
|
407
|
-
addSpanEvent(span, "websocket.closed");
|
|
518
|
+
if (span) addSpanEvent(span, "websocket.closed");
|
|
408
519
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "success"));
|
|
409
|
-
endSpan(span);
|
|
520
|
+
if (span) endSpan(span);
|
|
410
521
|
}, (error) => {
|
|
411
522
|
closeActiveConnection();
|
|
412
|
-
|
|
523
|
+
emitClientException(logger, {
|
|
524
|
+
request: req,
|
|
525
|
+
context: spanCtx,
|
|
526
|
+
error,
|
|
527
|
+
transport: "websocket",
|
|
528
|
+
phase: "connection"
|
|
529
|
+
});
|
|
530
|
+
if (span) addSpanEvent(span, "websocket.error", createErrorMetricAttributes(error));
|
|
413
531
|
metrics?.connectionDuration.record(durationSeconds(connectedAtMs), createConnectionMetricAttributes(req, "error", createErrorMetricAttributes(error)));
|
|
414
|
-
setSpanError(span, error);
|
|
532
|
+
if (span) setSpanError(span, error);
|
|
415
533
|
});
|
|
416
534
|
return session;
|
|
417
535
|
} catch (error) {
|
|
536
|
+
emitClientException(logger, {
|
|
537
|
+
request: req,
|
|
538
|
+
context: spanCtx,
|
|
539
|
+
error,
|
|
540
|
+
transport: "websocket",
|
|
541
|
+
phase: "connect"
|
|
542
|
+
});
|
|
418
543
|
metrics?.connectDuration.record(durationSeconds(connectStartMs), createConnectionMetricAttributes(req, "error", createErrorMetricAttributes(error)));
|
|
419
|
-
setSpanError(span, error);
|
|
544
|
+
if (span) setSpanError(span, error);
|
|
420
545
|
throw error;
|
|
421
546
|
}
|
|
422
547
|
});
|
|
423
548
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Put the injected pairs last and drop earlier pairs of the same name, so a receiver reading the first value gets the injected one.
|
|
551
|
+
* Every other pair keeps the bytes it was serialized with.
|
|
552
|
+
*/
|
|
553
|
+
function replaceInjectedPairs(queryString, injected) {
|
|
554
|
+
const added = injected.toString();
|
|
555
|
+
if (!added) return queryString;
|
|
556
|
+
const prefixed = queryString.startsWith("?");
|
|
557
|
+
const query = prefixed ? queryString.slice(1) : queryString;
|
|
558
|
+
const combined = [...query ? query.split("&").filter((pair) => !injected.has(new URLSearchParams(`&${pair}`).keys().next().value ?? "")) : [], added].join("&");
|
|
559
|
+
return prefixed || combined.startsWith("?") ? `?${combined}` : combined;
|
|
434
560
|
}
|
|
435
|
-
|
|
436
|
-
//#region src/option.ts
|
|
561
|
+
// src/option.ts
|
|
437
562
|
/**
|
|
438
563
|
* Client option that installs outbound OpenTelemetry interceptors for HTTP, SSE, and WebSocket.
|
|
439
564
|
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
565
|
+
* Appends one interceptor per enabled transport, in the order HTTP, SSE, WebSocket. Apply it at `createClient` time. It does not initialize the OpenTelemetry SDK or acquire global tracer, meter, or logger handles: pass the ones your application owns. Each transport can be toggled or customized via `http`, `sse`, and `webSocket`.
|
|
566
|
+
*
|
|
567
|
+
* `options` is required. It carries `tracer`, `meter`, `logger`, `propagator`, `requireParentSpan`, and the `http` / `sse` / `webSocket` transport options. Pass `{}` to install the interceptors with propagation only.
|
|
568
|
+
*
|
|
569
|
+
* Each of `tracer`, `meter` and `logger` is optional and works without the others. Without `tracer`, the plugin creates no local spans, never calls `trace.getTracer(...)`, and skips `startSpanHook`, `requestHook` and `responseHook`. Propagation still forwards the extracted parent context.
|
|
570
|
+
*
|
|
571
|
+
* HTTP, SSE, and WebSocket are on by default. Pass `{ enabled: false }` on a transport to skip it.
|
|
442
572
|
*
|
|
443
|
-
* @param options -
|
|
573
|
+
* @param options - Required settings object. Each of `tracer`, `meter`, `logger`, and `propagator` may be omitted; pass `{}` for propagation only.
|
|
444
574
|
* @returns A `ClientOption` for `createClient(...)`.
|
|
445
575
|
*
|
|
446
|
-
* @
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
* withOpenTelemetryServer({ tracer }),
|
|
451
|
-
* )
|
|
452
|
-
* ```
|
|
576
|
+
* @group opentelemetry-server
|
|
577
|
+
* @see {@link ClientOption}
|
|
578
|
+
* @see {@link OpenTelemetryServerOptions}
|
|
579
|
+
* @exampleFile examples/api/with-opentelemetry-server.ts#example
|
|
453
580
|
*/
|
|
454
581
|
function withOpenTelemetryServer(options) {
|
|
455
|
-
const { tracer, meter, propagator = new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()] }), requireParentSpan = false } = options;
|
|
582
|
+
const { tracer, meter, logger, propagator = new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()] }), requireParentSpan = false } = options;
|
|
456
583
|
return (config) => {
|
|
457
584
|
if (isTransportEnabled(options.http)) config.interceptors.push(createOpenTelemetryHttpInterceptor({
|
|
458
585
|
tracer,
|
|
586
|
+
logger,
|
|
459
587
|
propagator,
|
|
460
588
|
metrics: meter ? createHttpClientMetrics(meter) : void 0,
|
|
461
589
|
requireParentSpan,
|
|
@@ -465,6 +593,7 @@ function withOpenTelemetryServer(options) {
|
|
|
465
593
|
}));
|
|
466
594
|
if (isTransportEnabled(options.sse)) config.interceptors.push(createOpenTelemetrySSEInterceptor({
|
|
467
595
|
tracer,
|
|
596
|
+
logger,
|
|
468
597
|
propagator,
|
|
469
598
|
metrics: meter ? createSSEClientMetrics(meter) : void 0,
|
|
470
599
|
requireParentSpan,
|
|
@@ -474,6 +603,7 @@ function withOpenTelemetryServer(options) {
|
|
|
474
603
|
}));
|
|
475
604
|
if (isTransportEnabled(options.webSocket)) config.interceptors.push(createOpenTelemetryWebSocketInterceptor({
|
|
476
605
|
tracer,
|
|
606
|
+
logger,
|
|
477
607
|
propagator,
|
|
478
608
|
metrics: meter ? createWebSocketClientMetrics(meter) : void 0,
|
|
479
609
|
requireParentSpan,
|
|
@@ -487,5 +617,4 @@ function withOpenTelemetryServer(options) {
|
|
|
487
617
|
function isTransportEnabled(option) {
|
|
488
618
|
return option?.enabled !== false;
|
|
489
619
|
}
|
|
490
|
-
//#endregion
|
|
491
620
|
export { withOpenTelemetryServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defjs/opentelemetry-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Append OpenTelemetry interceptors to an application-owned Defjs client.",
|
|
4
5
|
"bugs": {
|
|
5
6
|
"url": "https://github.com/defjs/defjs/issues"
|
|
6
7
|
},
|
|
@@ -12,10 +13,13 @@
|
|
|
12
13
|
"type": "module",
|
|
13
14
|
"main": "./index.js",
|
|
14
15
|
"types": "./index.d.ts",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@opentelemetry/api-logs": "^0.223.0"
|
|
18
|
+
},
|
|
15
19
|
"peerDependencies": {
|
|
16
|
-
"@defjs/core": "0.
|
|
20
|
+
"@defjs/core": "^0.5.0",
|
|
17
21
|
"@opentelemetry/api": "^1.0.0",
|
|
18
|
-
"@opentelemetry/core": "^2.0.0"
|
|
22
|
+
"@opentelemetry/core": "^2.0.0 || ^3.0.0"
|
|
19
23
|
},
|
|
20
24
|
"module": "./index.js",
|
|
21
25
|
"typings": "./index.d.ts",
|