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

supabase / storage / 24798577000

22 Apr 2026 07:34PM UTC coverage: 71.209% (+1.3%) from 69.944%
24798577000

Pull #1041

github

web-flow
Merge 5def5ea43 into 8e937109d
Pull Request #1041: feat: add sb-request-id logging

3465 of 5424 branches covered (63.88%)

Branch coverage included in aggregate %.

51 of 58 new or added lines in 23 files covered. (87.93%)

5 existing lines in 4 files now uncovered.

7321 of 9723 relevant lines covered (75.3%)

376.84 hits per line

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

84.11
/src/http/plugins/log-request.ts
1
import { logSchema, redactQueryParamFromRequest } from '@internal/monitoring'
2
import { FastifyReply } from 'fastify/types/reply'
3
import { FastifyRequest } from 'fastify/types/request'
4
import fastifyPlugin from 'fastify-plugin'
5

6
interface RequestLoggerOptions {
7
  excludeUrls?: string[]
8
}
9

10
declare module 'fastify' {
11
  interface FastifyRequest {
12
    executionError?: Error
13
    operation?: { type: string }
14
    resources?: string[]
15
    startTime: number
16
    executionTime?: number
17
  }
18

19
  interface FastifyContextConfig {
20
    operation?: { type: string }
21
    resources?: (req: FastifyRequest<any>) => string[]
22
    logMetadata?: (req: FastifyRequest<any>) => Record<string, unknown>
23
  }
24
}
25

26
/**
27
 * Request logger plugin
28
 * @param options
29
 */
30
export const logRequest = (options: RequestLoggerOptions) =>
278✔
31
  fastifyPlugin(
32
    async (fastify) => {
33
      fastify.addHook('onRequest', async (req, res) => {
278✔
34
        req.startTime = Date.now()
1,551✔
35

36
        res.raw.once('close', () => {
1,551✔
37
          if (req.raw.aborted) {
1,551!
38
            doRequestLog(req, {
×
39
              excludeUrls: options.excludeUrls,
40
              statusCode: 'ABORTED REQ',
41
              responseTime: (Date.now() - req.startTime) / 1000,
42
            })
43
            return
×
44
          }
45

46
          if (!res.raw.writableFinished) {
1,551✔
47
            doRequestLog(req, {
1,204✔
48
              excludeUrls: options.excludeUrls,
49
              statusCode: 'ABORTED RES',
50
              responseTime: (Date.now() - req.startTime) / 1000,
51
            })
52
          }
53
        })
54
      })
55

56
      /**
57
       * Adds req.resources and req.operation to the request object
58
       */
59
      fastify.addHook('preHandler', async (req) => {
278✔
60
        let resources = req.resources
1,476✔
61

62
        if (resources === undefined) {
1,476!
63
          resources = req.routeOptions.config.resources?.(req)
1,476✔
64
        }
65

66
        if (resources === undefined) {
1,476✔
67
          resources = (req.raw as any).resources
1,399✔
68
        }
69

70
        if (resources === undefined) {
1,476✔
71
          const params = req.params as Record<string, unknown> | undefined
1,399✔
72
          let resourceFromParams = ''
1,399✔
73

74
          if (params) {
1,399!
75
            let first = true
1,399✔
76
            for (const key in params) {
1,399✔
77
              if (!Object.prototype.hasOwnProperty.call(params, key)) {
1,710!
78
                continue
×
79
              }
80

81
              if (!first) {
1,710✔
82
                resourceFromParams += '/'
490✔
83
              }
84

85
              const value = params[key]
1,710✔
86
              resourceFromParams += value == null ? '' : String(value)
1,710!
87
              first = false
1,710✔
88
            }
89
          }
90

91
          resources = resourceFromParams ? [resourceFromParams] : []
1,399✔
92
        }
93

94
        if (resources && resources.length > 0) {
1,476✔
95
          for (let index = 0; index < resources.length; index++) {
1,297✔
96
            const resource = resources[index]
41,332✔
97
            if (!resource.startsWith('/')) {
41,332✔
98
              resources[index] = `/${resource}`
41,331✔
99
            }
100
          }
101
        }
102

103
        req.resources = resources
1,476✔
104
        req.operation = req.routeOptions.config.operation
1,476✔
105

106
        if (req.operation && typeof req.opentelemetry === 'function') {
1,476!
107
          req.opentelemetry()?.span?.setAttribute('http.operation', req.operation.type)
×
108
        }
109
      })
110

111
      fastify.addHook('onSend', async (req, _, payload) => {
278✔
112
        req.executionTime = Date.now() - req.startTime
1,531✔
113
        return payload
1,531✔
114
      })
115

116
      fastify.addHook('onResponse', async (req, reply) => {
278✔
117
        doRequestLog(req, {
1,551✔
118
          reply,
119
          excludeUrls: options.excludeUrls,
120
          statusCode: reply.statusCode,
121
          responseTime: reply.elapsedTime,
122
          executionTime: req.executionTime,
123
        })
124
      })
125
    },
126
    { name: 'log-request' }
127
  )
128

129
interface LogRequestOptions {
130
  reply?: FastifyReply
131
  excludeUrls?: string[]
132
  statusCode: number | 'ABORTED REQ' | 'ABORTED RES'
133
  responseTime: number
134
  executionTime?: number
135
}
136

137
function doRequestLog(req: FastifyRequest, options: LogRequestOptions) {
138
  if (options.excludeUrls?.includes(req.url)) {
2,755!
139
    return
×
140
  }
141

142
  const rMeth = req.method
2,755✔
143
  const rUrl = redactQueryParamFromRequest(req, [
2,755✔
144
    'token',
145
    'X-Amz-Credential',
146
    'X-Amz-Signature',
147
    'X-Amz-Security-Token',
148
  ])
149
  const uAgent = req.headers['user-agent']
2,755✔
150
  const rId = req.id
2,755✔
151
  const cIP = req.ip
2,755✔
152
  const statusCode = options.statusCode
2,755✔
153
  const error = (req.raw as any).executionError || req.executionError
2,755✔
154
  const tenantId = req.tenantId
2,755✔
155

156
  let reqMetadata: Record<string, unknown> = {}
2,755✔
157

158
  if (req.routeOptions.config.logMetadata) {
2,755✔
159
    try {
380✔
160
      reqMetadata = req.routeOptions.config.logMetadata(req)
380✔
161

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

185
  const buildLogMessage = `${tenantId} | ${rMeth} | ${statusCode} | ${cIP} | ${rId} | ${rUrl} | ${uAgent}`
2,755✔
186

187
  logSchema.request(req.log, buildLogMessage, {
2,755✔
188
    type: 'request',
189
    req,
190
    reqMetadata,
191
    res: options.reply,
192
    responseTime: options.responseTime,
193
    executionTime: options.executionTime,
194
    error,
195
    owner: req.owner,
196
    role: req.jwtPayload?.role,
197
    resources: req.resources,
198
    operation: req.operation?.type ?? req.routeOptions.config.operation?.type,
3,666✔
199
    serverTimes: req.serverTimings,
200
    sbReqId: req.sbReqId,
201
  })
202
}
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