For some frameworks or situations (e.g., edge environments), automatic instrumentation isn't possible. However, you can still manually wrap your handlers to instrument them.
In essence, there are three steps to implement custom integration:
1
Normalise your request and response
Get and normalize Request & Response objects - this heavily depends on your framework and runtime
2
Process the operation
Call await process({ request, response, direction }) to process the request and response into an operation
3
Report to Appear
Call report({ operations, config }) to report the processed operations to Appear
Example integration
In the below example:
We wrap an Express-style request handler to add Appear reporting.
Since Express-style handlers typically use res.json() or res.send() to set the response body, we use a Proxy to intercept and capture the response content.
We normalize the Request, Response, and Headers objects so they work seamlessly with Appear's process() function.
We use Vercel’s waitUntil() to ensure the report is sent before the serverless function finishes, without delaying the response to the user.
This example is intentionally verbose to highlight edge cases you may need to consider. In most cases, your integration will be much simpler.
View example
import { process, report, AppearConfig } from "@appear.sh/introspector"
import { waitUntil } from "@vercel/functions"
import type {
IncomingHttpHeaders,
IncomingMessage,
OutgoingHttpHeaders,
ServerResponse,
} from "node:http"
type Handler = (
req: IncomingMessage & {
query: Partial<{ [key: string]: string | string[] }>
cookies: Partial<{ [key: string]: string }>
body: any
env: { [key: string]: string | undefined }
},
res: ServerResponse & {
send: any
json: any
status: any
},
) => void
const normalizeHeaders = (
headers: IncomingHttpHeaders | OutgoingHttpHeaders,
) => {
const entries = Object.entries(headers).reduce(
(acc, [key, value]) => {
if (typeof value === "string") acc.push([key, value])
if (typeof value === "number") acc.push([key, value.toString()])
if (Array.isArray(value)) value.forEach((v) => acc.push([key, v]))
return acc
},
[] as [string, string][],
)
return new Headers(entries)
}
const normalizeRequest = (req: IncomingMessage & { body: any }) => {
const protocol = req.headers["x-forwarded-proto"] || "http"
const host = req.headers["x-forwarded-host"] || req.headers.host || "unknown"
return new Request(new URL(req.url!, `${protocol}://${host}`), {
method: req.method,
headers: normalizeHeaders(req.headers),
body: req.body || null,
})
}
const normalizeResponse = (
res: ServerResponse,
body: object | string | Buffer | null | undefined,
) => {
const responseHeaders = normalizeHeaders(res.getHeaders())
// 204 No Content, 304 Not Modified don't allow body https://nextjs.org/docs/messages/invalid-api-status-body
if (res.statusCode === 204 || res.statusCode === 304) {
body = null
}
// Response accepts only string or Buffer and next supports objects
if (body && typeof body === "object" && !Buffer.isBuffer(body)) {
body = JSON.stringify(body)
}
return new Response(body, {
status: res.statusCode,
statusText: res.statusMessage,
headers: responseHeaders,
})
}
export function withAppear(handler: Handler, config: AppearConfig): Handler {
return async (req, baseRes) => {
// create a proxy to capture the response body
// we need to do this because the syntax is res.json({ some: content })
let body: object | string | Buffer | null | undefined
const res = new Proxy(baseRes, {
get(target, prop, receiver) {
if (prop === "json" || prop === "send") {
return (content: any) => {
body = content
return Reflect.get(target, prop, receiver)(content)
}
}
return Reflect.get(target, prop, receiver)
},
})
const result = await handler(req, res)
try {
const request = normalizeRequest(req)
const response = normalizeResponse(res, body)
const operation = await process({
request,
response,
direction: "incoming",
})
// report, don't await so we don't slow down response time
waitUntil(report(operation, config))
} catch (e) {
console.error("[Appear introspector] failed with error", e)
}
return result
}
}
If you have any queries or require support with the above instructions, please .