-
Notifications
You must be signed in to change notification settings - Fork 62
/
server.ts
192 lines (160 loc) · 6.42 KB
/
server.ts
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import os from 'os'
import domain from 'domain'
import { Client, Util, Types } from '@honeybadger-io/core'
import { getSourceFile, readConfigFromFileSystem } from './server/util'
import uncaughtException from './server/integrations/uncaught_exception_plugin'
import unhandledRejection from './server/integrations/unhandled_rejection_plugin'
import { errorHandler, requestHandler } from './server/middleware'
import { lambdaHandler } from './server/aws_lambda'
import { ServerTransport } from './server/transport'
import { StackedStore } from './server/stacked_store'
import { CheckInsConfig } from './server/check-ins-manager/types'
import { CheckInsClient } from './server/check-ins-manager/client';
const { endpoint } = Util
const DEFAULT_PLUGINS = [
uncaughtException(),
unhandledRejection()
]
type HoneybadgerServerConfig = (Types.Config | Types.ServerlessConfig) & CheckInsConfig
class Honeybadger extends Client {
private __checkInsClient: CheckInsClient
/** @internal */
protected __beforeNotifyHandlers: Types.BeforeNotifyHandler[] = [
(notice?: Types.Notice) => {
if (notice && notice.backtrace) {
notice.backtrace.forEach((line) => {
if (line.file) {
line.file = line.file.replace(/.*\/node_modules\/(.+)/, '[NODE_MODULES]/$1')
line.file = line.file.replace(notice.projectRoot, '[PROJECT_ROOT]')
}
return line
})
}
}
]
public errorHandler: typeof errorHandler
public requestHandler: typeof requestHandler
public lambdaHandler: typeof lambdaHandler
config: HoneybadgerServerConfig
constructor(opts: Partial<HoneybadgerServerConfig> = {}) {
const transport = new ServerTransport({
'User-Agent': userAgent(),
})
super({
projectRoot: process.cwd(),
hostname: os.hostname(),
...opts,
}, transport)
// serverless defaults
const config = this.config as Types.ServerlessConfig
config.reportTimeoutWarning = config.reportTimeoutWarning ?? true
config.timeoutWarningThresholdMs = config.timeoutWarningThresholdMs || 50
this.__checkInsClient = new CheckInsClient(this.config, transport)
this.__getSourceFileHandler = getSourceFile.bind(this)
this.errorHandler = errorHandler.bind(this)
this.requestHandler = requestHandler.bind(this)
this.lambdaHandler = lambdaHandler.bind(this)
}
factory(opts?: Partial<HoneybadgerServerConfig>): this {
const clone = new Honeybadger({
// fixme: this can create unwanted side-effects, needs to be tested thoroughly before enabling
// __plugins: DEFAULT_PLUGINS,
...(readConfigFromFileSystem() ?? {}),
...opts,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any
clone.setNotifier(this.getNotifier())
return clone
}
configure(opts: Partial<HoneybadgerServerConfig> = {}): this {
return super.configure(opts)
}
protected __initStore() {
// @ts-ignore
this.__store = new StackedStore(this.config.maxBreadcrumbs);
}
public showUserFeedbackForm(): Promise<void> {
throw new Error('Honeybadger.showUserFeedbackForm() is not supported on the server-side')
}
async checkIn(idOrName: string): Promise<void> {
try {
const id = await this.getCheckInId(idOrName)
await this.__transport
.send({
method: 'GET',
endpoint: endpoint(this.config.endpoint, `v1/check_in/${id}`),
logger: this.logger,
})
this.logger.info('CheckIn sent')
}
catch (err) {
this.logger.error(`CheckIn[${idOrName}] failed: an unknown error occurred.`, `message=${err.message}`)
}
}
private async getCheckInId(idOrName: string): Promise<string> {
if (!this.config.checkins || this.config.checkins.length === 0) {
return idOrName
}
const localCheckIn = this.config.checkins.find(c => c.name === idOrName)
if (!localCheckIn) {
return idOrName
}
if (localCheckIn.id) {
return localCheckIn.id
}
const projectCheckIns = await this.__checkInsClient.listForProject(localCheckIn.projectId)
const remoteCheckIn = projectCheckIns.find(c => c.name === localCheckIn.name)
if (!remoteCheckIn) {
this.logger.debug(`Checkin[${idOrName}] was not found on HB. This should not happen. Was the sync command executed?`)
return idOrName
}
// store the id in-memory, so subsequent check-ins won't have to call the API
localCheckIn.id = remoteCheckIn.id
return localCheckIn.id
}
// This method is intended for web frameworks.
// It allows us to track context for individual requests without leaking to other requests
// by doing two things:
// 1. Using AsyncLocalStorage so the context is tracked across async operations.
// 2. Attaching the store contents to the request object,
// so, even if the store is destroyed, we can still recover the context for a given request
// (for example, in an error-handling middleware)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public withRequest<R>(
request: Record<symbol, unknown>,
handler: (...args: never[]) => R,
onError?: (...args: unknown[]) => unknown
): R|void {
if (onError) {
// ALS is fine for context-tracking, but `domain` allows us to catch errors
// thrown asynchronously (timers, event emitters)
// We can't use unhandledRejection/uncaughtException listeners; they're global and shared across all requests
// But the `onError` handler might be request-specific.
// Note that this doesn't still handle all cases. `domain` has its own problems:
// See https://github.com/honeybadger-io/honeybadger-js/pull/711
const dom = domain.create();
const onErrorWithContext = (err) => this.__store.run(() => onError(err), request);
dom.on('error', onErrorWithContext);
handler = dom.bind(handler);
}
return this.__store.run(handler, request);
}
public run<R>(callback: (...args: never[]) => R): R {
return this.__store.run(callback);
}
}
const NOTIFIER = {
name: '@honeybadger-io/js',
url: 'https://github.com/honeybadger-io/honeybadger-js/tree/master/packages/js',
version: '__VERSION__'
}
const userAgent = () => {
return `Honeybadger JS Server Client ${NOTIFIER.version}, ${os.version()}; ${os.platform()}`
}
const singleton = new Honeybadger({
__plugins: DEFAULT_PLUGINS,
...(readConfigFromFileSystem() ?? {})
})
singleton.setNotifier(NOTIFIER)
export { Types } from '@honeybadger-io/core'
export default singleton