chore(medusa): Add handler path to the http tracing to be able to group by (#10835)

RESOLVES FRMW-2835

In this PR, we trace HTTP requests using the route pattern and not the request URL. This allows us to aggregate all HTTP requests under a single route.

In terms of implementation, we have to self find the route for a given request, since there is no API in express to do the same and we begin tracing even before the request is handed over to Express.

Since, the route matching happens via RegExp matches, we ensure that this does not add much performance overhead. The matching takes between 0.8ms-1.5ms for various different routes

Co-authored-by: Harminder Virk <1706381+thetutlage@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-01-07 14:16:20 +01:00
committed by GitHub
parent 5216ad2f15
commit 899b1fba4a
3 changed files with 61 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
chore(medusa): Add handler path to the http tracing to be able to group by

View File

@@ -19,6 +19,7 @@ import { logger } from "@medusajs/framework/logger"
import loaders from "../loaders"
import { MedusaModule } from "@medusajs/framework/modules-sdk"
import { MedusaContainer } from "@medusajs/framework/types"
import { parse } from "url"
const EVERY_SIXTH_HOUR = "0 */6 * * *"
const CRON_SCHEDULE = EVERY_SIXTH_HOUR
@@ -88,6 +89,44 @@ function displayAdminUrl({
logger.info(`Admin URL → http://${host || "localhost"}:${port}${adminPath}`)
}
type ExpressStack = {
name: string
match: (url: string) => boolean
route: { path: string }
handle: { stack: ExpressStack[] }
}
/**
* Retrieve the route path from the express stack based on the input url
* @param stack - The express stack
* @param url - The input url
* @returns The route path
*/
function findExpressRoutePath({
stack,
url,
}: {
stack: ExpressStack[]
url: string
}): string | void {
const stackToProcess = [...stack]
while (stackToProcess.length > 0) {
const layer = stackToProcess.pop()!
if (layer.name === "bound dispatch" && layer.match(url)) {
return layer.route.path
}
// Add nested stack items to be processed if they exist
if (layer.handle?.stack?.length) {
stackToProcess.push(...layer.handle.stack)
}
}
return undefined
}
async function start(args: {
directory: string
host?: string
@@ -104,15 +143,21 @@ async function start(args: {
const app = express()
const http_ = http.createServer(async (req, res) => {
const stack = app._router.stack
await new Promise((resolve) => {
res.on("finish", resolve)
if (traceRequestHandler) {
const expressHandlerPath = findExpressRoutePath({
stack,
url: parse(req.url!, false).pathname!,
})
void traceRequestHandler(
async () => {
app(req, res)
},
req,
res
res,
expressHandlerPath
)
} else {
app(req, res)

View File

@@ -28,14 +28,20 @@ export function instrumentHttpLayer() {
const HTTPTracer = new Tracer("@medusajs/http", "2.0.0")
const { SpanStatusCode } = require("@opentelemetry/api")
startCommand.traceRequestHandler = async (requestHandler, req, res) => {
startCommand.traceRequestHandler = async (
requestHandler,
req,
res,
handlerPath
) => {
if (shouldExcludeResource(req.url!)) {
return await requestHandler()
}
const traceName = `${req.method} ${req.url}`
const traceName = handlerPath ?? `${req.method} ${req.url}`
await HTTPTracer.trace(traceName, async (span) => {
span.setAttributes({
"http.route": handlerPath,
"http.url": req.url,
"http.method": req.method,
...req.headers,
@@ -66,7 +72,8 @@ export function instrumentHttpLayer() {
return await handler(req, res)
}
const traceName = `route: ${req.method} ${req.originalUrl}`
const label = req.route?.path ?? `${req.method} ${req.originalUrl}`
const traceName = `route handler: ${label}`
await HTTPTracer.trace(traceName, async (span) => {
try {