Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/h3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import type {

import { toRequest } from "./utils/request.ts";
import { toEventHandler } from "./handler.ts";
import {
publishInit,
publishMount,
traceRequestHandler,
} from "./utils/tracing.ts";

export const NoHandler: EventHandler = () => kNotFound;

Expand All @@ -44,6 +49,7 @@ export class H3Core implements H3CoreType {
this.config = config;
this.fetch = this.fetch.bind(this);
this.handler = this.handler.bind(this);
publishInit(this);
}

fetch(request: ServerRequest): Response | Promise<Response> {
Expand All @@ -63,7 +69,7 @@ export class H3Core implements H3CoreType {
);
return middleware.length > 0
? callMiddleware(event, middleware, routeHandler)
: routeHandler(event);
: traceRequestHandler(event, "route", async () => routeHandler(event));
}

"~request"(
Expand Down Expand Up @@ -164,6 +170,7 @@ export const H3 = /* @__PURE__ */ (() => {
return fetchHandler(new Request(url, event.req));
});
}
publishMount(this, base, input);
return this;
}

Expand Down
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,15 @@ export {
defineWebSocket,
} from "./utils/ws.ts";

// Tracing

export {
type HandlerType,
type H3InitPayload,
type H3MountPayload,
type H3RequestHandlerPayload,
} from "./utils/tracing.ts";

// ---- Deprecated ----

export * from "./_deprecated.ts";
17 changes: 8 additions & 9 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { routeToRegExp } from "rou3";
import { kNotFound } from "./response.ts";
import { traceRequestHandler } from "./utils/tracing.ts";

import type { H3Event } from "./event.ts";
import type { MiddlewareOptions } from "./types/h3.ts";
Expand Down Expand Up @@ -72,8 +73,9 @@ export function callMiddleware(
index: number = 0,
): unknown | Promise<unknown> {
if (index === middleware.length) {
return handler(event);
return traceRequestHandler(event, "route", async () => handler(event));
}

const fn = middleware[index];

let nextCalled: undefined | boolean;
Expand All @@ -88,14 +90,11 @@ export function callMiddleware(
return nextResult;
};

const ret = fn(event, next);
return isUnhandledResponse(ret)
? next()
: typeof (ret as PromiseLike<unknown>)?.then === "function"
? (ret as PromiseLike<unknown>).then((resolved) =>
isUnhandledResponse(resolved) ? next() : resolved,
)
: ret;
return traceRequestHandler(event, "middleware", async () => {
const ret = await fn(event, next);

return isUnhandledResponse(ret) ? next() : ret;
});
}

function isUnhandledResponse(val: unknown) {
Expand Down
67 changes: 67 additions & 0 deletions src/utils/tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { channel, tracingChannel } from "node:diagnostics_channel";
import type { H3Event } from "../event.ts";
import type { H3Core } from "../h3.ts";
import type { ServerRequest } from "srvx";

export type HandlerType = "middleware" | "route";

export interface H3InitPayload {
app: H3Core;
}

export interface H3MountPayload {
app: H3Core;
base: string;
mountedApp: unknown;
}

export interface H3RequestHandlerPayload {
request: ServerRequest;
event: H3Event;
type: HandlerType;
}

const initChannel = channel("h3.init");
const mountChannel = channel("h3.mount");

const requestHandlerChannel = tracingChannel("h3.request.handler");

/**
* Publish h3.init diagnostic event when H3 app is initialized.
*/
export function publishInit(app: H3Core): void {
if (initChannel.hasSubscribers) {
initChannel.publish({ app } satisfies H3InitPayload);
}
}

/**
* Publish h3.mount diagnostic event when a nested app is mounted.
*/
export function publishMount(
app: H3Core,
base: string,
mountedApp: unknown,
): void {
if (mountChannel.hasSubscribers) {
mountChannel.publish({ app, base, mountedApp } satisfies H3MountPayload);
}
}

/**
* Trace a request handler execution with the h3.request.handler tracing channel.
* This creates spans for middleware and route handlers that can be observed by APM tools.
*/
export function traceRequestHandler<T>(
event: H3Event,
type: HandlerType,
fn: () => Promise<T>,
): T | Promise<T> {
const payload: H3RequestHandlerPayload = {
request: event.req,
event,
type,
};

return requestHandlerChannel.tracePromise(fn, payload);
}
Loading