From 1fad416b905a88577c38eea3f1815f39cd467b98 Mon Sep 17 00:00:00 2001 From: Ankcorn Date: Thu, 19 Oct 2023 11:14:15 +0100 Subject: [PATCH] fix plugin structure and BetterHttpInstrumentation --- package-lock.json | 1 + package.json | 1 + src/http-plugins/captureBody.ts | 20 +++++++++++ src/http-plugins/plugin.ts | 22 ++++++++---- src/http-plugins/stripe.ts | 24 ++++++------- src/http-plugins/vercel.ts | 13 ++++--- src/http.ts | 62 +++++++++++++++++++-------------- src/index.ts | 4 +-- tsup.config.ts | 3 +- 9 files changed, 95 insertions(+), 55 deletions(-) create mode 100644 src/http-plugins/captureBody.ts diff --git a/package-lock.json b/package-lock.json index ce7cca7..3b9f62a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.2.6", "license": "MIT", "dependencies": { + "@opentelemetry/api": "^1.6.0", "@opentelemetry/exporter-trace-otlp-http": "^0.44.0", "@opentelemetry/instrumentation": "^0.44.0", "@opentelemetry/instrumentation-http": "^0.44.0", diff --git a/package.json b/package.json index 1b99d2a..30110fb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ }, "homepage": "https://github.com/baselime/node-opentelemetry#readme", "dependencies": { + "@opentelemetry/api": "^1.6.0", "@opentelemetry/exporter-trace-otlp-http": "^0.44.0", "@opentelemetry/instrumentation": "^0.44.0", "@opentelemetry/instrumentation-http": "^0.44.0", diff --git a/src/http-plugins/captureBody.ts b/src/http-plugins/captureBody.ts new file mode 100644 index 0000000..5df7d5f --- /dev/null +++ b/src/http-plugins/captureBody.ts @@ -0,0 +1,20 @@ +import { ClientRequest } from "http"; + +export function captureBody(request: ClientRequest): Promise { + return new Promise((resolve, reject) => { + const chunks: string[] = []; + const oldWrite = request.write.bind(request); + const oldEnd = request.end.bind(request); + request.on('data', chunk => { + chunks.push(decodeURIComponent(chunk.toString())) + return oldWrite(chunk); + }); + request.on('end', (chunk) => { + if (chunk) { + chunks.push(decodeURIComponent(chunk.toString())); + } + oldEnd(chunk); + return resolve(chunks.join('')) + }); + }); +} \ No newline at end of file diff --git a/src/http-plugins/plugin.ts b/src/http-plugins/plugin.ts index 4fb410f..190efbe 100644 --- a/src/http-plugins/plugin.ts +++ b/src/http-plugins/plugin.ts @@ -1,10 +1,18 @@ -import { ClientRequest, IncomingHttpHeaders, IncomingMessage, ServerResponse } from "http"; +import { ClientRequest, IncomingMessage, ServerResponse } from "http"; -export type Plugin = { - shouldParseRequest?(request: ClientRequest | IncomingMessage): boolean; - shouldParseResponse?(response: IncomingMessage | ServerResponse): boolean; +export class HttpPlugin { parseIncommingMessage?(request: IncomingMessage): Record; parseClientRequest?(request: ClientRequest): Record; - captureBody?: boolean; - name: string; -} + captureBody = false + name = 'base-plugin-should-extend' + constructor() { + + } + + shouldParseRequest(request: ClientRequest | IncomingMessage): boolean { + return false; + } + shouldParseResponse(response: IncomingMessage | ServerResponse): boolean { + return false + } +} \ No newline at end of file diff --git a/src/http-plugins/stripe.ts b/src/http-plugins/stripe.ts index b2215e6..ce7c2da 100644 --- a/src/http-plugins/stripe.ts +++ b/src/http-plugins/stripe.ts @@ -1,22 +1,20 @@ -import { ClientRequest, IncomingHttpHeaders, IncomingMessage, ServerResponse } from "http"; -import { Plugin } from "./plugin.ts"; +import { ClientRequest, IncomingMessage } from "http"; +import { HttpPlugin } from "./plugin.ts"; + +export class StripePlugin extends HttpPlugin implements HttpPlugin { + captureBody = true + name = 'stripe' -export const StripePlugin: Plugin = { - captureBody: true, - name: 'stripe', shouldParseRequest(request: ClientRequest | IncomingMessage): boolean { - if (request instanceof ClientRequest && request.host?.includes('api.stripe.com')) { return true; } return false; - }, - shouldParseResponse(response: IncomingMessage | ServerResponse) { - return false; - }, + } + parseClientRequest(request: ClientRequest) { const method = request.method; - + const [version, entity, entityIdOrOperation, operation] = request.path.split('/'); return { @@ -29,4 +27,6 @@ export const StripePlugin: Plugin = { } } } -} \ No newline at end of file +} + +const plugin = new StripePlugin(); \ No newline at end of file diff --git a/src/http-plugins/vercel.ts b/src/http-plugins/vercel.ts index 1cb5fdc..83187fa 100644 --- a/src/http-plugins/vercel.ts +++ b/src/http-plugins/vercel.ts @@ -1,16 +1,15 @@ -import { ClientRequest, IncomingHttpHeaders, IncomingMessage, ServerResponse } from "http"; -import { Plugin } from "./plugin.ts"; +import { ClientRequest, IncomingMessage } from "http"; +import { HttpPlugin } from "./plugin.ts"; -export const VercelPlugin: Plugin = { - name: 'vercel', +export class VercelPlugin extends HttpPlugin implements HttpPlugin { + name = 'vercel'; shouldParseRequest(request: ClientRequest | IncomingMessage): boolean { - + if (request instanceof IncomingMessage && request.headers['x-vercel-id']) { return true; } return false; - }, - + } parseIncommingMessage(request: IncomingMessage) { const headers = request.headers; diff --git a/src/http.ts b/src/http.ts index 2a59d97..21c19e2 100644 --- a/src/http.ts +++ b/src/http.ts @@ -1,38 +1,31 @@ import { Span } from "@opentelemetry/api"; -import { ClientRequest, IncomingHttpHeaders, IncomingMessage } from "http"; -import { Plugin } from "./http-plugins/plugin.ts"; +import { ClientRequest, IncomingMessage } from "http"; +import { HttpPlugin } from "./http-plugins/plugin.ts"; import { flatten } from "flat"; +import { HttpInstrumentation, HttpInstrumentationConfig } from "@opentelemetry/instrumentation-http"; +import { captureBody } from "./http-plugins/captureBody.ts"; +import { StripePlugin } from "./index.ts"; type BetterHttpInstrumentationOptions = { - plugins: Plugin[], + plugins?: HttpPlugin[], + requestHook?: HttpInstrumentationConfig['requestHook'] + responseHook?: HttpInstrumentationConfig['responseHook'] + ignoreIncomingRequestHook?: HttpInstrumentationConfig['ignoreIncomingRequestHook'] + ignoreOutgoingRequestHook?: HttpInstrumentationConfig['ignoreOutgoingRequestHook'] + startIncomingSpanHook?: HttpInstrumentationConfig['startIncomingSpanHook'] + startOutgoingSpanHook?: HttpInstrumentationConfig['startOutgoingSpanHook'] } -function captureBody(request: ClientRequest): Promise { - return new Promise((resolve, reject) => { - const chunks: string[] = []; - const oldWrite = request.write.bind(request); - const oldEnd = request.end.bind(request); - request.on('data', chunk => { - chunks.push(decodeURIComponent(chunk.toString())) - return oldWrite(chunk); - }); - request.on('end', (chunk) => { - if (chunk) { - chunks.push(decodeURIComponent(chunk.toString())); - } - oldEnd(chunk); - return resolve(chunks.join('')) - }); - }); -} - -export function betterHttpInstrumentation (options: BetterHttpInstrumentationOptions) { +export function _betterHttpInstrumentation (options: BetterHttpInstrumentationOptions = {}) { + options.plugins = options.plugins || []; return { requestHook (span: Span, request: ClientRequest | IncomingMessage) { if (request instanceof ClientRequest) { const plugin = options.plugins.find(plugin => plugin.shouldParseRequest && plugin.shouldParseRequest(request)); - + + span.setAttribute('http.plugin.name', plugin.name); + if (plugin.captureBody) { captureBody(request).then(body => { span.setAttributes(flatten({ body })); @@ -49,7 +42,24 @@ export function betterHttpInstrumentation (options: BetterHttpInstrumentationOpt span.setAttributes(flatten(attributes)); } } - } + + if(options.requestHook) { + options.requestHook(span, request); + } + }, + } } - \ No newline at end of file + +export class BetterHttpInstrumentation extends HttpInstrumentation { + constructor(options: BetterHttpInstrumentationOptions = {}) { + super({ + ..._betterHttpInstrumentation(options), + responseHook: options.responseHook, + ignoreIncomingRequestHook: options.ignoreIncomingRequestHook, + ignoreOutgoingRequestHook: options.ignoreOutgoingRequestHook, + startIncomingSpanHook: options.startIncomingSpanHook, + startOutgoingSpanHook: options.startOutgoingSpanHook, + }) + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index b5ab2fe..e0a2fb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export { BaselimeSDK } from './baselime.ts'; export { tracing as trpcTracingMiddleware } from './trpc.ts'; -export { betterHttpInstrumentation } from './http.ts'; +export { BetterHttpInstrumentation } from './http.ts'; export { StripePlugin } from './http-plugins/stripe.ts'; -export { Plugin } from './http-plugins/plugin.ts'; +export { HttpPlugin } from './http-plugins/plugin.ts'; export { VercelPlugin } from './http-plugins/vercel.ts'; \ No newline at end of file diff --git a/tsup.config.ts b/tsup.config.ts index 89158d8..090d64f 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ clean: true, format: ['esm', 'cjs'], target: 'node18', - noExternal: [/^.*/], minify: false, + // for now we include flat in the bundle because it is not exported correctly for both esm and cjs + noExternal: [/flat/], })