@@ -23,6 +23,12 @@ import {
2323 createTextParser ,
2424 createURLEncodedParser ,
2525 createMultipartParser ,
26+ // Prometheus middleware functions
27+ createPrometheusIntegration ,
28+ createPrometheusMiddleware ,
29+ createMetricsHandler ,
30+ createDefaultMetrics ,
31+ extractRoutePattern ,
2632 // Type definitions
2733 JWTAuthOptions ,
2834 APIKeyAuthOptions ,
@@ -38,6 +44,11 @@ import {
3844 MultipartParserOptions ,
3945 JWKSLike ,
4046 TokenExtractionOptions ,
47+ // Prometheus type definitions
48+ PrometheusMiddlewareOptions ,
49+ MetricsHandlerOptions ,
50+ PrometheusIntegration ,
51+ PrometheusMetrics ,
4152 // Available utility functions
4253 extractTokenFromHeader ,
4354 defaultKeyGenerator ,
@@ -390,6 +401,206 @@ const testBodyParserUtilities = (req: ZeroRequest) => {
390401 const shouldParseJson = shouldParse ( req , 'application/json' )
391402}
392403
404+ // =============================================================================
405+ // PROMETHEUS METRICS MIDDLEWARE VALIDATION
406+ // =============================================================================
407+
408+ console . log ( '✅ Prometheus Metrics Middleware' )
409+
410+ // Clear the Prometheus registry at the start to avoid conflicts
411+ try {
412+ const promClient = require ( 'prom-client' )
413+ promClient . register . clear ( )
414+ } catch ( error ) {
415+ // Ignore if prom-client is not available
416+ }
417+
418+ // Test comprehensive Prometheus middleware options
419+ const prometheusMiddlewareOptions : PrometheusMiddlewareOptions = {
420+ // Use custom metrics to avoid registry conflicts
421+ metrics : undefined , // Will create default metrics once
422+
423+ // Paths to exclude from metrics collection
424+ excludePaths : [ '/health' , '/ping' , '/favicon.ico' , '/metrics' ] ,
425+
426+ // Whether to collect default Node.js metrics
427+ collectDefaultMetrics : false , // Disable to avoid conflicts
428+
429+ // Custom route normalization function
430+ normalizeRoute : ( req : ZeroRequest ) => {
431+ const url = new URL ( req . url , 'http://localhost' )
432+ let pathname = url . pathname
433+
434+ // Custom normalization logic
435+ return pathname
436+ . replace ( / \/ u s e r s \/ \d + / , '/users/:id' )
437+ . replace ( / \/ a p i \/ v \d + / , '/api/:version' )
438+ . replace ( / \/ i t e m s \/ [ a - f 0 - 9 - ] { 36 } / , '/items/:uuid' )
439+ } ,
440+
441+ // Custom label extraction function
442+ extractLabels : ( req : ZeroRequest , response : Response ) => {
443+ return {
444+ user_type : req . headers . get ( 'x-user-type' ) || 'anonymous' ,
445+ api_version : req . headers . get ( 'x-api-version' ) || 'v1' ,
446+ region : req . headers . get ( 'x-region' ) || 'us-east-1' ,
447+ }
448+ } ,
449+
450+ // HTTP methods to skip from metrics collection
451+ skipMethods : [ 'OPTIONS' , 'HEAD' ] ,
452+ }
453+
454+ // Test metrics handler options
455+ const metricsHandlerOptions : MetricsHandlerOptions = {
456+ endpoint : '/custom-metrics' ,
457+ registry : undefined , // Would be prom-client registry in real usage
458+ }
459+
460+ // Test creating individual components (create only once to avoid registry conflicts)
461+ const defaultMetrics : PrometheusMetrics = createDefaultMetrics ( )
462+ const prometheusMiddleware = createPrometheusMiddleware ( {
463+ ...prometheusMiddlewareOptions ,
464+ metrics : defaultMetrics ,
465+ } )
466+ const metricsHandler = createMetricsHandler ( metricsHandlerOptions )
467+
468+ // Test the integration function (use existing metrics)
469+ const prometheusIntegration : PrometheusIntegration =
470+ createPrometheusIntegration ( {
471+ ...prometheusMiddlewareOptions ,
472+ ...metricsHandlerOptions ,
473+ metrics : defaultMetrics , // Reuse existing metrics
474+ } )
475+
476+ // Test the integration object structure
477+ const testPrometheusIntegration = ( ) => {
478+ // Test middleware function
479+ const middleware : RequestHandler = prometheusIntegration . middleware
480+
481+ // Test metrics handler function
482+ const handler : RequestHandler = prometheusIntegration . metricsHandler
483+
484+ // Test registry access
485+ const registry = prometheusIntegration . registry
486+
487+ // Test prom-client access for custom metrics
488+ const promClient = prometheusIntegration . promClient
489+ }
490+
491+ // Test default metrics structure
492+ const testDefaultMetrics = ( ) => {
493+ // Use the already created metrics to avoid registry conflicts
494+ const metrics = defaultMetrics
495+
496+ // Test that all expected metrics are present
497+ const duration = metrics . httpRequestDuration
498+ const total = metrics . httpRequestTotal
499+ const requestSize = metrics . httpRequestSize
500+ const responseSize = metrics . httpResponseSize
501+ const activeConnections = metrics . httpActiveConnections
502+
503+ // All should be defined (prom-client objects)
504+ console . assert (
505+ duration !== undefined ,
506+ 'httpRequestDuration should be defined' ,
507+ )
508+ console . assert ( total !== undefined , 'httpRequestTotal should be defined' )
509+ console . assert ( requestSize !== undefined , 'httpRequestSize should be defined' )
510+ console . assert (
511+ responseSize !== undefined ,
512+ 'httpResponseSize should be defined' ,
513+ )
514+ console . assert (
515+ activeConnections !== undefined ,
516+ 'httpActiveConnections should be defined' ,
517+ )
518+ }
519+
520+ // Test route pattern extraction
521+ const testRoutePatternExtraction = ( ) => {
522+ // Mock request objects for testing (using unknown casting for test purposes)
523+ const reqWithContext = {
524+ ctx : { route : '/users/:id' } ,
525+ url : 'http://localhost:3000/users/123' ,
526+ } as unknown as ZeroRequest
527+
528+ const reqWithParams = {
529+ url : 'http://localhost:3000/users/123' ,
530+ params : { id : '123' } ,
531+ } as unknown as ZeroRequest
532+
533+ const reqWithUUID = {
534+ url : 'http://localhost:3000/items/550e8400-e29b-41d4-a716-446655440000' ,
535+ } as unknown as ZeroRequest
536+
537+ const reqWithNumericId = {
538+ url : 'http://localhost:3000/posts/12345' ,
539+ } as unknown as ZeroRequest
540+
541+ const reqWithLongToken = {
542+ url : 'http://localhost:3000/auth/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' ,
543+ } as unknown as ZeroRequest
544+
545+ const reqMalformed = {
546+ url : 'not-a-valid-url' ,
547+ } as unknown as ZeroRequest
548+
549+ // Test route extraction
550+ const pattern1 = extractRoutePattern ( reqWithContext )
551+ const pattern2 = extractRoutePattern ( reqWithParams )
552+ const pattern3 = extractRoutePattern ( reqWithUUID )
553+ const pattern4 = extractRoutePattern ( reqWithNumericId )
554+ const pattern5 = extractRoutePattern ( reqWithLongToken )
555+ const pattern6 = extractRoutePattern ( reqMalformed )
556+
557+ // All should return strings (exact patterns depend on implementation)
558+ console . assert ( typeof pattern1 === 'string' , 'Route pattern should be string' )
559+ console . assert ( typeof pattern2 === 'string' , 'Route pattern should be string' )
560+ console . assert ( typeof pattern3 === 'string' , 'Route pattern should be string' )
561+ console . assert ( typeof pattern4 === 'string' , 'Route pattern should be string' )
562+ console . assert ( typeof pattern5 === 'string' , 'Route pattern should be string' )
563+ console . assert ( typeof pattern6 === 'string' , 'Route pattern should be string' )
564+ }
565+
566+ // Test custom metrics scenarios
567+ const testCustomMetricsScenarios = ( ) => {
568+ // Create custom metrics object (reuse existing to avoid conflicts)
569+ const customMetrics : PrometheusMetrics = defaultMetrics
570+
571+ // Use custom metrics in middleware
572+ const middlewareWithCustomMetrics = createPrometheusMiddleware ( {
573+ metrics : customMetrics ,
574+ collectDefaultMetrics : false ,
575+ } )
576+
577+ // Test minimal configuration (reuse existing metrics)
578+ const minimalMiddleware = createPrometheusMiddleware ( {
579+ metrics : customMetrics ,
580+ collectDefaultMetrics : false ,
581+ } )
582+ const minimalIntegration = createPrometheusIntegration ( {
583+ metrics : customMetrics ,
584+ collectDefaultMetrics : false ,
585+ } )
586+
587+ // Test with only specific options
588+ const selectiveOptions : PrometheusMiddlewareOptions = {
589+ excludePaths : [ '/api/internal/*' ] ,
590+ skipMethods : [ 'TRACE' , 'CONNECT' ] ,
591+ metrics : customMetrics , // Reuse existing
592+ collectDefaultMetrics : false , // Disable to avoid conflicts
593+ }
594+
595+ const selectiveMiddleware = createPrometheusMiddleware ( selectiveOptions )
596+ }
597+
598+ // Execute Prometheus tests
599+ testPrometheusIntegration ( )
600+ testDefaultMetrics ( )
601+ testRoutePatternExtraction ( )
602+ testCustomMetricsScenarios ( )
603+
393604// =============================================================================
394605// COMPLEX INTEGRATION SCENARIOS
395606// =============================================================================
@@ -434,6 +645,27 @@ const fullMiddlewareStack = () => {
434645 } ) ,
435646 )
436647
648+ // Prometheus metrics middleware (reuse existing metrics to avoid registry conflicts)
649+ router . use (
650+ createPrometheusMiddleware ( {
651+ metrics : defaultMetrics , // Reuse existing metrics
652+ collectDefaultMetrics : false , // Disable to avoid conflicts
653+ excludePaths : [ '/health' , '/metrics' ] ,
654+ extractLabels : ( req : ZeroRequest , response : Response ) => ( {
655+ user_type : req . ctx ?. user ?. type || 'anonymous' ,
656+ api_version : req . headers . get ( 'x-api-version' ) || 'v1' ,
657+ } ) ,
658+ } ) ,
659+ )
660+
661+ // Metrics endpoint (reuse existing metrics)
662+ const prometheusIntegration = createPrometheusIntegration ( {
663+ endpoint : '/metrics' ,
664+ metrics : defaultMetrics , // Reuse existing metrics
665+ collectDefaultMetrics : false , // Disable to avoid conflicts
666+ } )
667+ router . get ( '/metrics' , prometheusIntegration . metricsHandler )
668+
437669 // JWT authentication for API routes
438670 router . use (
439671 '/api/*' ,
@@ -554,6 +786,12 @@ const runValidations = async () => {
554786 testRateLimitUtilities ( mockRequest )
555787 testCORSUtilities ( mockRequest )
556788 testBodyParserUtilities ( mockRequest )
789+
790+ // Test Prometheus utilities
791+ testPrometheusIntegration ( )
792+ testDefaultMetrics ( )
793+ testRoutePatternExtraction ( )
794+ testCustomMetricsScenarios ( )
557795}
558796
559797// Run all validations
@@ -567,6 +805,7 @@ runValidations()
567805 console . log ( '✅ Rate limiting middleware' )
568806 console . log ( '✅ CORS middleware' )
569807 console . log ( '✅ Body parser middleware' )
808+ console . log ( '✅ Prometheus metrics middleware' )
570809 console . log ( '✅ Complex integration scenarios' )
571810 console . log ( '✅ Error handling scenarios' )
572811 console . log ( '✅ Async middleware patterns' )
0 commit comments