Skip to content

Commit 9f36a62

Browse files
authored
Merge pull request #5094 from cloudflare/maksym/WO-750/0
WO-750 Add tracing infrastructure for bindings
2 parents 94eb39c + f617216 commit 9f36a62

26 files changed

+1480
-40
lines changed

src/cloudflare/internal/d1-api.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
33
// https://opensource.org/licenses/Apache-2.0
44

5+
import { withSpan } from 'cloudflare-internal:tracing-helpers';
6+
57
interface Fetcher {
68
fetch: typeof fetch;
79
}
@@ -83,7 +85,10 @@ class D1Database {
8385
}
8486

8587
prepare(query: string): D1PreparedStatement {
86-
return new D1PreparedStatement(this.alwaysPrimarySession, query);
88+
return withSpan('prepare', (span) => {
89+
span.setAttribute('query', query);
90+
return new D1PreparedStatement(this.alwaysPrimarySession, query);
91+
});
8792
}
8893

8994
async batch<T = unknown>(
@@ -93,7 +98,10 @@ class D1Database {
9398
}
9499

95100
async exec(query: string): Promise<D1ExecResult> {
96-
return this.alwaysPrimarySession.exec(query);
101+
return withSpan('exec', async (span) => {
102+
span.setAttribute('query', query);
103+
return this.alwaysPrimarySession.exec(query);
104+
});
97105
}
98106

99107
withSession(
@@ -155,7 +163,10 @@ class D1DatabaseSession {
155163
}
156164

157165
prepare(sql: string): D1PreparedStatement {
158-
return new D1PreparedStatement(this, sql);
166+
return withSpan('prepare', (span) => {
167+
span.setAttribute('sql', sql);
168+
return new D1PreparedStatement(this, sql);
169+
});
159170
}
160171

161172
async batch<T = unknown>(

src/cloudflare/internal/images-api.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
createBase64DecoderTransformStream,
77
createBase64EncoderTransformStream,
88
} from 'cloudflare-internal:streaming-base64';
9+
import { withSpan } from 'cloudflare-internal:tracing-helpers';
910

1011
type Fetcher = {
1112
fetch: typeof fetch;
@@ -233,40 +234,49 @@ class ImagesBindingImpl implements ImagesBinding {
233234
stream: ReadableStream<Uint8Array>,
234235
options?: ImageInputOptions
235236
): Promise<ImageInfoResponse> {
236-
const body = new StreamableFormData();
237-
238-
const decodedStream =
239-
options?.encoding === 'base64'
240-
? stream.pipeThrough(createBase64DecoderTransformStream())
241-
: stream;
242-
243-
body.append('image', decodedStream, { type: 'file' });
237+
return await withSpan('images_info', async (span) => {
238+
const body = new StreamableFormData();
239+
240+
const decodedStream =
241+
options?.encoding === 'base64'
242+
? stream.pipeThrough(createBase64DecoderTransformStream())
243+
: stream;
244+
245+
span.setAttribute('cloudflare.images.info.encoding', options?.encoding);
246+
247+
body.append('image', decodedStream, { type: 'file' });
248+
249+
const response = await this.#fetcher.fetch(
250+
'https://js.images.cloudflare.com/info',
251+
{
252+
method: 'POST',
253+
headers: {
254+
'content-type': body.contentType(),
255+
},
256+
body: body.stream(),
257+
}
258+
);
244259

245-
const response = await this.#fetcher.fetch(
246-
'https://js.images.cloudflare.com/info',
247-
{
248-
method: 'POST',
249-
headers: {
250-
'content-type': body.contentType(),
251-
},
252-
body: body.stream(),
253-
}
254-
);
260+
await throwErrorIfErrorResponse('INFO', response);
255261

256-
await throwErrorIfErrorResponse('INFO', response);
262+
const r = (await response.json()) as RawInfoResponse;
257263

258-
const r = (await response.json()) as RawInfoResponse;
264+
span.setAttribute('cloudflare.images.info.format', r.format);
259265

260-
if ('file_size' in r) {
261-
return {
262-
fileSize: r.file_size,
263-
width: r.width,
264-
height: r.height,
265-
format: r.format,
266-
};
267-
}
266+
if ('file_size' in r) {
267+
span.setAttribute('cloudflare.images.info.file_size', r.file_size);
268+
span.setAttribute('cloudflare.images.info.width', r.width);
269+
span.setAttribute('cloudflare.images.info.height', r.height);
270+
return {
271+
fileSize: r.file_size,
272+
width: r.width,
273+
height: r.height,
274+
format: r.format,
275+
};
276+
}
268277

269-
return r;
278+
return r;
279+
});
270280
}
271281

272282
input(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2025 Cloudflare, Inc.
2+
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3+
// https://opensource.org/licenses/Apache-2.0
4+
5+
// TEST-ONLY MODULE - DO NOT USE IN PRODUCTION
6+
// This wrapper exists solely to expose internal tracing utilities for testing.
7+
// It must be in the internal/ directory to be compiled as part of the cloudflare bundle,
8+
// but it should never be used outside of test configurations.
9+
10+
import { withSpan } from 'cloudflare-internal:tracing-helpers';
11+
12+
interface TestWrapper {
13+
withSpan: typeof withSpan;
14+
}
15+
16+
// Wrapper function that provides test utilities for tracing
17+
export default function (_env: unknown): TestWrapper {
18+
return {
19+
// Export withSpan for testing
20+
withSpan,
21+
};
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
exports_files(
2+
["instrumentation-test-helper.js"],
3+
visibility = [
4+
"//src/cloudflare/internal/test/d1:__pkg__",
5+
"//src/cloudflare/internal/test/images:__pkg__",
6+
"//src/cloudflare/internal/test/tracing:__pkg__",
7+
"//src/workerd/api:__pkg__",
8+
],
9+
)

src/cloudflare/internal/test/d1/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ wd_test(
77
data = glob(
88
["*.js"],
99
exclude = ["d1-api-test-with-sessions.js"],
10-
),
10+
) + ["//src/cloudflare/internal/test:instrumentation-test-helper.js"],
1111
)
1212

1313
wd_test(

0 commit comments

Comments
 (0)