From d9fbee47c0fc7cc7cde1124a2eddb8a4e922298d Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 11:23:40 +0900 Subject: [PATCH 01/14] [#10] feat: Add new parameter `maxDepth` which trims URL when using `normalizeNextRoutesPath` --- packages/core/src/next/utils.ts | 7 +++++-- packages/core/src/types.ts | 2 ++ packages/hono/src/middleware/metrics.ts | 8 ++++++-- packages/koa/src/middleware/metrics.ts | 8 ++++++-- packages/next/src/index.ts | 3 ++- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 5aff60f..6c2c0b8 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -40,7 +40,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(maxDepth: number) { const {basePath, routes} = getNextRoutesManifest() return function getUrlGroup(originalUrl: string) { @@ -65,7 +65,10 @@ export function createNextRoutesUrlGroup() { for (const {regex, page} of routes) { if (regex.test(withoutBasePathUrl)) { - return page + return `/${page + .split('/') + .slice(1, maxDepth + 1) + .join('/')}` } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 72e15ee..b5a8f9d 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 + /** Max number to trim path */ + maxDepth?: number } diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index db89e08..cf37717 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -20,8 +20,12 @@ export function getHonoMetricsMiddleware({ bypass, normalizePath, formatStatusCode, -}: Pick): MiddlewareHandler { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup() : undefined + maxDepth = Number.MAX_SAFE_INTEGER, +}: Pick< + HonoPrometheusExporterOptions, + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' +>): MiddlewareHandler { + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index ca3c437..879531d 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -21,8 +21,12 @@ export function getKoaMetricsMiddleware({ bypass, normalizePath, formatStatusCode, -}: Pick): Middleware { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup() : undefined + maxDepth = Number.MAX_SAFE_INTEGER, +}: Pick< + KoaPrometheusExporterOptions, + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' +>): Middleware { + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined const extendedNormalizePath = (context: Context) => { return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || context.path diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index a26b294..8dace55 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -50,6 +50,7 @@ export async function createNextServerWithMetrics({ bypass, normalizePath, formatStatusCode, + maxDepth = Number.MAX_SAFE_INTEGER, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() @@ -73,7 +74,7 @@ export async function createNextServerWithMetrics({ registerGaugeUp() } - const normalizeNextRoutesPath = createNextRoutesUrlGroup() + const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth) const server = http.createServer(async (request, response) => { if (!pm2) { From da104a186416133d144c279fa94805d2db0df63c Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 14:10:47 +0900 Subject: [PATCH 02/14] [#10] feat: Add new parameter `trimDynamic` which trims dynamic routes when using `normalizeNextRoutesPath` --- packages/core/src/next/utils.ts | 28 ++++++++++++++----------- packages/core/src/types.ts | 2 ++ packages/hono/src/middleware/metrics.ts | 5 +++-- packages/koa/src/middleware/metrics.ts | 5 +++-- packages/next/src/index.ts | 3 ++- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 6c2c0b8..e11a6ef 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -26,13 +26,15 @@ export function getNextRoutesManifest() { throw new Error('No routes-manifest.json found') } const manifestJson = JSON.parse(fs.readFileSync(manifestsPath[0], 'utf8')) as RoutesManifest - const {basePath, dynamicRoutes, staticRoutes} = manifestJson - const routes = [...dynamicRoutes, ...staticRoutes].map(({page, regex}) => ({page, regex: new RegExp(regex)})) - return {basePath, routes} + const {basePath, dynamicRoutes: orgDynamicRoutes, staticRoutes: orgStaticRoutes} = manifestJson + const dynamicRoutes = orgDynamicRoutes.map(({page, regex}) => ({page, regex: new RegExp(regex)})) + const staticRoutes = orgStaticRoutes.map(({page, regex}) => ({page, regex: new RegExp(regex)})) + const routes = [...dynamicRoutes, ...staticRoutes] + return {basePath, dynamicRoutes, staticRoutes, routes} } catch { // eslint-disable-next-line no-console console.warn('No routes manifest found. Please make sure you are running in a Next.js environment.') - return {basePath: '', routes: []} + return {basePath: '', dynamicRoutes: [], staticRoutes: [], routes: []} } } @@ -40,8 +42,8 @@ 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(maxDepth: number) { - const {basePath, routes} = getNextRoutesManifest() +export function createNextRoutesUrlGroup(maxDepth: number, trimDynamic: boolean) { + const {basePath, dynamicRoutes, routes} = getNextRoutesManifest() return function getUrlGroup(originalUrl: string) { const [orgRoute] = originalUrl.split('?') @@ -63,12 +65,14 @@ export function createNextRoutesUrlGroup(maxDepth: number) { return 'STATIC' } - for (const {regex, page} of routes) { - if (regex.test(withoutBasePathUrl)) { - return `/${page - .split('/') - .slice(1, maxDepth + 1) - .join('/')}` + for (const it of routes) { + if (it.regex.test(withoutBasePathUrl)) { + return dynamicRoutes.includes(it) && !trimDynamic + ? it.page + : `/${it.page + .split('/') + .slice(1, maxDepth + 1) + .join('/')}` } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b5a8f9d..a4dd743 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -12,4 +12,6 @@ export interface CommonPrometheusExporterOptions { collectDefaultMetrics?: boolean /** Max number to trim path */ maxDepth?: number + /** Whether to trim dynamic path */ + trimDynamic?: boolean } diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index cf37717..7984546 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -21,11 +21,12 @@ export function getHonoMetricsMiddleware({ normalizePath, formatStatusCode, maxDepth = Number.MAX_SAFE_INTEGER, + trimDynamic = false, }: Pick< HonoPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' >): MiddlewareHandler { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth, trimDynamic) : undefined const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index 879531d..e7f4df0 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -22,11 +22,12 @@ export function getKoaMetricsMiddleware({ normalizePath, formatStatusCode, maxDepth = Number.MAX_SAFE_INTEGER, + trimDynamic = false, }: Pick< KoaPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' >): Middleware { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth, trimDynamic) : undefined const extendedNormalizePath = (context: Context) => { return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || context.path diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index 8dace55..39ddbe3 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -51,6 +51,7 @@ export async function createNextServerWithMetrics({ normalizePath, formatStatusCode, maxDepth = Number.MAX_SAFE_INTEGER, + trimDynamic = false, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() @@ -74,7 +75,7 @@ export async function createNextServerWithMetrics({ registerGaugeUp() } - const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth) + const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth, trimDynamic) const server = http.createServer(async (request, response) => { if (!pm2) { From e6e56394fdbc69dfb881ed45d0a8382cfeb4886e Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 15:17:40 +0900 Subject: [PATCH 03/14] [#10] bug: Fix hono and koa didn't accept additional parameters --- packages/hono/src/exporter.ts | 11 ++++++++++- packages/hono/src/middleware/metrics.ts | 4 ++-- packages/koa/src/exporter.ts | 4 +++- packages/koa/src/middleware/metrics.ts | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/hono/src/exporter.ts b/packages/hono/src/exporter.ts index bced9fa..b9bb106 100644 --- a/packages/hono/src/exporter.ts +++ b/packages/hono/src/exporter.ts @@ -35,6 +35,8 @@ export async function createHonoPrometheusExporter({ bypass, normalizePath, formatStatusCode, + maxDepth = Number.MAX_SAFE_INTEGER, + trimDynamic = false, }: HonoPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -65,7 +67,14 @@ export async function createHonoPrometheusExporter({ registerGaugeUp() - const middleware = getHonoMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode}) + const middleware = getHonoMetricsMiddleware({ + nextjs, + bypass, + normalizePath, + formatStatusCode, + maxDepth, + trimDynamic, + }) // 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 7984546..dc7c830 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -20,8 +20,8 @@ export function getHonoMetricsMiddleware({ bypass, normalizePath, formatStatusCode, - maxDepth = Number.MAX_SAFE_INTEGER, - trimDynamic = false, + maxDepth, + trimDynamic, }: Pick< HonoPrometheusExporterOptions, 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' diff --git a/packages/koa/src/exporter.ts b/packages/koa/src/exporter.ts index 3d47457..2853914 100644 --- a/packages/koa/src/exporter.ts +++ b/packages/koa/src/exporter.ts @@ -35,6 +35,8 @@ export async function createKoaPrometheusExporter({ bypass, normalizePath, formatStatusCode, + maxDepth = Number.MAX_SAFE_INTEGER, + trimDynamic = false, }: KoaPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -65,7 +67,7 @@ export async function createKoaPrometheusExporter({ registerGaugeUp() - const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode}) + const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode, maxDepth, trimDynamic}) // 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 e7f4df0..232ba83 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -21,8 +21,8 @@ export function getKoaMetricsMiddleware({ bypass, normalizePath, formatStatusCode, - maxDepth = Number.MAX_SAFE_INTEGER, - trimDynamic = false, + maxDepth, + trimDynamic, }: Pick< KoaPrometheusExporterOptions, 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' From 0707a6856ae815325ee8e328b89100cc46c8608a Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 15:53:50 +0900 Subject: [PATCH 04/14] Revert "[#10] feat: Add new parameter `trimDynamic` which trims dynamic routes when using `normalizeNextRoutesPath`" This reverts commit da104a186416133d144c279fa94805d2db0df63c. --- packages/core/src/next/utils.ts | 28 +++++++++++-------------- packages/core/src/types.ts | 2 -- packages/hono/src/middleware/metrics.ts | 5 ++--- packages/koa/src/middleware/metrics.ts | 5 ++--- packages/next/src/index.ts | 3 +-- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index e11a6ef..6c2c0b8 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -26,15 +26,13 @@ export function getNextRoutesManifest() { throw new Error('No routes-manifest.json found') } const manifestJson = JSON.parse(fs.readFileSync(manifestsPath[0], 'utf8')) as RoutesManifest - const {basePath, dynamicRoutes: orgDynamicRoutes, staticRoutes: orgStaticRoutes} = manifestJson - const dynamicRoutes = orgDynamicRoutes.map(({page, regex}) => ({page, regex: new RegExp(regex)})) - const staticRoutes = orgStaticRoutes.map(({page, regex}) => ({page, regex: new RegExp(regex)})) - const routes = [...dynamicRoutes, ...staticRoutes] - return {basePath, dynamicRoutes, staticRoutes, routes} + const {basePath, dynamicRoutes, staticRoutes} = manifestJson + const routes = [...dynamicRoutes, ...staticRoutes].map(({page, regex}) => ({page, regex: new RegExp(regex)})) + return {basePath, routes} } catch { // eslint-disable-next-line no-console console.warn('No routes manifest found. Please make sure you are running in a Next.js environment.') - return {basePath: '', dynamicRoutes: [], staticRoutes: [], routes: []} + return {basePath: '', routes: []} } } @@ -42,8 +40,8 @@ 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(maxDepth: number, trimDynamic: boolean) { - const {basePath, dynamicRoutes, routes} = getNextRoutesManifest() +export function createNextRoutesUrlGroup(maxDepth: number) { + const {basePath, routes} = getNextRoutesManifest() return function getUrlGroup(originalUrl: string) { const [orgRoute] = originalUrl.split('?') @@ -65,14 +63,12 @@ export function createNextRoutesUrlGroup(maxDepth: number, trimDynamic: boolean) return 'STATIC' } - for (const it of routes) { - if (it.regex.test(withoutBasePathUrl)) { - return dynamicRoutes.includes(it) && !trimDynamic - ? it.page - : `/${it.page - .split('/') - .slice(1, maxDepth + 1) - .join('/')}` + for (const {regex, page} of routes) { + if (regex.test(withoutBasePathUrl)) { + return `/${page + .split('/') + .slice(1, maxDepth + 1) + .join('/')}` } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a4dd743..b5a8f9d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -12,6 +12,4 @@ export interface CommonPrometheusExporterOptions { collectDefaultMetrics?: boolean /** Max number to trim path */ maxDepth?: number - /** Whether to trim dynamic path */ - trimDynamic?: boolean } diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index dc7c830..c0b77fe 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -21,12 +21,11 @@ export function getHonoMetricsMiddleware({ normalizePath, formatStatusCode, maxDepth, - trimDynamic, }: Pick< HonoPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' >): MiddlewareHandler { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth, trimDynamic) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index 232ba83..b7c65ea 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -22,12 +22,11 @@ export function getKoaMetricsMiddleware({ normalizePath, formatStatusCode, maxDepth, - trimDynamic, }: Pick< KoaPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' | 'trimDynamic' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' >): Middleware { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth, trimDynamic) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined const extendedNormalizePath = (context: Context) => { return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || context.path diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index 39ddbe3..8dace55 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -51,7 +51,6 @@ export async function createNextServerWithMetrics({ normalizePath, formatStatusCode, maxDepth = Number.MAX_SAFE_INTEGER, - trimDynamic = false, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() @@ -75,7 +74,7 @@ export async function createNextServerWithMetrics({ registerGaugeUp() } - const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth, trimDynamic) + const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth) const server = http.createServer(async (request, response) => { if (!pm2) { From 1d583254055646957fed9eb184f865a06597c642 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 15:55:10 +0900 Subject: [PATCH 05/14] [#10] revert: Remove trimDynamic --- packages/hono/src/exporter.ts | 4 +--- packages/koa/src/exporter.ts | 5 ++--- packages/next/src/index.ts | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/hono/src/exporter.ts b/packages/hono/src/exporter.ts index b9bb106..c6ded70 100644 --- a/packages/hono/src/exporter.ts +++ b/packages/hono/src/exporter.ts @@ -35,8 +35,7 @@ export async function createHonoPrometheusExporter({ bypass, normalizePath, formatStatusCode, - maxDepth = Number.MAX_SAFE_INTEGER, - trimDynamic = false, + maxDepth, }: HonoPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -73,7 +72,6 @@ export async function createHonoPrometheusExporter({ normalizePath, formatStatusCode, maxDepth, - trimDynamic, }) // PM2 mode: aggregated metrics from all workers diff --git a/packages/koa/src/exporter.ts b/packages/koa/src/exporter.ts index 2853914..415db7c 100644 --- a/packages/koa/src/exporter.ts +++ b/packages/koa/src/exporter.ts @@ -35,8 +35,7 @@ export async function createKoaPrometheusExporter({ bypass, normalizePath, formatStatusCode, - maxDepth = Number.MAX_SAFE_INTEGER, - trimDynamic = false, + maxDepth, }: KoaPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -67,7 +66,7 @@ export async function createKoaPrometheusExporter({ registerGaugeUp() - const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode, maxDepth, trimDynamic}) + const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode, maxDepth}) // PM2 mode: aggregated metrics from all workers // Standalone mode: single process metrics diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index 8dace55..fd81c7d 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -50,7 +50,7 @@ export async function createNextServerWithMetrics({ bypass, normalizePath, formatStatusCode, - maxDepth = Number.MAX_SAFE_INTEGER, + maxDepth, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() From 5fc56f34ed1867e32ad8d7904eafbb5525d20812 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 16 Mar 2026 17:52:00 +0900 Subject: [PATCH 06/14] [#10] Fix: Only apply to default case --- packages/core/src/next/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 6c2c0b8..636f91d 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -65,13 +65,10 @@ export function createNextRoutesUrlGroup(maxDepth: number) { for (const {regex, page} of routes) { if (regex.test(withoutBasePathUrl)) { - return `/${page - .split('/') - .slice(1, maxDepth + 1) - .join('/')}` + return page } } - return withoutBasePathUrl + return typeof maxDepth === 'number' ? withoutBasePathUrl.split('/', maxDepth + 1).join('/') : withoutBasePathUrl } } From 36166a897c123ce7c76be29cb331e130cacd280b Mon Sep 17 00:00:00 2001 From: pcfulife Date: Tue, 17 Mar 2026 11:51:58 +0900 Subject: [PATCH 07/14] [#10] Fix: Trim URL for unnormalized case --- packages/core/src/metrics/util.ts | 9 +++++++++ packages/core/src/next/utils.ts | 4 +++- packages/hono/src/middleware/metrics.ts | 3 ++- packages/koa/src/middleware/metrics.ts | 3 ++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 6992c31..9c03344 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -49,3 +49,12 @@ export function getStatusCodeGroup(statusCode: number) { } } } + +export const trimUrl = (url: string, maxDepth?: number) => { + if (typeof maxDepth === 'number') { + const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url + return withStartingSlashUrl.split('/', maxDepth + 1).join('/') + } else { + return url + } +} diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 636f91d..561a6d1 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 {trimUrl} from '../metrics/util' + interface RouteInfo { page: string regex: string @@ -69,6 +71,6 @@ export function createNextRoutesUrlGroup(maxDepth: number) { } } - return typeof maxDepth === 'number' ? withoutBasePathUrl.split('/', maxDepth + 1).join('/') : withoutBasePathUrl + return trimUrl(withoutBasePathUrl, maxDepth) } } diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index c0b77fe..ea8239f 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' @@ -29,7 +30,7 @@ export function getHonoMetricsMiddleware({ 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, maxDepth) } return async (context, next) => { diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index b7c65ea..b9255fb 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' @@ -29,7 +30,7 @@ export function getKoaMetricsMiddleware({ const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : 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) => { From 7d1d3bfe0d89c472e33555dd2f27525a7308c134 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Tue, 17 Mar 2026 13:50:18 +0900 Subject: [PATCH 08/14] [#10] Fix: trimUrl always returns withStartingSlashUrl --- packages/core/src/metrics/util.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 9c03344..65f9d83 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -51,10 +51,6 @@ export function getStatusCodeGroup(statusCode: number) { } export const trimUrl = (url: string, maxDepth?: number) => { - if (typeof maxDepth === 'number') { - const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url - return withStartingSlashUrl.split('/', maxDepth + 1).join('/') - } else { - return url - } + const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url + return typeof maxDepth === 'number' ? withStartingSlashUrl.split('/', maxDepth + 1).join('/') : withStartingSlashUrl } From 95637a8bf4daa30b0bec5eade8879bfc3ee3125b Mon Sep 17 00:00:00 2001 From: pcfulife Date: Thu, 19 Mar 2026 18:18:56 +0900 Subject: [PATCH 09/14] [#10] Fix: trimUrl now accepts only positive number or undefined --- packages/core/src/metrics/util.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 65f9d83..29b05b9 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -51,6 +51,14 @@ export function getStatusCodeGroup(statusCode: number) { } export const trimUrl = (url: string, maxDepth?: number) => { - const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url - return typeof maxDepth === 'number' ? withStartingSlashUrl.split('/', maxDepth + 1).join('/') : withStartingSlashUrl + if (typeof maxDepth === 'number') { + if (maxDepth < 0) { + throw new Error('maxDepth must be bigger than 0') + } + + const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url + return withStartingSlashUrl.split('/', maxDepth + 1).join('/') + } + + return url } From 17e3279f7ba9d89fb9c2796634a36e64d9524d26 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Fri, 20 Mar 2026 12:25:22 +0900 Subject: [PATCH 10/14] [#10] Fix: trimUrl now follow original starting slash of URL --- packages/core/src/metrics/util.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 29b05b9..95d7b81 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -56,8 +56,18 @@ export const trimUrl = (url: string, maxDepth?: number) => { throw new Error('maxDepth must be bigger than 0') } - const withStartingSlashUrl = url.startsWith('/') ? url : '/' + url - return withStartingSlashUrl.split('/', maxDepth + 1).join('/') + let nonEmptyUrlTokenCount = 0 + const urlTokens = [] + for (const token of url.split('/')) { + urlTokens.push(token) + if (token) { + nonEmptyUrlTokenCount = nonEmptyUrlTokenCount + 1 + } + if (nonEmptyUrlTokenCount === maxDepth) { + break + } + } + return urlTokens.join('/') } return url From 6eea4c021264e904cf1cccd9a425de8a282e33e8 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Fri, 20 Mar 2026 13:10:52 +0900 Subject: [PATCH 11/14] [#10] refactor: More concrete comment --- packages/core/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b5a8f9d..d0dff24 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -10,6 +10,6 @@ export interface CommonPrometheusExporterOptions { metricsPath?: string /** Whether to collect default Node.js metrics */ collectDefaultMetrics?: boolean - /** Max number to trim path */ + /** URL depth limit for path normalization */ maxDepth?: number } From 9962d0b1947b5f409988275d009f91032989d047 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 23 Mar 2026 13:07:32 +0900 Subject: [PATCH 12/14] [#10] refactor: Rename to normalizeUrlWithTrimming and relogic it. --- packages/core/src/metrics/util.ts | 29 +++++++++++------------------ packages/core/src/next/utils.ts | 4 ++-- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/core/src/metrics/util.ts b/packages/core/src/metrics/util.ts index 95d7b81..dc39371 100644 --- a/packages/core/src/metrics/util.ts +++ b/packages/core/src/metrics/util.ts @@ -50,25 +50,18 @@ export function getStatusCodeGroup(statusCode: number) { } } -export const trimUrl = (url: string, maxDepth?: number) => { - if (typeof maxDepth === 'number') { - if (maxDepth < 0) { - throw new Error('maxDepth must be bigger than 0') - } +/** + * 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') + } - let nonEmptyUrlTokenCount = 0 - const urlTokens = [] - for (const token of url.split('/')) { - urlTokens.push(token) - if (token) { - nonEmptyUrlTokenCount = nonEmptyUrlTokenCount + 1 - } - if (nonEmptyUrlTokenCount === maxDepth) { - break - } - } - return urlTokens.join('/') + if (maxDepth === 0) { + return url } - 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 561a6d1..1250e39 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -3,7 +3,7 @@ import path from 'node:path' import {glob} from 'glob' -import {trimUrl} from '../metrics/util' +import {normalizeUrlWithTrimming} from '../metrics/util' interface RouteInfo { page: string @@ -71,6 +71,6 @@ export function createNextRoutesUrlGroup(maxDepth: number) { } } - return trimUrl(withoutBasePathUrl, maxDepth) + return normalizeUrlWithTrimming(withoutBasePathUrl, maxDepth) } } From ea93a2c152056f2316b31f0aee943d2292df1e49 Mon Sep 17 00:00:00 2001 From: pcfulife Date: Mon, 23 Mar 2026 13:49:24 +0900 Subject: [PATCH 13/14] [#10] Refactor: Rename parameter --- packages/core/src/next/utils.ts | 4 ++-- packages/core/src/types.ts | 2 +- packages/hono/src/exporter.ts | 4 ++-- packages/hono/src/middleware/metrics.ts | 12 ++++++++---- packages/koa/src/exporter.ts | 4 ++-- packages/koa/src/middleware/metrics.ts | 6 +++--- packages/next/src/index.ts | 4 ++-- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/core/src/next/utils.ts b/packages/core/src/next/utils.ts index 1250e39..2444142 100644 --- a/packages/core/src/next/utils.ts +++ b/packages/core/src/next/utils.ts @@ -42,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(maxDepth: number) { +export function createNextRoutesUrlGroup(maxNormalizedUrlDepth: number) { const {basePath, routes} = getNextRoutesManifest() return function getUrlGroup(originalUrl: string) { @@ -71,6 +71,6 @@ export function createNextRoutesUrlGroup(maxDepth: number) { } } - return normalizeUrlWithTrimming(withoutBasePathUrl, maxDepth) + return normalizeUrlWithTrimming(withoutBasePathUrl, maxNormalizedUrlDepth) } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d0dff24..91b5a56 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -11,5 +11,5 @@ export interface CommonPrometheusExporterOptions { /** Whether to collect default Node.js metrics */ collectDefaultMetrics?: boolean /** URL depth limit for path normalization */ - maxDepth?: number + maxNormalizedUrlDepth?: number } diff --git a/packages/hono/src/exporter.ts b/packages/hono/src/exporter.ts index c6ded70..4e65538 100644 --- a/packages/hono/src/exporter.ts +++ b/packages/hono/src/exporter.ts @@ -35,7 +35,7 @@ export async function createHonoPrometheusExporter({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }: HonoPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -71,7 +71,7 @@ export async function createHonoPrometheusExporter({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }) // PM2 mode: aggregated metrics from all workers diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index ea8239f..8cd1997 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -21,16 +21,20 @@ export function getHonoMetricsMiddleware({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }: Pick< HonoPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxNormalizedUrlDepth' >): MiddlewareHandler { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxNormalizedUrlDepth) : undefined const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) - return normalizeNextRoutesPath?.(url.href) || normalizePath?.(context) || trimUrl(url.pathname, maxDepth) + 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 415db7c..0a0d701 100644 --- a/packages/koa/src/exporter.ts +++ b/packages/koa/src/exporter.ts @@ -35,7 +35,7 @@ export async function createKoaPrometheusExporter({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }: KoaPrometheusExporterOptions) { // Disabled: return noop if (!enabled) { @@ -66,7 +66,7 @@ export async function createKoaPrometheusExporter({ registerGaugeUp() - const middleware = getKoaMetricsMiddleware({nextjs, bypass, normalizePath, formatStatusCode, maxDepth}) + 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 b9255fb..bf478c4 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -22,12 +22,12 @@ export function getKoaMetricsMiddleware({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }: Pick< KoaPrometheusExporterOptions, - 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxDepth' + 'nextjs' | 'bypass' | 'normalizePath' | 'formatStatusCode' | 'maxNormalizedUrlDepth' >): Middleware { - const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxDepth) : undefined + const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxNormalizedUrlDepth) : undefined const extendedNormalizePath = (context: Context) => { return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || trimUrl(context.path) diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts index fd81c7d..0efcf60 100644 --- a/packages/next/src/index.ts +++ b/packages/next/src/index.ts @@ -50,7 +50,7 @@ export async function createNextServerWithMetrics({ bypass, normalizePath, formatStatusCode, - maxDepth, + maxNormalizedUrlDepth, }: NextjsPrometheusExporterOptions) { const app = next(nextOptions) await app.prepare() @@ -74,7 +74,7 @@ export async function createNextServerWithMetrics({ registerGaugeUp() } - const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxDepth) + const normalizeNextRoutesPath = createNextRoutesUrlGroup(maxNormalizedUrlDepth) const server = http.createServer(async (request, response) => { if (!pm2) { From 4d7defd0f6e10e130e032d3751d065e731992e6f Mon Sep 17 00:00:00 2001 From: pcfulife Date: Tue, 31 Mar 2026 16:15:34 +0900 Subject: [PATCH 14/14] [#10] Fix: Rename remaining functions --- packages/hono/src/middleware/metrics.ts | 4 ++-- packages/koa/src/middleware/metrics.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index 8cd1997..166de5d 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -4,7 +4,7 @@ import { getStatusCodeGroup, isBypassPath, startTraceHistogram, - trimUrl, + normalizeUrlWithTrimming, } from '@naverpay/prometheus-core' import type {HonoPrometheusExporterOptions} from '../types' @@ -33,7 +33,7 @@ export function getHonoMetricsMiddleware({ return ( normalizeNextRoutesPath?.(url.href) || normalizePath?.(context) || - trimUrl(url.pathname, maxNormalizedUrlDepth) + normalizeUrlWithTrimming(url.pathname, maxNormalizedUrlDepth) ) } diff --git a/packages/koa/src/middleware/metrics.ts b/packages/koa/src/middleware/metrics.ts index bf478c4..8ac1557 100644 --- a/packages/koa/src/middleware/metrics.ts +++ b/packages/koa/src/middleware/metrics.ts @@ -4,7 +4,7 @@ import { getStatusCodeGroup, isBypassPath, startTraceHistogram, - trimUrl, + normalizeUrlWithTrimming, } from '@naverpay/prometheus-core' import onFinished from 'on-finished' @@ -30,7 +30,9 @@ export function getKoaMetricsMiddleware({ const normalizeNextRoutesPath = nextjs ? createNextRoutesUrlGroup(maxNormalizedUrlDepth) : undefined const extendedNormalizePath = (context: Context) => { - return normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || trimUrl(context.path) + return ( + normalizeNextRoutesPath?.(context.url) || normalizePath?.(context) || normalizeUrlWithTrimming(context.path) + ) } return async (context, next) => {