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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ConfigurationOption } from 'autotel-edge';
|
|
2
|
+
export * from 'autotel-edge';
|
|
3
|
+
export { instrumentDO, instrumentWorkflow } from './handlers.js';
|
|
4
|
+
export { instrumentBindings, instrumentD1, instrumentKV, instrumentR2, instrumentServiceBinding } from './bindings.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handler instrumentation for Cloudflare Workers
|
|
8
|
+
*
|
|
9
|
+
* Note: This file uses Cloudflare Workers types (ExportedHandler, Request, Response, etc.)
|
|
10
|
+
* which are globally available via @cloudflare/workers-types when listed in tsconfig.json.
|
|
11
|
+
* These types are devDependencies only - they're not runtime dependencies.
|
|
12
|
+
* At runtime, Cloudflare Workers runtime provides the actual implementations.
|
|
13
|
+
*
|
|
14
|
+
* Provides automatic OpenTelemetry tracing for:
|
|
15
|
+
* - HTTP handlers (fetch)
|
|
16
|
+
* - Scheduled/cron handlers
|
|
17
|
+
* - Queue handlers (with message tracking)
|
|
18
|
+
* - Email handlers
|
|
19
|
+
* - Auto-instrumentation of Cloudflare bindings (KV, R2, D1, Service Bindings)
|
|
20
|
+
* - Global fetch and cache instrumentation
|
|
21
|
+
* - Post-processor support for span customization
|
|
22
|
+
* - Tail sampling support
|
|
23
|
+
* - Cold start tracking
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Instrument a Cloudflare Workers handler
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { instrument } from 'autotel-edge'
|
|
32
|
+
*
|
|
33
|
+
* const handler = {
|
|
34
|
+
* async fetch(request, env, ctx) {
|
|
35
|
+
* return new Response('Hello World')
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* export default instrument(handler, {
|
|
40
|
+
* exporter: {
|
|
41
|
+
* url: env.OTLP_ENDPOINT,
|
|
42
|
+
* headers: { 'x-api-key': env.API_KEY }
|
|
43
|
+
* },
|
|
44
|
+
* service: { name: 'my-worker' }
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
declare function instrument<E, Q = any, C = any>(handler: ExportedHandler<E, Q, C>, config: ConfigurationOption): ExportedHandler<E, Q, C>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* workers-honeycomb-logger style wrapper API
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { wrapModule } from 'autotel-cloudflare'
|
|
56
|
+
*
|
|
57
|
+
* const handler = {
|
|
58
|
+
* async fetch(req, env, ctx) {
|
|
59
|
+
* return new Response('Hello')
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* export default wrapModule(
|
|
64
|
+
* { service: { name: 'my-worker' } },
|
|
65
|
+
* handler
|
|
66
|
+
* )
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wrap a Cloudflare Workers module-style handler
|
|
72
|
+
* Alternative API style inspired by workers-honeycomb-logger
|
|
73
|
+
*
|
|
74
|
+
* @param config Configuration (can be static object or function)
|
|
75
|
+
* @param handler The worker handler to wrap
|
|
76
|
+
* @returns Instrumented handler
|
|
77
|
+
*/
|
|
78
|
+
declare function wrapModule<E, Q = any, C = any>(config: ConfigurationOption, handler: ExportedHandler<E, Q, C>): ExportedHandler<E, Q, C>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Durable Object wrapper
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* import { wrapDurableObject } from 'autotel-cloudflare'
|
|
86
|
+
*
|
|
87
|
+
* class Counter implements DurableObject {
|
|
88
|
+
* async fetch(request: Request) {
|
|
89
|
+
* return new Response('count')
|
|
90
|
+
* }
|
|
91
|
+
* }
|
|
92
|
+
*
|
|
93
|
+
* export default wrapDurableObject({ service: { name: 'counter-do' } }, Counter)
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Wrap a Durable Object class with instrumentation
|
|
99
|
+
* Alternative API style inspired by workers-honeycomb-logger
|
|
100
|
+
*
|
|
101
|
+
* @param config Configuration (can be static object or function)
|
|
102
|
+
* @param doClass The Durable Object class to wrap
|
|
103
|
+
* @returns Instrumented Durable Object class
|
|
104
|
+
*/
|
|
105
|
+
declare function wrapDurableObject<T extends DurableObject>(config: ConfigurationOption, doClass: new (state: DurableObjectState, env: any) => T): new (state: DurableObjectState, env: any) => T;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Global fetch() instrumentation for autotel-edge
|
|
109
|
+
*
|
|
110
|
+
* Automatically traces all outgoing fetch() calls with:
|
|
111
|
+
* - HTTP method, URL, status code
|
|
112
|
+
* - Request/response headers
|
|
113
|
+
* - Automatic context propagation
|
|
114
|
+
* - Error tracking
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* Instrument the global fetch function
|
|
118
|
+
*
|
|
119
|
+
* This wraps globalThis.fetch to automatically create spans for all outgoing HTTP requests.
|
|
120
|
+
*
|
|
121
|
+
* **Note:** This is called automatically when the library is initialized with
|
|
122
|
+
* `instrumentation.instrumentGlobalFetch: true` (default).
|
|
123
|
+
*/
|
|
124
|
+
declare function instrumentGlobalFetch(): void;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Global Cache API instrumentation for Cloudflare Workers
|
|
128
|
+
*
|
|
129
|
+
* Automatically traces cache operations:
|
|
130
|
+
* - cache.match() - Read from cache
|
|
131
|
+
* - cache.put() - Write to cache
|
|
132
|
+
* - cache.delete() - Delete from cache
|
|
133
|
+
*/
|
|
134
|
+
/**
|
|
135
|
+
* Instrument the global caches API
|
|
136
|
+
*
|
|
137
|
+
* This wraps globalThis.caches to automatically create spans for all cache operations.
|
|
138
|
+
*
|
|
139
|
+
* **Note:** This is called automatically when the library is initialized with
|
|
140
|
+
* `instrumentation.instrumentGlobalCache: true` (default).
|
|
141
|
+
*/
|
|
142
|
+
declare function instrumentGlobalCache(): void;
|
|
143
|
+
|
|
144
|
+
export { instrument, instrumentGlobalCache, instrumentGlobalFetch, wrapDurableObject, wrapModule };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { instrumentBindings } from './chunk-JDPN3HND.js';
|
|
2
|
+
export { instrumentBindings, instrumentD1, instrumentKV, instrumentR2, instrumentServiceBinding } from './chunk-JDPN3HND.js';
|
|
3
|
+
import { instrumentDO } from './chunk-QXFYTHQF.js';
|
|
4
|
+
export { instrumentDO, instrumentWorkflow } from './chunk-QXFYTHQF.js';
|
|
5
|
+
import { wrap, unwrap, proxyExecutionContext } from './chunk-SKKRPS5K.js';
|
|
6
|
+
import { createInitialiser, getActiveConfig, setConfig, WorkerTracerProvider, WorkerTracer } from 'autotel-edge';
|
|
7
|
+
export * from 'autotel-edge';
|
|
8
|
+
import { trace, SpanKind, propagation, context, SpanStatusCode } from '@opentelemetry/api';
|
|
9
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
10
|
+
|
|
11
|
+
function gatherRequestAttributes(request) {
|
|
12
|
+
const url = new URL(request.url);
|
|
13
|
+
return {
|
|
14
|
+
"http.request.method": request.method.toUpperCase(),
|
|
15
|
+
"url.full": request.url,
|
|
16
|
+
"url.scheme": url.protocol.replace(":", ""),
|
|
17
|
+
"server.address": url.host,
|
|
18
|
+
"url.path": url.pathname,
|
|
19
|
+
"url.query": url.search,
|
|
20
|
+
"network.protocol.name": "http",
|
|
21
|
+
"user_agent.original": request.headers.get("user-agent") || void 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function gatherResponseAttributes(response) {
|
|
25
|
+
return {
|
|
26
|
+
"http.response.status_code": response.status,
|
|
27
|
+
"http.response.body.size": response.headers.get("content-length") || void 0
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function instrumentGlobalFetch() {
|
|
31
|
+
const originalFetch = globalThis.fetch;
|
|
32
|
+
const instrumentedFetch = function fetch(input, init) {
|
|
33
|
+
const request = new Request(input, init);
|
|
34
|
+
if (!request.url.startsWith("http")) {
|
|
35
|
+
return originalFetch(input, init);
|
|
36
|
+
}
|
|
37
|
+
const config = getActiveConfig();
|
|
38
|
+
if (!config) {
|
|
39
|
+
return originalFetch(input, init);
|
|
40
|
+
}
|
|
41
|
+
const tracer = trace.getTracer("autotel-edge");
|
|
42
|
+
const url = new URL(request.url);
|
|
43
|
+
const spanName = `${request.method} ${url.host}`;
|
|
44
|
+
return tracer.startActiveSpan(
|
|
45
|
+
spanName,
|
|
46
|
+
{
|
|
47
|
+
kind: SpanKind.CLIENT,
|
|
48
|
+
attributes: gatherRequestAttributes(request)
|
|
49
|
+
},
|
|
50
|
+
async (span) => {
|
|
51
|
+
try {
|
|
52
|
+
const shouldIncludeContext = typeof config.fetch?.includeTraceContext === "function" ? config.fetch.includeTraceContext(request) : config.fetch?.includeTraceContext ?? true;
|
|
53
|
+
if (shouldIncludeContext) {
|
|
54
|
+
propagation.inject(context.active(), request.headers, {
|
|
55
|
+
set: (headers, key, value) => {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
headers.set(key, value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const response = await originalFetch(request);
|
|
63
|
+
span.setAttributes(gatherResponseAttributes(response));
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
66
|
+
} else {
|
|
67
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
68
|
+
}
|
|
69
|
+
return response;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
span.recordException(error);
|
|
72
|
+
span.setStatus({
|
|
73
|
+
code: SpanStatusCode.ERROR,
|
|
74
|
+
message: error instanceof Error ? error.message : String(error)
|
|
75
|
+
});
|
|
76
|
+
throw error;
|
|
77
|
+
} finally {
|
|
78
|
+
span.end();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
globalThis.fetch = instrumentedFetch;
|
|
84
|
+
}
|
|
85
|
+
function sanitizeURL(url) {
|
|
86
|
+
const u = new URL(url);
|
|
87
|
+
return `${u.protocol}//${u.host}${u.pathname}`;
|
|
88
|
+
}
|
|
89
|
+
function instrumentCacheMethod(fn, cacheName, operation) {
|
|
90
|
+
const handler = {
|
|
91
|
+
async apply(target, thisArg, argArray) {
|
|
92
|
+
const tracer = trace.getTracer("autotel-edge");
|
|
93
|
+
const firstArg = argArray[0];
|
|
94
|
+
const url = firstArg instanceof Request ? firstArg.url : typeof firstArg === "string" ? firstArg : void 0;
|
|
95
|
+
const spanName = `Cache ${cacheName}.${operation}`;
|
|
96
|
+
return tracer.startActiveSpan(
|
|
97
|
+
spanName,
|
|
98
|
+
{
|
|
99
|
+
kind: SpanKind.CLIENT,
|
|
100
|
+
attributes: {
|
|
101
|
+
"cache.name": cacheName,
|
|
102
|
+
"cache.operation": operation,
|
|
103
|
+
"cache.key": url ? sanitizeURL(url) : void 0
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
async (span) => {
|
|
107
|
+
try {
|
|
108
|
+
const result = await Reflect.apply(target, thisArg, argArray);
|
|
109
|
+
if (operation === "match") {
|
|
110
|
+
span.setAttribute("cache.hit", !!result);
|
|
111
|
+
}
|
|
112
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
span.recordException(error);
|
|
116
|
+
span.setStatus({
|
|
117
|
+
code: SpanStatusCode.ERROR,
|
|
118
|
+
message: error instanceof Error ? error.message : String(error)
|
|
119
|
+
});
|
|
120
|
+
throw error;
|
|
121
|
+
} finally {
|
|
122
|
+
span.end();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return wrap(fn, handler);
|
|
129
|
+
}
|
|
130
|
+
function instrumentCache(cache, cacheName) {
|
|
131
|
+
const handler = {
|
|
132
|
+
get(target, prop) {
|
|
133
|
+
const value = Reflect.get(target, prop);
|
|
134
|
+
if ((prop === "match" || prop === "put" || prop === "delete") && typeof value === "function") {
|
|
135
|
+
return instrumentCacheMethod(
|
|
136
|
+
value.bind(target),
|
|
137
|
+
cacheName,
|
|
138
|
+
prop
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (typeof value === "function") {
|
|
142
|
+
return value.bind(target);
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return wrap(cache, handler);
|
|
148
|
+
}
|
|
149
|
+
function instrumentCachesOpen(openFn) {
|
|
150
|
+
const handler = {
|
|
151
|
+
async apply(target, thisArg, argArray) {
|
|
152
|
+
const cacheName = argArray[0];
|
|
153
|
+
const cache = await Reflect.apply(target, thisArg, argArray);
|
|
154
|
+
return instrumentCache(cache, cacheName);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return wrap(openFn, handler);
|
|
158
|
+
}
|
|
159
|
+
function instrumentGlobalCache() {
|
|
160
|
+
const handler = {
|
|
161
|
+
get(target, prop) {
|
|
162
|
+
if (prop === "default") {
|
|
163
|
+
return instrumentCache(target.default, "default");
|
|
164
|
+
} else if (prop === "open") {
|
|
165
|
+
const openFn = Reflect.get(target, prop);
|
|
166
|
+
if (typeof openFn === "function") {
|
|
167
|
+
return instrumentCachesOpen(openFn.bind(target));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return Reflect.get(target, prop);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
globalThis.caches = wrap(caches, handler);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/wrappers/instrument.ts
|
|
177
|
+
function createFetchInstrumentation(config) {
|
|
178
|
+
return {
|
|
179
|
+
getInitialSpanInfo: (request) => {
|
|
180
|
+
const url = new URL(request.url);
|
|
181
|
+
return {
|
|
182
|
+
name: `${request.method} ${url.pathname}`,
|
|
183
|
+
options: {
|
|
184
|
+
kind: SpanKind.SERVER,
|
|
185
|
+
attributes: {
|
|
186
|
+
"http.request.method": request.method,
|
|
187
|
+
"url.full": request.url
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
context: propagation.extract(context.active(), request.headers)
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
getAttributesFromResult: (response) => ({
|
|
194
|
+
"http.response.status_code": response.status
|
|
195
|
+
}),
|
|
196
|
+
executionSucces: (span, trigger, result) => {
|
|
197
|
+
if (config.handlers.fetch.postProcess) {
|
|
198
|
+
const readableSpan = span;
|
|
199
|
+
config.handlers.fetch.postProcess(span, {
|
|
200
|
+
request: trigger,
|
|
201
|
+
response: result,
|
|
202
|
+
readable: readableSpan
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
var scheduledInstrumentation = {
|
|
209
|
+
getInitialSpanInfo: (event) => {
|
|
210
|
+
return {
|
|
211
|
+
name: `scheduledHandler ${event.cron || "unknown"}`,
|
|
212
|
+
options: {
|
|
213
|
+
kind: SpanKind.INTERNAL,
|
|
214
|
+
attributes: {
|
|
215
|
+
"faas.trigger": "timer",
|
|
216
|
+
"faas.cron": event.cron || "unknown",
|
|
217
|
+
"faas.scheduled_time": new Date(event.scheduledTime).toISOString()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var MessageStatusCount = class {
|
|
224
|
+
succeeded = 0;
|
|
225
|
+
failed = 0;
|
|
226
|
+
implicitly_acked = 0;
|
|
227
|
+
implicitly_retried = 0;
|
|
228
|
+
total;
|
|
229
|
+
constructor(total) {
|
|
230
|
+
this.total = total;
|
|
231
|
+
}
|
|
232
|
+
ack() {
|
|
233
|
+
this.succeeded = this.succeeded + 1;
|
|
234
|
+
}
|
|
235
|
+
ackRemaining() {
|
|
236
|
+
this.implicitly_acked = this.total - this.succeeded - this.failed;
|
|
237
|
+
this.succeeded = this.total - this.failed;
|
|
238
|
+
}
|
|
239
|
+
retry() {
|
|
240
|
+
this.failed = this.failed + 1;
|
|
241
|
+
}
|
|
242
|
+
retryRemaining() {
|
|
243
|
+
this.implicitly_retried = this.total - this.succeeded - this.failed;
|
|
244
|
+
this.failed = this.total - this.succeeded;
|
|
245
|
+
}
|
|
246
|
+
toAttributes() {
|
|
247
|
+
return {
|
|
248
|
+
"queue.messages_count": this.total,
|
|
249
|
+
"queue.messages_success": this.succeeded,
|
|
250
|
+
"queue.messages_failed": this.failed,
|
|
251
|
+
"queue.batch_success": this.succeeded === this.total,
|
|
252
|
+
"queue.implicitly_acked": this.implicitly_acked,
|
|
253
|
+
"queue.implicitly_retried": this.implicitly_retried
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
function addQueueEvent(name, msg, delaySeconds) {
|
|
258
|
+
const attrs = {};
|
|
259
|
+
if (msg) {
|
|
260
|
+
attrs["queue.message_id"] = msg.id;
|
|
261
|
+
attrs["queue.message_timestamp"] = msg.timestamp.toISOString();
|
|
262
|
+
if ("attempts" in msg && typeof msg.attempts === "number") {
|
|
263
|
+
attrs["queue.message_attempts"] = msg.attempts;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (delaySeconds !== void 0) {
|
|
267
|
+
attrs["queue.retry_delay_seconds"] = delaySeconds;
|
|
268
|
+
}
|
|
269
|
+
trace.getActiveSpan()?.addEvent(name, attrs);
|
|
270
|
+
}
|
|
271
|
+
function proxyQueueMessage(msg, count) {
|
|
272
|
+
const msgHandler = {
|
|
273
|
+
get: (target, prop) => {
|
|
274
|
+
if (prop === "ack") {
|
|
275
|
+
const ackFn = Reflect.get(target, prop);
|
|
276
|
+
return new Proxy(ackFn, {
|
|
277
|
+
apply: (fnTarget) => {
|
|
278
|
+
addQueueEvent("messageAck", msg);
|
|
279
|
+
count.ack();
|
|
280
|
+
Reflect.apply(fnTarget, msg, []);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
} else if (prop === "retry") {
|
|
284
|
+
const retryFn = Reflect.get(target, prop);
|
|
285
|
+
return new Proxy(retryFn, {
|
|
286
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
287
|
+
const retryOptions = args[0];
|
|
288
|
+
const delaySeconds = retryOptions?.delaySeconds;
|
|
289
|
+
addQueueEvent("messageRetry", msg, delaySeconds);
|
|
290
|
+
if (retryOptions?.contentType) {
|
|
291
|
+
const span = trace.getActiveSpan();
|
|
292
|
+
if (span) {
|
|
293
|
+
span.setAttribute("queue.message.content_type", retryOptions.contentType);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
count.retry();
|
|
297
|
+
const result = Reflect.apply(fnTarget, msg, args);
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
return Reflect.get(target, prop, msg);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
return wrap(msg, msgHandler);
|
|
307
|
+
}
|
|
308
|
+
function proxyMessageBatch(batch, count) {
|
|
309
|
+
const batchHandler = {
|
|
310
|
+
get: (target, prop) => {
|
|
311
|
+
if (prop === "messages") {
|
|
312
|
+
const messages = Reflect.get(target, prop);
|
|
313
|
+
const messagesHandler = {
|
|
314
|
+
get: (target2, prop2) => {
|
|
315
|
+
if (typeof prop2 === "string" && !isNaN(parseInt(prop2))) {
|
|
316
|
+
const message = Reflect.get(target2, prop2);
|
|
317
|
+
return proxyQueueMessage(message, count);
|
|
318
|
+
} else {
|
|
319
|
+
return Reflect.get(target2, prop2);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
return wrap(messages, messagesHandler);
|
|
324
|
+
} else if (prop === "ackAll") {
|
|
325
|
+
const ackFn = Reflect.get(target, prop);
|
|
326
|
+
return new Proxy(ackFn, {
|
|
327
|
+
apply: (fnTarget) => {
|
|
328
|
+
addQueueEvent("ackAll");
|
|
329
|
+
count.ackRemaining();
|
|
330
|
+
Reflect.apply(fnTarget, batch, []);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
} else if (prop === "retryAll") {
|
|
334
|
+
const retryFn = Reflect.get(target, prop);
|
|
335
|
+
return new Proxy(retryFn, {
|
|
336
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
337
|
+
const retryOptions = args[0];
|
|
338
|
+
const delaySeconds = retryOptions?.delaySeconds;
|
|
339
|
+
addQueueEvent("retryAll", void 0, delaySeconds);
|
|
340
|
+
count.retryRemaining();
|
|
341
|
+
Reflect.apply(fnTarget, batch, args);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return Reflect.get(target, prop);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
return wrap(batch, batchHandler);
|
|
349
|
+
}
|
|
350
|
+
var QueueInstrumentation = class {
|
|
351
|
+
count;
|
|
352
|
+
getInitialSpanInfo(batch) {
|
|
353
|
+
return {
|
|
354
|
+
name: `queueHandler ${batch.queue || "unknown"}`,
|
|
355
|
+
options: {
|
|
356
|
+
kind: SpanKind.CONSUMER,
|
|
357
|
+
attributes: {
|
|
358
|
+
"faas.trigger": "pubsub",
|
|
359
|
+
"queue.name": batch.queue || "unknown"
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
instrumentTrigger(batch) {
|
|
365
|
+
this.count = new MessageStatusCount(batch.messages.length);
|
|
366
|
+
return proxyMessageBatch(batch, this.count);
|
|
367
|
+
}
|
|
368
|
+
executionSucces(span, _trigger, _result) {
|
|
369
|
+
if (this.count) {
|
|
370
|
+
this.count.ackRemaining();
|
|
371
|
+
span.setAttributes(this.count.toAttributes());
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
executionFailed(span, _trigger, _error) {
|
|
375
|
+
if (this.count) {
|
|
376
|
+
this.count.retryRemaining();
|
|
377
|
+
span.setAttributes(this.count.toAttributes());
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
function headerAttributes(message) {
|
|
382
|
+
const attrs = {};
|
|
383
|
+
if (message.headers instanceof Headers) {
|
|
384
|
+
for (const [key, value] of message.headers.entries()) {
|
|
385
|
+
attrs[`email.header.${key}`] = value;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return attrs;
|
|
389
|
+
}
|
|
390
|
+
var emailInstrumentation = {
|
|
391
|
+
getInitialSpanInfo: (message) => {
|
|
392
|
+
const attributes = {
|
|
393
|
+
"faas.trigger": "other",
|
|
394
|
+
"messaging.destination.name": message.to || "unknown"
|
|
395
|
+
};
|
|
396
|
+
if ("headers" in message && message.headers instanceof Headers) {
|
|
397
|
+
const messageId = message.headers.get("Message-Id");
|
|
398
|
+
if (messageId) {
|
|
399
|
+
attributes["rpc.message.id"] = messageId;
|
|
400
|
+
}
|
|
401
|
+
Object.assign(attributes, headerAttributes(message));
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
name: `emailHandler ${message.to || "unknown"}`,
|
|
405
|
+
options: {
|
|
406
|
+
kind: SpanKind.CONSUMER,
|
|
407
|
+
attributes
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
async function exportSpans(traceId, tracker) {
|
|
413
|
+
const tracer = trace.getTracer("autotel-edge");
|
|
414
|
+
if (tracer instanceof WorkerTracer) {
|
|
415
|
+
try {
|
|
416
|
+
await scheduler.wait(1);
|
|
417
|
+
await tracker?.wait();
|
|
418
|
+
await tracer.forceFlush(traceId);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error("[autotel-edge] Failed to export spans:", error);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function createHandlerFlow(instrumentation) {
|
|
425
|
+
return (handlerFn, [trigger, env, context$1]) => {
|
|
426
|
+
const { ctx: proxiedCtx, tracker } = proxyExecutionContext(context$1);
|
|
427
|
+
const tracer = trace.getTracer("autotel-edge");
|
|
428
|
+
const { name, options, context: spanContext } = instrumentation.getInitialSpanInfo(trigger);
|
|
429
|
+
if (options.attributes) {
|
|
430
|
+
options.attributes["faas.coldstart"] = coldStart;
|
|
431
|
+
} else {
|
|
432
|
+
options.attributes = { "faas.coldstart": coldStart };
|
|
433
|
+
}
|
|
434
|
+
coldStart = false;
|
|
435
|
+
const parentContext = spanContext || context.active();
|
|
436
|
+
const instrumentedTrigger = instrumentation.instrumentTrigger ? instrumentation.instrumentTrigger(trigger) : trigger;
|
|
437
|
+
return tracer.startActiveSpan(name, options, parentContext, async (span) => {
|
|
438
|
+
try {
|
|
439
|
+
const result = await handlerFn(instrumentedTrigger, env, proxiedCtx);
|
|
440
|
+
if (instrumentation.getAttributesFromResult) {
|
|
441
|
+
const attributes = instrumentation.getAttributesFromResult(result);
|
|
442
|
+
span.setAttributes(attributes);
|
|
443
|
+
}
|
|
444
|
+
if (instrumentation.executionSucces) {
|
|
445
|
+
instrumentation.executionSucces(span, trigger, result);
|
|
446
|
+
}
|
|
447
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
448
|
+
return result;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
span.recordException(error);
|
|
451
|
+
span.setStatus({
|
|
452
|
+
code: SpanStatusCode.ERROR,
|
|
453
|
+
message: error instanceof Error ? error.message : String(error)
|
|
454
|
+
});
|
|
455
|
+
if (instrumentation.executionFailed) {
|
|
456
|
+
instrumentation.executionFailed(span, trigger, error);
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
} finally {
|
|
460
|
+
span.end();
|
|
461
|
+
context$1.waitUntil(exportSpans(span.spanContext().traceId, tracker));
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function createHandlerProxy(_handler, handlerFn, initialiser, instrumentation) {
|
|
467
|
+
return (trigger, env, ctx) => {
|
|
468
|
+
const config = initialiser(env, trigger);
|
|
469
|
+
if (config.instrumentation.disabled) {
|
|
470
|
+
return handlerFn(trigger, env, ctx);
|
|
471
|
+
}
|
|
472
|
+
const instrumentedEnv = instrumentBindings(env);
|
|
473
|
+
const configContext = setConfig(config);
|
|
474
|
+
initProvider(config);
|
|
475
|
+
const flowFn = createHandlerFlow(instrumentation);
|
|
476
|
+
return context.with(configContext, () => {
|
|
477
|
+
return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]);
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function createHandlerProxyWithConfig(_handler, handlerFn, initialiser, createInstrumentation) {
|
|
482
|
+
return (trigger, env, ctx) => {
|
|
483
|
+
const config = initialiser(env, trigger);
|
|
484
|
+
if (config.instrumentation.disabled) {
|
|
485
|
+
return handlerFn(trigger, env, ctx);
|
|
486
|
+
}
|
|
487
|
+
const instrumentedEnv = instrumentBindings(env);
|
|
488
|
+
const configContext = setConfig(config);
|
|
489
|
+
initProvider(config);
|
|
490
|
+
const instrumentation = createInstrumentation(config);
|
|
491
|
+
const flowFn = createHandlerFlow(instrumentation);
|
|
492
|
+
return context.with(configContext, () => {
|
|
493
|
+
return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]);
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
var providerInitialized = false;
|
|
498
|
+
var coldStart = true;
|
|
499
|
+
function initProvider(config) {
|
|
500
|
+
if (providerInitialized) return;
|
|
501
|
+
if (config.instrumentation.instrumentGlobalFetch) {
|
|
502
|
+
instrumentGlobalFetch();
|
|
503
|
+
}
|
|
504
|
+
if (config.instrumentation.instrumentGlobalCache) {
|
|
505
|
+
instrumentGlobalCache();
|
|
506
|
+
}
|
|
507
|
+
propagation.setGlobalPropagator(config.propagator);
|
|
508
|
+
const resource = resourceFromAttributes({
|
|
509
|
+
"service.name": config.service.name,
|
|
510
|
+
"service.version": config.service.version,
|
|
511
|
+
"service.namespace": config.service.namespace,
|
|
512
|
+
"cloud.provider": "cloudflare",
|
|
513
|
+
"cloud.platform": "cloudflare.workers",
|
|
514
|
+
"telemetry.sdk.name": "autotel-edge",
|
|
515
|
+
"telemetry.sdk.language": "js"
|
|
516
|
+
});
|
|
517
|
+
const provider = new WorkerTracerProvider(config.spanProcessors, resource);
|
|
518
|
+
provider.register();
|
|
519
|
+
const tracer = trace.getTracer("autotel-edge");
|
|
520
|
+
tracer.setHeadSampler(config.sampling.headSampler);
|
|
521
|
+
providerInitialized = true;
|
|
522
|
+
}
|
|
523
|
+
function instrument(handler, config) {
|
|
524
|
+
const initialiser = createInitialiser(config);
|
|
525
|
+
if (handler.fetch) {
|
|
526
|
+
const fetcher = unwrap(handler.fetch);
|
|
527
|
+
handler.fetch = createHandlerProxyWithConfig(
|
|
528
|
+
handler,
|
|
529
|
+
fetcher,
|
|
530
|
+
initialiser,
|
|
531
|
+
createFetchInstrumentation
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
if (handler.scheduled) {
|
|
535
|
+
const scheduled = unwrap(handler.scheduled);
|
|
536
|
+
handler.scheduled = createHandlerProxy(
|
|
537
|
+
handler,
|
|
538
|
+
scheduled,
|
|
539
|
+
initialiser,
|
|
540
|
+
scheduledInstrumentation
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (handler.queue) {
|
|
544
|
+
const queue = unwrap(handler.queue);
|
|
545
|
+
handler.queue = createHandlerProxy(
|
|
546
|
+
handler,
|
|
547
|
+
queue,
|
|
548
|
+
initialiser,
|
|
549
|
+
new QueueInstrumentation()
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if (handler.email) {
|
|
553
|
+
const email = unwrap(handler.email);
|
|
554
|
+
handler.email = createHandlerProxy(
|
|
555
|
+
handler,
|
|
556
|
+
email,
|
|
557
|
+
initialiser,
|
|
558
|
+
emailInstrumentation
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
return handler;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/wrappers/wrap-module.ts
|
|
565
|
+
function wrapModule(config, handler) {
|
|
566
|
+
return instrument(handler, config);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/wrappers/wrap-do.ts
|
|
570
|
+
function wrapDurableObject(config, doClass) {
|
|
571
|
+
return instrumentDO(doClass, config);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export { instrument, instrumentGlobalCache, instrumentGlobalFetch, wrapDurableObject, wrapModule };
|
|
575
|
+
//# sourceMappingURL=index.js.map
|
|
576
|
+
//# sourceMappingURL=index.js.map
|