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

supabase / storage / 28820220379

06 Jul 2026 08:13PM UTC coverage: 79.119% (-0.02%) from 79.134%
28820220379

Pull #1210

github

web-flow
Merge 23693c3e2 into a0a8d5f96
Pull Request #1210: fix: simplify operation shape

5212 of 7143 branches covered (72.97%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 4 files covered. (85.71%)

1 existing line in 1 file now uncovered.

10217 of 12358 relevant lines covered (82.68%)

418.31 hits per line

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

88.03
/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?: 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) =>
314✔
41
  fastifyPlugin(
42
    async (fastify) => {
43
      fastify.addHook('onRequest', (req, res, done) => {
314✔
44
        req.startTime = Date.now()
1,833✔
45

46
        res.raw.once('close', () => {
1,833✔
47
          if (req.raw.aborted) {
1,833✔
48
            doRequestLog(req, {
1✔
49
              excludeUrls: options.excludeUrls,
50
              statusCode: 'ABORTED REQ',
51
              responseTime: (Date.now() - req.startTime) / 1000,
52
            })
53
            return
1✔
54
          }
55

56
          if (!res.raw.writableFinished) {
1,832✔
57
            doRequestLog(req, {
1,400✔
58
              excludeUrls: options.excludeUrls,
59
              statusCode: 'ABORTED RES',
60
              responseTime: (Date.now() - req.startTime) / 1000,
61
            })
62
          }
63
        })
64
        done()
1,833✔
65
      })
66

67
      /**
68
       * Adds req.resources and req.operation to the request object
69
       */
70
      fastify.addHook('preHandler', (req, _reply, done) => {
314✔
71
        let resources = req.resources
1,722✔
72

73
        if (resources === undefined) {
1,722✔
74
          resources = req.routeOptions.config.resources?.(req)
1,721✔
75
        }
76

77
        if (resources === undefined) {
1,722✔
78
          resources = req.raw.resources
1,638✔
79
        }
80

81
        if (resources === undefined) {
1,722✔
82
          const resourceFromParams = getResourceFromParams(req.params)
1,638✔
83
          resources = resourceFromParams ? [resourceFromParams] : []
1,638✔
84
        }
85

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

95
        req.resources = resources
1,722✔
96
        req.operation = req.routeOptions.config.operation
1,722✔
97

98
        if (req.operation && typeof req.opentelemetry === 'function') {
1,722!
NEW
99
          req.opentelemetry()?.span?.setAttribute('http.operation', req.operation)
×
100
        }
101
        done()
1,722✔
102
      })
103

104
      fastify.addHook('onSend', (req, _reply, payload, done) => {
314✔
105
        req.executionTime = Date.now() - req.startTime
1,781✔
106
        done(null, payload)
1,781✔
107
      })
108

109
      fastify.addHook('onResponse', (req, reply, done) => {
314✔
110
        doRequestLog(req, {
1,832✔
111
          reply,
112
          excludeUrls: options.excludeUrls,
113
          statusCode: reply.statusCode,
114
          responseTime: reply.elapsedTime,
115
          executionTime: req.executionTime,
116
        })
117
        done()
1,832✔
118
      })
119
    },
120
    { name: 'log-request' }
121
  )
122

123
interface LogRequestOptions {
124
  reply?: FastifyReply
125
  excludeUrls?: string[]
126
  statusCode: number | 'ABORTED REQ' | 'ABORTED RES'
127
  responseTime: number
128
  executionTime?: number
129
}
130

131
function doRequestLog(req: FastifyRequest, options: LogRequestOptions) {
132
  if (options.excludeUrls?.includes(req.url)) {
3,233!
133
    return
×
134
  }
135

136
  const requestLog = serializeRequestLog(req)
3,233✔
137
  const replyLog = serializeReplyLog(options.reply)
3,233✔
138
  const rMeth = requestLog.method
3,233✔
139
  const rUrl = requestLog.url
3,233✔
140
  const uAgent = req.headers['user-agent']
3,233✔
141
  const rId = req.id
3,233✔
142
  const cIP = req.ip
3,233✔
143
  const statusCode = options.statusCode
3,233✔
144
  const error = req.raw.executionError || req.executionError
3,233✔
145
  const tenantId = req.tenantId
3,233✔
146

147
  let reqMetadata = '{}'
3,233✔
148

149
  if (req.routeOptions.config.logMetadata) {
3,233✔
150
    try {
382✔
151
      const metadata = req.routeOptions.config.logMetadata(req)
382✔
152

153
      if (metadata) {
382!
154
        try {
382✔
155
          reqMetadata = JSON.stringify(metadata)
382✔
156

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

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

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

206
function getResourceFromParams(params: unknown): string {
207
  if (!params || typeof params !== 'object') {
1,638!
208
    return ''
×
209
  }
210

211
  let resource = ''
1,638✔
212
  let first = true
1,638✔
213

214
  for (const key in params) {
1,638✔
215
    if (!Object.prototype.hasOwnProperty.call(params, key)) {
1,933!
216
      continue
×
217
    }
218

219
    if (!first) {
1,933✔
220
      resource += '/'
540✔
221
    }
222

223
    const value = (params as Record<string, unknown>)[key]
1,933✔
224
    resource += value == null ? '' : String(value)
1,933!
225
    first = false
1,933✔
226
  }
227

228
  return resource
1,638✔
229
}
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