1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-28 01:59:06 -05:00

feat(unstable): Instrument fetch (#27057)

Add basic tracing to `fetch`. Also fix span kinds so that we can
differentiate fetch and serve.
This commit is contained in:
snek 2024-11-25 16:38:07 +01:00 committed by GitHub
parent 08a56763d4
commit d59bd5e8c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 223 additions and 90 deletions

View file

@ -10,9 +10,10 @@
/// <reference path="./lib.deno_fetch.d.ts" /> /// <reference path="./lib.deno_fetch.d.ts" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
import { core, primordials } from "ext:core/mod.js"; import { core, internals, primordials } from "ext:core/mod.js";
import { import {
op_fetch, op_fetch,
op_fetch_promise_is_settled,
op_fetch_send, op_fetch_send,
op_wasm_streaming_feed, op_wasm_streaming_feed,
op_wasm_streaming_set_url, op_wasm_streaming_set_url,
@ -28,7 +29,9 @@ const {
PromisePrototypeThen, PromisePrototypeThen,
PromisePrototypeCatch, PromisePrototypeCatch,
SafeArrayIterator, SafeArrayIterator,
SafePromisePrototypeFinally,
String, String,
StringPrototypeSlice,
StringPrototypeStartsWith, StringPrototypeStartsWith,
StringPrototypeToLowerCase, StringPrototypeToLowerCase,
TypeError, TypeError,
@ -307,6 +310,13 @@ function httpRedirectFetch(request, response, terminator) {
* @param {RequestInit} init * @param {RequestInit} init
*/ */
function fetch(input, init = { __proto__: null }) { function fetch(input, init = { __proto__: null }) {
let span;
try {
if (internals.telemetry?.tracingEnabled) {
span = new internals.telemetry.Span("fetch", { kind: 2 });
internals.telemetry.enterSpan(span);
}
// There is an async dispatch later that causes a stack trace disconnect. // There is an async dispatch later that causes a stack trace disconnect.
// We reconnect it by assigning the result of that dispatch to `opPromise`, // We reconnect it by assigning the result of that dispatch to `opPromise`,
// awaiting `opPromise` in an inner function also named `fetch()` and // awaiting `opPromise` in an inner function also named `fetch()` and
@ -318,6 +328,20 @@ function fetch(input, init = { __proto__: null }) {
webidl.requiredArguments(arguments.length, 1, prefix); webidl.requiredArguments(arguments.length, 1, prefix);
// 2. // 2.
const requestObject = new Request(input, init); const requestObject = new Request(input, init);
if (span) {
span.updateName(requestObject.method);
span.setAttribute("http.request.method", requestObject.method);
const url = new URL(requestObject.url);
span.setAttribute("url.full", requestObject.url);
span.setAttribute(
"url.scheme",
StringPrototypeSlice(url.protocol, 0, -1),
);
span.setAttribute("url.path", url.pathname);
span.setAttribute("url.query", StringPrototypeSlice(url.search, 1));
}
// 3. // 3.
const request = toInnerRequest(requestObject); const request = toInnerRequest(requestObject);
// 4. // 4.
@ -325,7 +349,6 @@ function fetch(input, init = { __proto__: null }) {
reject(abortFetch(request, null, requestObject.signal.reason)); reject(abortFetch(request, null, requestObject.signal.reason));
return; return;
} }
// 7. // 7.
let responseObject = null; let responseObject = null;
// 9. // 9.
@ -376,6 +399,14 @@ function fetch(input, init = { __proto__: null }) {
return; return;
} }
responseObject = fromInnerResponse(response, "immutable"); responseObject = fromInnerResponse(response, "immutable");
if (span) {
span.setAttribute(
"http.response.status_code",
String(responseObject.status),
);
}
resolve(responseObject); resolve(responseObject);
requestObject.signal[abortSignal.remove](onabort); requestObject.signal[abortSignal.remove](onabort);
}, },
@ -386,14 +417,43 @@ function fetch(input, init = { __proto__: null }) {
}, },
); );
}); });
if (opPromise) { if (opPromise) {
PromisePrototypeCatch(result, () => {}); PromisePrototypeCatch(result, () => {});
return (async function fetch() { return (async function fetch() {
try {
await opPromise; await opPromise;
return result; return result;
} finally {
if (span) {
internals.telemetry.endSpan(span);
}
}
})(); })();
} }
// We need to end the span when the promise settles.
// WPT has a test that aborted fetch is settled in the same tick.
// This means we cannot wrap the promise if it is already settled.
// But this is OK, because we can just immediately end the span
// in that case.
if (span) {
// XXX: This should always be true, otherwise `opPromise` would be present.
if (op_fetch_promise_is_settled(result)) {
// It's already settled.
internals.telemetry.endSpan(span);
} else {
// Not settled yet, we can return a new wrapper promise.
return SafePromisePrototypeFinally(result, () => {
internals.telemetry.endSpan(span);
});
}
}
return result; return result;
} finally {
if (span) {
internals.telemetry.exitSpan(span);
}
}
} }
function abortFetch(request, responseObject, error) { function abortFetch(request, responseObject, error) {

View file

@ -27,6 +27,7 @@ use deno_core::futures::TryFutureExt;
use deno_core::op2; use deno_core::op2;
use deno_core::url; use deno_core::url;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::v8;
use deno_core::AsyncRefCell; use deno_core::AsyncRefCell;
use deno_core::AsyncResult; use deno_core::AsyncResult;
use deno_core::BufView; use deno_core::BufView;
@ -141,6 +142,7 @@ deno_core::extension!(deno_fetch,
op_fetch_send, op_fetch_send,
op_utf8_to_byte_string, op_utf8_to_byte_string,
op_fetch_custom_client<FP>, op_fetch_custom_client<FP>,
op_fetch_promise_is_settled,
], ],
esm = [ esm = [
"20_headers.js", "20_headers.js",
@ -1206,3 +1208,8 @@ pub fn extract_authority(url: &mut Url) -> Option<(String, Option<String>)> {
None None
} }
#[op2(fast)]
fn op_fetch_promise_is_settled(promise: v8::Local<v8::Promise>) -> bool {
promise.state() != v8::PromiseState::Pending
}

View file

@ -617,13 +617,13 @@ function mapToCallback(context, callback, onError) {
fastSyncResponseOrStream(req, inner.body, status, innerRequest); fastSyncResponseOrStream(req, inner.body, status, innerRequest);
}; };
if (internals.telemetry.tracingEnabled) { if (internals.telemetry?.tracingEnabled) {
const { Span, enterSpan, endSpan } = internals.telemetry; const { Span, enterSpan, endSpan } = internals.telemetry;
const origMapped = mapped; const origMapped = mapped;
mapped = function (req, _span) { mapped = function (req, _span) {
const oldCtx = getAsyncContext(); const oldCtx = getAsyncContext();
setAsyncContext(context.asyncContext); setAsyncContext(context.asyncContext);
const span = new Span("deno.serve"); const span = new Span("deno.serve", { kind: 1 });
try { try {
enterSpan(span); enterSpan(span);
return SafePromisePrototypeFinally( return SafePromisePrototypeFinally(

View file

@ -41,6 +41,8 @@ const { AsyncVariable, setAsyncContext } = core;
let TRACING_ENABLED = false; let TRACING_ENABLED = false;
let DETERMINISTIC = false; let DETERMINISTIC = false;
// Note: These start at 0 in the JS library,
// but start at 1 when serialized with JSON.
enum SpanKind { enum SpanKind {
INTERNAL = 0, INTERNAL = 0,
SERVER = 1, SERVER = 1,
@ -91,6 +93,11 @@ interface Attributes {
type SpanAttributes = Attributes; type SpanAttributes = Attributes;
interface SpanOptions {
attributes?: Attributes;
kind?: SpanKind;
}
interface Link { interface Link {
context: SpanContext; context: SpanContext;
attributes?: SpanAttributes; attributes?: SpanAttributes;
@ -354,7 +361,7 @@ export class Span {
#recording = TRACING_ENABLED; #recording = TRACING_ENABLED;
#kind: number = 0; #kind: number = SpanKind.INTERNAL;
#name: string; #name: string;
#startTime: number; #startTime: number;
#status: { code: number; message?: string } | null = null; #status: { code: number; message?: string } | null = null;
@ -429,7 +436,7 @@ export class Span {
constructor( constructor(
name: string, name: string,
attributes?: Attributes, options?: SpanOptions,
) { ) {
if (!this.isRecording) { if (!this.isRecording) {
this.#name = ""; this.#name = "";
@ -442,7 +449,8 @@ export class Span {
this.#name = name; this.#name = name;
this.#startTime = now(); this.#startTime = now();
this.#attributes = attributes ?? { __proto__: null } as never; this.#attributes = options?.attributes ?? { __proto__: null } as never;
this.#kind = options?.kind ?? SpanKind.INTERNAL;
const currentSpan: Span | { const currentSpan: Span | {
spanContext(): { traceId: string; spanId: string }; spanContext(): { traceId: string; spanId: string };

View file

@ -7,7 +7,7 @@
"parentSpanId": "", "parentSpanId": "",
"flags": 1, "flags": 1,
"name": "GET", "name": "GET",
"kind": 1, "kind": 3,
"startTimeUnixNano": "[WILDCARD]", "startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]", "endTimeUnixNano": "[WILDCARD]",
"attributes": [ "attributes": [
@ -59,10 +59,68 @@
} }
}, },
{ {
"traceId": "00000000000000000000000000000001", "traceId": "00000000000000000000000000000003",
"spanId": "0000000000000004",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "GET",
"kind": 2,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [
{
"key": "http.request.method",
"value": {
"stringValue": "GET"
}
},
{
"key": "url.full",
"value": {
"stringValue": "http://localhost:[WILDCARD]/"
}
},
{
"key": "url.scheme",
"value": {
"stringValue": "http"
}
},
{
"key": "url.path",
"value": {
"stringValue": "/"
}
},
{
"key": "url.query",
"value": {
"stringValue": ""
}
},
{
"key": "http.response.status_code",
"value": {
"stringValue": "200"
}
}
],
"droppedAttributesCount": 0,
"events": [],
"droppedEventsCount": 0,
"links": [],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
},
{
"traceId": "00000000000000000000000000000003",
"spanId": "1000000000000001", "spanId": "1000000000000001",
"traceState": "", "traceState": "",
"parentSpanId": "0000000000000002", "parentSpanId": "0000000000000004",
"flags": 1, "flags": 1,
"name": "outer span", "name": "outer span",
"kind": 1, "kind": 1,
@ -80,7 +138,7 @@
} }
}, },
{ {
"traceId": "00000000000000000000000000000001", "traceId": "00000000000000000000000000000003",
"spanId": "1000000000000002", "spanId": "1000000000000002",
"traceState": "", "traceState": "",
"parentSpanId": "1000000000000001", "parentSpanId": "1000000000000001",
@ -113,7 +171,7 @@
"attributes": [], "attributes": [],
"droppedAttributesCount": 0, "droppedAttributesCount": 0,
"flags": 1, "flags": 1,
"traceId": "00000000000000000000000000000001", "traceId": "00000000000000000000000000000003",
"spanId": "1000000000000002" "spanId": "1000000000000002"
}, },
{ {
@ -127,7 +185,7 @@
"attributes": [], "attributes": [],
"droppedAttributesCount": 0, "droppedAttributesCount": 0,
"flags": 1, "flags": 1,
"traceId": "00000000000000000000000000000001", "traceId": "00000000000000000000000000000003",
"spanId": "1000000000000002" "spanId": "1000000000000002"
} }
] ]