diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 6992c31..dc39371 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -49,3 +49,19 @@ export function getStatusCodeGroup(statusCode: number) { } } } + +/** + * Make URL start with slash and have maximum depth. + */ +export const normalizeUrlWithTrimming = (url: string, maxDepth = 0) => { + if (maxDepth < 0) { + throw new Error('maxDepth must be bigger than 0') + } + + if (maxDepth === 0) { + return url + } + + const withStartingSlashUrl = url.startsWith('/') ? url : `/${url}` + return withStartingSlashUrl.split('/', maxDepth + 1).join('/') +} diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 5aff60f..2444142 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -3,6 +3,8 @@ import path from 'node:path' import {glob} from 'glob' +import {normalizeUrlWithTrimming} from '../metrics/util' + interface RouteInfo { page: string regex: string @@ -40,7 +42,7 @@ export function getNextRoutesManifest() { * Creates a function that normalizes Next.js URLs to route patterns for metrics * @returns Function that takes a URL and returns the normalized route pattern */ -export function createNextRoutesUrlGroup() { +export function createNextRoutesUrlGroup(maxNormalizedUrlDepth: number) { const {basePath, routes} = getNextRoutesManifest() return function getUrlGroup(originalUrl: string) { @@ -69,6 +71,6 @@ export function createNextRoutesUrlGroup() { } } - return withoutBasePathUrl + return normalizeUrlWithTrimming(withoutBasePathUrl, maxNormalizedUrlDepth) } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 72e15ee..91b5a56 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -10,4 +10,6 @@ export interface CommonPrometheusExporterOptions { metricsPath?: string /** Whether to collect default Node.js metrics */ collectDefaultMetrics?: boolean + /** URL depth limit for path normalization */ + maxNormalizedUrlDepth?: number } diff --git a/packages/hono/src/exporter.ts b/packages/hono/src/exporter.ts index bced9fa..4e65538 100644 --- a/packages/hono/src/exporter.ts +++ b/packages/hono/src/exporter.ts @@ -35,6 +35,7 @@ export async function createHonoPrometheusExporter({ bypass, normalizePath, formatStatusCode, + maxNormalizedUrlDepth, }: HonoPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -65,7 +66,13 @@ export async function createHonoPrometheusExporter({ registerGaugeUp() - const middleware = getHonoMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode}) + const middleware = getHonoMetricsMiddleware({ + nextjs, + bypass, + normalizePath, + formatStatusCode, + maxNormalizedUrlDepth, + }) // PM2 mode: aggregated metrics from all workers // Standalone mode: single process metrics diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index db89e08..8cd1997 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -4,6 +4,7 @@ import { getStatusCodeGroup, isBypassPath, startTraceHistogram, + trimUrl, } from '@naverpay/prometheus-core' import type {HonoPrometheusExporterOptions} from '../types' @@ -20,12 +21,20 @@ export function getHonoMetricsMiddleware({ bypass, normalizePath, formatStatusCode, -}: Pick): MiddlewareHandler { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup() : undefined + maxNormalizedUrlDepth, +}: Pick< + HonoPrometheusExporterOptions, + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxNormalizedUrlDepth' +>): MiddlewareHandler { + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxNormalizedUrlDepth) : undefined const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) - return normalizeNextRoutesPath?.(url.href) || normalizePath?.(context) || url.pathname + return ( + normalizeNextRoutesPath?.(url.href) || + normalizePath?.(context) || + trimUrl(url.pathname, maxNormalizedUrlDepth) + ) } return async (context, next) => { diff --git a/packages/koa/src/exporter.ts b/packages/koa/src/exporter.ts index 3d47457..0a0d701 100644 --- a/packages/koa/src/exporter.ts +++ b/packages/koa/src/exporter.ts @@ -35,6 +35,7 @@ export async function createKoaPrometheusExporter({ bypass, normalizePath, formatStatusCode, + maxNormalizedUrlDepth, }: KoaPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -65,7 +66,7 @@ export async function createKoaPrometheusExporter({ registerGaugeUp() - const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode}) + const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode, maxNormalizedUrlDepth}) // PM2 mode: aggregated metrics from all workers // Standalone mode: single process metrics diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index ca3c437..bf478c4 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -4,6 +4,7 @@ import { getStatusCodeGroup, isBypassPath, startTraceHistogram, + trimUrl, } from '@naverpay/prometheus-core' import onFinished from 'on-finished' @@ -21,11 +22,15 @@ export function getKoaMetricsMiddleware({ bypass, normalizePath, formatStatusCode, -}: Pick): Middleware { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup() : undefined + maxNormalizedUrlDepth, +}: Pick< + KoaPrometheusExporterOptions, + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxNormalizedUrlDepth' +>): Middleware { + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxNormalizedUrlDepth) : undefined const extendedNormalizePath = (context: Context) => { - return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || context.path + return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || trimUrl(context.path) } return async (context, next) => { diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index a26b294..0efcf60 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -50,6 +50,7 @@ export async function createNextServerWithMetrics({ bypass, normalizePath, formatStatusCode, + maxNormalizedUrlDepth, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() @@ -73,7 +74,7 @@ export async function createNextServerWithMetrics({ registerGaugeUp() } - const normalizeNextRoutesPath = createNextRoutesUrlGroup() + const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxNormalizedUrlDepth) const server = http.createServer(async (request, response) => { if (!pm2) {