• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 29011440956

09 Jul 2026 10:23AM UTC coverage: 79.237% (-0.02%) from 79.254%
29011440956

Pull #1220

github

web-flow
Merge dd735ae4a into 1bfdcd74e
Pull Request #1220: fix: do not allocate log options for excluded urls

5338 of 7299 branches covered (73.13%)

Branch coverage included in aggregate %.

14 of 20 new or added lines in 5 files covered. (70.0%)

10423 of 12592 relevant lines covered (82.77%)

423.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/src/http/plugins/log-request.ts
1
import { logSchema, serializeReplyLog, serializeRequestLog } from '@internal/monitoring'
2
import type { FastifyReply, FastifyRequest } from 'fastify'
3
import fastifyPlugin from 'fastify-plugin'
4

5
interface RequestLoggerOptions {
6
  excludeUrls?: Set<string>
7
}
8

9
type BivariantHandler<Args extends unknown[], Return> = {
10
  bivarianceHack(...args: Args): Return
11
}['bivarianceHack']
12

13
declare module 'http' {
14
  interface IncomingMessage {
15
    executionError?: Error
16
    resources?: string[]
17
  }
18
}
19

20
declare module 'fastify' {
21
  interface FastifyRequest {
22
    executionError?: Error
23
    operation?: string
24
    resources?: string[]
25
    startTime: number
26
    executionTime?: number
27
  }
28

29
  interface FastifyContextConfig {
30
    operation?: string
31
    resources?: BivariantHandler<[req: FastifyRequest], string[]>
32
    logMetadata?: BivariantHandler<[req: FastifyRequest], Record<string, unknown>>
33
  }
34
}
35

36
/**
37
 * Request logger plugin
38
 * @param options
39
 */
40
export const logRequest = (options: RequestLoggerOptions) =>
307✔
41
  fastifyPlugin(
42
    async (fastify) => {
43
      const excludeUrls = options.excludeUrls?.size ? options.excludeUrls : undefined
307✔
44

45
      fastify.addHook('onRequest', (req, res, done) => {
307✔
46
        req.startTime = performance.now()
1,833✔
47

48
        if (excludeUrls?.has(req.url)) {
1,833!
NEW
49
          done()
×
NEW
50
          return
×
51
        }
52

53
        res.raw.once('close', () => {
1,833✔
54
          if (req.raw.aborted) {
1,833✔
55
            doRequestLog(req, {
1✔
56
              statusCode: 'ABORTED REQ',
57
              responseTime: performance.now() - req.startTime,
58
            })
59
            return
1✔
60
          }
61

62
          if (!res.raw.writableFinished) {
1,832✔
63
            doRequestLog(req, {
1,400✔
64
              statusCode: 'ABORTED RES',
65
              responseTime: performance.now() - req.startTime,
66
            })
67
          }
68
        })
69
        done()
1,833✔
70
      })
71

72
      /**
73
       * Adds req.resources and req.operation to the request object
74
       */
75
      fastify.addHook('preHandler', (req, _reply, done) => {
307✔
76
        let resources = req.resources
1,722✔
77

78
        if (resources === undefined) {
1,722✔
79
          resources = req.routeOptions.config.resources?.(req)
1,721✔
80
        }
81

82
        if (resources === undefined) {
1,722✔
83
          resources = req.raw.resources
1,638✔
84
        }
85

86
        if (resources === undefined) {
1,722✔
87
          const resourceFromParams = getResourceFromParams(req.params)
1,638✔
88
          resources = resourceFromParams ? [resourceFromParams] : []
1,638✔
89
        }
90

91
        if (resources && resources.length > 0) {
1,722✔
92
          for (let index = 0; index < resources.length; index++) {
1,477✔
93
            const resource = resources[index]
7,509✔
94
            if (!resource.startsWith('/')) {
7,509✔
95
              resources[index] = `/${resource}`
7,507✔
96
            }
97
          }
98
        }
99

100
        req.resources = resources
1,722✔
101
        req.operation = req.routeOptions.config.operation
1,722✔
102

103
        if (req.operation && typeof req.opentelemetry === 'function') {
1,722!
104
          req.opentelemetry()?.span?.setAttribute('http.operation', req.operation)
×
105
        }
106
        done()
1,722✔
107
      })
108

109
      fastify.addHook('onSend', (req, _reply, payload, done) => {
307✔
110
        req.executionTime = performance.now() - req.startTime
1,781✔
111
        done(null, payload)
1,781✔
112
      })
113

114
      fastify.addHook('onResponse', (req, reply, done) => {
307✔
115
        if (excludeUrls?.has(req.url)) {
1,832!
NEW
116
          done()
×
NEW
117
          return
×
118
        }
119

120
        doRequestLog(req, {
1,832✔
121
          reply,
122
          statusCode: reply.statusCode,
123
          responseTime: reply.elapsedTime,
124
          executionTime: req.executionTime,
125
        })
126
        done()
1,832✔
127
      })
128
    },
129
    { name: 'log-request' }
130
  )
131

132
interface LogRequestOptions {
133
  reply?: FastifyReply
134
  statusCode: number | 'ABORTED REQ' | 'ABORTED RES'
135
  responseTime: number
136
  executionTime?: number
137
}
138

139
function doRequestLog(req: FastifyRequest, options: LogRequestOptions) {
140
  const requestLog = serializeRequestLog(req)
3,233✔
141
  const replyLog = serializeReplyLog(options.reply)
3,233✔
142
  const rMeth = requestLog.method
3,233✔
143
  const rUrl = requestLog.url
3,233✔
144
  const uAgent = req.headers['user-agent']
3,233✔
145
  const rId = req.id
3,233✔
146
  const cIP = req.ip
3,233✔
147
  const statusCode = options.statusCode
3,233✔
148
  const error = req.raw.executionError || req.executionError
3,233✔
149
  const tenantId = req.tenantId
3,233✔
150

151
  let reqMetadata = '{}'
3,233✔
152

153
  if (req.routeOptions.config.logMetadata) {
3,233✔
154
    try {
382✔
155
      const metadata = req.routeOptions.config.logMetadata(req)
382✔
156

157
      if (metadata) {
382!
158
        try {
382✔
159
          reqMetadata = JSON.stringify(metadata)
382✔
160

161
          if (typeof req.opentelemetry === 'function') {
382!
162
            req.opentelemetry()?.span?.setAttribute('http.metadata', reqMetadata)
×
163
          }
164
        } catch (e) {
165
          // do nothing
166
          logSchema.warning(req.log, 'Failed to serialize log metadata', {
×
167
            type: 'otel',
168
            tenantId,
169
            project: tenantId,
170
            reqId: rId,
171
            sbReqId: req.sbReqId,
172
            error: e,
173
          })
174
        }
175
      }
176
    } catch (e) {
177
      logSchema.error(req.log, 'Failed to get log metadata', {
×
178
        type: 'request',
179
        tenantId,
180
        project: tenantId,
181
        reqId: rId,
182
        sbReqId: req.sbReqId,
183
        error: e,
184
      })
185
    }
186
  }
187

188
  const buildLogMessage = `${tenantId} | ${rMeth} | ${statusCode} | ${cIP} | ${rId} | ${rUrl} | ${uAgent}`
3,233✔
189

190
  logSchema.request(req.log, buildLogMessage, {
3,233✔
191
    type: 'request',
192
    tenantId,
193
    project: tenantId,
194
    reqId: rId,
195
    sbReqId: req.sbReqId,
196
    req: requestLog,
197
    reqMetadata,
198
    res: replyLog,
199
    responseTime: options.responseTime,
200
    executionTime: options.executionTime,
201
    error,
202
    owner: req.owner,
203
    role: req.jwtPayload?.role,
204
    resources: req.resources,
205
    operation: req.operation ?? req.routeOptions.config.operation,
4,403✔
206
    serverTimes: req.serverTimings,
207
  })
208
}
209

210
function getResourceFromParams(params: unknown): string {
211
  if (!params || typeof params !== 'object') {
1,638!
212
    return ''
×
213
  }
214

215
  let resource = ''
1,638✔
216
  let first = true
1,638✔
217

218
  for (const key in params) {
1,638✔
219
    if (!Object.prototype.hasOwnProperty.call(params, key)) {
1,933!
220
      continue
×
221
    }
222

223
    if (!first) {
1,933✔
224
      resource += '/'
540✔
225
    }
226

227
    const value = (params as Record<string, unknown>)[key]
1,933✔
228
    resource += value == null ? '' : String(value)
1,933!
229
    first = false
1,933✔
230
  }
231

232
  return resource
1,638✔
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc