forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path_error.tsx
104 lines (97 loc) · 4.12 KB
/
_error.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import type { NextPageContext } from 'next'
import { GenericError } from 'components/GenericError'
function Error() {
return <GenericError />
}
Error.getInitialProps = async (ctx: NextPageContext) => {
// If this getInitialProps() is called in client-side rendering,
// you won't have a `.res` object. It's only present when it's
// rendered Node (SSR). That's our clue to know that, we should
// send this error to Failbot.
// In client-side, it's undefined. In server, it's a ServerResponse object.
const { err, req, res } = ctx
let statusCode = 500
if (res?.statusCode) {
statusCode = res.statusCode
}
// 'err' will by falsy if it's a 404
// But note, at the time of writing this comment, we have a dedicated
// `pages/404.tsx` which takes care of 404 messages.
if (err && res && req) {
// This is a (necessary) hack!
// You can't import `../lib/failbot.js` here in this
// file because it gets imported by webpack to be used in the
// client-side JS bundle. It *could* be solved by overriding
// the webpack configuration in our `next.config.js` but this is prone
// to be fragile since ignoring code can be hard to get right
// and the more we override there, the harder it will become to
// upgrade NextJS in the future because of moving parts.
// So the solution is to essentially do what the contextualizers
// do which mutate the Express request object by attaching
// callables to it. This way it's only ever present in SSR executed
// code and doesn't need any custom webpack configuration.
const expressRequest = req as any
const FailBot = expressRequest.FailBot
if (FailBot) {
try {
// An inclusion-list of headers we're OK with sending because
// they don't contain an PII.
const OK_HEADER_KEYS = ['user-agent', 'referer', 'accept-encoding', 'accept-language']
const reported = FailBot.report(err, {
path: req.url,
request: JSON.stringify(
{
method: expressRequest.method,
query: expressRequest.query,
language: expressRequest.language,
},
undefined,
2
),
headers: JSON.stringify(
Object.fromEntries(
Object.entries(req.headers).filter(([k]) => OK_HEADER_KEYS.includes(k))
),
undefined,
2
),
})
// Within FailBot.report() (which is our wrapper for Failbot in
// the `@github/failbot` package), it might exit only because
// it has no configured backends to send to. I.e. it returns undefined.
// Otherwise, it should return `Array<Promise<Response | void>>`.
if (!reported) {
console.warn(
'The FailBot.report() returned undefined which means the error was NOT sent to Failbot.'
)
} else if (
Array.isArray(reported) &&
reported.length &&
reported.every((thing) => thing instanceof Promise)
) {
// Make sure we await the promises even though we don't care
// about the results. This makes the code cleaner rather than
// letting the eventloop take care of it which could result
// in cryptic error messages if the await does fail for some
// reason.
try {
await Promise.all(reported)
} catch (error) {
console.warn('Unable to await reported FailBot errors', error)
}
}
} catch (error) {
// This does not necessarily mean FailBot failed to send. It's
// most likely that we failed to *send to* FailBot before it
// even has a chance to use the network. This is because
// `FailBot.report` returns an array of Promises which themselves
// could go wrong, but that's a story for another try/catch.
// Basically, this catch it just to avoid other errors that
// might prevent the pretty error page to render at all.
console.warn('Failed to send error to FailBot.', error)
}
}
}
return { statusCode, message: err?.message }
}
export default Error