autotel-edge 3.0.0
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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/chunk-F32WSLNX.js +309 -0
- package/dist/chunk-F32WSLNX.js.map +1 -0
- package/dist/events.d.ts +86 -0
- package/dist/events.js +157 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +921 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +89 -0
- package/dist/logger.js +81 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +166 -0
- package/dist/sampling.js +108 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/dist/types-Dj85cPUj.d.ts +182 -0
- package/package.json +88 -0
- package/src/api/logger.test.ts +367 -0
- package/src/api/logger.ts +197 -0
- package/src/compose.ts +243 -0
- package/src/core/buffer.ts +16 -0
- package/src/core/config.test.ts +388 -0
- package/src/core/config.ts +167 -0
- package/src/core/context.ts +224 -0
- package/src/core/exporter.ts +99 -0
- package/src/core/provider.ts +45 -0
- package/src/core/span.ts +222 -0
- package/src/core/spanprocessor.test.ts +521 -0
- package/src/core/spanprocessor.ts +232 -0
- package/src/core/trace-context.ts +66 -0
- package/src/core/tracer.test.ts +123 -0
- package/src/core/tracer.ts +216 -0
- package/src/events/index.test.ts +242 -0
- package/src/events/index.ts +338 -0
- package/src/events.ts +6 -0
- package/src/functional.test.ts +702 -0
- package/src/functional.ts +846 -0
- package/src/index.ts +81 -0
- package/src/logger.ts +13 -0
- package/src/sampling/index.test.ts +297 -0
- package/src/sampling/index.ts +276 -0
- package/src/sampling.ts +6 -0
- package/src/testing/index.ts +9 -0
- package/src/testing.ts +6 -0
- package/src/types.ts +267 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Jag Reehal 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# autotel-edge
|
|
2
|
+
|
|
3
|
+
**Vendor-agnostic OpenTelemetry for edge runtimes** - the foundation for Cloudflare Workers, Vercel Edge, Netlify Edge, Deno Deploy, and more.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/autotel-edge)
|
|
6
|
+
[](https://bundlephobia.com/package/autotel-edge)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`autotel-edge` is a lightweight (~20KB), vendor-agnostic OpenTelemetry implementation designed specifically for edge runtimes. It provides the core functionality for tracing, sampling, events, and logging without any vendor-specific dependencies.
|
|
12
|
+
|
|
13
|
+
### For Cloudflare Workers Users
|
|
14
|
+
|
|
15
|
+
If you're using Cloudflare Workers, use **[autotel-cloudflare](../autotel-cloudflare)** instead, which includes complete Cloudflare bindings instrumentation (KV, R2, D1, etc.) and handler wrappers.
|
|
16
|
+
|
|
17
|
+
### When to Use autotel-edge Directly
|
|
18
|
+
|
|
19
|
+
Use this package directly if you're:
|
|
20
|
+
- Building for Vercel Edge Functions
|
|
21
|
+
- Building for Netlify Edge Functions
|
|
22
|
+
- Building for Deno Deploy
|
|
23
|
+
- Building a custom edge runtime
|
|
24
|
+
- Creating a vendor-specific package (like `autotel-vercel`)
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- ✅ **Zero-boilerplate functional API** - `trace()`, `span()`, `instrument()`
|
|
29
|
+
- ✅ **Advanced sampling strategies** - Adaptive, error-only, slow-only, custom
|
|
30
|
+
- ✅ **Events integration** - Product analytics with trace correlation
|
|
31
|
+
- ✅ **Zero-dependency logger** - Trace-aware logging
|
|
32
|
+
- ✅ **Tree-shakeable** - Import only what you need
|
|
33
|
+
- ✅ **Bundle size optimized** - ~20KB minified (~8KB gzipped)
|
|
34
|
+
- ✅ **OpenTelemetry compliant** - Works with any OTLP backend
|
|
35
|
+
- ✅ **TypeScript native** - Full type safety
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install autotel-edge
|
|
41
|
+
# or
|
|
42
|
+
pnpm add autotel-edge
|
|
43
|
+
# or
|
|
44
|
+
yarn add autotel-edge
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### Basic Usage
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { trace, init } from 'autotel-edge'
|
|
53
|
+
|
|
54
|
+
// Initialize once at startup
|
|
55
|
+
init({
|
|
56
|
+
service: { name: 'my-edge-function' },
|
|
57
|
+
exporter: {
|
|
58
|
+
url: process.env.OTEL_ENDPOINT || 'http://localhost:4318/v1/traces'
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Zero-boilerplate function tracing
|
|
63
|
+
export const handler = trace(async (request: Request) => {
|
|
64
|
+
return new Response('Hello World')
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Factory Pattern (for context access)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { trace } from 'autotel-edge'
|
|
72
|
+
|
|
73
|
+
// Factory pattern - receives context, returns handler
|
|
74
|
+
export const processOrder = trace(ctx => async (orderId: string) => {
|
|
75
|
+
ctx.setAttribute('order.id', orderId)
|
|
76
|
+
|
|
77
|
+
// Your business logic
|
|
78
|
+
const order = await getOrder(orderId)
|
|
79
|
+
|
|
80
|
+
return order
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Entry Points (Tree-Shaking)
|
|
85
|
+
|
|
86
|
+
The package provides multiple entry points for optimal tree-shaking:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Core API
|
|
90
|
+
import { trace, span, init } from 'autotel-edge'
|
|
91
|
+
|
|
92
|
+
// Sampling strategies
|
|
93
|
+
import { createAdaptiveSampler, SamplingPresets } from 'autotel-edge/sampling'
|
|
94
|
+
|
|
95
|
+
// Events system
|
|
96
|
+
import { createEdgeSubscribers, publishEvent } from 'autotel-edge/events'
|
|
97
|
+
|
|
98
|
+
// Logger
|
|
99
|
+
import { createEdgeLogger } from 'autotel-edge/logger'
|
|
100
|
+
|
|
101
|
+
// Testing utilities
|
|
102
|
+
import { createTraceCollector, assertTraceCreated } from 'autotel-edge/testing'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Sampling Strategies
|
|
106
|
+
|
|
107
|
+
### Adaptive Sampling (Recommended for Production)
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { SamplingPresets } from 'autotel-edge/sampling'
|
|
111
|
+
|
|
112
|
+
init({
|
|
113
|
+
service: { name: 'my-app' },
|
|
114
|
+
exporter: { url: '...' },
|
|
115
|
+
sampling: {
|
|
116
|
+
tailSampler: SamplingPresets.production()
|
|
117
|
+
// 10% baseline, 100% errors, 100% slow requests (>1s)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Available Presets
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { SamplingPresets } from 'autotel-edge/sampling'
|
|
126
|
+
|
|
127
|
+
// Development - 100% sampling
|
|
128
|
+
SamplingPresets.development()
|
|
129
|
+
|
|
130
|
+
// Production - 10% baseline, all errors, slow >1s
|
|
131
|
+
SamplingPresets.production()
|
|
132
|
+
|
|
133
|
+
// High traffic - 1% baseline, all errors, slow >1s
|
|
134
|
+
SamplingPresets.highTraffic()
|
|
135
|
+
|
|
136
|
+
// Debugging - errors only
|
|
137
|
+
SamplingPresets.debugging()
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Custom Sampling
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { createCustomTailSampler } from 'autotel-edge/sampling'
|
|
144
|
+
|
|
145
|
+
const customSampler = createCustomTailSampler((trace) => {
|
|
146
|
+
const span = trace.localRootSpan
|
|
147
|
+
|
|
148
|
+
// Sample all /api/* requests
|
|
149
|
+
if (span.attributes['http.route']?.toString().startsWith('/api/')) {
|
|
150
|
+
return true
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sample errors
|
|
154
|
+
if (span.status.code === SpanStatusCode.ERROR) {
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Drop everything else
|
|
159
|
+
return false
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Events Integration
|
|
164
|
+
|
|
165
|
+
Track product events with automatic trace correlation:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { publishEvent } from 'autotel-edge/events'
|
|
169
|
+
|
|
170
|
+
// Track user events
|
|
171
|
+
await publishEvent({
|
|
172
|
+
name: 'order.completed',
|
|
173
|
+
userId: '123',
|
|
174
|
+
properties: {
|
|
175
|
+
orderId: 'abc',
|
|
176
|
+
amount: 99.99
|
|
177
|
+
}
|
|
178
|
+
// Automatically includes current trace ID
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Logger
|
|
183
|
+
|
|
184
|
+
Zero-dependency logger with trace context:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { createEdgeLogger } from 'autotel-edge/logger'
|
|
188
|
+
|
|
189
|
+
const log = createEdgeLogger('my-service')
|
|
190
|
+
|
|
191
|
+
log.info('Processing request', { userId: '123' })
|
|
192
|
+
log.error('Request failed', { error })
|
|
193
|
+
// Automatically includes trace ID, span ID
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Testing
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { createTraceCollector, assertTraceCreated } from 'autotel-edge/testing'
|
|
200
|
+
|
|
201
|
+
// In your tests
|
|
202
|
+
const collector = createTraceCollector()
|
|
203
|
+
|
|
204
|
+
await myFunction()
|
|
205
|
+
|
|
206
|
+
assertTraceCreated(collector, 'myFunction')
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Supported Runtimes
|
|
210
|
+
|
|
211
|
+
- ✅ Cloudflare Workers (use [autotel-cloudflare](../autotel-cloudflare))
|
|
212
|
+
- ✅ Vercel Edge Functions
|
|
213
|
+
- ✅ Netlify Edge Functions
|
|
214
|
+
- ✅ Deno Deploy
|
|
215
|
+
- ✅ AWS Lambda@Edge (with caveats)
|
|
216
|
+
- ✅ Any edge runtime with `fetch()` and `AsyncLocalStorage` support
|
|
217
|
+
|
|
218
|
+
## Configuration
|
|
219
|
+
|
|
220
|
+
### Service Configuration
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
init({
|
|
224
|
+
service: {
|
|
225
|
+
name: 'my-edge-function',
|
|
226
|
+
version: '1.0.0',
|
|
227
|
+
namespace: 'production'
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Exporter Configuration
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
init({
|
|
236
|
+
exporter: {
|
|
237
|
+
url: 'https://api.honeycomb.io/v1/traces',
|
|
238
|
+
headers: {
|
|
239
|
+
'x-honeycomb-team': process.env.HONEYCOMB_API_KEY
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Dynamic Configuration
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Configuration can be a function
|
|
249
|
+
init((env) => ({
|
|
250
|
+
service: { name: env.SERVICE_NAME },
|
|
251
|
+
exporter: { url: env.OTEL_ENDPOINT }
|
|
252
|
+
}))
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## API Reference
|
|
256
|
+
|
|
257
|
+
### Core Functions
|
|
258
|
+
|
|
259
|
+
#### `trace(fn)` / `trace(options, fn)`
|
|
260
|
+
|
|
261
|
+
Zero-boilerplate function tracing with automatic span management.
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Simple function
|
|
265
|
+
const handler = trace(async (request: Request) => {
|
|
266
|
+
return new Response('OK')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// With options
|
|
270
|
+
const handler = trace({
|
|
271
|
+
name: 'custom-name',
|
|
272
|
+
attributesFromArgs: ([request]) => ({
|
|
273
|
+
'http.method': request.method
|
|
274
|
+
})
|
|
275
|
+
}, async (request: Request) => {
|
|
276
|
+
return new Response('OK')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// Factory pattern (for context access)
|
|
280
|
+
const handler = trace(ctx => async (request: Request) => {
|
|
281
|
+
ctx.setAttribute('custom', 'value')
|
|
282
|
+
return new Response('OK')
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `span(options, fn)`
|
|
287
|
+
|
|
288
|
+
Create a named span for a code block.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const result = await span(
|
|
292
|
+
{ name: 'database.query', attributes: { table: 'users' } },
|
|
293
|
+
async (span) => {
|
|
294
|
+
const data = await db.query('SELECT * FROM users')
|
|
295
|
+
span.setAttribute('rows', data.length)
|
|
296
|
+
return data
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### `init(config)`
|
|
302
|
+
|
|
303
|
+
Initialize the OpenTelemetry SDK.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
init({
|
|
307
|
+
service: { name: 'my-app' },
|
|
308
|
+
exporter: { url: '...' },
|
|
309
|
+
sampling: { tailSampler: SamplingPresets.production() }
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Bundle Size
|
|
314
|
+
|
|
315
|
+
- **Core:** ~20KB minified (~8KB gzipped)
|
|
316
|
+
- **With all entry points:** ~25KB minified (~10KB gzipped)
|
|
317
|
+
- **Tree-shakeable:** Import only what you need
|
|
318
|
+
|
|
319
|
+
## Vendor Packages
|
|
320
|
+
|
|
321
|
+
- **[autotel-cloudflare](../autotel-cloudflare)** - Cloudflare Workers (with KV, R2, D1, etc.)
|
|
322
|
+
- **autotel-vercel** - Coming soon
|
|
323
|
+
- **autotel-netlify** - Coming soon
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
MIT © [Jag Reehal](https://github.com/jagreehal)
|
|
328
|
+
|
|
329
|
+
## Links
|
|
330
|
+
|
|
331
|
+
- [GitHub Repository](https://github.com/jagreehal/autotel)
|
|
332
|
+
- [Documentation](https://github.com/jagreehal/autotel#readme)
|
|
333
|
+
- [Issues](https://github.com/jagreehal/autotel/issues)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { ExportResultCode, W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
2
|
+
import { OTLPExporterError } from '@opentelemetry/otlp-exporter-base';
|
|
3
|
+
import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer';
|
|
4
|
+
import { ParentBasedSampler, AlwaysOnSampler } from '@opentelemetry/sdk-trace-base';
|
|
5
|
+
import { createContextKey, context } from '@opentelemetry/api';
|
|
6
|
+
|
|
7
|
+
// src/core/exporter.ts
|
|
8
|
+
var PACKAGE_VERSION = "3.0.0";
|
|
9
|
+
var defaultHeaders = {
|
|
10
|
+
accept: "application/json",
|
|
11
|
+
"content-type": "application/json",
|
|
12
|
+
"user-agent": `autotel-edge v${PACKAGE_VERSION}`
|
|
13
|
+
};
|
|
14
|
+
var OTLPExporter = class {
|
|
15
|
+
headers;
|
|
16
|
+
url;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.url = config.url;
|
|
19
|
+
this.headers = Object.assign({}, defaultHeaders, config.headers);
|
|
20
|
+
}
|
|
21
|
+
export(items, resultCallback) {
|
|
22
|
+
this._export(items).then(() => {
|
|
23
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
24
|
+
}).catch((error) => {
|
|
25
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
_export(items) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
try {
|
|
31
|
+
this.send(items, resolve, reject);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
reject(error);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
send(items, onSuccess, onError) {
|
|
38
|
+
const decoder = new TextDecoder();
|
|
39
|
+
const exportMessage = JsonTraceSerializer.serializeRequest(items);
|
|
40
|
+
const body = decoder.decode(exportMessage);
|
|
41
|
+
const params = {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: this.headers,
|
|
44
|
+
body
|
|
45
|
+
};
|
|
46
|
+
fetch(this.url, params).then((response) => {
|
|
47
|
+
if (response.ok) {
|
|
48
|
+
onSuccess();
|
|
49
|
+
} else {
|
|
50
|
+
onError(
|
|
51
|
+
new OTLPExporterError(
|
|
52
|
+
`Exporter received a statusCode: ${response.status}`
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
onError(
|
|
58
|
+
new OTLPExporterError(
|
|
59
|
+
`Exception during export: ${error.toString()}`,
|
|
60
|
+
error.code,
|
|
61
|
+
error.stack
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async shutdown() {
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/types.ts
|
|
71
|
+
function isSpanProcessorConfig(config) {
|
|
72
|
+
return !!config.spanProcessors;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/core/spanprocessor.ts
|
|
76
|
+
var SpanProcessorWithFlush = class {
|
|
77
|
+
exporter;
|
|
78
|
+
postProcessor;
|
|
79
|
+
spans = /* @__PURE__ */ new Map();
|
|
80
|
+
constructor(exporter, postProcessor) {
|
|
81
|
+
this.exporter = exporter;
|
|
82
|
+
this.postProcessor = postProcessor;
|
|
83
|
+
}
|
|
84
|
+
onStart(_span, _parentContext) {
|
|
85
|
+
}
|
|
86
|
+
onEnd(span) {
|
|
87
|
+
const traceId = span.spanContext().traceId;
|
|
88
|
+
if (!this.spans.has(traceId)) {
|
|
89
|
+
this.spans.set(traceId, []);
|
|
90
|
+
}
|
|
91
|
+
this.spans.get(traceId).push(span);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Force flush spans for a specific trace
|
|
95
|
+
*/
|
|
96
|
+
async forceFlush(traceId) {
|
|
97
|
+
if (traceId) {
|
|
98
|
+
const spans = this.spans.get(traceId);
|
|
99
|
+
if (spans && spans.length > 0) {
|
|
100
|
+
await this.exportSpans(spans);
|
|
101
|
+
this.spans.delete(traceId);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
const promises = [];
|
|
105
|
+
for (const [id, spans] of this.spans.entries()) {
|
|
106
|
+
promises.push(this.exportSpans(spans));
|
|
107
|
+
this.spans.delete(id);
|
|
108
|
+
}
|
|
109
|
+
await Promise.all(promises);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async shutdown() {
|
|
113
|
+
await this.forceFlush();
|
|
114
|
+
if (this.exporter) {
|
|
115
|
+
await this.exporter.shutdown();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Export spans with post-processing
|
|
120
|
+
* Errors are caught and logged but don't throw to prevent worker instability
|
|
121
|
+
*/
|
|
122
|
+
async exportSpans(spans) {
|
|
123
|
+
if (spans.length === 0) return;
|
|
124
|
+
if (!this.exporter) return;
|
|
125
|
+
let processedSpans = spans;
|
|
126
|
+
if (this.postProcessor) {
|
|
127
|
+
try {
|
|
128
|
+
processedSpans = this.postProcessor(spans);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("[autotel-edge] Post-processor error:", error);
|
|
131
|
+
processedSpans = spans;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
this.exporter.export(processedSpans, (result) => {
|
|
136
|
+
if (result.code === 0) {
|
|
137
|
+
resolve();
|
|
138
|
+
} else {
|
|
139
|
+
console.error(
|
|
140
|
+
"[autotel-edge] Exporter error:",
|
|
141
|
+
result.error?.message || "Unknown error"
|
|
142
|
+
);
|
|
143
|
+
resolve();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var TailSamplingSpanProcessor = class {
|
|
150
|
+
wrapped;
|
|
151
|
+
tailSampler;
|
|
152
|
+
traces = /* @__PURE__ */ new Map();
|
|
153
|
+
constructor(exporter, postProcessor, tailSampler) {
|
|
154
|
+
this.wrapped = new SpanProcessorWithFlush(exporter, postProcessor);
|
|
155
|
+
this.tailSampler = tailSampler;
|
|
156
|
+
}
|
|
157
|
+
onStart(span, parentContext) {
|
|
158
|
+
this.wrapped.onStart(span, parentContext);
|
|
159
|
+
}
|
|
160
|
+
onEnd(span) {
|
|
161
|
+
const traceId = span.spanContext().traceId;
|
|
162
|
+
const spanId = span.spanContext().spanId;
|
|
163
|
+
const parentSpanId = "parentSpanId" in span ? span.parentSpanId : void 0;
|
|
164
|
+
if (!this.traces.has(traceId)) {
|
|
165
|
+
this.traces.set(traceId, {
|
|
166
|
+
traceId,
|
|
167
|
+
spans: [],
|
|
168
|
+
localRootSpan: void 0
|
|
169
|
+
// Will be set when we identify the local root
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const trace = this.traces.get(traceId);
|
|
173
|
+
const hasLocalParent = parentSpanId && trace.spans.some((s) => s.spanContext().spanId === parentSpanId);
|
|
174
|
+
if (!hasLocalParent) {
|
|
175
|
+
trace.localRootSpan = span;
|
|
176
|
+
}
|
|
177
|
+
trace.spans.push(span);
|
|
178
|
+
const isDefinitiveRoot = !parentSpanId;
|
|
179
|
+
const shouldAutoFlush = isDefinitiveRoot && trace.localRootSpan && trace.localRootSpan.spanContext().spanId === spanId;
|
|
180
|
+
if (shouldAutoFlush) {
|
|
181
|
+
if (this.tailSampler) {
|
|
182
|
+
const shouldKeep = this.tailSampler(trace);
|
|
183
|
+
if (shouldKeep) {
|
|
184
|
+
for (const bufferedSpan of trace.spans) {
|
|
185
|
+
this.wrapped.onEnd(bufferedSpan);
|
|
186
|
+
}
|
|
187
|
+
void this.wrapped.forceFlush(traceId);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
for (const bufferedSpan of trace.spans) {
|
|
191
|
+
this.wrapped.onEnd(bufferedSpan);
|
|
192
|
+
}
|
|
193
|
+
void this.wrapped.forceFlush(traceId);
|
|
194
|
+
}
|
|
195
|
+
this.traces.delete(traceId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async forceFlush(traceId) {
|
|
199
|
+
if (traceId) {
|
|
200
|
+
const trace = this.traces.get(traceId);
|
|
201
|
+
if (trace) {
|
|
202
|
+
if (!trace.localRootSpan && trace.spans.length > 0) {
|
|
203
|
+
trace.localRootSpan = trace.spans[0];
|
|
204
|
+
}
|
|
205
|
+
if (this.tailSampler) {
|
|
206
|
+
const shouldKeep = this.tailSampler(trace);
|
|
207
|
+
if (shouldKeep) {
|
|
208
|
+
for (const bufferedSpan of trace.spans) {
|
|
209
|
+
this.wrapped.onEnd(bufferedSpan);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
for (const bufferedSpan of trace.spans) {
|
|
214
|
+
this.wrapped.onEnd(bufferedSpan);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.traces.delete(traceId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return this.wrapped.forceFlush(traceId);
|
|
221
|
+
}
|
|
222
|
+
async shutdown() {
|
|
223
|
+
this.traces.clear();
|
|
224
|
+
return this.wrapped.shutdown();
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/core/config.ts
|
|
229
|
+
var CONFIG_KEY = createContextKey("autotel-edge-config");
|
|
230
|
+
function getActiveConfig() {
|
|
231
|
+
const value = context.active().getValue(CONFIG_KEY);
|
|
232
|
+
return value ?? null;
|
|
233
|
+
}
|
|
234
|
+
function setConfig(config) {
|
|
235
|
+
return context.active().setValue(CONFIG_KEY, config);
|
|
236
|
+
}
|
|
237
|
+
function parseConfig(config) {
|
|
238
|
+
const headSampler = config.sampling?.headSampler ?? new ParentBasedSampler({
|
|
239
|
+
root: new AlwaysOnSampler()
|
|
240
|
+
});
|
|
241
|
+
const parsedHeadSampler = typeof headSampler === "object" && "ratio" in headSampler ? createParentRatioSampler(headSampler) : headSampler;
|
|
242
|
+
const tailSampler = config.sampling?.tailSampler ?? ((traceInfo) => {
|
|
243
|
+
const localRootSpan = traceInfo.localRootSpan;
|
|
244
|
+
const ctx = localRootSpan.spanContext();
|
|
245
|
+
return (ctx.traceFlags & 1) === 1 || localRootSpan.status.code === 2;
|
|
246
|
+
});
|
|
247
|
+
const spanProcessors = isSpanProcessorConfig(config) ? Array.isArray(config.spanProcessors) ? config.spanProcessors : [config.spanProcessors] : [
|
|
248
|
+
// Use TailSamplingSpanProcessor to enable tail sampling
|
|
249
|
+
new TailSamplingSpanProcessor(
|
|
250
|
+
typeof config.exporter === "object" && "url" in config.exporter ? new OTLPExporter(config.exporter) : config.exporter,
|
|
251
|
+
config.postProcessor,
|
|
252
|
+
tailSampler
|
|
253
|
+
// Wire up the tail sampler!
|
|
254
|
+
)
|
|
255
|
+
];
|
|
256
|
+
const resolved = {
|
|
257
|
+
service: config.service,
|
|
258
|
+
handlers: {
|
|
259
|
+
fetch: config.handlers?.fetch ?? {}
|
|
260
|
+
},
|
|
261
|
+
fetch: {
|
|
262
|
+
includeTraceContext: config.fetch?.includeTraceContext ?? true
|
|
263
|
+
},
|
|
264
|
+
postProcessor: config.postProcessor ?? ((spans) => spans),
|
|
265
|
+
sampling: {
|
|
266
|
+
headSampler: parsedHeadSampler,
|
|
267
|
+
tailSampler
|
|
268
|
+
},
|
|
269
|
+
spanProcessors,
|
|
270
|
+
propagator: config.propagator ?? new W3CTraceContextPropagator(),
|
|
271
|
+
instrumentation: {
|
|
272
|
+
instrumentGlobalFetch: config.instrumentation?.instrumentGlobalFetch ?? true,
|
|
273
|
+
instrumentGlobalCache: config.instrumentation?.instrumentGlobalCache ?? false,
|
|
274
|
+
disabled: config.instrumentation?.disabled ?? false
|
|
275
|
+
},
|
|
276
|
+
subscribers: config.subscribers ?? []
|
|
277
|
+
};
|
|
278
|
+
return resolved;
|
|
279
|
+
}
|
|
280
|
+
function createParentRatioSampler(config) {
|
|
281
|
+
const { ratio, acceptRemote = true } = config;
|
|
282
|
+
const ratioSampler = {
|
|
283
|
+
shouldSample: () => ({
|
|
284
|
+
decision: Math.random() < ratio ? 1 : 0,
|
|
285
|
+
// RECORD_AND_SAMPLED : NOT_RECORD
|
|
286
|
+
attributes: {}
|
|
287
|
+
}),
|
|
288
|
+
toString: () => `ParentRatioSampler{ratio=${ratio}}`
|
|
289
|
+
};
|
|
290
|
+
if (acceptRemote) {
|
|
291
|
+
return new ParentBasedSampler({ root: ratioSampler });
|
|
292
|
+
}
|
|
293
|
+
return ratioSampler;
|
|
294
|
+
}
|
|
295
|
+
function createInitialiser(config) {
|
|
296
|
+
if (typeof config === "function") {
|
|
297
|
+
return (env, trigger) => {
|
|
298
|
+
const conf = parseConfig(config(env, trigger));
|
|
299
|
+
return conf;
|
|
300
|
+
};
|
|
301
|
+
} else {
|
|
302
|
+
const parsed = parseConfig(config);
|
|
303
|
+
return () => parsed;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export { OTLPExporter, createInitialiser, getActiveConfig, parseConfig, setConfig };
|
|
308
|
+
//# sourceMappingURL=chunk-F32WSLNX.js.map
|
|
309
|
+
//# sourceMappingURL=chunk-F32WSLNX.js.map
|