feature: add telemetry to the HTTP layer (#9116)

---------

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
This commit is contained in:
Harminder Virk
2024-09-13 12:36:54 +05:30
committed by GitHub
parent 8c2a5fbcf2
commit 9cf0df53b5
12 changed files with 951 additions and 44 deletions

View File

@@ -20,7 +20,10 @@ const API_KEY_AUTH = "api-key"
// This is the only hard-coded actor type, as API keys have special handling for now. We could also generalize API keys to carry the actor type with them.
const ADMIN_ACTOR_TYPE = "user"
type AuthType = typeof SESSION_AUTH | typeof BEARER_AUTH | typeof API_KEY_AUTH
export type AuthType =
| typeof SESSION_AUTH
| typeof BEARER_AUTH
| typeof API_KEY_AUTH
type MedusaSession = {
auth_context: AuthContext
@@ -31,7 +34,7 @@ export const authenticate = (
authType: AuthType | AuthType[],
options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {}
): RequestHandler => {
const handler = async (
const authenticateMiddleware = async (
req: MedusaRequest,
res: MedusaResponse,
next: NextFunction
@@ -105,7 +108,7 @@ export const authenticate = (
res.status(401).json({ message: "Unauthorized" })
}
return handler as unknown as RequestHandler
return authenticateMiddleware as unknown as RequestHandler
}
const getApiKeyInfo = async (req: MedusaRequest): Promise<ApiKeyDTO | null> => {

View File

@@ -1,6 +1,13 @@
import { parseCorsOrigins, promiseAll, wrapHandler } from "@medusajs/utils"
import cors from "cors"
import { type Express, json, Router, text, urlencoded } from "express"
import {
type Express,
json,
RequestHandler,
Router,
text,
urlencoded,
} from "express"
import { readdir } from "fs/promises"
import { extname, join, parse, sep } from "path"
import {
@@ -8,15 +15,17 @@ import {
HTTP_METHODS,
MedusaRequest,
MedusaResponse,
MiddlewareFunction,
MiddlewareRoute,
MiddlewaresConfig,
MiddlewareVerb,
ParserConfigArgs,
RouteConfig,
RouteDescriptor,
RouteHandler,
RouteVerb,
} from "./types"
import { authenticate, errorHandler } from "./middlewares"
import { authenticate, AuthType, errorHandler } from "./middlewares"
import { configManager } from "../config"
import { logger } from "../logger"
@@ -212,6 +221,24 @@ class ApiRoutesLoader {
*/
readonly #sourceDir: string
/**
* Wrap the original route handler implementation for
* instrumentation.
*/
static traceRoute?: (
handler: RouteHandler,
route: { route: string; method: string }
) => RouteHandler
/**
* Wrap the original middleware handler implementation for
* instrumentation.
*/
static traceMiddleware?: (
handler: RequestHandler | MiddlewareFunction,
route: { route: string; method?: string }
) => RequestHandler
constructor({
app,
activityId,
@@ -560,6 +587,27 @@ class ApiRoutesLoader {
return
}
/**
* Applies the route middleware on a route. Encapsulates the logic
* needed to pass the middleware via the trace calls
*/
applyAuthMiddleware(
route: string,
actorType: string | string[],
authType: AuthType | AuthType[],
options?: { allowUnauthenticated?: boolean; allowUnregistered?: boolean }
) {
let authenticateMiddleware = authenticate(actorType, authType, options)
if (ApiRoutesLoader.traceMiddleware) {
authenticateMiddleware = ApiRoutesLoader.traceMiddleware(
authenticateMiddleware,
{ route: route }
)
}
this.#router.use(route, authenticateMiddleware)
}
/**
* Apply the route specific middlewares to the router,
* this includes the cors, authentication and
@@ -629,20 +677,22 @@ class ApiRoutesLoader {
// We only apply the auth middleware to store routes to populate the auth context. For actual authentication, users can just reapply the middleware.
if (!config.optedOutOfAuth && config.routeType === "store") {
this.#router.use(
this.applyAuthMiddleware(
descriptor.route,
authenticate("customer", ["bearer", "session"], {
"customer",
["bearer", "session"],
{
allowUnauthenticated: true,
})
}
)
}
if (!config.optedOutOfAuth && config.routeType === "admin") {
// We probably don't want to allow access to all endpoints using an api key, but it will do until we revamp our routing.
this.#router.use(
descriptor.route,
authenticate("user", ["bearer", "session", "api-key"])
)
this.applyAuthMiddleware(descriptor.route, "user", [
"bearer",
"session",
"api-key",
])
}
for (const route of routes) {
@@ -708,13 +758,26 @@ class ApiRoutesLoader {
}`,
})
let handler: RequestHandler | RouteHandler = route.handler
/**
* Give handler to the trace route handler for instrumentation
* from outside-in.
*/
if (ApiRoutesLoader.traceRoute) {
handler = ApiRoutesLoader.traceRoute(handler, {
method: route.method!,
route: descriptor.route,
})
}
/**
* If the user hasn't opted out of error handling then
* we wrap the handler in a try/catch block.
*/
const handler = shouldWrapHandler
? wrapHandler(route.handler as Parameters<typeof wrapHandler>[0])
: route.handler
if (shouldWrapHandler) {
handler = wrapHandler(handler as Parameters<typeof wrapHandler>[0])
}
this.#router[route.method!.toLowerCase()](descriptor.route, handler)
}
@@ -756,7 +819,17 @@ class ApiRoutesLoader {
message: `Registering middleware [${method}] - ${route.matcher}`,
})
this.#router[method.toLowerCase()](route.matcher, ...route.middlewares)
let middlewares = route.middlewares
if (ApiRoutesLoader.traceMiddleware) {
middlewares = middlewares.map((middleware) =>
ApiRoutesLoader.traceMiddleware!(middleware, {
route: String(route.matcher),
method,
})
)
}
this.#router[method.toLowerCase()](route.matcher, ...middlewares)
}
}
}
@@ -840,6 +913,27 @@ export class RoutesLoader {
*/
readonly #sourceDir: string | string[]
static instrument: {
/**
* Instrument middleware function calls by wrapping the original
* middleware handler inside a custom implementation
*/
middleware: (callback: (typeof ApiRoutesLoader)["traceMiddleware"]) => void
/**
* Instrument route handler function calls by wrapping the original
* middleware handler inside a custom implementation
*/
route: (callback: (typeof ApiRoutesLoader)["traceRoute"]) => void
} = {
middleware(callback) {
ApiRoutesLoader.traceMiddleware = callback
},
route(callback) {
ApiRoutesLoader.traceRoute = callback
},
}
constructor({
app,
activityId,

View File

@@ -36,7 +36,7 @@ export type AsyncRouteHandler = (
res: MedusaResponse
) => Promise<void>
type RouteHandler = SyncRouteHandler | AsyncRouteHandler
export type RouteHandler = SyncRouteHandler | AsyncRouteHandler
export type RouteImplementation = {
method?: RouteVerb