autotel-cloudflare 2.1.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 +432 -0
- package/dist/actors.d.ts +248 -0
- package/dist/actors.js +1030 -0
- package/dist/actors.js.map +1 -0
- package/dist/agents.d.ts +219 -0
- package/dist/agents.js +276 -0
- package/dist/agents.js.map +1 -0
- package/dist/bindings.d.ts +40 -0
- package/dist/bindings.js +4 -0
- package/dist/bindings.js.map +1 -0
- package/dist/chunk-JDPN3HND.js +520 -0
- package/dist/chunk-JDPN3HND.js.map +1 -0
- package/dist/chunk-QXFYTHQF.js +298 -0
- package/dist/chunk-QXFYTHQF.js.map +1 -0
- package/dist/chunk-SKKRPS5K.js +50 -0
- package/dist/chunk-SKKRPS5K.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.js +3 -0
- package/dist/events.js.map +1 -0
- package/dist/handlers.d.ts +121 -0
- package/dist/handlers.js +4 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +576 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +4 -0
- package/dist/sampling.js +3 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/package.json +107 -0
- package/src/actors/alarms.ts +225 -0
- package/src/actors/index.ts +36 -0
- package/src/actors/instrument-actor.test.ts +179 -0
- package/src/actors/instrument-actor.ts +574 -0
- package/src/actors/sockets.ts +217 -0
- package/src/actors/storage.ts +263 -0
- package/src/actors/traced-handler.ts +300 -0
- package/src/actors/types.ts +98 -0
- package/src/actors.ts +50 -0
- package/src/agents/index.ts +42 -0
- package/src/agents/otel-observability.test.ts +329 -0
- package/src/agents/otel-observability.ts +465 -0
- package/src/agents/types.ts +167 -0
- package/src/agents.ts +76 -0
- package/src/bindings/bindings.ts +621 -0
- package/src/bindings/common.ts +75 -0
- package/src/bindings/index.ts +12 -0
- package/src/bindings.ts +6 -0
- package/src/events.ts +6 -0
- package/src/global/cache.test.ts +292 -0
- package/src/global/cache.ts +164 -0
- package/src/global/fetch.test.ts +344 -0
- package/src/global/fetch.ts +134 -0
- package/src/global/index.ts +7 -0
- package/src/handlers/durable-objects.test.ts +524 -0
- package/src/handlers/durable-objects.ts +250 -0
- package/src/handlers/index.ts +6 -0
- package/src/handlers/workflows.ts +318 -0
- package/src/handlers.ts +6 -0
- package/src/index.ts +57 -0
- package/src/logger.ts +6 -0
- package/src/sampling.ts +6 -0
- package/src/testing.ts +6 -0
- package/src/wrappers/index.ts +8 -0
- package/src/wrappers/instrument.integration.test.ts +468 -0
- package/src/wrappers/instrument.ts +643 -0
- package/src/wrappers/wrap-do.ts +34 -0
- package/src/wrappers/wrap-module.ts +37 -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,432 @@
|
|
|
1
|
+
# autotel-cloudflare
|
|
2
|
+
|
|
3
|
+
**The #1 OpenTelemetry package for Cloudflare Workers** - complete bindings coverage, native CF OTel integration, advanced sampling, zero vendor lock-in.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/autotel-cloudflare)
|
|
6
|
+
[](https://bundlephobia.com/package/autotel-cloudflare)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- ✅ **Native Cloudflare OTel integration** - Works with `wrangler.toml` destinations
|
|
12
|
+
- ✅ **Complete bindings coverage** - KV, R2, D1, DO, AI, Vectorize, Hyperdrive, and more
|
|
13
|
+
- ✅ **Multiple API styles** - `instrument()`, `wrapModule()`, `wrapDurableObject()`, functional
|
|
14
|
+
- ✅ **Advanced sampling** - Adaptive tail sampling (10% baseline, 100% errors/slow)
|
|
15
|
+
- ✅ **Events integration** - Product analytics with trace correlation
|
|
16
|
+
- ✅ **Zero vendor lock-in** - OTLP compatible, works with any backend
|
|
17
|
+
- ✅ **Tree-shakeable** - Import only what you need
|
|
18
|
+
- ✅ **TypeScript native** - Full type safety
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install autotel-cloudflare
|
|
24
|
+
# or
|
|
25
|
+
pnpm add autotel-cloudflare
|
|
26
|
+
# or
|
|
27
|
+
yarn add autotel-cloudflare
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Configure Cloudflare Native OTel (wrangler.toml)
|
|
33
|
+
|
|
34
|
+
```toml
|
|
35
|
+
[observability.traces]
|
|
36
|
+
enabled = true
|
|
37
|
+
destinations = ["honeycomb"] # Configure in CF dashboard
|
|
38
|
+
head_sampling_rate = 1.0 # Let autotel handle sampling
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Instrument Your Worker
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { wrapModule, trace } from 'autotel-cloudflare'
|
|
45
|
+
|
|
46
|
+
// Zero-boilerplate function tracing
|
|
47
|
+
const processOrder = trace(async (orderId: string) => {
|
|
48
|
+
const order = await env.ORDERS_KV.get(orderId) // Auto-instrumented!
|
|
49
|
+
return order
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export default wrapModule(
|
|
53
|
+
{
|
|
54
|
+
service: { name: 'my-worker' },
|
|
55
|
+
instrumentBindings: true, // Auto-instrument KV, R2, D1, etc.
|
|
56
|
+
sampling: 'adaptive' // 10% baseline, 100% errors/slow
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
async fetch(req, env, ctx) {
|
|
60
|
+
return Response.json(await processOrder('123'))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Styles
|
|
67
|
+
|
|
68
|
+
### Style 1: wrapModule (Recommended)
|
|
69
|
+
|
|
70
|
+
Inspired by workers-honeycomb-logger:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { wrapModule } from 'autotel-cloudflare'
|
|
74
|
+
|
|
75
|
+
const handler = {
|
|
76
|
+
async fetch(req, env, ctx) {
|
|
77
|
+
return new Response('Hello')
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default wrapModule(
|
|
82
|
+
{ service: { name: 'my-worker' } },
|
|
83
|
+
handler
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Style 2: instrument
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { instrument } from 'autotel-cloudflare'
|
|
91
|
+
|
|
92
|
+
export default instrument(
|
|
93
|
+
{
|
|
94
|
+
async fetch(req, env, ctx) {
|
|
95
|
+
return new Response('Hello')
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{ service: { name: 'my-worker' } }
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Style 3: Functional API (Unique)
|
|
103
|
+
|
|
104
|
+
Zero-boilerplate function tracing:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { trace, span } from 'autotel-cloudflare'
|
|
108
|
+
|
|
109
|
+
// Automatic trace name inference
|
|
110
|
+
export const createUser = trace(async (data: UserData) => {
|
|
111
|
+
return await db.insert(data)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Factory pattern for context access
|
|
115
|
+
export const processPayment = trace(ctx => async (amount: number) => {
|
|
116
|
+
ctx.setAttribute('amount', amount)
|
|
117
|
+
|
|
118
|
+
await span('validate.card', () => validateCard())
|
|
119
|
+
await span('charge.card', () => chargeCard(amount))
|
|
120
|
+
|
|
121
|
+
return { success: true }
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Complete Bindings Coverage
|
|
126
|
+
|
|
127
|
+
### Auto-Instrumented Bindings
|
|
128
|
+
|
|
129
|
+
All bindings are automatically instrumented when `instrumentBindings: true`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// KV
|
|
133
|
+
await env.MY_KV.get('key') // → Span: "KV MY_KV: get"
|
|
134
|
+
await env.MY_KV.put('key', 'value') // → Span: "KV MY_KV: put"
|
|
135
|
+
|
|
136
|
+
// R2
|
|
137
|
+
await env.MY_R2.get('file.txt') // → Span: "R2 MY_R2: get"
|
|
138
|
+
await env.MY_R2.put('file.txt', data) // → Span: "R2 MY_R2: put"
|
|
139
|
+
|
|
140
|
+
// D1
|
|
141
|
+
await env.MY_D1.prepare('SELECT * FROM users').all() // → Span: "D1 MY_D1: all"
|
|
142
|
+
|
|
143
|
+
// Durable Objects
|
|
144
|
+
await env.MY_DO.get(id).fetch(req) // → Span: "DO MY_DO: fetch"
|
|
145
|
+
|
|
146
|
+
// Workers AI
|
|
147
|
+
await env.AI.run('@cf/meta/llama', { prompt: '...' }) // → Span: "AI: run"
|
|
148
|
+
|
|
149
|
+
// Vectorize
|
|
150
|
+
await env.VECTOR.query(vector) // → Span: "Vectorize VECTOR: query"
|
|
151
|
+
|
|
152
|
+
// Service Bindings
|
|
153
|
+
await env.MY_SERVICE.fetch(req) // → Span: "Service MY_SERVICE: fetch"
|
|
154
|
+
|
|
155
|
+
// Queue
|
|
156
|
+
await env.MY_QUEUE.send({ data }) // → Span: "Queue MY_QUEUE: send"
|
|
157
|
+
|
|
158
|
+
// Analytics Engine
|
|
159
|
+
await env.ANALYTICS.writeDataPoint({ ... }) // → Span: "Analytics: writeDataPoint"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Supported Bindings:**
|
|
163
|
+
|
|
164
|
+
- ✅ KV (get, put, delete, list, getWithMetadata)
|
|
165
|
+
- ✅ R2 (head, get, put, delete, list, createMultipartUpload)
|
|
166
|
+
- ✅ D1 (prepare, batch, exec, dump)
|
|
167
|
+
- ✅ Durable Objects (fetch, alarm)
|
|
168
|
+
- ✅ Workflows (get, create, getInstance)
|
|
169
|
+
- ✅ Workers AI (run)
|
|
170
|
+
- ✅ Vectorize (insert, query, getByIds, deleteByIds, upsert)
|
|
171
|
+
- ✅ Hyperdrive (all queries)
|
|
172
|
+
- ✅ Service Bindings (fetch)
|
|
173
|
+
- ✅ Queue (send, sendBatch)
|
|
174
|
+
- ✅ Analytics Engine (writeDataPoint)
|
|
175
|
+
- ✅ Email (send, forward)
|
|
176
|
+
|
|
177
|
+
## Sampling Strategies
|
|
178
|
+
|
|
179
|
+
### Adaptive Sampling (Recommended)
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { SamplingPresets } from 'autotel-cloudflare/sampling'
|
|
183
|
+
|
|
184
|
+
wrapModule(
|
|
185
|
+
{
|
|
186
|
+
service: { name: 'my-worker' },
|
|
187
|
+
sampling: {
|
|
188
|
+
tailSampler: SamplingPresets.production()
|
|
189
|
+
// 10% baseline, 100% errors, 100% slow requests (>1s)
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
handler
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Available Presets
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// Development - 100% sampling
|
|
200
|
+
sampling: { tailSampler: SamplingPresets.development() }
|
|
201
|
+
|
|
202
|
+
// Production - 10% baseline, all errors, slow >1s
|
|
203
|
+
sampling: { tailSampler: SamplingPresets.production() }
|
|
204
|
+
|
|
205
|
+
// High traffic - 1% baseline, all errors, slow >1s
|
|
206
|
+
sampling: { tailSampler: SamplingPresets.highTraffic() }
|
|
207
|
+
|
|
208
|
+
// Debugging - errors only
|
|
209
|
+
sampling: { tailSampler: SamplingPresets.debugging() }
|
|
210
|
+
|
|
211
|
+
// Or use shorthand
|
|
212
|
+
sampling: 'adaptive' // Same as SamplingPresets.production()
|
|
213
|
+
sampling: 'error-only' // Same as SamplingPresets.debugging()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Custom Sampling
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { createCustomTailSampler } from 'autotel-cloudflare/sampling'
|
|
220
|
+
|
|
221
|
+
const customSampler = createCustomTailSampler((trace) => {
|
|
222
|
+
const span = trace.localRootSpan
|
|
223
|
+
|
|
224
|
+
// Always sample /api/* endpoints
|
|
225
|
+
if (span.attributes['http.route']?.toString().startsWith('/api/')) {
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Sample all errors
|
|
230
|
+
if (span.status.code === SpanStatusCode.ERROR) {
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Sample slow requests
|
|
235
|
+
const duration = (span.endTime[0] - span.startTime[0]) / 1_000_000
|
|
236
|
+
if (duration > 1000) {
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return Math.random() < 0.1 // 10% of everything else
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Durable Objects
|
|
245
|
+
|
|
246
|
+
### Instrument Durable Object Class
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { wrapDurableObject } from 'autotel-cloudflare'
|
|
250
|
+
|
|
251
|
+
class Counter implements DurableObject {
|
|
252
|
+
async fetch(request: Request) {
|
|
253
|
+
// Auto-traced with span "Counter: fetch"
|
|
254
|
+
const count = await this.state.storage.get('count') || 0
|
|
255
|
+
await this.state.storage.put('count', count + 1)
|
|
256
|
+
return new Response(String(count + 1))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async alarm() {
|
|
260
|
+
// Auto-traced with span "Counter: alarm"
|
|
261
|
+
console.log('Alarm triggered')
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default wrapDurableObject(
|
|
266
|
+
{ service: { name: 'counter-do' } },
|
|
267
|
+
Counter
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Events Integration
|
|
272
|
+
|
|
273
|
+
Track product events with automatic trace correlation:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { publishEvent } from 'autotel-cloudflare/events'
|
|
277
|
+
|
|
278
|
+
wrapModule(
|
|
279
|
+
{
|
|
280
|
+
service: { name: 'my-worker' },
|
|
281
|
+
// Configure event subscribers
|
|
282
|
+
subscribers: [
|
|
283
|
+
async (event) => {
|
|
284
|
+
// Send to your analytics platform
|
|
285
|
+
await fetch('https://analytics.example.com/events', {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
body: JSON.stringify(event)
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
async fetch(req, env, ctx) {
|
|
294
|
+
// Track user events
|
|
295
|
+
await publishEvent({
|
|
296
|
+
name: 'order.completed',
|
|
297
|
+
userId: '123',
|
|
298
|
+
properties: {
|
|
299
|
+
orderId: 'abc',
|
|
300
|
+
amount: 99.99
|
|
301
|
+
}
|
|
302
|
+
// Automatically includes current trace ID
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
return new Response('OK')
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Configuration
|
|
312
|
+
|
|
313
|
+
### Complete Example
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { wrapModule, SamplingPresets } from 'autotel-cloudflare'
|
|
317
|
+
|
|
318
|
+
export default wrapModule(
|
|
319
|
+
{
|
|
320
|
+
// Service identification
|
|
321
|
+
service: {
|
|
322
|
+
name: 'my-worker',
|
|
323
|
+
version: '1.0.0',
|
|
324
|
+
namespace: 'production'
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// Auto-instrument bindings
|
|
328
|
+
instrumentBindings: true,
|
|
329
|
+
|
|
330
|
+
// Global instrumentations
|
|
331
|
+
instrumentation: {
|
|
332
|
+
instrumentGlobalFetch: true, // Trace all fetch() calls
|
|
333
|
+
instrumentGlobalCache: true, // Trace cache API
|
|
334
|
+
disabled: false // Set true to disable all tracing
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// Sampling strategy
|
|
338
|
+
sampling: {
|
|
339
|
+
tailSampler: SamplingPresets.production()
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// Handler-specific config
|
|
343
|
+
handlers: {
|
|
344
|
+
fetch: {
|
|
345
|
+
postProcess: (span, { request, response }) => {
|
|
346
|
+
// Add custom attributes
|
|
347
|
+
const url = new URL(request.url)
|
|
348
|
+
if (url.pathname.startsWith('/api/')) {
|
|
349
|
+
span.setAttribute('api.endpoint', url.pathname)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
handler
|
|
356
|
+
)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Dynamic Configuration
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Configuration can be a function
|
|
363
|
+
export default wrapModule(
|
|
364
|
+
(env, trigger) => ({
|
|
365
|
+
service: { name: env.SERVICE_NAME || 'my-worker' },
|
|
366
|
+
exporter: {
|
|
367
|
+
url: env.OTEL_ENDPOINT,
|
|
368
|
+
headers: { 'x-api-key': env.API_KEY }
|
|
369
|
+
},
|
|
370
|
+
sampling: {
|
|
371
|
+
tailSampler: env.ENVIRONMENT === 'production'
|
|
372
|
+
? SamplingPresets.production()
|
|
373
|
+
: SamplingPresets.development()
|
|
374
|
+
}
|
|
375
|
+
}),
|
|
376
|
+
handler
|
|
377
|
+
)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Entry Points (Tree-Shaking)
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// Main export (everything)
|
|
384
|
+
import { wrapModule, trace, instrument } from 'autotel-cloudflare'
|
|
385
|
+
|
|
386
|
+
// Tree-shakeable entry points
|
|
387
|
+
import { instrumentKV, instrumentR2 } from 'autotel-cloudflare/bindings'
|
|
388
|
+
import { instrumentDO } from 'autotel-cloudflare/handlers'
|
|
389
|
+
import { SamplingPresets } from 'autotel-cloudflare/sampling'
|
|
390
|
+
import { publishEvent } from 'autotel-cloudflare/events'
|
|
391
|
+
import { createEdgeLogger } from 'autotel-cloudflare/logger'
|
|
392
|
+
import { createTraceCollector } from 'autotel-cloudflare/testing'
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Testing
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { createTraceCollector, assertTraceCreated } from 'autotel-cloudflare/testing'
|
|
399
|
+
|
|
400
|
+
describe('my worker', () => {
|
|
401
|
+
it('creates traces', async () => {
|
|
402
|
+
const collector = createTraceCollector()
|
|
403
|
+
|
|
404
|
+
await myFunction()
|
|
405
|
+
|
|
406
|
+
assertTraceCreated(collector, 'myFunction')
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Examples
|
|
412
|
+
|
|
413
|
+
See [apps/cloudflare-example](../../apps/cloudflare-example) for a complete working example with:
|
|
414
|
+
|
|
415
|
+
- ✅ All bindings instrumented (KV, R2, D1, etc.)
|
|
416
|
+
- ✅ Multiple handler types (fetch, scheduled, queue, email)
|
|
417
|
+
- ✅ Durable Objects
|
|
418
|
+
- ✅ Custom spans and attributes
|
|
419
|
+
- ✅ Error handling
|
|
420
|
+
- ✅ Sampling strategies
|
|
421
|
+
- ✅ Events tracking
|
|
422
|
+
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT © [Jag Reehal](https://github.com/jagreehal)
|
|
426
|
+
|
|
427
|
+
## Links
|
|
428
|
+
|
|
429
|
+
- [GitHub Repository](https://github.com/jagreehal/autotel)
|
|
430
|
+
- [Documentation](https://github.com/jagreehal/autotel#readme)
|
|
431
|
+
- [Issues](https://github.com/jagreehal/autotel/issues)
|
|
432
|
+
- [autotel-edge](../autotel-edge) - Vendor-agnostic foundation
|
package/dist/actors.d.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { ConfigurationOption } from 'autotel-edge';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for @cloudflare/actors integration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Actor-specific instrumentation options
|
|
9
|
+
*/
|
|
10
|
+
interface ActorInstrumentationOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Whether to instrument storage operations (sql queries, etc.)
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
instrumentStorage?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to instrument alarm operations
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
instrumentAlarms?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to instrument socket operations
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
instrumentSockets?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to capture persist events as spans
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
capturePersistEvents?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Custom span name formatter for lifecycle methods
|
|
33
|
+
*/
|
|
34
|
+
spanNameFormatter?: (actorName: string, lifecycle: string) => string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Actor-specific configuration
|
|
38
|
+
* Can be a static config object with actors options, or a function that returns config
|
|
39
|
+
*/
|
|
40
|
+
type ActorConfig = ConfigurationOption & {
|
|
41
|
+
/**
|
|
42
|
+
* Actor-specific instrumentation options
|
|
43
|
+
*/
|
|
44
|
+
actors?: ActorInstrumentationOptions;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Actor lifecycle events that can be traced
|
|
48
|
+
*/
|
|
49
|
+
type ActorLifecycle = 'init' | 'request' | 'alarm' | 'persist' | 'websocket.connect' | 'websocket.message' | 'websocket.disconnect' | 'websocket.upgrade' | 'destroy';
|
|
50
|
+
/**
|
|
51
|
+
* Minimal interface matching @cloudflare/actors Actor class
|
|
52
|
+
* We don't import the actual type to avoid coupling
|
|
53
|
+
*/
|
|
54
|
+
interface ActorLike {
|
|
55
|
+
name?: string;
|
|
56
|
+
identifier?: string;
|
|
57
|
+
storage?: unknown;
|
|
58
|
+
alarms?: unknown;
|
|
59
|
+
sockets?: unknown;
|
|
60
|
+
fetch?(request: Request): Promise<Response>;
|
|
61
|
+
alarm?(alarmInfo?: unknown): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Constructor type for Actor classes
|
|
65
|
+
*/
|
|
66
|
+
type ActorConstructor<T extends ActorLike = ActorLike> = new (state: DurableObjectState, env: unknown) => T;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Actor class instrumentation for @cloudflare/actors
|
|
70
|
+
*
|
|
71
|
+
* Wraps Actor lifecycle methods with OpenTelemetry tracing:
|
|
72
|
+
* - onInit: Traced as 'actor.lifecycle': 'init'
|
|
73
|
+
* - onRequest: Traced with full HTTP semantics
|
|
74
|
+
* - onAlarm: Traced as 'actor.lifecycle': 'alarm'
|
|
75
|
+
* - onPersist: Traced as 'actor.lifecycle': 'persist'
|
|
76
|
+
* - WebSocket methods: Traced with socket semantics
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Instrument an Actor class for comprehensive OpenTelemetry tracing
|
|
81
|
+
*
|
|
82
|
+
* This wraps the Actor class to automatically trace all lifecycle methods:
|
|
83
|
+
* - onInit: Actor initialization
|
|
84
|
+
* - onRequest: HTTP request handling
|
|
85
|
+
* - onAlarm: Alarm triggers
|
|
86
|
+
* - onPersist: Property persistence events
|
|
87
|
+
* - WebSocket methods: Connection, message, disconnect
|
|
88
|
+
*
|
|
89
|
+
* It also optionally instruments:
|
|
90
|
+
* - actor.storage: SQL queries and storage operations
|
|
91
|
+
* - actor.alarms: Alarm scheduling operations
|
|
92
|
+
* - actor.sockets: WebSocket operations
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import { Actor } from '@cloudflare/actors'
|
|
97
|
+
* import { instrumentActor } from 'autotel-cloudflare/actors'
|
|
98
|
+
*
|
|
99
|
+
* class Counter extends Actor<Env> {
|
|
100
|
+
* protected onInit() {
|
|
101
|
+
* console.log('Counter initialized')
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* protected onRequest(request: Request) {
|
|
105
|
+
* return new Response('count: 42')
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* // Wrap the class
|
|
110
|
+
* export const InstrumentedCounter = instrumentActor(Counter, (env: Env) => ({
|
|
111
|
+
* service: { name: 'counter-actor' },
|
|
112
|
+
* exporter: { url: env.OTLP_ENDPOINT },
|
|
113
|
+
* actors: {
|
|
114
|
+
* instrumentStorage: true,
|
|
115
|
+
* capturePersistEvents: true
|
|
116
|
+
* }
|
|
117
|
+
* }))
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @param actorClass - The Actor class to instrument
|
|
121
|
+
* @param config - Configuration (static object or function)
|
|
122
|
+
* @returns Instrumented Actor class
|
|
123
|
+
*/
|
|
124
|
+
declare function instrumentActor<C extends ActorConstructor>(actorClass: C, config: ActorConfig | ((env: unknown, trigger?: unknown) => ActorConfig)): C;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Traced handler wrapper for @cloudflare/actors
|
|
128
|
+
*
|
|
129
|
+
* Wraps the Actors handler() to provide:
|
|
130
|
+
* - Root span for the entire request lifecycle
|
|
131
|
+
* - Actor name extraction and correlation
|
|
132
|
+
* - Request routing tracing
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Worker handler type matching @cloudflare/actors output
|
|
137
|
+
*/
|
|
138
|
+
interface WorkerHandler<E = unknown> {
|
|
139
|
+
fetch(request: Request, env: E, ctx: ExecutionContext): Promise<Response>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a traced handler that combines Actor instrumentation with request tracing
|
|
143
|
+
*
|
|
144
|
+
* This is an all-in-one wrapper that:
|
|
145
|
+
* 1. Initializes telemetry for the Worker
|
|
146
|
+
* 2. Creates a root span for each incoming request
|
|
147
|
+
* 3. Extracts the Actor name using `nameFromRequest`
|
|
148
|
+
* 4. Instruments the Actor class with lifecycle tracing
|
|
149
|
+
* 5. Routes the request to the instrumented Actor
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* import { Actor } from '@cloudflare/actors'
|
|
154
|
+
* import { tracedHandler } from 'autotel-cloudflare/actors'
|
|
155
|
+
*
|
|
156
|
+
* class MyActor extends Actor<Env> {
|
|
157
|
+
* protected onRequest(request: Request) {
|
|
158
|
+
* return new Response('Hello!')
|
|
159
|
+
* }
|
|
160
|
+
* }
|
|
161
|
+
*
|
|
162
|
+
* // Export the Actor class and use tracedHandler
|
|
163
|
+
* export { MyActor }
|
|
164
|
+
* export default tracedHandler(MyActor, (env) => ({
|
|
165
|
+
* service: { name: 'my-actor-service' },
|
|
166
|
+
* exporter: { url: env.OTLP_ENDPOINT }
|
|
167
|
+
* }))
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @param actorClass - The Actor class to handle requests
|
|
171
|
+
* @param config - Configuration (static object or function)
|
|
172
|
+
* @returns A Worker handler with full tracing
|
|
173
|
+
*/
|
|
174
|
+
declare function tracedHandler<E, A extends ActorLike>(actorClass: ActorConstructor<A> & {
|
|
175
|
+
nameFromRequest?(request: Request): Promise<string | undefined>;
|
|
176
|
+
configuration?(request: Request): {
|
|
177
|
+
locationHint?: DurableObjectLocationHint;
|
|
178
|
+
};
|
|
179
|
+
}, config: ActorConfig | ((env: E, trigger?: unknown) => ActorConfig)): WorkerHandler<E>;
|
|
180
|
+
/**
|
|
181
|
+
* Alternative: Create a handler wrapper that uses the existing @cloudflare/actors handler
|
|
182
|
+
*
|
|
183
|
+
* This is useful if you want to use the original handler() but add tracing around it.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* import { Actor, handler } from '@cloudflare/actors'
|
|
188
|
+
* import { wrapHandler } from 'autotel-cloudflare/actors'
|
|
189
|
+
*
|
|
190
|
+
* class MyActor extends Actor<Env> {}
|
|
191
|
+
*
|
|
192
|
+
* export { MyActor }
|
|
193
|
+
* export default wrapHandler(handler(MyActor), (env) => ({
|
|
194
|
+
* service: { name: 'my-service' }
|
|
195
|
+
* }))
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
declare function wrapHandler<E>(originalHandler: WorkerHandler<E>, config: ActorConfig | ((env: E, trigger?: unknown) => ActorConfig)): WorkerHandler<E>;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Actor storage instrumentation
|
|
202
|
+
*
|
|
203
|
+
* Traces operations on actor.storage including SQL queries
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Instrument Actor storage for tracing
|
|
208
|
+
*
|
|
209
|
+
* Captures:
|
|
210
|
+
* - SQL query operations
|
|
211
|
+
* - Key-value operations (if available)
|
|
212
|
+
*/
|
|
213
|
+
declare function instrumentActorStorage(storage: unknown, actorInstance: ActorLike, actorClass: object): unknown;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Actor alarms instrumentation
|
|
217
|
+
*
|
|
218
|
+
* Traces operations on actor.alarms
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Instrument Actor alarms for tracing
|
|
223
|
+
*
|
|
224
|
+
* Captures:
|
|
225
|
+
* - set: Schedule a single alarm
|
|
226
|
+
* - setMultiple: Schedule multiple alarms
|
|
227
|
+
* - cancel: Cancel an alarm
|
|
228
|
+
* - cancelAll: Cancel all alarms
|
|
229
|
+
*/
|
|
230
|
+
declare function instrumentActorAlarms(alarms: unknown, actorInstance: ActorLike, actorClass: object): unknown;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Actor sockets instrumentation
|
|
234
|
+
*
|
|
235
|
+
* Traces operations on actor.sockets
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Instrument Actor sockets for tracing
|
|
240
|
+
*
|
|
241
|
+
* Captures:
|
|
242
|
+
* - acceptWebSocket: Accept an incoming WebSocket connection
|
|
243
|
+
* - broadcast: Send message to all connected sockets
|
|
244
|
+
* - send: Send message to a specific socket
|
|
245
|
+
*/
|
|
246
|
+
declare function instrumentActorSockets(sockets: unknown, actorInstance: ActorLike, actorClass: object): unknown;
|
|
247
|
+
|
|
248
|
+
export { type ActorConfig, type ActorInstrumentationOptions, type ActorLifecycle, instrumentActor, instrumentActorAlarms, instrumentActorSockets, instrumentActorStorage, tracedHandler, wrapHandler };
|