From 5439cee3f909edae5abd9c05adb200b9a0ab718e Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:51:58 +0100 Subject: [PATCH] feat(api): track request content length (#3316) ## Changes Fixes https://linear.app/nango/issue/NAN-2540/track-request-content-length - API track request content length Inside traces as tag and as a metric to be able to graph it properly. I'm not the best at math so hopefully the computation is correct. ![Screenshot 2025-01-16 at 11 31 05](https://github.com/user-attachments/assets/1732262f-c8a0-4891-b42d-458920ce214b) ![Screenshot 2025-01-16 at 11 24 19](https://github.com/user-attachments/assets/cedd961c-56e8-490c-9004-6d61438dfe1f) --- packages/server/lib/utils/asyncWrapper.ts | 11 ++++++++++- packages/utils/lib/express/route.ts | 12 +++++++++++- packages/utils/lib/telemetry/metrics.ts | 8 +++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/server/lib/utils/asyncWrapper.ts b/packages/server/lib/utils/asyncWrapper.ts index c55a1590fab..0b3d5cd8944 100644 --- a/packages/server/lib/utils/asyncWrapper.ts +++ b/packages/server/lib/utils/asyncWrapper.ts @@ -3,6 +3,7 @@ import type { Endpoint } from '@nangohq/types'; import type { RequestHandler, Request, Response, NextFunction } from 'express'; import { isAsyncFunction } from 'util/types'; import type { RequestLocals } from './express.js'; +import { metrics } from '@nangohq/utils'; export function asyncWrapper, Locals extends Record = Required>( fn: ( @@ -13,7 +14,15 @@ export function asyncWrapper, Locals extends Rec ): RequestHandler { return (req, res, next) => { const active = tracer.scope().active(); - active?.setTag('http.route', req.route?.path || req.originalUrl); + if (active) { + active.setTag('http.route', req.route?.path || req.originalUrl); + const contentLength = req.header('content-length'); + if (contentLength) { + const int = parseInt(contentLength, 10); + active.setTag('http.request.body.size', int); + metrics.histogram(metrics.Types.API_REQUEST_CONTENT_LENGTH, int); + } + } if (isAsyncFunction(fn)) { return (fn(req, res, next) as unknown as Promise).catch((err: unknown) => { next(err); diff --git a/packages/utils/lib/express/route.ts b/packages/utils/lib/express/route.ts index 4627511ba0d..065c2953ecb 100644 --- a/packages/utils/lib/express/route.ts +++ b/packages/utils/lib/express/route.ts @@ -1,6 +1,7 @@ import tracer from 'dd-trace'; import type { Request, Response, NextFunction, Express } from 'express'; import type { Endpoint } from '@nangohq/types'; +import * as metrics from '../telemetry/metrics.js'; export type EndpointRequest> = Request; export type EndpointResponse> = Response; @@ -18,7 +19,16 @@ export interface RouteHandler> extends Route { export const createRoute = >(server: Express, rh: RouteHandler): void => { const safeHandler = (req: EndpointRequest, res: EndpointResponse, next: NextFunction): void => { const active = tracer.scope().active(); - active?.setTag('http.route', req.route?.path || req.originalUrl); + if (active) { + active?.setTag('http.route', req.route?.path || req.originalUrl); + const contentLength = req.header('content-length'); + if (contentLength) { + const int = parseInt(contentLength, 10); + active.setTag('http.request.body.size', int); + metrics.histogram(metrics.Types.API_REQUEST_CONTENT_LENGTH, int); + } + } + Promise.resolve(rh.handler(req, res, next)).catch((err: unknown) => next(err)); }; diff --git a/packages/utils/lib/telemetry/metrics.ts b/packages/utils/lib/telemetry/metrics.ts index ebef0100d11..349c0f7d07a 100644 --- a/packages/utils/lib/telemetry/metrics.ts +++ b/packages/utils/lib/telemetry/metrics.ts @@ -54,7 +54,9 @@ export enum Types { ORCH_TASKS_SUCCEEDED = 'nango.orch.tasks.succeeded', ORCH_TASKS_FAILED = 'nango.orch.tasks.failed', ORCH_TASKS_EXPIRED = 'nango.orch.tasks.expired', - ORCH_TASKS_CANCELLED = 'nango.orch.tasks.cancelled' + ORCH_TASKS_CANCELLED = 'nango.orch.tasks.cancelled', + + API_REQUEST_CONTENT_LENGTH = 'nango.api.request.content_length' } export function increment(metricName: Types, value = 1, dimensions?: Record): void { @@ -69,6 +71,10 @@ export function gauge(metricName: Types, value?: number): void { tracer.dogstatsd.gauge(metricName, value ?? 1); } +export function histogram(metricName: Types, value: number): void { + tracer.dogstatsd.histogram(metricName, value); +} + export function duration(metricName: Types, value: number): void { tracer.dogstatsd.distribution(metricName, value); }