Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ $ bunx --bun srvx
| `jsx` | [examples/jsx](https://github.com/h3js/srvx/tree/main/examples/jsx/) | `npx giget gh:h3js/srvx/examples/jsx srvx-jsx` |
| `node-handler` | [examples/node-handler](https://github.com/h3js/srvx/tree/main/examples/node-handler/) | `npx giget gh:h3js/srvx/examples/node-handler srvx-node-handler` |
| `service-worker` | [examples/service-worker](https://github.com/h3js/srvx/tree/main/examples/service-worker/) | `npx giget gh:h3js/srvx/examples/service-worker srvx-service-worker` |
| `tracing` | [examples/tracing](https://github.com/h3js/srvx/tree/main/examples/tracing/) | `npx giget gh:h3js/srvx/examples/tracing srvx-tracing` |
| `websocket` | [examples/websocket](https://github.com/h3js/srvx/tree/main/examples/websocket/) | `npx giget gh:h3js/srvx/examples/websocket srvx-websocket` |

<!-- /automd -->
Expand Down
1 change: 1 addition & 0 deletions build.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default defineBuildConfig({
"src/cli.ts",
"src/static.ts",
"src/log.ts",
"src/tracing.ts",
...[
"deno",
"bun",
Expand Down
1 change: 1 addition & 0 deletions docs/1.guide/1.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ bun run server.mjs
| `jsx` | [examples/jsx](https://github.com/h3js/srvx/tree/main/examples/jsx/) | `npx giget gh:h3js/srvx/examples/jsx srvx-jsx` |
| `node-handler` | [examples/node-handler](https://github.com/h3js/srvx/tree/main/examples/node-handler/) | `npx giget gh:h3js/srvx/examples/node-handler srvx-node-handler` |
| `service-worker` | [examples/service-worker](https://github.com/h3js/srvx/tree/main/examples/service-worker/) | `npx giget gh:h3js/srvx/examples/service-worker srvx-service-worker` |
| `tracing` | [examples/tracing](https://github.com/h3js/srvx/tree/main/examples/tracing/) | `npx giget gh:h3js/srvx/examples/tracing srvx-tracing` |
| `websocket` | [examples/websocket](https://github.com/h3js/srvx/tree/main/examples/websocket/) | `npx giget gh:h3js/srvx/examples/websocket srvx-websocket` |

<!-- /automd -->
12 changes: 12 additions & 0 deletions examples/tracing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "srvx-examples-tracing",
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "srvx --prod",
"dev": "srvx"
},
"devDependencies": {
"srvx": "latest"
}
}
45 changes: 45 additions & 0 deletions examples/tracing/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { tracingPlugin } from "srvx/tracing";

export default {
plugins: [tracingPlugin()],
fetch(req: Request) {
return Response.json({ hello: "world!" });
},
};

// --- debug tracing channels ---

debugChannel("srvx.middleware");
debugChannel("srvx.fetch");

function debugChannel(name: string) {
const { tracingChannel } = process.getBuiltinModule(
"node:diagnostics_channel",
);

const log = (...args: unknown[]) => console.log(`[tracing:${name}]`, ...args);
const noop = () => {};
const serializeData = (data: any) =>
Object.entries(data)
.map(([key, value]) => {
if (key === "request") {
return `request(url=${(value as Request).url})`;
}
if (key === "server") {
return "server";
}
if (key === "result") {
return `result(status=${(value as Response).status})`;
}
return `${key}=${value}`;
})
.join(", ");

tracingChannel(name).subscribe({
start: noop,
end: noop,
asyncStart: (data) => log("asyncStart", serializeData(data)),
asyncEnd: (data) => log("asyncEnd", serializeData(data)),
error: (data) => log("error", serializeData(data)),
});
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"./cli": "./dist/cli.mjs",
"./static": "./dist/static.mjs",
"./log": "./dist/log.mjs",
"./tracing": "./dist/tracing.mjs",
".": {
"types": "./dist/types.d.mts",
"deno": "./dist/adapters/deno.mjs",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 84 additions & 0 deletions src/tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type {
Server,
ServerRequest,
ServerPlugin,
ServerMiddleware,
} from "./types.ts";

export type RequestEvent = {
server: Server;
request: ServerRequest;
middleware?: {
index: number;
handler: ServerMiddleware;
};
};

/**
* Tracing plugin that adds diagnostics channel tracing to middleware and fetch handlers.
*
* This plugin wraps all middleware and the fetch handler with tracing instrumentation,
* allowing you to subscribe to `srvx.fetch` and `srvx.middleware` tracing channels.
*
* @example
* ```ts
* import { serve } from "srvx";
* import { tracingPlugin } from "srvx/tracing";
*
* const server = serve({
* fetch: (req) => new Response("OK"),
* middleware: [myMiddleware],
* plugins: [tracingPlugin()],
* });
* ```
*/
export function tracingPlugin(
opts: { middleware?: boolean; fetch?: boolean } = {},
): ServerPlugin {
return (server) => {
// No-op if tracingChannel is not available
const { tracingChannel } =
globalThis.process?.getBuiltinModule?.("node:diagnostics_channel") || {};
if (!tracingChannel) {
return;
}

// Wrap the fetch handler with tracing
if (opts.fetch !== false) {
const fetchChannel = tracingChannel<unknown, RequestEvent>("srvx.fetch");
const originalFetch = server.options.fetch;
server.options.fetch = (request) => {
return fetchChannel.tracePromise(
async () => await originalFetch(request),
{ request, server },
);
};
}

// Wrap middleware with tracing
if (opts.middleware !== false) {
const middlewareChannel = tracingChannel<unknown, RequestEvent>(
"srvx.middleware",
);
const originalMiddleware = server.options.middleware;
const wrappedMiddleware: ServerMiddleware[] = originalMiddleware.map(
(handler, index) => {
const middleware = Object.freeze({ index, handler });
return (request, next) => {
return middlewareChannel.tracePromise(
async () => await handler(request, next),
{ request, server, middleware },
);
};
},
);

// Replace middleware array with wrapped versions
server.options.middleware.splice(
0,
server.options.middleware.length,
...wrappedMiddleware,
);
}
};
}
Loading
Loading