@mpen/routekit 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.d.mts +4 -0
- package/dist/client/react.d.mts +178 -0
- package/dist/client/react.mjs +142 -0
- package/dist/client.d.mts +433 -0
- package/dist/client.mjs +264 -0
- package/dist/content-BuDOmhH_.mjs +102 -0
- package/dist/core-CzUCxvGk.d.mts +140 -0
- package/dist/core-DbmQauwS.mjs +81 -0
- package/dist/handlers.d.mts +72 -0
- package/dist/handlers.mjs +153 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1152 -0
- package/dist/middleware.d.mts +388 -0
- package/dist/middleware.mjs +1222 -0
- package/dist/request-Dn0zc-xm.mjs +1025 -0
- package/dist/response/content.d.mts +79 -0
- package/dist/response/content.mjs +2 -0
- package/dist/response/json-rpc.d.mts +1 -0
- package/dist/response/json-rpc.mjs +1 -0
- package/dist/response/problem/valibot.d.mts +230 -0
- package/dist/response/problem/valibot.mjs +258 -0
- package/dist/response/problem.d.mts +415 -0
- package/dist/response/problem.mjs +183 -0
- package/dist/response/status.d.mts +45 -0
- package/dist/response/status.mjs +2 -0
- package/dist/responses-B379Ep9Y.d.mts +296 -0
- package/dist/responses-BpVrgeYi.mjs +101 -0
- package/dist/router-Cwb7ak0J.d.mts +1819 -0
- package/dist/routes.d.mts +282 -0
- package/dist/routes.mjs +311 -0
- package/dist/status-C-8mw-FB.mjs +59 -0
- package/dist/valibot-D7liFYyB.d.mts +290 -0
- package/dist/valibot-Du97X-TS.mjs +326 -0
- package/package.json +8 -2
- package/src/bin/gen-api-client.test.ts +0 -70
- package/src/bin/gen-api-client.ts +0 -986
- package/src/client/headers.ts +0 -31
- package/src/client/index.ts +0 -8
- package/src/client/promise.ts +0 -11
- package/src/client/react/index.test.tsx +0 -266
- package/src/client/react/index.ts +0 -431
- package/src/client/responses.test.ts +0 -151
- package/src/client/responses.ts +0 -278
- package/src/client/transport.ts +0 -74
- package/src/client/transports/body-codec.ts +0 -61
- package/src/client/transports/fetch.ts +0 -113
- package/src/client/tsconfig.json +0 -9
- package/src/client/types.ts +0 -15
- package/src/client/url.ts +0 -31
- package/src/index.ts +0 -63
- package/src/router/fetch-types.ts +0 -13
- package/src/router/handlers/index.ts +0 -2
- package/src/router/handlers/openapi/index.ts +0 -2
- package/src/router/handlers/openapi/openapi.ts +0 -293
- package/src/router/integration/zod-openapi.test.ts +0 -74
- package/src/router/lib/charset.test.ts +0 -22
- package/src/router/lib/charset.ts +0 -133
- package/src/router/lib/collections.ts +0 -3
- package/src/router/lib/format.test.ts +0 -67
- package/src/router/lib/format.ts +0 -35
- package/src/router/lib/host.ts +0 -4
- package/src/router/lib/json-schema.ts +0 -6
- package/src/router/lib/media-type.test.ts +0 -122
- package/src/router/lib/media-type.ts +0 -289
- package/src/router/lib/pathname.test.ts +0 -18
- package/src/router/lib/pathname.ts +0 -19
- package/src/router/lib/route-names.ts +0 -70
- package/src/router/lib/route-normalize.test.ts +0 -36
- package/src/router/lib/route-normalize.ts +0 -67
- package/src/router/lib/schema-merge.ts +0 -56
- package/src/router/middleware/accept-ctx.test.ts +0 -33
- package/src/router/middleware/accept-ctx.ts +0 -12
- package/src/router/middleware/body-limit.test.ts +0 -112
- package/src/router/middleware/body-limit.ts +0 -121
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +0 -269
- package/src/router/middleware/cors.ts +0 -490
- package/src/router/middleware/csrf.test.ts +0 -106
- package/src/router/middleware/csrf.ts +0 -192
- package/src/router/middleware/define.ts +0 -249
- package/src/router/middleware/index.ts +0 -34
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +0 -886
- package/src/router/middleware/rate-limit.ts +0 -920
- package/src/router/middleware/request-id-ctx.test.ts +0 -183
- package/src/router/middleware/request-id-ctx.ts +0 -135
- package/src/router/middleware/request-logger-format.test.ts +0 -16
- package/src/router/middleware/request-logger-format.ts +0 -269
- package/src/router/middleware/request-logger.test.ts +0 -267
- package/src/router/middleware/request-logger.ts +0 -131
- package/src/router/middleware/start-time-ctx.ts +0 -5
- package/src/router/request.ts +0 -611
- package/src/router/response/core.ts +0 -181
- package/src/router/response/directives.ts +0 -233
- package/src/router/response/formats/content/bodyless.ts +0 -54
- package/src/router/response/formats/content/content.ts +0 -79
- package/src/router/response/formats/content/index.ts +0 -2
- package/src/router/response/formats/json-rpc/index.ts +0 -2
- package/src/router/response/formats/problem/badRequest.ts +0 -90
- package/src/router/response/formats/problem/conflict.ts +0 -90
- package/src/router/response/formats/problem/created.ts +0 -40
- package/src/router/response/formats/problem/index.ts +0 -27
- package/src/router/response/formats/problem/notFound.ts +0 -90
- package/src/router/response/formats/problem/permissionDenied.ts +0 -90
- package/src/router/response/formats/problem/problem.test.ts +0 -888
- package/src/router/response/formats/problem/rateLimited.ts +0 -90
- package/src/router/response/formats/problem/responses.ts +0 -219
- package/src/router/response/formats/problem/root-errors.ts +0 -48
- package/src/router/response/formats/problem/sessionExpired.ts +0 -90
- package/src/router/response/formats/problem/types.ts +0 -170
- package/src/router/response/formats/problem/unauthenticated.ts +0 -90
- package/src/router/response/formats/problem/valibot.ts +0 -410
- package/src/router/response/formats/status/index.ts +0 -1
- package/src/router/response/formats/status/responses.ts +0 -59
- package/src/router/response/formats/status/status.test.ts +0 -21
- package/src/router/response/framers.ts +0 -85
- package/src/router/response/index.ts +0 -28
- package/src/router/response/openapi.test.ts +0 -96
- package/src/router/response/openapi.ts +0 -1
- package/src/router/response/serializers.ts +0 -66
- package/src/router/response/stream.ts +0 -35
- package/src/router/router.test.ts +0 -1571
- package/src/router/router.ts +0 -1965
- package/src/router/routes/index.ts +0 -46
- package/src/router/routes/valibot/index.ts +0 -18
- package/src/router/routes/valibot/valibot.ts +0 -1393
- package/src/router/routes/valibot.test.ts +0 -286
- package/src/router/routes/zod/index.ts +0 -18
- package/src/router/routes/zod/zod.ts +0 -1318
- package/src/router/routes/zod.test.ts +0 -280
- package/src/router/server-interface.ts +0 -31
- package/src/router/types.ts +0 -657
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun test
|
|
2
|
-
import { describe, expect, it } from 'bun:test'
|
|
3
|
-
import { HttpMethod, HttpStatus } from '@mpen/http'
|
|
4
|
-
import { JsonLogger, MemoryLogger, TerminalLogger } from '@mpen/logger'
|
|
5
|
-
import { Router } from '../router'
|
|
6
|
-
import { response as routekitResponse } from '../response'
|
|
7
|
-
import { requestIdCtx } from './request-id-ctx'
|
|
8
|
-
import { requestLogger } from './request-logger'
|
|
9
|
-
import {
|
|
10
|
-
formatRoutekitTerminalLogRecord,
|
|
11
|
-
transformRoutekitJsonLogRecord,
|
|
12
|
-
} from './request-logger-format'
|
|
13
|
-
|
|
14
|
-
describe(requestLogger.name, () => {
|
|
15
|
-
it('logs correlated start and completion activity records for matched requests', async () => {
|
|
16
|
-
const logger = new MemoryLogger()
|
|
17
|
-
const router = new Router()
|
|
18
|
-
.setLogger(logger)
|
|
19
|
-
.useRequest(requestIdCtx({ generate: () => 'req.1' }))
|
|
20
|
-
.useRequest(requestLogger({ trustedClientAddressHeader: 'x-forwarded-for' }))
|
|
21
|
-
.get('/users/:id', () => routekitResponse({ ok: true }))
|
|
22
|
-
|
|
23
|
-
const response = await router.fetch(
|
|
24
|
-
new Request('https://example.com/users/1', {
|
|
25
|
-
method: HttpMethod.GET,
|
|
26
|
-
headers: {
|
|
27
|
-
'user-agent': 'routekit-test/1',
|
|
28
|
-
'x-forwarded-for': '203.0.113.9, 10.0.0.1',
|
|
29
|
-
},
|
|
30
|
-
}),
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
expect(response.status).toBe(HttpStatus.OK)
|
|
34
|
-
expect(logger.logs).toHaveLength(2)
|
|
35
|
-
expect(logger.logs[0]).toEqual({
|
|
36
|
-
level: 'info',
|
|
37
|
-
data: ['request started'],
|
|
38
|
-
context: {
|
|
39
|
-
'client.address': '203.0.113.9',
|
|
40
|
-
'http.request.method': HttpMethod.GET,
|
|
41
|
-
'http.route': '/users/:id',
|
|
42
|
-
'request.id': 'req.1',
|
|
43
|
-
'url.path': '/users/1',
|
|
44
|
-
'user_agent.original': 'routekit-test/1',
|
|
45
|
-
},
|
|
46
|
-
metadata: { activity: { name: 'request', phase: 'start' } },
|
|
47
|
-
})
|
|
48
|
-
expect(logger.logs[1]?.level).toBe('info')
|
|
49
|
-
expect(logger.logs[1]?.data).toEqual(['request completed'])
|
|
50
|
-
expect(logger.logs[1]?.context).toMatchObject({
|
|
51
|
-
'event.name': 'http.server.request',
|
|
52
|
-
'http.request.method': HttpMethod.GET,
|
|
53
|
-
'http.response.body.size': 11,
|
|
54
|
-
'http.response.status_code': HttpStatus.OK,
|
|
55
|
-
'http.route': '/users/:id',
|
|
56
|
-
'request.id': 'req.1',
|
|
57
|
-
'url.path': '/users/1',
|
|
58
|
-
'user_agent.original': 'routekit-test/1',
|
|
59
|
-
'client.address': '203.0.113.9',
|
|
60
|
-
})
|
|
61
|
-
expect(logger.logs[1]?.metadata).toEqual({ activity: { name: 'request', phase: 'end' } })
|
|
62
|
-
expect(typeof logger.logs[1]?.context.duration_ms).toBe('number')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('covers generated responses and reports server failures at error severity', async () => {
|
|
66
|
-
const logger = new MemoryLogger()
|
|
67
|
-
const router = new Router()
|
|
68
|
-
.setLogger(logger)
|
|
69
|
-
.useRequest(requestIdCtx({ generate: () => 'req.generated' }))
|
|
70
|
-
.useRequest(requestLogger())
|
|
71
|
-
.get('/boom', () => {
|
|
72
|
-
throw new Error('boom')
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const missing = await router.fetch(new Request('https://example.com/missing'))
|
|
76
|
-
const failed = await router.fetch(new Request('https://example.com/boom'))
|
|
77
|
-
|
|
78
|
-
expect(missing.status).toBe(HttpStatus.NOT_FOUND)
|
|
79
|
-
expect(failed.status).toBe(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
80
|
-
|
|
81
|
-
const completions = logger.logs.filter((record) => record.data[0] === 'request completed')
|
|
82
|
-
expect(completions).toHaveLength(2)
|
|
83
|
-
expect(completions[0]?.context['http.response.status_code']).toBe(HttpStatus.NOT_FOUND)
|
|
84
|
-
expect(completions[0]?.level).toBe('info')
|
|
85
|
-
expect(completions[1]?.context['http.response.status_code']).toBe(
|
|
86
|
-
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
87
|
-
)
|
|
88
|
-
expect(completions[1]?.level).toBe('error')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('does not record forwarded client addresses without an explicitly trusted header', async () => {
|
|
92
|
-
const logger = new MemoryLogger()
|
|
93
|
-
const router = new Router()
|
|
94
|
-
.setLogger(logger)
|
|
95
|
-
.useRequest(requestIdCtx({ generate: () => 'req.ignored' }))
|
|
96
|
-
.useRequest(requestLogger())
|
|
97
|
-
.get('/users', () => new Response(null))
|
|
98
|
-
|
|
99
|
-
await router.fetch(
|
|
100
|
-
new Request('https://example.com/users', {
|
|
101
|
-
headers: {
|
|
102
|
-
'x-forwarded-for': '203.0.113.9',
|
|
103
|
-
},
|
|
104
|
-
}),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
expect(logger.logs[0]?.context['client.address']).toBeUndefined()
|
|
108
|
-
expect(logger.logs[1]?.context['client.address']).toBeUndefined()
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('writes one named JSON access event without activity lifecycle details', async () => {
|
|
112
|
-
const lines: string[] = []
|
|
113
|
-
const logger = new JsonLogger({
|
|
114
|
-
transformRecord: transformRoutekitJsonLogRecord,
|
|
115
|
-
writeLine: (line) => lines.push(line),
|
|
116
|
-
})
|
|
117
|
-
const router = new Router()
|
|
118
|
-
.setLogger(logger)
|
|
119
|
-
.useRequest(requestIdCtx({ generate: () => 'req.json' }))
|
|
120
|
-
.useRequest(requestLogger())
|
|
121
|
-
.get('/users', () => routekitResponse({ ok: true }))
|
|
122
|
-
|
|
123
|
-
await router.fetch(new Request('https://example.com/users'))
|
|
124
|
-
|
|
125
|
-
expect(lines).toHaveLength(1)
|
|
126
|
-
const payload = JSON.parse(lines[0]!)
|
|
127
|
-
expect(payload['event.name']).toBe('http.server.request')
|
|
128
|
-
expect(payload.message).toBeUndefined()
|
|
129
|
-
expect(payload['activity.name']).toBeUndefined()
|
|
130
|
-
expect(payload['activity.phase']).toBeUndefined()
|
|
131
|
-
expect(payload['request.id']).toBe('req.json')
|
|
132
|
-
expect(payload['http.response.status_code']).toBe(HttpStatus.OK)
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
describe(formatRoutekitTerminalLogRecord.name, () => {
|
|
137
|
-
it('renders compact contextual activity start and completion output', () => {
|
|
138
|
-
const lines: string[] = []
|
|
139
|
-
const logger = new TerminalLogger({
|
|
140
|
-
color: false,
|
|
141
|
-
formatRecord: formatRoutekitTerminalLogRecord,
|
|
142
|
-
maxWidth: 200,
|
|
143
|
-
write: (line) => lines.push(line),
|
|
144
|
-
}).withContext({ 'service.name': 'api' })
|
|
145
|
-
const activity = logger.startActivity('request', {
|
|
146
|
-
'request.id': 'req.1',
|
|
147
|
-
'http.request.method': 'GET',
|
|
148
|
-
'url.path': '/users/1',
|
|
149
|
-
hidden: 'not-rendered',
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
activity.end({
|
|
153
|
-
context: { 'http.response.body.size': 1536, 'http.response.status_code': 200 },
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const output = lines.join('')
|
|
157
|
-
|
|
158
|
-
expect(output).toContain('[api] [req.1] \u2192 GET /users/1')
|
|
159
|
-
expect(output).toContain('[api] [req.1] \u2190 GET /users/1 200')
|
|
160
|
-
expect(output).toContain('ms 1.50 KiB')
|
|
161
|
-
expect(output).not.toContain('request started')
|
|
162
|
-
expect(output).not.toContain('request completed')
|
|
163
|
-
expect(output).not.toContain('not-rendered')
|
|
164
|
-
expect(
|
|
165
|
-
output
|
|
166
|
-
.trimEnd()
|
|
167
|
-
.split('\n')
|
|
168
|
-
.every((line) => !line.endsWith(' ')),
|
|
169
|
-
).toBe(true)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('colors request correlations, completion status, and slow duration indicators', () => {
|
|
173
|
-
const lines: string[] = []
|
|
174
|
-
const logger = new TerminalLogger({
|
|
175
|
-
color: true,
|
|
176
|
-
formatRecord: formatRoutekitTerminalLogRecord,
|
|
177
|
-
maxWidth: 200,
|
|
178
|
-
write: (line) => lines.push(line),
|
|
179
|
-
}).withContext({ 'service.name': 'api' })
|
|
180
|
-
const fast = logger.startActivity('request', {
|
|
181
|
-
'request.id': '1',
|
|
182
|
-
'http.request.method': 'GET',
|
|
183
|
-
'url.path': '/fast',
|
|
184
|
-
})
|
|
185
|
-
const slow = logger.startActivity('request', {
|
|
186
|
-
'request.id': '2',
|
|
187
|
-
'http.request.method': 'POST',
|
|
188
|
-
'url.path': '/slow',
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
fast.end({ context: { 'http.response.status_code': 204, duration_ms: 1 } })
|
|
192
|
-
slow.end({ context: { 'http.response.status_code': 503, duration_ms: 1200 } })
|
|
193
|
-
logger
|
|
194
|
-
.withContext({
|
|
195
|
-
'request.id': '3',
|
|
196
|
-
'http.request.method': 'GET',
|
|
197
|
-
'url.path': '/manual-slow',
|
|
198
|
-
duration_ms: 1200,
|
|
199
|
-
})
|
|
200
|
-
.info('slow checkpoint')
|
|
201
|
-
logger
|
|
202
|
-
.withContext({
|
|
203
|
-
'request.id': '4',
|
|
204
|
-
'http.request.method': 'GET',
|
|
205
|
-
'url.path': '/manual-medium',
|
|
206
|
-
duration_ms: 300,
|
|
207
|
-
})
|
|
208
|
-
.info('medium checkpoint')
|
|
209
|
-
|
|
210
|
-
const output = lines.join('')
|
|
211
|
-
|
|
212
|
-
expect(output).toContain('\x1B[1m\x1B[94m[api]\x1B[39m\x1B[22m')
|
|
213
|
-
expect(output).toContain('\x1B[1m\x1B[97mGET\x1B[39m\x1B[22m')
|
|
214
|
-
expect(output).toContain('\x1B[1m\x1B[92m204\x1B[39m\x1B[22m')
|
|
215
|
-
expect(output).toContain('\x1B[1m\x1B[91m503\x1B[39m\x1B[22m')
|
|
216
|
-
expect(output).toContain('\x1B[91m1200ms\x1B[39m')
|
|
217
|
-
expect(output).toContain('\x1B[93m300ms\x1B[39m')
|
|
218
|
-
expect(output.match(/\x1B\[(?:9[1-6]|3[5-6])m\[[1234]\]/gu)).toHaveLength(6)
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('formats durations with precision based on elapsed time', () => {
|
|
222
|
-
const lines: string[] = []
|
|
223
|
-
const logger = new TerminalLogger({
|
|
224
|
-
color: false,
|
|
225
|
-
formatRecord: formatRoutekitTerminalLogRecord,
|
|
226
|
-
maxWidth: 200,
|
|
227
|
-
write: (line) => lines.push(line),
|
|
228
|
-
}).withContext({
|
|
229
|
-
'request.id': 'req.1',
|
|
230
|
-
'http.request.method': 'GET',
|
|
231
|
-
'url.path': '/timing',
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
logger.withContext({ duration_ms: 0.493 }).info('sub-ms')
|
|
235
|
-
logger.withContext({ duration_ms: 1.088 }).info('single-ms')
|
|
236
|
-
logger.withContext({ duration_ms: 16.726 }).info('double-ms')
|
|
237
|
-
logger.withContext({ duration_ms: 100 }).info('hundred-ms')
|
|
238
|
-
logger.withContext({ duration_ms: 1200.4 }).info('slow-ms')
|
|
239
|
-
|
|
240
|
-
const output = lines.join('')
|
|
241
|
-
|
|
242
|
-
expect(output).toContain('0.49ms')
|
|
243
|
-
expect(output).toContain('1.1ms')
|
|
244
|
-
expect(output).toContain('16.7ms')
|
|
245
|
-
expect(output).toContain('100ms')
|
|
246
|
-
expect(output).toContain('1200ms')
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('keeps ordinary request-context messages as messages', () => {
|
|
250
|
-
const lines: string[] = []
|
|
251
|
-
const logger = new TerminalLogger({
|
|
252
|
-
color: false,
|
|
253
|
-
formatRecord: formatRoutekitTerminalLogRecord,
|
|
254
|
-
maxWidth: 200,
|
|
255
|
-
write: (line) => lines.push(line),
|
|
256
|
-
}).withContext({
|
|
257
|
-
'request.id': 'req.1',
|
|
258
|
-
'http.request.method': 'POST',
|
|
259
|
-
'url.path': '/todos',
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
logger.info('request started')
|
|
263
|
-
|
|
264
|
-
expect(lines.join('')).toContain('[req.1] POST /todos request started')
|
|
265
|
-
expect(lines.join('')).not.toContain('\u2192')
|
|
266
|
-
})
|
|
267
|
-
})
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { LogLevel } from '@mpen/logger'
|
|
2
|
-
import type { AnyContext, RequestMiddleware } from '../types'
|
|
3
|
-
import { ROUTEKIT_HTTP_SERVER_REQUEST_EVENT_NAME } from './request-logger-format'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Options for [`requestLogger`]{@link requestLogger}.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const options: RequestLoggerOptions = {
|
|
11
|
-
* completionLevel: response => response.status >= 500 ? LogLevel.ERROR : LogLevel.INFO,
|
|
12
|
-
* }
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
export interface RequestLoggerOptions {
|
|
16
|
-
/**
|
|
17
|
-
* Activity label used for start and completion records.
|
|
18
|
-
* @defaultValue `'request'`
|
|
19
|
-
*/
|
|
20
|
-
activityName?: string
|
|
21
|
-
/**
|
|
22
|
-
* Completion severity or a function selecting severity from the final response.
|
|
23
|
-
*/
|
|
24
|
-
completionLevel?: LogLevel | ((response: Response) => LogLevel)
|
|
25
|
-
/**
|
|
26
|
-
* Header containing a trusted proxy-provided original client address.
|
|
27
|
-
*
|
|
28
|
-
* Only configure this when direct clients cannot provide or alter this header. For
|
|
29
|
-
* `x-forwarded-for`, the first comma-separated address is recorded as `client.address`.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```ts
|
|
33
|
-
* requestLogger({ trustedClientAddressHeader: 'x-forwarded-for' })
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
trustedClientAddressHeader?: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function defaultCompletionLevel(response: Response): LogLevel {
|
|
40
|
-
return response.status >= 500 ? LogLevel.ERROR : LogLevel.INFO
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const HTTP_SERVER_REQUEST_EVENT = {
|
|
44
|
-
'event.name': ROUTEKIT_HTTP_SERVER_REQUEST_EVENT_NAME,
|
|
45
|
-
} as const
|
|
46
|
-
|
|
47
|
-
function getResponseBodySize(response: Response): number | undefined {
|
|
48
|
-
if (response.body == null) {
|
|
49
|
-
return 0
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const value = response.headers.get('content-length')
|
|
53
|
-
if (value == null || !/^\d+$/u.test(value)) {
|
|
54
|
-
return undefined
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const size = Number(value)
|
|
58
|
-
return Number.isSafeInteger(size) ? size : undefined
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function getClientAddress(
|
|
62
|
-
request: { headers: Headers },
|
|
63
|
-
trustedHeader: string | undefined,
|
|
64
|
-
): string | undefined {
|
|
65
|
-
if (trustedHeader == null) {
|
|
66
|
-
return undefined
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const value = request.headers.get(trustedHeader)?.trim()
|
|
70
|
-
if (!value) {
|
|
71
|
-
return undefined
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return trustedHeader.toLowerCase() === 'x-forwarded-for'
|
|
75
|
-
? value.split(',', 1)[0]?.trim()
|
|
76
|
-
: value
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Log a timed request activity through the request's contextual logger.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* ```ts
|
|
84
|
-
* router.useRequest(requestIdCtx())
|
|
85
|
-
* router.useRequest(requestLogger())
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* @param options - Request activity naming and severity options.
|
|
89
|
-
* @returns Request-boundary middleware that logs final status and duration.
|
|
90
|
-
* @typeParam Ctx - Context containing the correlated request identifier.
|
|
91
|
-
*/
|
|
92
|
-
export function requestLogger<
|
|
93
|
-
Ctx extends { requestId: string } & object = AnyContext & { requestId: string },
|
|
94
|
-
>(options: RequestLoggerOptions = {}): RequestMiddleware<{}, Ctx> {
|
|
95
|
-
const activityName = options.activityName ?? 'request'
|
|
96
|
-
|
|
97
|
-
return async (ctx, next) => {
|
|
98
|
-
const clientAddress = getClientAddress(ctx.request, options.trustedClientAddressHeader)
|
|
99
|
-
const userAgent = ctx.request.headers.get('user-agent') ?? undefined
|
|
100
|
-
const activity = ctx.logger.startActivity(activityName, {
|
|
101
|
-
...(clientAddress == null ? {} : { 'client.address': clientAddress }),
|
|
102
|
-
...(userAgent == null ? {} : { 'user_agent.original': userAgent }),
|
|
103
|
-
})
|
|
104
|
-
ctx.logger = activity.logger
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const response = await next()
|
|
108
|
-
const bodySize = getResponseBodySize(response)
|
|
109
|
-
const level =
|
|
110
|
-
typeof options.completionLevel === 'function'
|
|
111
|
-
? options.completionLevel(response)
|
|
112
|
-
: (options.completionLevel ?? defaultCompletionLevel(response))
|
|
113
|
-
activity.end({
|
|
114
|
-
context: {
|
|
115
|
-
...HTTP_SERVER_REQUEST_EVENT,
|
|
116
|
-
'http.response.status_code': response.status,
|
|
117
|
-
...(bodySize == null ? {} : { 'http.response.body.size': bodySize }),
|
|
118
|
-
},
|
|
119
|
-
level,
|
|
120
|
-
})
|
|
121
|
-
return response
|
|
122
|
-
} catch (error) {
|
|
123
|
-
activity.end({
|
|
124
|
-
context: HTTP_SERVER_REQUEST_EVENT,
|
|
125
|
-
data: [error],
|
|
126
|
-
level: LogLevel.ERROR,
|
|
127
|
-
})
|
|
128
|
-
throw error
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|