From 4961157e6bb0d7e19e05f388b99ef0fe70174f5c Mon Sep 17 00:00:00 2001 From: 2-one-week Date: Sun, 14 Jun 2026 18:11:41 +0900 Subject: [PATCH] =?UTF-8?q?fix(hono):=20=EA=B2=BD=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=ED=99=94=EA=B0=80=20=EB=AC=B4=EC=8B=9C=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EB=8F=99=EC=A0=81=20=EA=B2=BD=EB=A1=9C=EA=B0=80=20?= =?UTF-8?q?=EB=A9=94=ED=8A=B8=EB=A6=AD=20=EB=9D=BC=EB=B2=A8=EC=97=90=20?= =?UTF-8?q?=EB=88=84=EC=A0=81=EB=90=98=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - metrics: Next.js 그룹핑에 url.href 대신 url.pathname 전달 (`/http://host/...` 라벨 방지) - metrics: 사용자 normalizePath 우선 적용 + undefined 반환 시 Next.js 그룹핑으로 fall-through - types: normalizePath 반환 타입을 string | undefined로 확장 - router/utils: createNormalizedHonoRouterPath의 prefix 이중부착 버그 수정 --- .changeset/fix-hono-path-normalization.md | 10 ++++++++++ packages/hono/README.md | 4 ++-- packages/hono/src/middleware/metrics.ts | 22 +++++++++++++++++----- packages/hono/src/router/utils.ts | 4 +++- packages/hono/src/types.ts | 8 ++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-hono-path-normalization.md diff --git a/.changeset/fix-hono-path-normalization.md b/.changeset/fix-hono-path-normalization.md new file mode 100644 index 0000000..f6688bb --- /dev/null +++ b/.changeset/fix-hono-path-normalization.md @@ -0,0 +1,10 @@ +--- +'@naverpay/prometheus-hono': minor +--- + +fix(hono): 경로 정규화가 동작하지 않아 동적 경로가 메트릭 라벨에 그대로 쌓이던 문제 수정 + +- `getHonoMetricsMiddleware`: Next.js 라우트 그룹핑에 `url.href`(scheme/host 포함)가 아니라 `url.pathname`을 넘기도록 수정. 기존에는 `/http://host/...` 형태의 비정규화 라벨이 생성되었습니다. +- `getHonoMetricsMiddleware`: 사용자가 제공한 `normalizePath`가 우선 적용되도록 수정. 기존 `||` 체이닝에서는 Next.js 그룹핑이 미매칭 시에도 비어있지 않은 값을 반환해 `normalizePath`가 무시되었습니다. 이제 `normalizePath`가 값을 반환하면 그 값을 사용하고, `undefined`를 반환하면 Next.js 그룹핑 → 기본 정규화로 위임합니다(하이브리드 앱에서 "API 경로만 직접 정규화, 나머지는 Next.js에 위임" 가능). +- `normalizePath` 반환 타입을 `string | undefined`로 확장. +- `createNormalizedHonoRouterPath`: `prefix`가 두 번 부착되던 버그 수정(`/prefix/prefix/...`). 이제 `prefix` 인자가 정상 동작합니다. diff --git a/packages/hono/README.md b/packages/hono/README.md index 937972f..48d8f1d 100644 --- a/packages/hono/README.md +++ b/packages/hono/README.md @@ -116,8 +116,8 @@ interface HonoPrometheusExporterOptions { collectDefaultMetrics?: boolean /** 요청 바이패스 함수 */ bypass?: (context: Context) => boolean - /** 경로 정규화 함수 */ - normalizePath?: (context: Context) => string + /** 경로 정규화 함수 (undefined 반환 시 Next.js 그룹핑/기본 정규화로 위임) */ + normalizePath?: (context: Context) => string | undefined /** 상태 코드 포맷팅 함수 */ formatStatusCode?: (context: Context) => string } diff --git a/packages/hono/src/middleware/metrics.ts b/packages/hono/src/middleware/metrics.ts index 166de5d..d6eba40 100644 --- a/packages/hono/src/middleware/metrics.ts +++ b/packages/hono/src/middleware/metrics.ts @@ -30,11 +30,23 @@ export function getHonoMetricsMiddleware({ const extendedNormalizePath = (context: Context) => { const url = new URL(context.req.url) - return ( - normalizeNextRoutesPath?.(url.href) || - normalizePath?.(context) || - normalizeUrlWithTrimming(url.pathname, maxNormalizedUrlDepth) - ) + + // 사용자가 제공한 normalizePath를 최우선으로 사용한다. + // 값을 반환하면 그 값을 쓰고, undefined를 반환하면 "이 경로는 처리하지 않음"으로 보고 + // Next.js 그룹핑 → 기본 정규화 순서로 위임한다. + // (기존 `||` 체이닝은 Next.js 그룹핑이 미매칭 시에도 비어있지 않은 값을 반환해 normalizePath가 무시되었다) + const normalized = normalizePath?.(context) + if (normalized !== undefined) { + return normalized + } + + // Next.js 라우트 그룹핑은 전체 URL(href)이 아닌 pathname 기준으로 매칭해야 한다. + // (href를 넘기면 scheme/host가 경로에 섞여 `/http://host/...` 형태의 비정규화 라벨이 생성된다) + if (normalizeNextRoutesPath) { + return normalizeNextRoutesPath(url.pathname) + } + + return normalizeUrlWithTrimming(url.pathname, maxNormalizedUrlDepth) } return async (context, next) => { diff --git a/packages/hono/src/router/utils.ts b/packages/hono/src/router/utils.ts index 66a508e..731c6c0 100644 --- a/packages/hono/src/router/utils.ts +++ b/packages/hono/src/router/utils.ts @@ -50,7 +50,9 @@ export function createNormalizedHonoRouterPath, prefix = '', ) { - const paths = getHonoRouterPaths(app, prefix) + // getHonoRouterPaths에 prefix를 넘기지 않는다. prefix 부착은 createPathTesters가 전담하며, + // 양쪽에 모두 넘기면 prefix가 두 번 붙어(`/prefix/prefix/...`) 정규식 매칭이 깨진다. + const paths = getHonoRouterPaths(app) const testers = createPathTesters(paths, prefix) return function getNormalizedHonoRouterPath(pathname: string) { diff --git a/packages/hono/src/types.ts b/packages/hono/src/types.ts index ab3cd25..8be9bd1 100644 --- a/packages/hono/src/types.ts +++ b/packages/hono/src/types.ts @@ -5,8 +5,12 @@ import type {Context} from 'hono' export interface HonoPrometheusExporterOptions extends CommonPrometheusExporterOptions { /** Function to determine if a request should be bypassed from metrics collection */ bypass?: (context: Context) => boolean - /** Function to normalize/group request paths for metrics */ - normalizePath?: (context: Context) => string + /** + * Function to normalize/group request paths for metrics. + * Return a string to use it as the path label. Return `undefined` to skip this path and + * fall through to Next.js route grouping (when `nextjs` is enabled) and then default normalization. + */ + normalizePath?: (context: Context) => string | undefined /** Function to format status codes for metrics */ formatStatusCode?: (context: Context) => string }