1+ import { RequestHandler } from 'express' ;
2+
3+ export type CreatorReadEndpoint = 'list' | 'detail' ;
4+
5+ const DURATION_BUCKETS_MS = [ 5 , 10 , 25 , 50 , 100 , 250 , 500 , 1000 , 2500 , 5000 ] as const ;
6+
7+ export interface CreatorReadHistogramBucket {
8+ leMs : number | null ;
9+ count : number ;
10+ }
11+
12+ export interface CreatorReadEndpointSnapshot {
13+ requests : number ;
14+ success : number ;
15+ clientErrors : number ;
16+ serverErrors : number ;
17+ histogram : {
18+ count : number ;
19+ sumMs : number ;
20+ buckets : CreatorReadHistogramBucket [ ] ;
21+ } ;
22+ }
23+
24+ export interface CreatorReadMetricsSnapshot {
25+ counters : {
26+ totalRequests : number ;
27+ totalSuccess : number ;
28+ totalClientErrors : number ;
29+ totalServerErrors : number ;
30+ byEndpoint : Record < CreatorReadEndpoint , CreatorReadEndpointSnapshot > ;
31+ } ;
32+ }
33+
34+ type EndpointState = {
35+ requests : number ;
36+ success : number ;
37+ clientErrors : number ;
38+ serverErrors : number ;
39+ histogram : {
40+ count : number ;
41+ sumMs : number ;
42+ buckets : CreatorReadHistogramBucket [ ] ;
43+ } ;
44+ } ;
45+
46+ const createHistogramBuckets = ( ) : CreatorReadHistogramBucket [ ] =>
47+ [ ...DURATION_BUCKETS_MS , null ] . map ( leMs => ( {
48+ leMs,
49+ count : 0 ,
50+ } ) ) ;
51+
52+ const createEndpointState = ( ) : EndpointState => ( {
53+ requests : 0 ,
54+ success : 0 ,
55+ clientErrors : 0 ,
56+ serverErrors : 0 ,
57+ histogram : {
58+ count : 0 ,
59+ sumMs : 0 ,
60+ buckets : createHistogramBuckets ( ) ,
61+ } ,
62+ } ) ;
63+
64+ const registry : Record < CreatorReadEndpoint , EndpointState > = {
65+ list : createEndpointState ( ) ,
66+ detail : createEndpointState ( ) ,
67+ } ;
68+
69+ function classifyStatus ( statusCode : number ) : 'success' | 'clientErrors' | 'serverErrors' {
70+ if ( statusCode >= 500 ) {
71+ return 'serverErrors' ;
72+ }
73+
74+ if ( statusCode >= 400 ) {
75+ return 'clientErrors' ;
76+ }
77+
78+ return 'success' ;
79+ }
80+
81+ function getHistogramBucketIndex ( durationMs : number ) : number {
82+ const bucketIndex = DURATION_BUCKETS_MS . findIndex ( limit => durationMs <= limit ) ;
83+ return bucketIndex === - 1 ? DURATION_BUCKETS_MS . length : bucketIndex ;
84+ }
85+
86+ export function recordCreatorReadMetric (
87+ endpoint : CreatorReadEndpoint ,
88+ durationMs : number ,
89+ statusCode : number
90+ ) : void {
91+ const entry = registry [ endpoint ] ;
92+ const statusGroup = classifyStatus ( statusCode ) ;
93+
94+ entry . requests += 1 ;
95+ entry [ statusGroup ] += 1 ;
96+ entry . histogram . count += 1 ;
97+ entry . histogram . sumMs += durationMs ;
98+
99+ const bucketIndex = getHistogramBucketIndex ( durationMs ) ;
100+ for ( let index = bucketIndex ; index < entry . histogram . buckets . length ; index += 1 ) {
101+ entry . histogram . buckets [ index ] . count += 1 ;
102+ }
103+ }
104+
105+ export function createCreatorReadMetricsMiddleware (
106+ endpoint : CreatorReadEndpoint ,
107+ now : ( ) => bigint = ( ) => process . hrtime . bigint ( )
108+ ) : RequestHandler {
109+ return ( _req , res , next ) => {
110+ const startedAt = now ( ) ;
111+
112+ res . on ( 'finish' , ( ) => {
113+ const durationMs = Number ( now ( ) - startedAt ) / 1e6 ;
114+ recordCreatorReadMetric ( endpoint , durationMs , res . statusCode ) ;
115+ } ) ;
116+
117+ next ( ) ;
118+ } ;
119+ }
120+
121+ function snapshotEndpoint ( entry : EndpointState ) : CreatorReadEndpointSnapshot {
122+ return {
123+ requests : entry . requests ,
124+ success : entry . success ,
125+ clientErrors : entry . clientErrors ,
126+ serverErrors : entry . serverErrors ,
127+ histogram : {
128+ count : entry . histogram . count ,
129+ sumMs : entry . histogram . sumMs ,
130+ buckets : entry . histogram . buckets . map ( bucket => ( { ...bucket } ) ) ,
131+ } ,
132+ } ;
133+ }
134+
135+ export function getCreatorReadMetrics ( ) : CreatorReadMetricsSnapshot {
136+ return {
137+ counters : {
138+ totalRequests : registry . list . requests + registry . detail . requests ,
139+ totalSuccess : registry . list . success + registry . detail . success ,
140+ totalClientErrors : registry . list . clientErrors + registry . detail . clientErrors ,
141+ totalServerErrors : registry . list . serverErrors + registry . detail . serverErrors ,
142+ byEndpoint : {
143+ list : snapshotEndpoint ( registry . list ) ,
144+ detail : snapshotEndpoint ( registry . detail ) ,
145+ } ,
146+ } ,
147+ } ;
148+ }
149+
150+ export function resetCreatorReadMetrics ( ) : void {
151+ registry . list = createEndpointState ( ) ;
152+ registry . detail = createEndpointState ( ) ;
153+ }
0 commit comments