From ba45186c372841b29cb826e54b62b5d74ace45d2 Mon Sep 17 00:00:00 2001 From: Andrii Andreiev <129078694+AndriiAndreiev@users.noreply.github.com> Date: Sun, 29 Sep 2024 21:59:57 +0300 Subject: [PATCH] structure changes, add web fetch api support --- packages/node/src/index.ts | 6 - packages/node/src/lib/ReadMe.ts | 6 +- .../lib/metrics-fetch/construct-payload.ts | 64 +++++++++ .../src/lib/metrics-fetch/extract-body.ts | 16 +++ packages/node/src/lib/metrics-fetch/index.ts | 5 + packages/node/src/lib/metrics-fetch/log.ts | 111 ++++++++++++++++ .../src/lib/metrics-fetch/process-request.ts | 118 +++++++++++++++++ .../src/lib/metrics-fetch/process-response.ts | 64 +++++++++ .../lib/metrics-fetch/transform-request.ts | 19 +++ .../lib/metrics-fetch/transform-response.ts | 36 +++++ .../src/lib/metrics-node/construct-payload.ts | 73 ++++++++++ packages/node/src/lib/metrics-node/index.ts | 7 + .../src/lib/{ => metrics-node}/is-request.ts | 0 .../node/src/lib/{ => metrics-node}/log.ts | 22 ++- .../lib/{ => metrics-node}/patch-request.ts | 0 .../lib/{ => metrics-node}/patch-response.ts | 0 .../lib/{ => metrics-node}/process-request.ts | 125 ++---------------- .../{ => metrics-node}/process-response.ts | 6 +- .../src/lib/{ => shared}/console-logger.ts | 0 packages/node/src/lib/shared/do-send.ts | 26 ++++ .../lib/{ => shared}/get-project-base-url.ts | 4 +- packages/node/src/lib/{ => shared}/logger.ts | 0 packages/node/src/lib/shared/mask.ts | 17 +++ .../node/src/lib/{ => shared}/metrics-log.ts | 12 +- .../src/lib/{ => shared}/object-to-array.ts | 0 .../options.ts} | 92 +------------ .../node/src/lib/{ => shared}/pino-logger.ts | 0 .../node/src/lib/shared/processing-helpers.ts | 112 ++++++++++++++++ packages/node/test/index.test.ts | 8 +- packages/node/test/lib/console-logger.test.ts | 2 +- .../node/test/lib/construct-payload.test.ts | 5 +- .../test/lib/get-project-base-url.test.ts | 3 +- packages/node/test/lib/is-request.test.ts | 2 +- packages/node/test/lib/log.test.ts | 11 +- packages/node/test/lib/logger.test.ts | 2 +- .../node/test/lib/object-to-array.test.ts | 2 +- .../node/test/lib/process-request.test.ts | 4 +- .../node/test/lib/process-response.test.ts | 2 +- 38 files changed, 730 insertions(+), 252 deletions(-) delete mode 100644 packages/node/src/index.ts create mode 100644 packages/node/src/lib/metrics-fetch/construct-payload.ts create mode 100644 packages/node/src/lib/metrics-fetch/extract-body.ts create mode 100644 packages/node/src/lib/metrics-fetch/index.ts create mode 100644 packages/node/src/lib/metrics-fetch/log.ts create mode 100644 packages/node/src/lib/metrics-fetch/process-request.ts create mode 100644 packages/node/src/lib/metrics-fetch/process-response.ts create mode 100644 packages/node/src/lib/metrics-fetch/transform-request.ts create mode 100644 packages/node/src/lib/metrics-fetch/transform-response.ts create mode 100644 packages/node/src/lib/metrics-node/construct-payload.ts create mode 100644 packages/node/src/lib/metrics-node/index.ts rename packages/node/src/lib/{ => metrics-node}/is-request.ts (100%) rename packages/node/src/lib/{ => metrics-node}/log.ts (92%) rename packages/node/src/lib/{ => metrics-node}/patch-request.ts (100%) rename packages/node/src/lib/{ => metrics-node}/patch-response.ts (100%) rename packages/node/src/lib/{ => metrics-node}/process-request.ts (54%) rename packages/node/src/lib/{ => metrics-node}/process-response.ts (93%) rename packages/node/src/lib/{ => shared}/console-logger.ts (100%) create mode 100644 packages/node/src/lib/shared/do-send.ts rename packages/node/src/lib/{ => shared}/get-project-base-url.ts (97%) rename packages/node/src/lib/{ => shared}/logger.ts (100%) create mode 100644 packages/node/src/lib/shared/mask.ts rename packages/node/src/lib/{ => shared}/metrics-log.ts (94%) rename packages/node/src/lib/{ => shared}/object-to-array.ts (100%) rename packages/node/src/lib/{construct-payload.ts => shared/options.ts} (50%) rename packages/node/src/lib/{ => shared}/pino-logger.ts (100%) create mode 100644 packages/node/src/lib/shared/processing-helpers.ts diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts deleted file mode 100644 index 554ddbdd64..0000000000 --- a/packages/node/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { getProjectBaseUrl } from './lib/get-project-base-url'; -import { log } from './lib/log'; -import ReadMe from './lib/ReadMe'; -import verifyWebhook from './lib/verify-webhook'; - -export { verifyWebhook, log, ReadMe, getProjectBaseUrl }; diff --git a/packages/node/src/lib/ReadMe.ts b/packages/node/src/lib/ReadMe.ts index cf98c22f29..fe0d5fe096 100644 --- a/packages/node/src/lib/ReadMe.ts +++ b/packages/node/src/lib/ReadMe.ts @@ -1,4 +1,4 @@ -import type { Options } from './log'; +import type { Options } from './shared/options'; import type { NextFunction, Request, Response } from 'express'; import pkg from '../../package.json'; @@ -6,9 +6,9 @@ import config from '../config'; import findAPIKey from './find-api-key'; import { getGroupByApiKey } from './get-group-id'; -import { log } from './log'; -import { logger } from './logger'; +import { log } from './metrics-node/log'; import { buildSetupView } from './setup-readme-view'; +import { logger } from './shared/logger'; import { testVerifyWebhook } from './test-verify-webhook'; import verifyWebhook from './verify-webhook'; diff --git a/packages/node/src/lib/metrics-fetch/construct-payload.ts b/packages/node/src/lib/metrics-fetch/construct-payload.ts new file mode 100644 index 0000000000..47423d3a27 --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/construct-payload.ts @@ -0,0 +1,64 @@ +import type { OutgoingLogBody } from '../shared/metrics-log'; +import type { PayloadData, LogOptions } from '../shared/options'; + +import { randomUUID } from 'node:crypto'; +import os from 'os'; + +import { version } from '../../../package.json'; +import { mask } from '../shared/mask'; + +import { processRequest } from './process-request'; +import { processResponse } from './process-response'; + +export function getProto(req: Request): 'http' | 'https' { + return req.url.startsWith('https://') ? 'https' : 'http'; +} + +export function constructPayload( + req: Request, + res: Response, + payloadData: PayloadData, + logOptions: LogOptions, +): OutgoingLogBody { + const serverTime = payloadData.responseEndDateTime.getTime() - payloadData.startedDateTime.getTime(); + + return { + _id: payloadData.logId || randomUUID(), + _version: 3, + group: { + id: mask(payloadData.apiKey), + label: payloadData.label, + email: payloadData.email, + }, + clientIPAddress: req.headers.get('x-forwarded-for') || '', + development: !!logOptions?.development, + request: { + log: { + version: '1.2', + creator: { + name: 'readme-metrics (node)', + version, + // x64-darwin21.3.0/14.19.3 + comment: `${os.arch()}-${os.platform()}${os.release()}/${process.versions.node}`, + }, + entries: [ + { + pageref: payloadData.routePath + ? payloadData.routePath + : new URL(req.url || '', `${getProto(req)}://${req.headers.get('host')}`).toString(), + startedDateTime: payloadData.startedDateTime.toISOString(), + time: serverTime, + request: processRequest(req, payloadData.requestBody, logOptions), + response: processResponse(res, payloadData.responseBody, logOptions), + cache: {}, + timings: { + // This requires us to know the time the request was sent to the server, so we're skipping it for now + wait: 0, + receive: serverTime, + }, + }, + ], + }, + }, + }; +} diff --git a/packages/node/src/lib/metrics-fetch/extract-body.ts b/packages/node/src/lib/metrics-fetch/extract-body.ts new file mode 100644 index 0000000000..25b87cbbdb --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/extract-body.ts @@ -0,0 +1,16 @@ +export default function extractBody(target: Request | Response) { + const contentType = target.headers.get('content-type'); + let body; + + if (contentType?.includes('json')) { + target.json().then(data => { + body = data; + }); + } else { + target.text().then(data => { + body = data; + }); + } + + return body; +} diff --git a/packages/node/src/lib/metrics-fetch/index.ts b/packages/node/src/lib/metrics-fetch/index.ts new file mode 100644 index 0000000000..3d83fa0b97 --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/index.ts @@ -0,0 +1,5 @@ +import { getProjectBaseUrl } from '../shared/get-project-base-url'; + +import { log } from './log'; + +export { getProjectBaseUrl, log }; diff --git a/packages/node/src/lib/metrics-fetch/log.ts b/packages/node/src/lib/metrics-fetch/log.ts new file mode 100644 index 0000000000..8b5f12d0f5 --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/log.ts @@ -0,0 +1,111 @@ +import type { GroupingObject, OutgoingLogBody } from '../shared/metrics-log'; +import type { Options } from '../shared/options'; + +import { randomUUID } from 'node:crypto'; + +import clamp from 'lodash/clamp'; + +import config from '../../config'; +import { getProjectBaseUrl } from '../shared/get-project-base-url'; +import { logger } from '../shared/logger'; +import { metricsAPICall } from '../shared/metrics-log'; + +import { constructPayload } from './construct-payload'; +import extractBody from './extract-body'; + +let queue: OutgoingLogBody[] = []; + +export function doSend(readmeApiKey: string, options: Options) { + // Copy the queue so we can send all the requests in one batch + const json = [...queue]; + // Clear out the queue so we don't resend any data in the future + queue = []; + + // Make the log call + metricsAPICall(readmeApiKey, json, options).catch(err => { + // Silently discard errors and timeouts. + if (options.development) { + logger.error({ message: 'Failed to capture API request.', err }); + } + }); + + logger.debug({ message: 'Queue flushed.', args: { queue } }); +} +// Make sure we flush the queue if the process is exited +process.on('exit', doSend); + +function setDocumentationHeader(res: Response, baseLogUrl: string, logId: string) { + // This is to catch the potential race condition where `getProjectBaseUrl()` + // takes longer to respond than the original req/res to finish. Without this + // we would get an error that would be very difficult to trace. This could + // do with a test, but it's a little difficult to test. Maybe with a nock() + // delay timeout. + const documentationUrl = `${baseLogUrl}/logs/${logId}`; + logger.verbose({ + message: 'Created URL to your API request log.', + args: { 'x-documentation-url': documentationUrl }, + }); + res.headers.set('x-documentation-url', documentationUrl); +} +/** + * This method will send supplied API requests to ReadMe Metrics. + * + * @see {@link https://readme.com/metrics} + * @see {@link https://docs.readme.com/docs/sending-logs-to-readme-with-nodejs} + * @param readmeApiKey The API key for your ReadMe project. This ensures your requests end up in + * your dashboard. You can read more about the API key in + * [our docs](https://docs.readme.com/reference/authentication). + * @param req This is your incoming request object from your HTTP server and/or framework. + * @param res This is your outgoing response object for your HTTP server and/or framework. + * @param group A function that helps translate incoming request data to our metrics grouping data. + * @param options Additional options. See the documentation for more details. + */ +export function log(readmeApiKey: string, req: Request, res: Response, group: GroupingObject, options: Options = {}) { + if (req.method === 'OPTIONS') return undefined; + if (!readmeApiKey) throw new Error('You must provide your ReadMe API key'); + if (!group) throw new Error('You must provide a group'); + if (options.logger) { + if (typeof options.logger === 'boolean') logger.configure({ isLoggingEnabled: true }); + else logger.configure({ isLoggingEnabled: true, strategy: options.logger }); + } + + // Ensures the buffer length is between 1 and 30 + const bufferLength = clamp(options.bufferLength || config.bufferLength, 1, 30); + + const startedDateTime = new Date(); + const logId = randomUUID(); + + // baseLogUrl can be provided, but if it isn't then we + // attempt to fetch it from the ReadMe API + if (typeof options.baseLogUrl === 'string') { + setDocumentationHeader(res, options.baseLogUrl, logId); + } else { + getProjectBaseUrl(readmeApiKey).then(baseLogUrl => { + setDocumentationHeader(res, baseLogUrl, logId); + }); + } + + const requestBody = extractBody(req); + const responseBody = extractBody(res); + + const payload = constructPayload( + req, + res, + { + ...group, + logId, + startedDateTime, + responseEndDateTime: new Date(), + routePath: '', + responseBody, + requestBody, + }, + options, + ); + + queue.push(payload); + logger.debug({ message: 'Request enqueued.', args: { queue } }); + if (queue.length >= bufferLength) doSend(readmeApiKey, options); + + return logId; +} diff --git a/packages/node/src/lib/metrics-fetch/process-request.ts b/packages/node/src/lib/metrics-fetch/process-request.ts new file mode 100644 index 0000000000..47f3eae4ae --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/process-request.ts @@ -0,0 +1,118 @@ +import type { LogOptions } from '../shared/options'; +import type { Cookie, Param, PostData, Request as HarRequest } from 'har-format'; + +import * as qs from 'querystring'; +import url, { URL } from 'url'; + +import * as contentType from 'content-type'; + +import { mask } from '../shared/mask'; +import { objectToArray, searchToArray } from '../shared/object-to-array'; +import { + fixHeader, + redactOtherProperties, + redactProperties, + isApplicationJson, + parseRequestBody, +} from '../shared/processing-helpers'; + +import { getProto } from './construct-payload'; +import { headersToObject } from './process-response'; + +export function processRequest( + req: Request, + requestBody?: Record | string, + options?: LogOptions, +): HarRequest { + const protocol = fixHeader(req.headers.get('x-forwarded-proto') || '')?.toLowerCase() || getProto(req); + const host = fixHeader(req.headers.get('x-forwarded-host') || '') || req.headers.get('host'); + + const denylist = options?.denylist || options?.blacklist; + const allowlist = options?.allowlist || options?.whitelist; + + let mimeType = ''; + try { + mimeType = contentType.parse(req.headers.get('content-type') || '').type; + } catch (e) {} // eslint-disable-line no-empty + + let reqBody = typeof requestBody === 'string' ? parseRequestBody(requestBody, mimeType) : requestBody; + let postData: PostData | undefined; + + let headers = headersToObject(req.headers); + + if (denylist) { + reqBody = typeof reqBody === 'object' ? redactProperties(reqBody, denylist) : reqBody; + headers = redactProperties(headers, denylist); + } + + if (allowlist && !denylist) { + reqBody = typeof reqBody === 'object' ? redactOtherProperties(reqBody, allowlist) : reqBody; + headers = redactOtherProperties(headers, allowlist); + } + + if (mimeType === 'application/x-www-form-urlencoded') { + postData = { + mimeType, + // `reqBody` is likely to be an object, but can be empty if no HTTP body sent + params: objectToArray((reqBody || {}) as Record) as Param[], + }; + } else if (isApplicationJson(mimeType)) { + postData = { + mimeType, + text: typeof reqBody === 'object' || Array.isArray(reqBody) ? JSON.stringify(reqBody) : reqBody || '', + }; + } else if (mimeType) { + let stringBody = ''; + + try { + stringBody = typeof reqBody === 'string' ? reqBody : JSON.stringify(reqBody); + } catch (e) { + stringBody = '[ReadMe is unable to handle circular JSON. Please contact support if you have any questions.]'; + } + + postData = { + mimeType, + // Do our best to record *some sort of body* even if it's not 100% accurate. + text: stringBody, + }; + } + + // We use a fake host here because we rely on the host header which could be redacted. + // We only ever use this reqUrl with the fake hostname for the pathname and querystring. + // req.originalUrl is express specific, req.url is node.js + const reqUrl = new URL(req.url || '', 'https://readme.io'); + + if (headers.authorization) { + req.headers.set('authorization', mask(headers.authorization as string)); + } + + const requestData: HarRequest = { + method: req.method || '', + url: url.format({ + // Handle cases where some reverse proxies put two protocols into x-forwarded-proto + // This line does the following: "https,http" -> "https" + // https://github.com/readmeio/metrics-sdks/issues/378 + protocol: protocol.split(',')[0], + host, + pathname: reqUrl.pathname, + // Search includes the leading questionmark, format assumes there isn't one, so we trim that off. + query: qs.parse(reqUrl.search.substring(1)), + }), + httpVersion: `${getProto(req).toUpperCase()}/5`, // todo: figure out what we can do with this, there is no analogue in fetch api + headers: objectToArray(headers, { castToString: true }), + queryString: searchToArray(reqUrl.searchParams), + postData, + // TODO: When readme starts accepting these, send the correct values + cookies: [] satisfies Cookie[], + headersSize: -1, + bodySize: -1, + } as const; + + if (typeof requestData.postData === 'undefined') { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { postData: postDataToBeOmitted, ...remainingRequestData } = requestData; + return remainingRequestData; + } + + return requestData; +} diff --git a/packages/node/src/lib/metrics-fetch/process-response.ts b/packages/node/src/lib/metrics-fetch/process-response.ts new file mode 100644 index 0000000000..bb18a117fa --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/process-response.ts @@ -0,0 +1,64 @@ +import type { LogOptions } from '../shared/options'; +import type { Response as HarResponse } from 'har-format'; + +import removeProperties from 'lodash/omit'; +import removeOtherProperties from 'lodash/pick'; + +import { objectToArray } from '../shared/object-to-array'; +import { fixHeader } from '../shared/processing-helpers'; + +export function headersToObject(requestHeaders: Headers): Record { + const headers: Record = {}; + requestHeaders.forEach((value, key) => { + headers[key] = value; + }); + return headers; +} + +export function processResponse(res: Response, responseBody?: string, options?: LogOptions): HarResponse { + const denylist = options?.denylist || options?.blacklist; + const allowlist = options?.allowlist || options?.whitelist; + let body; + try { + body = JSON.parse(responseBody || ''); + + // Only apply blacklist/whitelist if it's an object + if (denylist) { + body = removeProperties(body, denylist); + } + + if (allowlist && !denylist) { + body = removeOtherProperties(body, allowlist); + } + } catch (e) { + // Non JSON body + body = responseBody; + } + + let headers = headersToObject(res.headers); + + if (denylist) { + headers = removeProperties(headers, denylist); + } + + if (allowlist && !denylist) { + headers = removeOtherProperties(headers, allowlist); + } + + return { + status: res.status, + statusText: res.statusText || '', + headers: objectToArray(headers, { castToString: true }), + content: { + text: JSON.stringify(body), + size: Number(fixHeader(res.headers.get('content-length') || 0)), + mimeType: fixHeader(res.headers.get('content-type') || '') || 'text/plain', + }, + // TODO: Once readme starts accepting these, send the correct values + httpVersion: '', + cookies: [], + redirectURL: '', + headersSize: 0, + bodySize: 0, + }; +} diff --git a/packages/node/src/lib/metrics-fetch/transform-request.ts b/packages/node/src/lib/metrics-fetch/transform-request.ts new file mode 100644 index 0000000000..21bcab5ff8 --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/transform-request.ts @@ -0,0 +1,19 @@ +import type { ExtendedIncomingMessage } from '../metrics-node/log'; + +import { IncomingMessage } from 'http'; +import * as net from 'net'; + +export function transfromRequest(req: Request): ExtendedIncomingMessage { + const incomingMessage = new IncomingMessage(new net.Socket()) as ExtendedIncomingMessage; + const headers: Record = {}; + + req.headers.forEach((value, key) => { + headers[key] = value; + }); + + incomingMessage.url = req.url || ''; + incomingMessage.method = req.method || 'GET'; + incomingMessage.headers = headers; + + return incomingMessage; +} diff --git a/packages/node/src/lib/metrics-fetch/transform-response.ts b/packages/node/src/lib/metrics-fetch/transform-response.ts new file mode 100644 index 0000000000..0f4b8ff197 --- /dev/null +++ b/packages/node/src/lib/metrics-fetch/transform-response.ts @@ -0,0 +1,36 @@ +import type { ExtendedResponse } from '../metrics-node/log'; +import type { IncomingMessage } from 'http'; + +import { ServerResponse } from 'http'; +import { ReadableStream } from 'stream/web'; + +export function transformResponse(incomingMessage: IncomingMessage, res: Response): ExtendedResponse { + const serverResponse = new ServerResponse(incomingMessage) as ExtendedResponse; + + res.headers.forEach((value, key) => { + serverResponse.setHeader(key, value); + }); + + serverResponse.statusCode = res.status; + serverResponse.statusMessage = res.statusText; + + if (res.body instanceof ReadableStream) { + const reader = res.body?.getReader(); + const readStream = async () => { + const { done, value } = await reader.read(); + if (done) { + serverResponse.end(); + return; + } + serverResponse.write(Buffer.from(value)); + readStream(); + }; + + readStream(); + } else { + serverResponse.write(res.body); + serverResponse.end(); + } + + return serverResponse; +} diff --git a/packages/node/src/lib/metrics-node/construct-payload.ts b/packages/node/src/lib/metrics-node/construct-payload.ts new file mode 100644 index 0000000000..44f2aecb41 --- /dev/null +++ b/packages/node/src/lib/metrics-node/construct-payload.ts @@ -0,0 +1,73 @@ +import type { OutgoingLogBody } from '../shared/metrics-log'; +import type { PayloadData, LogOptions } from '../shared/options'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { TLSSocket } from 'tls'; + +import { randomUUID } from 'node:crypto'; +import os from 'os'; +import { URL } from 'url'; + +import { version } from '../../../package.json'; + +import processRequest from './process-request'; +import processResponse from './process-response'; +import { mask } from '../shared/mask'; + +/** + * Extracts the protocol string from the incoming request + * + * @param req + * @returns + */ +export function getProto(req: IncomingMessage): 'http' | 'https' { + return (req.socket as TLSSocket).encrypted ? 'https' : 'http'; +} + +export function constructPayload( + req: IncomingMessage, + res: ServerResponse, + payloadData: PayloadData, + logOptions: LogOptions, +): OutgoingLogBody { + const serverTime = payloadData.responseEndDateTime.getTime() - payloadData.startedDateTime.getTime(); + + return { + _id: payloadData.logId || randomUUID(), + _version: 3, + group: { + id: mask(payloadData.apiKey), + label: payloadData.label, + email: payloadData.email, + }, + clientIPAddress: req.socket.remoteAddress || '', + development: !!logOptions?.development, + request: { + log: { + version: '1.2', + creator: { + name: 'readme-metrics (node)', + version, + // x64-darwin21.3.0/14.19.3 + comment: `${os.arch()}-${os.platform()}${os.release()}/${process.versions.node}`, + }, + entries: [ + { + pageref: payloadData.routePath + ? payloadData.routePath + : new URL(req.url || '', `${getProto(req)}://${req.headers.host}`).toString(), + startedDateTime: payloadData.startedDateTime.toISOString(), + time: serverTime, + request: processRequest(req, payloadData.requestBody, logOptions), + response: processResponse(res, payloadData.responseBody, logOptions), + cache: {}, + timings: { + // This requires us to know the time the request was sent to the server, so we're skipping it for now + wait: 0, + receive: serverTime, + }, + }, + ], + }, + }, + }; +} diff --git a/packages/node/src/lib/metrics-node/index.ts b/packages/node/src/lib/metrics-node/index.ts new file mode 100644 index 0000000000..7712ef875a --- /dev/null +++ b/packages/node/src/lib/metrics-node/index.ts @@ -0,0 +1,7 @@ +import ReadMe from '../ReadMe'; +import { getProjectBaseUrl } from '../shared/get-project-base-url'; +import verifyWebhook from '../verify-webhook'; + +import { log } from './log'; + +export { verifyWebhook, log, ReadMe, getProjectBaseUrl }; diff --git a/packages/node/src/lib/is-request.ts b/packages/node/src/lib/metrics-node/is-request.ts similarity index 100% rename from packages/node/src/lib/is-request.ts rename to packages/node/src/lib/metrics-node/is-request.ts diff --git a/packages/node/src/lib/log.ts b/packages/node/src/lib/metrics-node/log.ts similarity index 92% rename from packages/node/src/lib/log.ts rename to packages/node/src/lib/metrics-node/log.ts index bf2d7d49ac..ee46a4f171 100644 --- a/packages/node/src/lib/log.ts +++ b/packages/node/src/lib/metrics-node/log.ts @@ -1,5 +1,5 @@ -import type { LogOptions } from './construct-payload'; -import type { GroupingObject, OutgoingLogBody } from './metrics-log'; +import type { GroupingObject, OutgoingLogBody } from '../shared/metrics-log'; +import type { Options } from '../shared/options'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { randomUUID } from 'node:crypto'; @@ -7,18 +7,19 @@ import * as url from 'url'; import clamp from 'lodash/clamp'; -import config from '../config'; +import config from '../../config'; +import { getProjectBaseUrl } from '../shared/get-project-base-url'; +import { logger } from '../shared/logger'; +import { metricsAPICall } from '../shared/metrics-log'; import { constructPayload } from './construct-payload'; -import { getProjectBaseUrl } from './get-project-base-url'; import isRequest from './is-request'; -import { logger } from './logger'; -import { metricsAPICall } from './metrics-log'; import { patchRequest } from './patch-request'; import { patchResponse } from './patch-response'; let queue: OutgoingLogBody[] = []; -function doSend(readmeApiKey: string, options: Options) { + +export function doSend(readmeApiKey: string, options: Options) { // Copy the queue so we can send all the requests in one batch const json = [...queue]; // Clear out the queue so we don't resend any data in the future @@ -67,13 +68,6 @@ export interface ExtendedResponse extends ServerResponse { _body?: string; } -export interface Options extends LogOptions { - baseLogUrl?: string; - bufferLength?: number; - disableMetrics?: boolean; - disableWebhook?: boolean; -} - function setDocumentationHeader(res: ServerResponse, baseLogUrl: string, logId: string) { // This is to catch the potential race condition where `getProjectBaseUrl()` // takes longer to respond than the original req/res to finish. Without this diff --git a/packages/node/src/lib/patch-request.ts b/packages/node/src/lib/metrics-node/patch-request.ts similarity index 100% rename from packages/node/src/lib/patch-request.ts rename to packages/node/src/lib/metrics-node/patch-request.ts diff --git a/packages/node/src/lib/patch-response.ts b/packages/node/src/lib/metrics-node/patch-response.ts similarity index 100% rename from packages/node/src/lib/patch-response.ts rename to packages/node/src/lib/metrics-node/patch-response.ts diff --git a/packages/node/src/lib/process-request.ts b/packages/node/src/lib/metrics-node/process-request.ts similarity index 54% rename from packages/node/src/lib/process-request.ts rename to packages/node/src/lib/metrics-node/process-request.ts index 0e1ab5391b..de6f9278b8 100644 --- a/packages/node/src/lib/process-request.ts +++ b/packages/node/src/lib/metrics-node/process-request.ts @@ -1,124 +1,23 @@ -import type { LogOptions } from './construct-payload'; import type { ExtendedIncomingMessage } from './log'; +import type { LogOptions } from '../shared/options'; import type { Cookie, Param, PostData, Request } from 'har-format'; import * as qs from 'querystring'; import url, { URL } from 'url'; import * as contentType from 'content-type'; -import get from 'lodash/get'; -import merge from 'lodash/merge'; -import pick from 'lodash/pick'; -import set from 'lodash/set'; -import { getProto, mask } from './construct-payload'; -import { logger } from './logger'; -import { objectToArray, searchToArray } from './object-to-array'; - -/** - * Ensure we have a string or undefined response for any header. - * - * @param header - * @returns - */ -export function fixHeader(header: string[] | number | string): string | undefined { - if (header === undefined) { - return undefined; - } - - if (Array.isArray(header)) { - return header.join(','); - } - - return String(header); -} - -/** - * Redacts a value by replacing it with a string like [REDACTED 6] - * - * @param value the value to be redacted - * @returns A redacted string potentially containing the length of the original value, if it was a string - */ -function redactValue(value: unknown) { - const redactedVal = typeof value === 'string' ? ` ${value.length}` : ''; - return `[REDACTED${redactedVal}]`; -} - -/** - * Redacts all the properties in an object - * - * @param obj The data object that is operated upon - * @param redactedPaths a list of paths that point values which should be redacted - * @returns An object with the redacted values - */ -function redactProperties>(obj: T, redactedPaths: string[] = []): T { - const nextObj = { ...obj }; - return redactedPaths.reduce((acc, path) => { - const value = get(acc, path); - if (value !== undefined) set(acc, path, redactValue(value)); - return acc; - }, nextObj); -} - -/** - * @param obj The data object that is operated upon - * @param cb A callback that is invoked for each value found, the return value being the next value that is set in the returned object - * @returns An object with the replaced values - */ -function replaceEach>( - obj: T, - cb: (input: unknown) => string, -): Record { - return Object.keys(obj).reduce>((acc, key) => { - const value = obj[key]; - if (typeof value === 'object' && value !== null) { - acc[key] = replaceEach(value as Record, cb); - } else if (value !== undefined) { - acc[key] = cb(value); - } - return acc; - }, {}); -} - -/** - * Redacts everything but the provided fields - * - * @param obj The data object with fields to redact - * @param nonRedactedPaths A list of all object paths that shouldn't be redacted - * @returns A merged objects that is entirely redacted except for the values of the nonRedactedPaths - */ -function redactOtherProperties>(obj: T, nonRedactedPaths: string[]): T { - const allowedFields = pick(obj, nonRedactedPaths); - const redactedFields = obj ? replaceEach(obj, redactValue) : obj; - return merge(redactedFields, allowedFields) as T; -} - -function isApplicationJson(mimeType: string) { - if (!mimeType) { - return false; - } - - return ( - ['application/json', 'application/x-json', 'text/json', 'text/x-json'].includes(mimeType) || - mimeType.indexOf('+json') !== -1 - ); -} - -function parseRequestBody(body: string, mimeType: string): Record | string { - if (mimeType === 'application/x-www-form-urlencoded') { - return qs.parse(body); - } - - if (isApplicationJson(mimeType)) { - try { - return JSON.parse(body); - } catch (err) { - logger.error({ message: 'Error parsing request body JSON.', err }); - } - } - - return body; -} +import { mask } from '../shared/mask'; +import { objectToArray, searchToArray } from '../shared/object-to-array'; +import { + fixHeader, + redactProperties, + redactOtherProperties, + parseRequestBody, + isApplicationJson, +} from '../shared/processing-helpers'; + +import { getProto } from './construct-payload'; /** * This transforms the IncommingMessage and additional provided information into the relevant HAR structure diff --git a/packages/node/src/lib/process-response.ts b/packages/node/src/lib/metrics-node/process-response.ts similarity index 93% rename from packages/node/src/lib/process-response.ts rename to packages/node/src/lib/metrics-node/process-response.ts index 5a70bf8c58..799baed997 100644 --- a/packages/node/src/lib/process-response.ts +++ b/packages/node/src/lib/metrics-node/process-response.ts @@ -1,4 +1,4 @@ -import type { LogOptions } from './construct-payload'; +import type { LogOptions } from '../shared/options'; import type { Response } from 'har-format'; import type { ServerResponse } from 'http'; @@ -7,8 +7,8 @@ import { STATUS_CODES } from 'http'; import removeProperties from 'lodash/omit'; import removeOtherProperties from 'lodash/pick'; -import { objectToArray } from './object-to-array'; -import { fixHeader } from './process-request'; +import { objectToArray } from '../shared/object-to-array'; +import { fixHeader } from '../shared/processing-helpers'; /** * Transforms the provided ServerResponse and additional information into the appropriate HAR structure diff --git a/packages/node/src/lib/console-logger.ts b/packages/node/src/lib/shared/console-logger.ts similarity index 100% rename from packages/node/src/lib/console-logger.ts rename to packages/node/src/lib/shared/console-logger.ts diff --git a/packages/node/src/lib/shared/do-send.ts b/packages/node/src/lib/shared/do-send.ts new file mode 100644 index 0000000000..a641272655 --- /dev/null +++ b/packages/node/src/lib/shared/do-send.ts @@ -0,0 +1,26 @@ +// import type { OutgoingLogBody } from './metrics-log'; +// import type { Options } from './options'; + +// import { logger } from './logger'; +// import { metricsAPICall } from './metrics-log'; + +// // eslint-disable-next-line import/no-mutable-exports +// export let queue: OutgoingLogBody[] = []; + +// export function doSend(readmeApiKey: string, options: Options) { +// console.log(options); +// // Copy the queue so we can send all the requests in one batch +// const json = [...queue]; +// // Clear out the queue so we don't resend any data in the future +// queue = []; + +// // Make the log call +// metricsAPICall(readmeApiKey, json, options).catch(err => { +// // Silently discard errors and timeouts. +// if (options.development) { +// logger.error({ message: 'Failed to capture API request.', err }); +// } +// }); + +// logger.debug({ message: 'Queue flushed.', args: { queue } }); +// } diff --git a/packages/node/src/lib/get-project-base-url.ts b/packages/node/src/lib/shared/get-project-base-url.ts similarity index 97% rename from packages/node/src/lib/get-project-base-url.ts rename to packages/node/src/lib/shared/get-project-base-url.ts index b74eb9185f..88787ce9bb 100644 --- a/packages/node/src/lib/get-project-base-url.ts +++ b/packages/node/src/lib/shared/get-project-base-url.ts @@ -4,8 +4,8 @@ import findCacheDir from 'find-cache-dir'; import flatCache from 'flat-cache'; import timeoutSignal from 'timeout-signal'; -import pkg from '../../package.json'; -import config from '../config'; +import pkg from '../../../package.json'; +import config from '../../config'; import { logger } from './logger'; diff --git a/packages/node/src/lib/logger.ts b/packages/node/src/lib/shared/logger.ts similarity index 100% rename from packages/node/src/lib/logger.ts rename to packages/node/src/lib/shared/logger.ts diff --git a/packages/node/src/lib/shared/mask.ts b/packages/node/src/lib/shared/mask.ts new file mode 100644 index 0000000000..da90311008 --- /dev/null +++ b/packages/node/src/lib/shared/mask.ts @@ -0,0 +1,17 @@ +import ssri from 'ssri'; + +/** + * This will generate an integrity hash that looks something like this: + * + * sha512-Naxska/M1INY/thefLQ49sExJ8E+89Q2bz/nC4Pet52iSRPtI9w3Cyg0QdZExt0uUbbnfMJZ0qTabiLJxw6Wrg==?1345 + * + * With the last 4 digits on the end for us to use to identify it later in a list. + */ +export function mask(apiKey: string) { + return ssri + .fromData(apiKey, { + algorithms: ['sha512'], + options: [apiKey.slice(-4)], + }) + .toString(); +} diff --git a/packages/node/src/lib/metrics-log.ts b/packages/node/src/lib/shared/metrics-log.ts similarity index 94% rename from packages/node/src/lib/metrics-log.ts rename to packages/node/src/lib/shared/metrics-log.ts index d9af47731a..f87554f3a8 100644 --- a/packages/node/src/lib/metrics-log.ts +++ b/packages/node/src/lib/shared/metrics-log.ts @@ -1,11 +1,11 @@ -import type { Options } from './log'; +import type { Options } from './options'; import type { Har } from 'har-format'; import type { UUID } from 'node:crypto'; import timeoutSignal from 'timeout-signal'; -import pkg from '../../package.json'; -import config from '../config'; +import pkg from '../../../package.json'; +import config from '../../config'; import { logger } from './logger'; @@ -89,7 +89,11 @@ function getLogIds(body: OutgoingLogBody | OutgoingLogBody[]): LogId { return body._id; } -export function metricsAPICall(readmeAPIKey: string, body: OutgoingLogBody[], options: Options): Promise { +export function metricsAPICall( + readmeAPIKey: string, + body: OutgoingLogBody[], + options: Options, +): Promise { const signal = timeoutSignal(config.timeout); const encodedKey = Buffer.from(`${readmeAPIKey}:`).toString('base64'); diff --git a/packages/node/src/lib/object-to-array.ts b/packages/node/src/lib/shared/object-to-array.ts similarity index 100% rename from packages/node/src/lib/object-to-array.ts rename to packages/node/src/lib/shared/object-to-array.ts diff --git a/packages/node/src/lib/construct-payload.ts b/packages/node/src/lib/shared/options.ts similarity index 50% rename from packages/node/src/lib/construct-payload.ts rename to packages/node/src/lib/shared/options.ts index 425cd1b574..f8cb8f49c7 100644 --- a/packages/node/src/lib/construct-payload.ts +++ b/packages/node/src/lib/shared/options.ts @@ -1,29 +1,5 @@ import type { LoggerStrategy } from './logger'; -import type { OutgoingLogBody } from './metrics-log'; import type { UUID } from 'node:crypto'; -import type { IncomingMessage, ServerResponse } from 'node:http'; -import type { TLSSocket } from 'tls'; - -import { randomUUID } from 'node:crypto'; -import os from 'os'; -import { URL } from 'url'; - -import ssri from 'ssri'; - -import { version } from '../../package.json'; - -import processRequest from './process-request'; -import processResponse from './process-response'; - -/** - * Extracts the protocol string from the incoming request - * - * @param req - * @returns - */ -export function getProto(req: IncomingMessage): 'http' | 'https' { - return (req.socket as TLSSocket).encrypted ? 'https' : 'http'; -} export interface LogOptions { /** @@ -123,67 +99,9 @@ export interface PayloadData { startedDateTime: Date; } -/** - * This will generate an integrity hash that looks something like this: - * - * sha512-Naxska/M1INY/thefLQ49sExJ8E+89Q2bz/nC4Pet52iSRPtI9w3Cyg0QdZExt0uUbbnfMJZ0qTabiLJxw6Wrg==?1345 - * - * With the last 4 digits on the end for us to use to identify it later in a list. - */ -export function mask(apiKey: string) { - return ssri - .fromData(apiKey, { - algorithms: ['sha512'], - options: [apiKey.slice(-4)], - }) - .toString(); -} - -export function constructPayload( - req: IncomingMessage, - res: ServerResponse, - payloadData: PayloadData, - logOptions: LogOptions, -): OutgoingLogBody { - const serverTime = payloadData.responseEndDateTime.getTime() - payloadData.startedDateTime.getTime(); - - return { - _id: payloadData.logId || randomUUID(), - _version: 3, - group: { - id: mask(payloadData.apiKey), - label: payloadData.label, - email: payloadData.email, - }, - clientIPAddress: req.socket.remoteAddress || '', - development: !!logOptions?.development, - request: { - log: { - version: '1.2', - creator: { - name: 'readme-metrics (node)', - version, - // x64-darwin21.3.0/14.19.3 - comment: `${os.arch()}-${os.platform()}${os.release()}/${process.versions.node}`, - }, - entries: [ - { - pageref: payloadData.routePath - ? payloadData.routePath - : new URL(req.url || '', `${getProto(req)}://${req.headers.host}`).toString(), - startedDateTime: payloadData.startedDateTime.toISOString(), - time: serverTime, - request: processRequest(req, payloadData.requestBody, logOptions), - response: processResponse(res, payloadData.responseBody, logOptions), - cache: {}, - timings: { - // This requires us to know the time the request was sent to the server, so we're skipping it for now - wait: 0, - receive: serverTime, - }, - }, - ], - }, - }, - }; +export interface Options extends LogOptions { + baseLogUrl?: string; + bufferLength?: number; + disableMetrics?: boolean; + disableWebhook?: boolean; } diff --git a/packages/node/src/lib/pino-logger.ts b/packages/node/src/lib/shared/pino-logger.ts similarity index 100% rename from packages/node/src/lib/pino-logger.ts rename to packages/node/src/lib/shared/pino-logger.ts diff --git a/packages/node/src/lib/shared/processing-helpers.ts b/packages/node/src/lib/shared/processing-helpers.ts new file mode 100644 index 0000000000..48e964c651 --- /dev/null +++ b/packages/node/src/lib/shared/processing-helpers.ts @@ -0,0 +1,112 @@ +import * as qs from 'querystring'; + +import get from 'lodash/get'; +import merge from 'lodash/merge'; +import pick from 'lodash/pick'; +import set from 'lodash/set'; + +import { logger } from './logger'; +/** + * Ensure we have a string or undefined response for any header. + * + * @param header + * @returns + */ +export function fixHeader(header: string[] | number | string): string | undefined { + if (header === undefined) { + return undefined; + } + + if (Array.isArray(header)) { + return header.join(','); + } + + return String(header); +} + +/** + * Redacts a value by replacing it with a string like [REDACTED 6] + * + * @param value the value to be redacted + * @returns A redacted string potentially containing the length of the original value, if it was a string + */ +export function redactValue(value: unknown) { + const redactedVal = typeof value === 'string' ? ` ${value.length}` : ''; + return `[REDACTED${redactedVal}]`; +} + +/** + * Redacts all the properties in an object + * + * @param obj The data object that is operated upon + * @param redactedPaths a list of paths that point values which should be redacted + * @returns An object with the redacted values + */ +export function redactProperties>(obj: T, redactedPaths: string[] = []): T { + const nextObj = { ...obj }; + return redactedPaths.reduce((acc, path) => { + const value = get(acc, path); + if (value !== undefined) set(acc, path, redactValue(value)); + return acc; + }, nextObj); +} + +/** + * @param obj The data object that is operated upon + * @param cb A callback that is invoked for each value found, the return value being the next value that is set in the returned object + * @returns An object with the replaced values + */ +export function replaceEach>( + obj: T, + cb: (input: unknown) => string, +): Record { + return Object.keys(obj).reduce>((acc, key) => { + const value = obj[key]; + if (typeof value === 'object' && value !== null) { + acc[key] = replaceEach(value as Record, cb); + } else if (value !== undefined) { + acc[key] = cb(value); + } + return acc; + }, {}); +} + +/** + * Redacts everything but the provided fields + * + * @param obj The data object with fields to redact + * @param nonRedactedPaths A list of all object paths that shouldn't be redacted + * @returns A merged objects that is entirely redacted except for the values of the nonRedactedPaths + */ +export function redactOtherProperties>(obj: T, nonRedactedPaths: string[]): T { + const allowedFields = pick(obj, nonRedactedPaths); + const redactedFields = obj ? replaceEach(obj, redactValue) : obj; + return merge(redactedFields, allowedFields) as T; +} + +export function isApplicationJson(mimeType: string) { + if (!mimeType) { + return false; + } + + return ( + ['application/json', 'application/x-json', 'text/json', 'text/x-json'].includes(mimeType) || + mimeType.indexOf('+json') !== -1 + ); +} + +export function parseRequestBody(body: string, mimeType: string): Record | string { + if (mimeType === 'application/x-www-form-urlencoded') { + return qs.parse(body); + } + + if (isApplicationJson(mimeType)) { + try { + return JSON.parse(body); + } catch (err) { + logger.error({ message: 'Error parsing request body JSON.', err }); + } + } + + return body; +} diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 2fbfe9d27e..b29579cbed 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -1,6 +1,6 @@ /* eslint-disable vitest/no-conditional-expect */ /* eslint-disable vitest/no-standalone-expect */ -import type { OutgoingLogBody } from '../src/lib/metrics-log'; +import type { OutgoingLogBody } from '../src/lib/shared/metrics-log'; import type { Express } from 'express'; import * as crypto from 'crypto'; @@ -13,10 +13,10 @@ import request from 'supertest'; import { describe, afterAll, beforeEach, afterEach, expect, it } from 'vitest'; import pkg from '../package.json'; -import * as readmeio from '../src'; import config from '../src/config'; -import { getCache } from '../src/lib/get-project-base-url'; -import { setBackoff } from '../src/lib/metrics-log'; +import * as readmeio from '../src/lib/metrics-node'; +import { getCache } from '../src/lib/shared/get-project-base-url'; +import { setBackoff } from '../src/lib/shared/metrics-log'; import getReadMeApiMock from './helpers/getReadMeApiMock'; import { MockLoggerStrategy } from './lib/logger.test'; diff --git a/packages/node/test/lib/console-logger.test.ts b/packages/node/test/lib/console-logger.test.ts index 89d3928491..59c48125d8 100644 --- a/packages/node/test/lib/console-logger.test.ts +++ b/packages/node/test/lib/console-logger.test.ts @@ -2,7 +2,7 @@ import type { ErrorLog, Log } from 'src/lib/logger'; import { describe, beforeEach, afterEach, vi, expect, it } from 'vitest'; -import ConsoleLogger from '../../src/lib/console-logger'; +import ConsoleLogger from '../../src/lib/shared/console-logger'; describe('ConsoleLogger', () => { let logger: ConsoleLogger; diff --git a/packages/node/test/lib/construct-payload.test.ts b/packages/node/test/lib/construct-payload.test.ts index 96cabeab28..a5bf971c1b 100644 --- a/packages/node/test/lib/construct-payload.test.ts +++ b/packages/node/test/lib/construct-payload.test.ts @@ -1,5 +1,5 @@ -import type { LogOptions, PayloadData } from '../../src/lib/construct-payload'; import type { IncomingMessage, ServerResponse } from 'node:http'; +import type { LogOptions, PayloadData } from 'src/lib/shared/options'; import { createServer } from 'http'; import os from 'os'; @@ -10,7 +10,8 @@ import request from 'supertest'; import { describe, expect, it } from 'vitest'; import pkg from '../../package.json'; -import { constructPayload, mask } from '../../src/lib/construct-payload'; +import { constructPayload } from '../../src/lib/metrics-node/construct-payload'; +import { mask } from '../../src/lib/shared/mask'; function createApp(options?: LogOptions, payloadData?: PayloadData) { const requestListener = function (req: IncomingMessage, res: ServerResponse) { diff --git a/packages/node/test/lib/get-project-base-url.test.ts b/packages/node/test/lib/get-project-base-url.test.ts index 08e7ad6ed1..22e7a781e2 100644 --- a/packages/node/test/lib/get-project-base-url.test.ts +++ b/packages/node/test/lib/get-project-base-url.test.ts @@ -2,9 +2,8 @@ import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { describe, beforeAll, afterAll, afterEach, expect, it } from 'vitest'; -import { getProjectBaseUrl } from '../../src'; import config from '../../src/config'; -import { getCache } from '../../src/lib/get-project-base-url'; +import { getProjectBaseUrl, getCache } from '../../src/lib/shared/get-project-base-url'; import getReadMeApiMock from '../helpers/getReadMeApiMock'; const apiKey = 'mockReadMeApiKey'; diff --git a/packages/node/test/lib/is-request.test.ts b/packages/node/test/lib/is-request.test.ts index eb1ea642db..03443fcd52 100644 --- a/packages/node/test/lib/is-request.test.ts +++ b/packages/node/test/lib/is-request.test.ts @@ -2,7 +2,7 @@ import MockReq from 'mock-req'; import { describe, it, expect } from 'vitest'; -import isRequest from '../../src/lib/is-request'; +import isRequest from '../../src/lib/metrics-node/is-request'; describe('isRequest', function () { it('should detect `text/plain', function () { diff --git a/packages/node/test/lib/log.test.ts b/packages/node/test/lib/log.test.ts index 0875ba3f80..c0f6f36b83 100644 --- a/packages/node/test/lib/log.test.ts +++ b/packages/node/test/lib/log.test.ts @@ -1,13 +1,14 @@ -import type { ExtendedIncomingMessage, ExtendedResponse, Options } from 'src/lib/log'; -import type { GroupingObject } from 'src/lib/metrics-log'; +import type { ExtendedIncomingMessage, ExtendedResponse } from 'src/lib/metrics-node/log'; +import type { GroupingObject } from 'src/lib/shared/metrics-log'; +import type { Options } from 'src/lib/shared/options'; import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; -import { log } from '../../src/lib/log'; -import { metricsAPICall } from '../../src/lib/metrics-log'; +import { log } from '../../src/lib/metrics-node/log'; +import { metricsAPICall } from '../../src/lib/shared/metrics-log'; describe('log', function () { - vi.mock('../../src/lib/metrics-log'); + vi.mock('../../src/lib/shared/metrics-log'); let req: ExtendedIncomingMessage; let res: ExtendedResponse; diff --git a/packages/node/test/lib/logger.test.ts b/packages/node/test/lib/logger.test.ts index fde3616f26..d748eb0c7b 100644 --- a/packages/node/test/lib/logger.test.ts +++ b/packages/node/test/lib/logger.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; -import { type LoggerStrategy, type Log, type ErrorLog, logger } from '../../src/lib/logger'; +import { type LoggerStrategy, type Log, type ErrorLog, logger } from '../../src/lib/shared/logger'; export class MockLoggerStrategy implements LoggerStrategy { debug = vi.fn(); diff --git a/packages/node/test/lib/object-to-array.test.ts b/packages/node/test/lib/object-to-array.test.ts index e69df781c6..7c418cff0a 100644 --- a/packages/node/test/lib/object-to-array.test.ts +++ b/packages/node/test/lib/object-to-array.test.ts @@ -2,7 +2,7 @@ import { URLSearchParams } from 'url'; import { describe, it, expect } from 'vitest'; -import { objectToArray, searchToArray } from '../../src/lib/object-to-array'; +import { objectToArray, searchToArray } from '../../src/lib/shared/object-to-array'; describe('#object-to-array', function () { it('should transform a nested object of query parameters into an array', function () { diff --git a/packages/node/test/lib/process-request.test.ts b/packages/node/test/lib/process-request.test.ts index e052a1e6e8..78012ba654 100644 --- a/packages/node/test/lib/process-request.test.ts +++ b/packages/node/test/lib/process-request.test.ts @@ -1,12 +1,12 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; -import type { LogOptions } from 'src/lib/construct-payload'; +import type { LogOptions } from 'src/lib/shared/options'; import { createServer } from 'http'; import request from 'supertest'; import { describe, expect, it } from 'vitest'; -import processRequest from '../../src/lib/process-request'; +import processRequest from '../../src/lib/metrics-node/process-request'; function createApp(reqOptions?: LogOptions, shouldPreParse = false, bodyOverride?: Record) { const requestListener = function (req: IncomingMessage, res: ServerResponse) { diff --git a/packages/node/test/lib/process-response.test.ts b/packages/node/test/lib/process-response.test.ts index 0ed30a57ae..989f15f7a1 100644 --- a/packages/node/test/lib/process-response.test.ts +++ b/packages/node/test/lib/process-response.test.ts @@ -3,7 +3,7 @@ import * as http from 'http'; import request from 'supertest'; import { describe, it, expect } from 'vitest'; -import processResponse from '../../src/lib/process-response'; +import processResponse from '../../src/lib/metrics-node/process-response'; interface TestServerResponse extends http.ServerResponse { __bodyCache?: string;