|
1 | | -# Bugsnag zero |
| 1 | +# Bugsnag zero monorepo |
2 | 2 |
|
3 | | -## What is this? |
| 3 | +This repository contains the source for the published packages: |
4 | 4 |
|
5 | | -This is a rebuilt version of the |
6 | | -[bugsnag-js](https://github.com/bugsnag/bugsnag-js) client with the following |
7 | | -goals: |
| 5 | +- [`@birchill/bugsnag-zero`](./packages/bugsnag-zero) – the lightweight Bugsnag |
| 6 | + client. |
| 7 | +- [`@birchill/bugsnag-zero-lambda-context`](./packages/bugsnag-zero-lambda-context) - |
| 8 | + a plugin for enriching events with AWS Lambda request metadata. |
8 | 9 |
|
9 | | -- Reduced bundle size |
10 | | -- Support for non-main thread contexts (e.g. Web workers) |
11 | | - |
12 | | -It does this using the following approach: |
13 | | - |
14 | | -- Dropping support for older browsers including IE |
15 | | -- Leaning heavily on the plugin approach — every feature is a plugin. As a |
16 | | - result unused features are tree-shaked and don't affect your bundle size |
17 | | -- Being written entirely in TypeScript - by doing more checking at build time we |
18 | | - can drop some runtime checks |
19 | | - |
20 | | -It doesn't include support for quite a number of features simply because we |
21 | | -haven't found a need for them yet. Some noteable ones include: |
22 | | - |
23 | | -- Sessions |
24 | | -- Inline scripts (these days CSP generally makes inline scripts harder to use |
25 | | - anyway) |
26 | | -- Loggers |
27 | | -- RegExps for redacted keys |
28 | | -- Callbacks for breadcrumbs |
29 | | -- User IP collection (we prefer to respect user privacy) |
30 | | - |
31 | | -Many of these could be added, if needed, by adding further plugins. |
32 | | - |
33 | | -On the other hand, it adds a few other features: |
34 | | - |
35 | | -- The ability to substitute in custom delivery providers (e.g. so you can send |
36 | | - to an SNS topic). |
37 | | -- `Bugsnag.notify()` returns a Promise so you can wait on it to ensure delivery |
38 | | - was successful. |
39 | | -- `Bugsnag.notify()` can take `metadata` and `severity` settings as a object |
40 | | - rather than you having to provide an on-error callback (see below). |
41 | | -- If an `Error` object has a `metadata` field, it will be merged into the |
42 | | - reported error's metadata. |
43 | | -- A `browserHandledRejectionBreadcrumbs` plugin for logging _handled_ rejections. |
44 | | -- Post-error callbacks - called after fully preparing the error but just before |
45 | | - sending it. This was mostly added as a means of supporting "error" breadcrumbs. |
46 | | -- `redactKeys` exports its functions so you can re-use them for other logging |
47 | | - etc. |
48 | | - |
49 | | -So far very little effort has been spent on optimizing the code size of the |
50 | | -generated code. A little code golf and manual minification could likely reduce |
51 | | -the bundle size much further still. |
52 | | - |
53 | | -## Usage |
54 | | - |
55 | | -The easiest way to use this for an existing installation is to use the legacy |
56 | | -config helper, `fromLegacyConfig`. However, note that doing this will produce |
57 | | -much less significant code savings since much less code can be tree-shaken. |
58 | | - |
59 | | -Better still is to manually configure the plugins one-by-one according to your |
60 | | -needs. For example, |
61 | | - |
62 | | -```typescript |
63 | | -import Bugsnag, { |
64 | | - appDuration, |
65 | | - browserContext, |
66 | | - browserHandledRejectionBreadcrumbs, |
67 | | - browserNotifyUnhandledExceptions, |
68 | | - browserNotifyUnhandledRejections, |
69 | | - consoleBreadcrumbs, |
70 | | - deviceOrientation, |
71 | | - errorBreadcrumbs, |
72 | | - fetchBreadcrumbs, |
73 | | - interactionBreadcrumbs, |
74 | | - limitEvents, |
75 | | - navigationBreadcrumbs, |
76 | | - ReactPlugin, |
77 | | - redactKeys, |
78 | | - stringifyValues, |
79 | | -} from '@birchill/bugsnag-zero'; |
80 | | - |
81 | | -const plugins = [ |
82 | | - appDuration, |
83 | | - browserContext, |
84 | | - browserHandledRejectionBreadcrumbs, |
85 | | - browserNotifyUnhandledExceptions, |
86 | | - browserNotifyUnhandledRejections, |
87 | | - deviceOrientation, |
88 | | - errorBreadcrumbs, |
89 | | - fetchBreadcrumbs, |
90 | | - interactionBreadcrumbs, |
91 | | - limitEvents(10), |
92 | | - navigationBreadcrumbs, |
93 | | - ReactPlugin, |
94 | | - redactKeys(['accessToken', 'password']), |
95 | | - stringifyValues, |
96 | | -]; |
97 | | - |
98 | | -if (__RELEASE_STAGE__ !== 'test') { |
99 | | - plugins.push(consoleBreadcrumbs); |
100 | | -} |
101 | | - |
102 | | -Bugsnag.start({ |
103 | | - apiKey: '<apiKey>', |
104 | | - appType: 'browser', |
105 | | - collectUserIp: false, |
106 | | - enabledReleaseStages: ['prod', 'beta'], |
107 | | - plugins, |
108 | | - releaseStage: __RELEASE_STAGE__, |
109 | | -}); |
110 | | -``` |
111 | | - |
112 | | -There are a few API differences from the official client. Hopefully the |
113 | | -TypeScript interfaces make these more obvious. |
114 | | - |
115 | | -For example, we've often found it useful to set the severity of an error while |
116 | | -notifying. The official client requires supplying a callback to do this but this |
117 | | -module allows specifying it as a property on the second argument to the notify |
118 | | -callback: |
119 | | - |
120 | | -```typescript |
121 | | -// bugsnag-js |
122 | | -Bugsnag.notify(message, (event) => { |
123 | | - event.severity = 'warning'; |
124 | | -}); |
125 | | - |
126 | | -// @birchill/bugsnag-zero |
127 | | -Bugsnag.notify(message, { severity: 'warning' }); |
128 | | -``` |
129 | | - |
130 | | -Similarly, it is possible to specify `metadata` directly on this second |
131 | | -argument. |
132 | | - |
133 | | -It is, of course, still possible to pass an error callback as per the official |
134 | | -client's API. |
135 | | - |
136 | | -### React plugin |
137 | | - |
138 | | -In order to allow using Preact with the React plugin and to avoid introducing a |
139 | | -dependency on React itself just for its types, the typings for the React plugin |
140 | | -are a bit convoluted. |
141 | | - |
142 | | -For Preact something like the following is needed: |
143 | | - |
144 | | -```typescript |
145 | | -const MyBugsnagErrorBoundary = React.useMemo( |
146 | | - () => |
147 | | - Bugsnag.getPlugin('react')!.createErrorBoundary< |
148 | | - typeof React.Component, |
149 | | - ComponentType, |
150 | | - VNode |
151 | | - >(React.Component, React.createElement), |
152 | | - [] |
153 | | -); |
154 | | -``` |
155 | | - |
156 | | -Furthermore, unlike the official bugsnag-js client, we don't allow passing in |
157 | | -React to the constructor. Instead we always require a call to |
158 | | -`createErrorBoundary`. |
159 | | - |
160 | | -### Usage with Node.js |
161 | | - |
162 | | -We don't properly support Node.js at this time. In particular, there's no |
163 | | -delivery mechanism defined for it. It would be trivial to write, but we haven't |
164 | | -needed it yet. |
165 | | - |
166 | | -That said, there are some plugins that should work with node including the |
167 | | -`lambdaContext` plugin for logging from AWS Lambda. |
168 | | - |
169 | | -At Birchill, we use a custom `Delivery` class to post the events to an SNS topic |
170 | | -and have that send to Bugsnag since that's faster than having the Lambda wait on |
171 | | -the Bugsnag server and more flexible too (e.g. you can post your events to Slack |
172 | | -etc. too as needed). |
173 | | - |
174 | | -The setup looks something like: |
175 | | - |
176 | | -```typescript |
177 | | -import Bugsnag, { |
178 | | - appDuration, |
179 | | - errorBreadcrumbs, |
180 | | - lambdaContext, |
181 | | - nodeNotifyUnhandledExceptions, |
182 | | - nodeNotifyUnhandledRejections, |
183 | | - redactKeys, |
184 | | -} from '@birchill/bugsnag-zero'; |
185 | | -import { lambdaContext } from '@birchill/bugsnag-zero/lambda-context'; |
186 | | - |
187 | | -Bugsnag.start({ |
188 | | - apiKey: '<unused>', |
189 | | - appType: 'nodejs', |
190 | | - plugins: [ |
191 | | - appDuration, |
192 | | - errorBreadcrumbs, |
193 | | - lambdaContext(), |
194 | | - nodeNotifyUnhandledExceptions, |
195 | | - nodeNotifyUnhandledRejections, |
196 | | - redactKeys(keysToRedact), |
197 | | - ], |
198 | | -}); |
199 | | - |
200 | | -Bugsnag.setDelivery({ |
201 | | - sendEvent: async ({ events }): Promise<void> => { |
202 | | - const errorClass = events[0].exceptions[0]?.errorClass || 'Unknown'; |
203 | | - const context = events[0].context; |
204 | | - const subject = `Error: ${errorClass} in ${context}`; |
205 | | - |
206 | | - const publishCommand = new PublishCommand({ |
207 | | - Subject: subject, |
208 | | - TopicArn: errorTopicArn, |
209 | | - Message: JSON.stringify(events), |
210 | | - }); |
211 | | - |
212 | | - await snsClient.send(publishCommand); |
213 | | - }, |
214 | | -}); |
215 | | - |
216 | | -// When a Lambda handler is called, update the lambdaContext plugin: |
217 | | - |
218 | | -async function handler(event: Event, context: Context): Promise<void> { |
219 | | - Bugsnag.getPlugin('lambdaContext')?.setContext(event, context); |
220 | | -} |
221 | | -``` |
222 | | - |
223 | | -### Using a custom user agent string parser |
224 | | - |
225 | | -Bugsnag's [v5 reporting API](https://bugsnagerrorreportingapi.docs.apiary.io/#reference/0/notify/send-error-reports) |
226 | | -requires passing in the browser name, browser version, OS name etc. explicitly. |
227 | | -In other words, it requires you to parse the user agent string on the _client_. |
228 | | - |
229 | | -By comparison, the v4 API that the official client uses just passes the user |
230 | | -agent string to the API and lets the server parse it. |
231 | | - |
232 | | -Adding a full-blown user agent string parser would bloat this library a lot so |
233 | | -we provide a very simple one that covers the basic cases. For example, it |
234 | | -doesn't handle things like bots etc. since hopefully they're probably not going |
235 | | -to be triggering your error reporting (and if they are, the raw user agent |
236 | | -string is still included so you can detect that). |
237 | | - |
238 | | -However, perhaps your app already has a user agent string parser included and |
239 | | -you want to re-use that? You can do that by using the |
240 | | -`browserContextWithUaParser` plugin in place of the `browserContext` plugin and |
241 | | -supplying a function that takes a string and returns an object of the following |
242 | | -shape: |
243 | | - |
244 | | -```typescript |
245 | | -type UserAgentInfo = { |
246 | | - browserName?: string; |
247 | | - browserVersion?: string; |
248 | | - osName?: string; |
249 | | - osVersion?: string; |
250 | | - manufacturer?: string; |
251 | | - model?: string; |
252 | | - modelNumber?: string; |
253 | | -}; |
254 | | -``` |
255 | | - |
256 | | -For an unrecognized user agent string, you would just return an empty object |
257 | | -(`{}`). |
258 | | - |
259 | | -For example: |
260 | | - |
261 | | -```typescript |
262 | | -import Bugsnag, { browserContextWithUaParser } from '@birchill/bugsnag-zero'; |
263 | | - |
264 | | -const myUaParser = new Parser(); |
265 | | -const parseUaString = (uaString: string) => { |
266 | | - const result = myUaParser.parse(uaString); |
267 | | - return result |
268 | | - ? { |
269 | | - browserName: result.browser, |
270 | | - browserVersion: `${result.major}.${result.minor}`, |
271 | | - } |
272 | | - : {}; |
273 | | -}; |
274 | | - |
275 | | -Bugsnag.start({ |
276 | | - apiKey: '<unused>', |
277 | | - appType: 'nodejs', |
278 | | - plugins: [browserContextWithUaParser(parseUaString)], |
279 | | -}); |
280 | | -``` |
281 | | - |
282 | | -This also gives you full control on how browsers are grouped together (e.g. do |
283 | | -you want Chrome on iOS to be treated the same as real Chrome? Do you want an |
284 | | -EdgeHTML version of Edge to be grouped together with Chromium Edge?) |
285 | | - |
286 | | -Similarly, if you have no need for user agent string parsing and want to use |
287 | | -`browserContext` without the user agent string parsing bloating your code, the |
288 | | -following should hopefully mean it gets tree-shaken out: |
289 | | - |
290 | | -```typescript |
291 | | -import Bugsnag, { browserContextWithUaParser } from '@birchill/bugsnag-zero'; |
292 | | - |
293 | | -Bugsnag.start({ |
294 | | - apiKey: '<unused>', |
295 | | - appType: 'nodejs', |
296 | | - plugins: [browserContextWithUaParser(() => {})], |
297 | | -}); |
298 | | -``` |
299 | | - |
300 | | -Note that none of this is tested at all so let me know if it doesn't work. |
301 | | - |
302 | | -## Development |
303 | | - |
304 | | -### Building |
305 | | - |
306 | | -``` |
307 | | -pnpm build |
308 | | -``` |
309 | | - |
310 | | -### Releasing |
311 | | - |
312 | | -``` |
313 | | -pnpm release |
314 | | -git push --follow-tags |
315 | | -``` |
316 | | - |
317 | | -Hopefully GitHub Actions will take care of publishing the release. |
318 | | - |
319 | | -(Note that it's going to default to applying the `latest` tag so if we ever need |
320 | | -to publish an update to an older version we'll need to do it manually.) |
| 10 | +Each package can be built with `pnpm -r build` and tested individually. See the |
| 11 | +respective package directories for full README files and documentation. |
0 commit comments