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

supabase / storage / 24911182098

24 Apr 2026 08:48PM UTC coverage: 71.595% (+0.2%) from 71.373%
24911182098

Pull #1048

github

web-flow
Merge ada90f1e1 into a8de96e6a
Pull Request #1048: fix(s3): set NextMarker in ListObjects V1 response when truncated

3515 of 5466 branches covered (64.31%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

78 existing lines in 7 files now uncovered.

7391 of 9767 relevant lines covered (75.67%)

378.94 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 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?: { type: string }
24
    resources?: string[]
25
    startTime: number
26
    executionTime?: number
27
  }
28

29
  interface FastifyContextConfig {
30
    operation?: { type: 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) =>
279✔
41
  fastifyPlugin(
42
    async (fastify) => {
43
      fastify.addHook('onRequest', async (req, res) => {
279✔
44
        req.startTime = Date.now()
1,570✔
45

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

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

66
      /**
67
       * Adds req.resources and req.operation to the request object
68
       */
69
      fastify.addHook('preHandler', async (req) => {
279✔
70
        let resources = req.resources
1,495✔
71

72
        if (resources === undefined) {
1,495!
73
          resources = req.routeOptions.config.resources?.(req)
1,495✔
74
        }
75

76
        if (resources === undefined) {
1,495✔
77
          resources = req.raw.resources
1,418✔
78
        }
79

80
        if (resources === undefined) {
1,495✔
81
          const params = req.params as Record<string, unknown> | undefined
1,418✔
82
          let resourceFromParams = ''
1,418✔
83

84
          if (params) {
1,418!
85
            let first = true
1,418✔
86
            for (const key in params) {
1,418✔
87
              if (!Object.prototype.hasOwnProperty.call(params, key)) {
1,733!
88
                continue
×
89
              }
90

91
              if (!first) {
1,733✔
92
                resourceFromParams += '/'
497✔
93
              }
94

95
              const value = params[key]
1,733✔
96
              resourceFromParams += value == null ? '' : String(value)
1,733!
97
              first = false
1,733✔
98
            }
99
          }
100

101
          resources = resourceFromParams ? [resourceFromParams] : []
1,418✔
102
        }
103

104
        if (resources && resources.length > 0) {
1,495✔
105
          for (let index = 0; index < resources.length; index++) {
1,313✔
106
            const resource = resources[index]
41,348✔
107
            if (!resource.startsWith('/')) {
41,348✔
108
              resources[index] = `/${resource}`
41,347✔
109
            }
110
          }
111
        }
112

113
        req.resources = resources
1,495✔
114
        req.operation = req.routeOptions.config.operation
1,495✔
115

116
        if (req.operation && typeof req.opentelemetry === 'function') {
1,495!
117
          req.opentelemetry()?.span?.setAttribute('http.operation', req.operation.type)
×
118
        }
119
      })
120

121
      fastify.addHook('onSend', async (req, _, payload) => {
279✔
122
        req.executionTime = Date.now() - req.startTime
1,550✔
123
        return payload
1,550✔
124
      })
125

126
      fastify.addHook('onResponse', async (req, reply) => {
279✔
127
        doRequestLog(req, {
1,570✔
128
          reply,
129
          excludeUrls: options.excludeUrls,
130
          statusCode: reply.statusCode,
131
          responseTime: reply.elapsedTime,
132
          executionTime: req.executionTime,
133
        })
134
      })
135
    },
136
    { name: 'log-request' }
137
  )
138

139
interface LogRequestOptions {
140
  reply?: FastifyReply
141
  excludeUrls?: string[]
142
  statusCode: number | 'ABORTED REQ' | 'ABORTED RES'
143
  responseTime: number
144
  executionTime?: number
145
}
146

147
function doRequestLog(req: FastifyRequest, options: LogRequestOptions) {
148
  if (options.excludeUrls?.includes(req.url)) {
2,779!
149
    return
×
150
  }
151

152
  const rMeth = req.method
2,779✔
153
  const rUrl = redactQueryParamFromRequest(req, [
2,779✔
154
    'token',
155
    'X-Amz-Credential',
156
    'X-Amz-Signature',
157
    'X-Amz-Security-Token',
158
  ])
159
  const uAgent = req.headers['user-agent']
2,779✔
160
  const rId = req.id
2,779✔
161
  const cIP = req.ip
2,779✔
162
  const statusCode = options.statusCode
2,779✔
163
  const error = req.raw.executionError || req.executionError
2,779✔
164
  const tenantId = req.tenantId
2,779✔
165

166
  let reqMetadata: Record<string, unknown> = {}
2,779✔
167

168
  if (req.routeOptions.config.logMetadata) {
2,779✔
169
    try {
380✔
170
      reqMetadata = req.routeOptions.config.logMetadata(req)
380✔
171

172
      if (reqMetadata) {
380!
173
        try {
380✔
174
          if (typeof req.opentelemetry === 'function') {
380!
175
            req.opentelemetry()?.span?.setAttribute('http.metadata', JSON.stringify(reqMetadata))
×
176
          }
177
        } catch (e) {
178
          // do nothing
179
          logSchema.warning(req.log, 'Failed to serialize log metadata', {
×
180
            type: 'otel',
181
            tenantId,
182
            project: tenantId,
183
            reqId: rId,
184
            sbReqId: req.sbReqId,
185
            error: e,
186
          })
187
        }
188
      }
189
    } catch (e) {
UNCOV
190
      logSchema.error(req.log, 'Failed to get log metadata', {
×
191
        type: 'request',
192
        tenantId,
193
        project: tenantId,
194
        reqId: rId,
195
        sbReqId: req.sbReqId,
196
        error: e,
197
      })
198
    }
199
  }
200

201
  const buildLogMessage = `${tenantId} | ${rMeth} | ${statusCode} | ${cIP} | ${rId} | ${rUrl} | ${uAgent}`
2,779✔
202

203
  logSchema.request(req.log, buildLogMessage, {
2,779✔
204
    type: 'request',
205
    tenantId,
206
    project: tenantId,
207
    reqId: rId,
208
    sbReqId: req.sbReqId,
209
    req,
210
    reqMetadata,
211
    res: options.reply,
212
    responseTime: options.responseTime,
213
    executionTime: options.executionTime,
214
    error,
215
    owner: req.owner,
216
    role: req.jwtPayload?.role,
217
    resources: req.resources,
218
    operation: req.operation?.type ?? req.routeOptions.config.operation?.type,
3,700✔
219
    serverTimes: req.serverTimings,
220
  })
221
}
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