1+ import { Socket } from 'net'
12import fastifyPlugin from 'fastify-plugin'
23import { logSchema , redactQueryParamFromRequest } from '@internal/monitoring'
34import { trace } from '@opentelemetry/api'
45import { FastifyRequest } from 'fastify/types/request'
56import { FastifyReply } from 'fastify/types/reply'
7+ import { IncomingMessage } from 'http'
8+ import { getConfig } from '../../config'
9+ import { parsePartialHttp , PartialHttpData } from '@internal/http'
10+ import { FastifyInstance } from 'fastify'
611
712interface RequestLoggerOptions {
813 excludeUrls ?: string [ ]
@@ -22,13 +27,65 @@ declare module 'fastify' {
2227 }
2328}
2429
30+ const { version } = getConfig ( )
31+
2532/**
2633 * Request logger plugin
2734 * @param options
2835 */
2936export const logRequest = ( options : RequestLoggerOptions ) =>
3037 fastifyPlugin (
3138 async ( fastify ) => {
39+ // Watch for connections that timeout or disconnect before complete HTTP headers are received
40+ // Log if socket closes before request is triggered
41+ fastify . server . on ( 'connection' , ( socket ) => {
42+ let hasRequest = false
43+ const startTime = Date . now ( )
44+ const partialData : Buffer [ ] = [ ]
45+ // Only store 2kb per request
46+ const captureByteLimit = 2048
47+
48+ // Capture partial data sent before connection closes
49+ const onData = ( chunk : Buffer ) => {
50+ const remaining = captureByteLimit - partialData . length
51+ if ( remaining > 0 ) {
52+ partialData . push ( chunk . subarray ( 0 , Math . min ( chunk . length , remaining ) ) )
53+ }
54+
55+ if ( partialData . length >= captureByteLimit ) {
56+ socket . removeListener ( 'data' , onData )
57+ }
58+ }
59+ socket . on ( 'data' , onData )
60+
61+ // Track if this socket ever receives an HTTP request
62+ const onRequest = ( req : IncomingMessage ) => {
63+ if ( req . socket === socket ) {
64+ hasRequest = true
65+ socket . removeListener ( 'data' , onData )
66+ fastify . server . removeListener ( 'request' , onRequest )
67+ }
68+ }
69+ fastify . server . on ( 'request' , onRequest )
70+
71+ socket . once ( 'close' , ( ) => {
72+ socket . removeListener ( 'data' , onData )
73+ fastify . server . removeListener ( 'request' , onRequest )
74+ if ( hasRequest ) {
75+ return
76+ }
77+
78+ const parsedHttp = parsePartialHttp ( partialData )
79+ const req = createPartialLogRequest ( fastify , socket , parsedHttp , startTime )
80+
81+ doRequestLog ( req , {
82+ excludeUrls : options . excludeUrls ,
83+ statusCode : 'ABORTED CONN' ,
84+ responseTime : ( Date . now ( ) - req . startTime ) / 1000 ,
85+ } )
86+ } )
87+ } )
88+
3289 fastify . addHook ( 'onRequest' , async ( req , res ) => {
3390 req . startTime = Date . now ( )
3491
@@ -95,7 +152,7 @@ export const logRequest = (options: RequestLoggerOptions) =>
95152interface LogRequestOptions {
96153 reply ?: FastifyReply
97154 excludeUrls ?: string [ ]
98- statusCode : number | 'ABORTED REQ' | 'ABORTED RES'
155+ statusCode : number | 'ABORTED REQ' | 'ABORTED RES' | 'ABORTED CONN'
99156 responseTime : number
100157}
101158
@@ -142,3 +199,34 @@ function getFirstDefined<T>(...values: any[]): T | undefined {
142199 }
143200 return undefined
144201}
202+
203+ /**
204+ * Creates a minimal FastifyRequest from partial HTTP data.
205+ * Used for consistent logging when request parsing fails.
206+ */
207+ export function createPartialLogRequest (
208+ fastify : FastifyInstance ,
209+ socket : Socket ,
210+ httpData : PartialHttpData ,
211+ startTime : number
212+ ) {
213+ return {
214+ method : httpData . method ,
215+ url : httpData . url ,
216+ headers : httpData . headers ,
217+ ip : socket . remoteAddress || 'unknown' ,
218+ id : 'no-request' ,
219+ log : fastify . log . child ( {
220+ tenantId : httpData . tenantId ,
221+ project : httpData . tenantId ,
222+ reqId : 'no-request' ,
223+ appVersion : version ,
224+ dataLength : httpData . length ,
225+ } ) ,
226+ startTime,
227+ tenantId : httpData . tenantId ,
228+ raw : { } ,
229+ routeOptions : { config : { } } ,
230+ resources : [ ] ,
231+ } as unknown as FastifyRequest
232+ }
0 commit comments