├── .eslintignore
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE.md
└── release.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .travis.yml
├── AUTHORS
├── CONTRIBUTING.md
├── History.md
├── LICENSE
├── Makefile
├── README.md
├── bin
└── raven
├── docs
├── Makefile
├── coffeescript.rst
├── conf.py
├── config.rst
├── index.rst
├── integrations
│ ├── connect.rst
│ ├── express.rst
│ ├── index.rst
│ ├── koa.rst
│ ├── loopback.rst
│ └── sails.rst
├── make.bat
├── sentry-doc-config.json
├── sourcemaps.rst
├── typescript.rst
└── usage.rst
├── index.js
├── lib
├── client.js
├── instrumentation
│ ├── console.js
│ ├── http.js
│ ├── instrumentor.js
│ └── pg.js
├── parsers.js
├── transports.js
└── utils.js
├── package-lock.json
├── package.json
├── test
├── exit
│ ├── capture.js
│ ├── capture_callback.js
│ ├── capture_with_second_domain_error.js
│ ├── capture_with_second_error.js
│ ├── domain_capture.js
│ ├── domain_capture_callback.js
│ ├── domain_capture_with_second_error.js
│ ├── domain_throw_on_send.js
│ ├── throw_on_fatal.js
│ └── throw_on_send.js
├── fixtures
│ ├── file with spaces in path.js
│ └── long-line.js
├── instrumentation
│ ├── README.md
│ ├── http.test.js
│ ├── run-node-http-test.js
│ └── run.sh
├── manual
│ ├── README.md
│ ├── context-memory.js
│ ├── express-patient.js
│ ├── largeModule.js
│ ├── manager.js
│ └── poke-patient.sh
├── mocha.opts
├── raven.client.js
├── raven.parsers.js
├── raven.transports.js
├── raven.utils.js
├── run.coffee
└── vendor
│ └── json-stringify-safe.js
└── vendor
├── json-stringify-safe.js
└── node-lsmod.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | test/instrumentation/node-*
3 | test/manual/largeModule.js
4 | test/fixtures/*
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "mocha": true,
5 | "es6": true
6 | },
7 | "rules": {
8 | "block-scoped-var": 2,
9 | "camelcase": [1, "properties": "never"],
10 | "comma-dangle": 0,
11 | "consistent-return": 2,
12 | "consistent-this": [2, "self"],
13 | "default-case": 2,
14 | "dot-notation": 2,
15 | "eqeqeq": [2, "allow-null"],
16 | "guard-for-in": 2,
17 | "new-cap": 2,
18 | "no-alert": 2,
19 | "no-caller": 2,
20 | "no-catch-shadow": 1,
21 | "no-console": 1,
22 | "no-debugger": 2,
23 | "no-else-return": 2,
24 | "no-empty": [2, {"allowEmptyCatch": true}],
25 | "no-eval": 2,
26 | "no-extra-bind": 2,
27 | "no-extend-native": 2,
28 | "no-fallthrough": 2,
29 | "no-floating-decimal": 2,
30 | "no-implied-eval": 2,
31 | "no-inner-declarations": 2,
32 | "no-iterator": 2,
33 | "no-lonely-if": 2,
34 | "no-loop-func": 2,
35 | "no-multi-str": 2,
36 | "no-new": 2,
37 | "no-param-reassign": 1,
38 | "no-proto": 2,
39 | "no-redeclare": 2,
40 | "no-return-assign": 2,
41 | "no-script-url": 2,
42 | "no-self-compare": 2,
43 | "no-sequences": 2,
44 | "no-shadow": 2,
45 | "no-shadow-restricted-names": 2,
46 | "no-undef": 2,
47 | "no-undefined": 2,
48 | "no-underscore-dangle": 0,
49 | "no-unused-vars": [2, {"vars": "all", "args": "none"}],
50 | "no-use-before-define": [2, "nofunc"],
51 | "no-useless-call": 2,
52 | "no-useless-escape": 2,
53 | "no-warning-comments": 0,
54 | "no-with": 2,
55 | "spaced-comment": [2, "always"],
56 | "strict": [2, "global"],
57 | "valid-jsdoc": 0,
58 | "valid-typeof": 2,
59 | "yoda": [2, "never"]
60 | },
61 | "extends": [
62 | "prettier"
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Do you want to request a *feature* or report a *bug*?**
2 |
3 | (If this is a *usage question*, please **do not post it here**—post it on [forum.sentry.io](https://forum.sentry.io) instead. If this is not a “feature” or a “bug”, or the phrase “How do I...?” applies, then it's probably a usage question.)
4 |
5 | **Has someone had this problem before?**
6 |
7 | Before opening an issue, please search the issue tracker to make sure your issue hasn't already been reported.
8 |
9 | **What is the current behavior?**
10 |
11 | If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal code sample.
12 |
13 | **What is the expected behavior?**
14 |
15 | Which versions of raven-node are affected by this issue? How are you setting up raven-node in your application? Did this work previously, or in any other configuration? Are you using [hosted Sentry](https://sentry.io) or on-premises? If on-premises, which version (e.g. 8.7.0)?
16 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | store: zeus
2 | targets:
3 | - name: github
4 | changelog: History.md
5 | - npm
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | docs/_build
15 | docs/doctrees
16 |
17 | node_modules
18 | npm-debug.log
19 |
20 | working/
21 | coverage/
22 | test/instrumentation/node-*
23 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "docs/_sentryext"]
2 | path = docs/_sentryext
3 | url = https://github.com/getsentry/sentry-doc-support
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs/
2 | test/
3 | working/
4 | coverage/
5 |
6 | .git/
7 | .github/
8 | .gitignore
9 | .gitmodules
10 |
11 | .eslintignore
12 | .eslintrc
13 |
14 | .npmignore
15 | .travis.yml
16 | Makefile
17 | AUTHORS
18 | CONTRIBUTING.md
19 |
20 | .idea/
21 |
22 | lib-cov
23 | *.seed
24 | *.log
25 | *.csv
26 | *.dat
27 | *.out
28 | *.pid
29 | *.gz
30 |
31 | pids
32 | logs
33 | results
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "4"
5 | - "5"
6 | - "6"
7 | - "7"
8 | - "8"
9 | - "9"
10 | - "10"
11 |
12 | cache:
13 | directories:
14 | - node_modules
15 |
16 | branches:
17 | only:
18 | - master
19 |
20 | script: npm run test-full
21 |
22 | matrix:
23 | include:
24 | - script: npm pack
25 | node_js: "8"
26 | after_success:
27 | - npm install -g @zeus-ci/cli
28 | - zeus upload -t "application/tar+npm" *.tgz
29 |
30 | notifications:
31 | webhooks:
32 | urls:
33 | - https://zeus.ci/hooks/b152d48c-d694-11e7-99e7-0a580a28020f/public/provider/travis/webhook
34 | on_success: always
35 | on_failure: always
36 | on_start: always
37 | on_cancel: always
38 | on_error: always
39 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | https://github.com/mattrobenolt/raven-node/graphs/contributors
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to release raven-node:
2 |
3 | * [ ] Run the manual memory tests in `test/manual` to make sure we didn't introduce a memory leak
4 | * [ ] Consider whether any changes warrant additions to these tests
5 | * [ ] Stop and think "What version number should this be according to SemVer?"
6 | * [ ] Add an entry to the [History](https://github.com/getsentry/raven-node/blob/master/History.md) file.
7 | * [ ] Bump version number in `package.json`
8 | * [ ] Commit changes `git commit -am ""`
9 | * [ ] Create a tag `git tag -a -m ""`
10 | * [ ] Push to GitHub (`git push origin master --follow-tags`).
11 | * [ ] Once CI builds pass, `sentry-probot` will publish a release on npm and GitHub.
12 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 2.6.2 - 2018-05-17
4 |
5 | - ref: Emit transaction instead of culprit (#458)
6 |
7 | ## 2.6.1 - 2018-05-10
8 |
9 | - fix: correctly detect errors from vm (#457)
10 | - ref: use console.warn for alerts and store them in Set (#455)
11 | - ci: Add node 9 and 10 to travis builds (#456)
12 |
13 | ## 2.6.0 - 2018-04-24
14 |
15 | - fix: Use shallow-copy instead of deep-copy when working with context to prevent too large memory usage and slowing down request resonses [See #452]
16 |
17 | ## 2.5.0 - 2018-04-09
18 |
19 | - feat: log non-standard ports in breadcrumbs (#440)
20 | - feat: add flag for unhandled promise rejections (#446)
21 | - fix: Remove a redundant try-catch block (#445)
22 | - fix: Do not override context when capturing non-error exceptions (#444)
23 | - fix: Update stack-trace to handle spaces in paths (#437)
24 | - docs: Remove private DSNs from the docs (#447)
25 | - docs: Update Usage docs to include Domains in Promise support (#438)
26 |
27 | ## 2.4.2 - 2018-02-27
28 |
29 | - fix: Dont throw exception when called captureException without config (#431)
30 | - fix: Preserve context in for rejected promises (#428)
31 | - ref: Log promise rejection reason alongside eventid (#434)
32 | - ref: Use named function for middlewares (#429)
33 |
34 | ## 2.4.1 - 2018-02-09
35 |
36 | - fix: Handle scoped packages structure in node-lsmod (#426)
37 | - fix: Report fatal errors as indeed "fatal" (#425)
38 | - ref: Use a named function for the middleware over an anonymous (#424)
39 |
40 | ## 2.4.0 - 2018-01-24
41 |
42 | - feat: Sensible non-Error exception serializer (#416)
43 | - fix: workaround for express.js 'host' deprecation message (#413)
44 |
45 | ## 2.3.0 - 12/11/2017
46 |
47 | - fix: attach remaining non-enumerables to req [See #387]
48 | - feat: Allow to configure stacktrace for captureMessage calls [See #388]
49 | - fix: access 'process' through global variable [See #399]
50 | - ref: Enable http instrumentation by default [See #401]
51 | - ref: Warn user when using capture/wrap without installing Raven [See #402]
52 | - ci: Integrate Zeus and release with the bot [See #403]
53 | - ref: Delete empty properties before sending event to the server [See #407]
54 | - feat: Support Boom library statusCode [See #408]
55 |
56 | ## 2.2.1 - 10/02/2017
57 |
58 | - Remove unintentional memwatch-next dependency
59 |
60 | ## 2.2.0 - 10/02/2017
61 | - Fixed all Node v8 tests to ensure everything is working correctly [See #366]
62 | - Raven now uses Prettier to format it's code [See #364]
63 | - Prevent Raven from queueing too many requests in case server is down [See #132]
64 | - Enable keep-alive on socket connection and limit number of sockets [See #284]
65 | - Pull Error's name from constructor, not Error itself to always get correct error type [See #372]
66 | - Updated Errors serialization to store all additional properties and allow for attaching other object instances directly to it [See #376]
67 | - Preserve some non-enumerable properties from request [See #379]
68 | - Fall back to `NODE_ENV` for Sentry Environment [See #384]
69 |
70 | ## 2.1.2 - 8/16/2017
71 | - Remove errant large file that accidentally got published in 2.1.1. [See #361]
72 |
73 | ## 2.1.1 - 7/27/2017
74 | - Fix issue where HTTP request was duplicated as `req` (and dropped by Sentry server). [See #340]
75 |
76 | ## 2.1.0 - 6/20/2017
77 | - Truncate long lines in surrounding source to avoid sending large amounts of minified code [See #329]
78 | - Refactor automatic breadcrumb instrumentation of modules to accommodate compilation tools [See #322]
79 | - Testing for Node 8 [See #328]
80 |
81 | ## 2.0.2 - 5/24/2017
82 | - Fix issue with sending empty request details when no request is present [See #324]
83 |
84 | ## 2.0.1 - 5/16/2017
85 | - Fix memory explosion when parsing stack for large files [See #320]
86 |
87 | ## 2.0.0 - 5/10/2017
88 | - Properly surface errors to preserve process exit conditions [See #308, #257]
89 | - Node processes with raven will now exit in exactly the same situations as if raven were not present
90 | - Previously, there were failure scenarios where raven would erroneously cause the process to continue to run when it should have shut down
91 | - **Be aware when upgrading:** it is possible that with raven-node 2.0, your node process will shut down from exceptions where it previously did not
92 | - This also includes changes to more reliably finish capturing the exception that induces process shutdown
93 | - Don't include `domain` property as extra property on `Error` objects [See #309]
94 | - Parse `req` object from context or kwargs [See #310]
95 | - For Express middleware users in particular, raven will now automatically report request details and user context in situations where it previously did not
96 | - Tidied up `.npmignore` to exclude unnecessary files in npm package [See #311]
97 | - Install size reduced from 484kB to 84kB, which should save npm ~100GB/month in bandwidth
98 | - Removed various deprecated methods [See #313]
99 | - Removed postgres autoBreadcrumbs to avoid webpack dependency issue [See #315, #254]
100 | - postgres (and more) autoBreadcrumbs will return in version 2.1
101 |
102 | ## 1.1.6, 1.2.1 - 4/7/2017
103 | - Fix memory leak in `consoleAlert` (and thus, if not disabled, in `captureException`) [See #300]
104 |
105 | ## 1.2.0 - 3/16/2017
106 | - Add sampleRate config option [See #292]
107 |
108 | ## 1.1.5 - 3/16/2017
109 | - Fix memory leak in http autoBreadcrumb instrumentation [See #296]
110 |
111 | ## 1.1.4 - 3/6/2017
112 | - Use `util.format` to get message string in `console` instrumentation [See #289]
113 |
114 | ## 1.1.3 - 2/27/2017
115 | - Add `parseUser` option to control user parsing behavior [See #274]
116 | - Make http instrumentation use `req.emit` instead of response event handler [See #276]
117 | - Add alert about raven-node vs raven-js when it seems like a browser env [See #277]
118 |
119 | ## 1.1.2 - 2/8/2017
120 | - Send kwargs to `shouldSendCallback` [See #251]
121 | - Capture breadcrumbs from global context [See #267]
122 | - Make stack parsing native-frame-check work on Windows paths [See #268]
123 | - Bind req/res to context domain in express requestHandler [See #269]
124 | - Fix postgres/pg name mismatch [See #270]
125 |
126 | ## 1.1.1 and 1.0.1 - 12/13/2016
127 | - Fix middleware backwards compatibility [See #246]
128 |
129 | ## 1.1.0 - 12/12/2016
130 | - Added support for (automatic) breadcrumbs [See #240]
131 | - `Raven.captureBreadcrumb` manual method
132 | - `autoBreadcrumbs` config field to automatically capture breadcrumbs for:
133 | - console logs
134 | - http requests
135 | - postgres queries
136 | - Deprecate `captureQuery` [See #239]
137 |
138 | ## 1.0.0 - 12/12/2016
139 | - `Raven.config(...)` instead of `new raven.Client(...)`
140 | - `Raven.install()` instead of `client.patchGlobal()`
141 | - The callback to `Raven.captureException` now fires after transmission [See #217]
142 | - Added `captureUnhandledRejections` option for Promise rejections
143 | - Introduced contexts and associated `set/merge/getContext` methods [See #207]
144 | - Added `shouldSendCallback` config option and `set*Callback` methods [See #220]
145 | - Added `intercept()` method [See #225]
146 | - Backwards compatibility was mostly maintained, but lots of stuff was deprecated
147 | - We'll print console messages if you're doing anything the old way
148 | - We'll also print console messages in certain situations where behavior might be surprising, like if no DSN is configured
149 | - You can disable these alerts with `Raven.disableConsoleAlerts();`
150 |
151 | ## 0.12.3 - 11/21/2016
152 | * Replace `node-uuid` dependency with `uuid` [See #236]
153 |
154 | ## 0.12.2 - 11/17/2016
155 | * Add column number to stack frames [See #235]
156 | * Check that `require.main.filename` is defined [See #233]
157 |
158 | ## 0.12.1 - 8/4/2016
159 | * Fix bug where `environment` option was not actually being transmitted to Sentry [See #185]
160 |
161 | ## 0.12.0 - 8/1/2016
162 | * Add `environment` config option and `setRelease` method [See #179]
163 | * No longer passes `process.env` values [See #181]
164 | * Connect/Express middleware now attempts to attach `req.user` as User interface [See #177]
165 | * Use json-stringify-safe to prevent circular refs [See #182]
166 |
167 | ## 0.11.0 - 5/5/2016
168 | * `captureError` renamed to `captureException` to match raven-js (alias exists for backwards compat)
169 | * `parsers.parseError` now coerces Error type to string. [See #155]
170 |
171 | ## 0.10.0 - 1/24/2016
172 | * Now supports global context for extra data, tags, user [See #141]
173 | * Added `setUserContext`, `setExtraContext`, `setTagsContext`
174 |
175 | ## 0.9.0 - 11/23/2015
176 | * Always coerce req.body to string. [See 2061d4efbf269c5e2096f2b7b55f5af2249c4aa7]
177 | * Allow passing options to HTTP transports. [See #123]
178 | * Fixed tests for node 4.0/5.0
179 | * Don't send a body for GET/HEAD requests unless one has been passed. [See 0476a6e9818135b8b258b0be0724c369fe30e3c7]
180 |
181 | ## 0.8.1 - 06/15/2015
182 | * Fixed a missing `domain` import in the Express/Connect middleware [See #120]
183 |
184 | ## 0.8.0 - 06/15/2015
185 | * Drop support for node 0.6
186 | * Remove `SENTRY_SITE` environment variable usage
187 | * Fixed `express deprecated req.host: Use req.hostname instead` warning [See #101]
188 | * Allow passing custom `ca` options for an https request [See #110 #108]
189 | * Use newer API endpoint [See #105]
190 | * Added support for Sentry's new Releases feature
191 | * Update Express/Connect middleware to support domains [See #116]
192 |
193 | ## 0.7.3 - 03/05/2015
194 | * When calling `captureError` without an Error, generate a fake `Error` object to pull stacktrace from. [See #87]
195 | * Prevent `patchGlobal` from causing recursion [See #84]
196 | * Fixed issues arond capturing tags.
197 | * Removed deprecated `site` parameter.
198 | * Allow explicitly declaring the `culprit` [See #107]
199 | * Publicly export out the various parsers [See #111]
200 | * Support for iojs and node 0.12
201 |
202 | ## 0.7.2 - 09/09/2014
203 | * Added `dataCallback` option to Client configuration. See: https://github.com/getsentry/raven-node#pre-processing-data
204 |
205 | ## 0.7.1 - 08/24/2014
206 | * Fixed package.json to not install junk from `optionalDependencies`. TIL `optionalDependencies` are still installed. [See #89]
207 |
208 | ## 0.7.0 - 06/24/2014
209 | * Moved from mattrobenolt/raven-node into getsentry/raven-node
210 | * Bumped to sentry protocol version 5
211 | * Capture all properties off of an Error object and send them along as as `extra` [See #72]
212 | * Better feature detection support for capturing request parameters. Adds support for use in Koa. [See #78 #79]
213 |
214 | ## 0.6.3 - 04/02/2014
215 | * Fix another issue that was breaking when running Raven from the REPL [See #66]
216 | * Add additional meta data on the error callbacks [See #67 #73]
217 |
218 | ## 0.6.2 - 02/14/2014
219 | * Allow overriding the logger name for an individual event
220 | * Update lsmod to not break when running Raven from the REPL
221 | * Added a `raven` bin so you can run `raven test [DSN]`
222 |
223 | ## 0.6.1 - 01/23/2014
224 | * Use lsmod for getting the list of installed modules [See #55]
225 | * Parse cookies on the http request always [See #56]
226 | * Use `stack-trace` to assist in capturing stacks. This should fix compat with the New Relic plugin [See #57]
227 |
228 | ## 0.6.0 - 11/9/2013
229 | * Updated sentry protocol to version 4 (Requires Sentry 6.0+ now)
230 | * Module names now include the full path
231 | * Attach client IP address to env object
232 |
233 | ## 0.5.6 - 11/8/2013
234 | * Include module and function name in stacktrace culprit
235 |
236 | ## 0.5.5 - 11/8/2013
237 | * Only record exceptions for 500 status codes from Connect middleware
238 |
239 | ## 0.5.4 - 10/13/2013
240 | * Fix DSN parser when using Sentry at a non-root URL, thanks @rcoup [See #44]
241 |
242 | ## 0.5.3 - 10/4/2013
243 | * Bump raw-stacktrace version
244 |
245 | ## 0.5.2 - 9/10/2013
246 | * Fix compatibilities with CoffeeScript [Fixes #47] [Fixes #50]
247 | * Doesnt chose on circular references
248 |
249 | ## 0.5.1 - 5/1/2013
250 | * Add support for third party transports, thanks @crankycoder
251 |
252 | ## 0.5.0 - 4/8/2013
253 | * Remove NODE_ENV entirely, fixes many issues since people have different opinions on wtf this means
254 | * Several fixes in collecting a better stack trace, thanks @azylman
255 | * Pass exception through to the patchGlobal callback, thanks @ktmud [See #28]
256 | * Official 0.10 support!
257 | * Other misc things. https://github.com/mattrobenolt/raven-node/compare/v0.4.7...v0.5.0
258 |
259 | ## 0.4.7 - 1/13/2013
260 | * Actually disable when NODE_ENV does not equal 'production' [Fixes #25]
261 |
262 | ## 0.4.6 - 1/13/2013
263 | * Added `platform=node` to payload for Sentry 5.1
264 |
265 | ## 0.4.5 - 12/05/2012
266 | * Resolve `node_modules` path properly [Fixes #23]
267 |
268 | ## 0.4.4 - 11/10/2012
269 | * Prevent 'error' event from bubbling up due to no listeners [See #22]
270 | * Augment 'error' event emitter with an actual Error object [See #22]
271 |
272 | ## 0.4.3 - 10/02/2012
273 | * Allow a callback to be given to `patchGlobal()` [Fixes #19]
274 | * Removed old `patch_global()` alias
275 |
276 | ## 0.4.2 - 9/29/2012
277 | * Added test coverage to `patchGlobal()`
278 | * Quit using my own deprecated `get_ident()` method inside `patchGlobal`
279 | * Send string errors as a normal message to prevent Sentry server from crying [Fixes #18]
280 |
281 | ## 0.4.1 - 9/3/2012
282 | * patchGlobal() was actually broken. :( Thanks @ligthyear [Fixes #17]
283 |
284 | ## 0.4.0 - 8/14/2012
285 | * Silence and disable Raven/Sentry when using a non-existent or falsey DSN value
286 |
287 | ## 0.3.0 - 6/23/2012
288 | * Separate transports out into their own module for portability
289 | * Added UDP transport [Fixes #10]
290 | * Ignore sub-transports, such as gevent+https, raven sees it as just https
291 |
292 | ## 0.2.4 - 6/16/2012
293 | * Added parsing DSNs with non-standard port. [Fixes #4]
294 | * Added BSD license
295 |
296 | ## 0.2.3 - 3/30/2012
297 | * Prevent any potentially odd stack traces from causing Raven to crash. [Fixes #2]
298 |
299 | ## 0.2.2 - 3/22/2012
300 | * raven.Client now emits `logged` and `error` events.
301 |
302 | ## 0.2.1 - 3/22/2012
303 | * Fixed connect/express middleware, thanks Almad!
304 |
305 | ## 0.2.0 - 3/18/2012
306 | * Renamed all methods to follow `client.capture*()` pattern. (Sorry if you were already using it!)
307 | * All `npm` installed modules are shoved into Sentry for debugging
308 | * Toggle actual sending based on `NODE_ENV` variable. Check README for information.
309 | * Fixes for more types of stack traces.
310 | * Added `client.captureQuery()`
311 | * Support for `SENTRY_DSN`, `SENTRY_NAME`, and `SENTRY_SITE` enviornment variables
312 | * More test coverage
313 |
314 | ## 0.1.0 - 3/17/2012
315 | * Initial release
316 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Sentry (https://sentry.io) and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | develop: update-submodules
2 | npm install .
3 |
4 | update-submodules:
5 | git submodule init
6 | git submodule update
7 |
8 | docs:
9 | cd docs; $(MAKE) html
10 |
11 | docs-live:
12 | while true; do \
13 | sleep 2; \
14 | $(MAKE) docs; \
15 | done
16 |
17 | clean:
18 | rm -rf docs/html
19 |
20 | .PHONY: develop update-submodules docs docs-live clean
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # We are in the process of unifying all our JavaScript based SDKs. Because of that, `raven-node` has been moved into the main [Sentry JS SDK](https://github.com/getsentry/raven-js/) mono-repo and this repo has been archived.
2 |
3 | Please report any issues and requests there, as this repo is read-only.
4 |
5 | ---
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Raven-node - Sentry SDK for Node.js
13 |
14 |
15 | [](https://travis-ci.org/getsentry/raven-node)
16 |
17 | `raven-node` supports only LTS versions of Node, therefore currently required release is `>= v4.0.0`.
18 | The last known working version for `v0.10` and `v0.12` is `raven-node@2.1.2`.
19 | Please use this version if you need to support those releases of Node.
20 |
21 | To see up-to-date Node.js LTS Release Schedule, go to https://github.com/nodejs/LTS.
22 |
23 | ## Resources
24 |
25 | * [Documentation](https://docs.getsentry.com/hosted/clients/node/)
26 | * [Bug Tracker](https://github.com/getsentry/raven-node/issues)
27 | * [IRC](irc://chat.freenode.net/sentry) (chat.freenode.net, #sentry)
28 |
--------------------------------------------------------------------------------
/bin/raven:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var os = require('os');
4 | var raven = require('../');
5 |
6 | function usage() {
7 | var path = require('path');
8 | console.log('usage:', path.basename(process.argv[1]), 'test [SENTRY_DSN]');
9 | }
10 |
11 | if (process.argv[2] !== 'test') {
12 | usage();
13 | process.exit(1);
14 | }
15 |
16 | var dsn = process.argv.slice(3).join(' ') || process.env.SENTRY_DSN;
17 | if (!dsn) {
18 | usage();
19 | console.log();
20 | console.log('Error: No configuration detected!');
21 | console.log('You must either pass a DSN to the command or set the SENTRY_DSN environment variable.');
22 | process.exit(1);
23 | }
24 |
25 | console.log('Using DSN configuration:');
26 | console.log(' ', dsn);
27 | console.log();
28 |
29 | var client = new raven.Client(dsn);
30 |
31 | console.log('Sending a test message...');
32 |
33 | client.on('logged', function(result) {
34 | console.log('success!');
35 | console.log('Event ID was', client.getIdent(result));
36 | });
37 | client.on('error', function(err) {
38 | console.log('error!');
39 | throw err;
40 | });
41 |
42 | try {
43 | test
44 | } catch (ex) {
45 | client.captureException(ex, {
46 | message: 'This is a test message generated using ``raven test``',
47 | level: 'info',
48 | logger: 'sentry.test',
49 | transaction: 'bin:raven at main',
50 | request: {
51 | method: 'GET',
52 | url: 'http://example.com'
53 | },
54 | extra: {
55 | user: process.getuid && process.getuid(),
56 | loadavg: os.loadavg()
57 | }
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/raven-node.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/raven-node.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/raven-node"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/raven-node"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/coffeescript.rst:
--------------------------------------------------------------------------------
1 | CoffeeScript
2 | ============
3 |
4 | In order to use raven-node with coffee-script or another library which overwrites
5 | Error.prepareStackTrace you might run into the exception "Traceback does not
6 | support Error.prepareStackTrace being defined already."
7 |
8 | In order to not have raven-node (and the underlying raw-stacktrace library) require
9 | Traceback you can pass your own stackFunction in the options. For example:
10 |
11 | .. code-block:: coffeescript
12 |
13 | client = new raven.Client('___PUBLIC_DSN___', {
14 | stackFunction: {{ Your stack function }}
15 | });
16 |
17 |
18 | So for example:
19 |
20 | .. code-block:: coffeescript
21 |
22 | client = new raven.Client('___PUBLIC_DSN___', {
23 | stackFunction: Error.prepareStackTrace
24 | });
25 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # raven-node documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Jan 21 21:04:27 2013.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os, datetime
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 |
21 | # -- General configuration -----------------------------------------------------
22 |
23 | # If your documentation needs a minimal Sphinx version, state it here.
24 | #needs_sphinx = '1.0'
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = []
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'raven-node'
44 | copyright = u'%s, Sentry' % datetime.date.today().year
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 |
51 | import json
52 | # The full version, including alpha/beta/rc tags.
53 | release = json.load(open('../package.json'))['version']
54 | # The short X.Y version.
55 | version = release.rsplit('.', 1)[0]
56 |
57 | # The language for content autogenerated by Sphinx. Refer to documentation
58 | # for a list of supported languages.
59 | #language = None
60 |
61 | # There are two options for replacing |today|: either, you set today to some
62 | # non-false value, then it is used:
63 | #today = ''
64 | # Else, today_fmt is used as the format for a strftime call.
65 | #today_fmt = '%B %d, %Y'
66 |
67 | # List of patterns, relative to source directory, that match files and
68 | # directories to ignore when looking for source files.
69 | exclude_patterns = ['_build']
70 |
71 | # The reST default role (used for this markup: `text`) to use for all documents.
72 | #default_role = None
73 |
74 | # If true, '()' will be appended to :func: etc. cross-reference text.
75 | #add_function_parentheses = True
76 |
77 | # If true, the current module name will be prepended to all description
78 | # unit titles (such as .. function::).
79 | #add_module_names = True
80 |
81 | # If true, sectionauthor and moduleauthor directives will be shown in the
82 | # output. They are ignored by default.
83 | #show_authors = False
84 |
85 | # The name of the Pygments (syntax highlighting) style to use.
86 | pygments_style = 'sphinx'
87 |
88 | # A list of ignored prefixes for module index sorting.
89 | #modindex_common_prefix = []
90 |
91 |
92 | # -- Options for HTML output ---------------------------------------------------
93 |
94 | # The theme to use for HTML and HTML Help pages. See the documentation for
95 | # a list of builtin themes.
96 | html_theme = 'default'
97 |
98 | # Theme options are theme-specific and customize the look and feel of a theme
99 | # further. For a list of options available for each theme, see the
100 | # documentation.
101 | #html_theme_options = {}
102 |
103 | # Add any paths that contain custom themes here, relative to this directory.
104 | #html_theme_path = []
105 |
106 | # The name for this set of Sphinx documents. If None, it defaults to
107 | # " v documentation".
108 | #html_title = None
109 |
110 | # A shorter title for the navigation bar. Default is the same as html_title.
111 | #html_short_title = None
112 |
113 | # The name of an image file (relative to this directory) to place at the top
114 | # of the sidebar.
115 | #html_logo = None
116 |
117 | # The name of an image file (within the static path) to use as favicon of the
118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
119 | # pixels large.
120 | #html_favicon = None
121 |
122 | # Add any paths that contain custom static files (such as style sheets) here,
123 | # relative to this directory. They are copied after the builtin static files,
124 | # so a file named "default.css" will overwrite the builtin "default.css".
125 | html_static_path = ['_static']
126 |
127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
128 | # using the given strftime format.
129 | #html_last_updated_fmt = '%b %d, %Y'
130 |
131 | # If true, SmartyPants will be used to convert quotes and dashes to
132 | # typographically correct entities.
133 | #html_use_smartypants = True
134 |
135 | # Custom sidebar templates, maps document names to template names.
136 | #html_sidebars = {}
137 |
138 | # Additional templates that should be rendered to pages, maps page names to
139 | # template names.
140 | #html_additional_pages = {}
141 |
142 | # If false, no module index is generated.
143 | #html_domain_indices = True
144 |
145 | # If false, no index is generated.
146 | #html_use_index = True
147 |
148 | # If true, the index is split into individual pages for each letter.
149 | #html_split_index = False
150 |
151 | # If true, links to the reST sources are added to the pages.
152 | #html_show_sourcelink = True
153 |
154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
155 | #html_show_sphinx = True
156 |
157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
158 | #html_show_copyright = True
159 |
160 | # If true, an OpenSearch description file will be output, and all pages will
161 | # contain a tag referring to it. The value of this option must be the
162 | # base URL from which the finished HTML is served.
163 | #html_use_opensearch = ''
164 |
165 | # This is the file name suffix for HTML files (e.g. ".xhtml").
166 | #html_file_suffix = None
167 |
168 | # Output file base name for HTML help builder.
169 | htmlhelp_basename = 'raven-nodedoc'
170 |
171 |
172 | # -- Options for LaTeX output --------------------------------------------------
173 |
174 | latex_elements = {
175 | # The paper size ('letterpaper' or 'a4paper').
176 | #'papersize': 'letterpaper',
177 |
178 | # The font size ('10pt', '11pt' or '12pt').
179 | #'pointsize': '10pt',
180 |
181 | # Additional stuff for the LaTeX preamble.
182 | #'preamble': '',
183 | }
184 |
185 | # Grouping the document tree into LaTeX files. List of tuples
186 | # (source start file, target name, title, author, documentclass [howto/manual]).
187 | latex_documents = [
188 | ('index', 'raven-node.tex', u'raven-node Documentation',
189 | u'Matt Robenolt', 'manual'),
190 | ]
191 |
192 | # The name of an image file (relative to this directory) to place at the top of
193 | # the title page.
194 | #latex_logo = None
195 |
196 | # For "manual" documents, if this is true, then toplevel headings are parts,
197 | # not chapters.
198 | #latex_use_parts = False
199 |
200 | # If true, show page references after internal links.
201 | #latex_show_pagerefs = False
202 |
203 | # If true, show URL addresses after external links.
204 | #latex_show_urls = False
205 |
206 | # Documents to append as an appendix to all manuals.
207 | #latex_appendices = []
208 |
209 | # If false, no module index is generated.
210 | #latex_domain_indices = True
211 |
212 |
213 | # -- Options for manual page output --------------------------------------------
214 |
215 | # One entry per manual page. List of tuples
216 | # (source start file, name, description, authors, manual section).
217 | man_pages = [
218 | ('index', 'raven-node', u'raven-node Documentation',
219 | [u'Sentry'], 1)
220 | ]
221 |
222 | # If true, show URL addresses after external links.
223 | #man_show_urls = False
224 |
225 |
226 | # -- Options for Texinfo output ------------------------------------------------
227 |
228 | # Grouping the document tree into Texinfo files. List of tuples
229 | # (source start file, target name, title, author,
230 | # dir menu entry, description, category)
231 | texinfo_documents = [
232 | ('index', 'raven-node', u'raven-node Documentation',
233 | u'Sentry', 'raven-node', 'One line description of project.',
234 | 'Miscellaneous'),
235 | ]
236 |
237 | # Documents to append as an appendix to all manuals.
238 | #texinfo_appendices = []
239 |
240 | # If false, no module index is generated.
241 | #texinfo_domain_indices = True
242 |
243 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
244 | #texinfo_show_urls = 'footnote'
245 |
246 | if os.environ.get('SENTRY_FEDERATED_DOCS') != '1':
247 | sys.path.insert(0, os.path.abspath('_sentryext'))
248 | import sentryext
249 | sentryext.activate()
250 |
--------------------------------------------------------------------------------
/docs/config.rst:
--------------------------------------------------------------------------------
1 | Configuration
2 | =============
3 |
4 | To get started, you need to configure Raven to use your Sentry DSN:
5 |
6 | .. sourcecode:: javascript
7 |
8 | var Raven = require('raven');
9 | Raven.config('___PUBLIC_DSN___').install();
10 |
11 | At this point, Raven is ready to capture any uncaught exceptions.
12 |
13 | Note that the ``install`` method can optionally take a callback function that is invoked if a fatal, non-recoverable error occurs.
14 | You can use this callback to perform any cleanup that should occur before the Node process exits.
15 |
16 | .. sourcecode:: javascript
17 |
18 | Raven.config('___PUBLIC_DSN___').install(function (err, initialErr, eventId) {
19 | console.error(err);
20 | process.exit(1);
21 | });
22 |
23 | Optional settings
24 | -----------------
25 |
26 | ``Raven.config()`` can optionally be passed an additional argument for extra configuration:
27 |
28 | .. sourcecode:: javascript
29 |
30 | Raven.config('___PUBLIC_DSN___', {
31 | release: '1.3.0'
32 | }).install();
33 |
34 | Those configuration options are documented below:
35 |
36 | .. describe:: logger
37 |
38 | The name of the logger used by Sentry.
39 |
40 | .. code-block:: javascript
41 |
42 | {
43 | logger: 'default'
44 | }
45 |
46 | .. describe:: name
47 |
48 | Set the server name for the client to use. Default: ``require('os').hostname()``
49 | Optionally, use ``SENTRY_NAME`` environment variable.
50 |
51 | .. code-block:: javascript
52 |
53 | {
54 | name: 'primary'
55 | }
56 |
57 | .. describe:: release
58 |
59 | Track the version of your application in Sentry.
60 | Optionally, use ``SENTRY_RELEASE`` environment variable.
61 |
62 | .. code-block:: javascript
63 |
64 | {
65 | release: '721e41770371db95eee98ca2707686226b993eda'
66 | }
67 |
68 | This is usually a Git SHA hash, which can be obtained using various npm packages, e.g.
69 |
70 | .. code-block:: javascript
71 |
72 | var git = require('git-rev-sync');
73 |
74 | {
75 | // this will return 40 characters long hash
76 | // eg. '75bf4eea9aa1a7fd6505d0d0aa43105feafa92ef'
77 | release: git.long()
78 | }
79 |
80 | .. describe:: environment
81 |
82 | Track the environment name inside Sentry.
83 | Optionally, use ``SENTRY_ENVIRONMENT`` environment variable.
84 |
85 | .. code-block:: javascript
86 |
87 | {
88 | environment: 'staging'
89 | }
90 |
91 | .. describe:: tags
92 |
93 | Additional tags to assign to each event.
94 |
95 | .. code-block:: javascript
96 |
97 | {
98 | tags: {git_commit: 'c0deb10c4'}
99 | }
100 |
101 | .. describe:: extra
102 |
103 | Arbitrary data to associate with the event.
104 |
105 | .. code-block:: javascript
106 |
107 | {
108 | extra: {planet: {name: 'Earth'}}
109 | }
110 |
111 | .. describe:: parseUser
112 |
113 | Controls how Raven tries to parse user context when parsing a request object.
114 |
115 | An array of strings will serve as a whitelist for fields to grab from ``req.user``.
116 | ``true`` will collect all keys from ``req.user``. ``false`` will collect nothing.
117 |
118 | Defaults to ``['id', 'username', 'email']``.
119 |
120 | Alternatively, a function can be provided for fully custom parsing:
121 |
122 | .. code-block:: javascript
123 |
124 | {
125 | parseUser: function (req) {
126 | // custom user parsing logic
127 | return {
128 | username: req.specialUserField.username,
129 | id: req.specialUserField.getId()
130 | };
131 | }
132 | }
133 |
134 | .. describe:: sampleRate
135 |
136 | A sampling rate to apply to events. A value of 0.0 will send no events,
137 | and a value of 1.0 will send all events (default).
138 |
139 | .. code-block:: javascript
140 |
141 | {
142 | sampleRate: 0.5 // send 50% of events, drop the other half
143 | }
144 |
145 | .. describe:: sendTimeout
146 |
147 | The time to wait to connect to the server or receive a response when capturing an exception, in seconds.
148 |
149 | If it takes longer, the transport considers it a failed request and will pass back a timeout error.
150 |
151 | Defaults to 1 second. Make it longer if you run into timeout problems when sending exceptions to Sentry.
152 |
153 | .. code-block:: javascript
154 |
155 | {
156 | sendTimeout: 5 // wait 5 seconds before considering the capture to have failed
157 | }
158 |
159 | .. describe:: dataCallback
160 |
161 | A function that allows mutation of the data payload right before being
162 | sent to Sentry.
163 |
164 | .. code-block:: javascript
165 |
166 | {
167 | dataCallback: function(data) {
168 | // add a user context
169 | data.user = {
170 | id: 1337,
171 | name: 'janedoe',
172 | email: 'janedoe@example.com'
173 | };
174 | return data;
175 | }
176 | }
177 |
178 | .. describe:: shouldSendCallback
179 |
180 | A callback function that allows you to apply your own filters to determine if the event should be sent to Sentry.
181 |
182 | .. code-block:: javascript
183 |
184 | {
185 | shouldSendCallback: function (data) {
186 | // randomly omit half of events
187 | return Math.random() > 0.5;
188 | }
189 | }
190 |
191 | .. describe:: autoBreadcrumbs
192 |
193 | Enables/disables automatic collection of breadcrumbs. Possible values are:
194 |
195 | * `false` - all automatic breadcrumb collection disabled (default)
196 | * `true` - all automatic breadcrumb collection enabled
197 | * A dictionary of individual breadcrumb types that can be enabled/disabled:
198 |
199 | .. code-block:: javascript
200 |
201 | autoBreadcrumbs: {
202 | 'console': false, // console logging
203 | 'http': true, // http and https requests
204 | }
205 |
206 | .. describe:: maxBreadcrumbs
207 |
208 | Raven captures up to 30 breadcrumb entries by default. You can increase this to
209 | be as high as 100, or reduce it if you find 30 is too noisy, by setting `maxBreadcrumbs`.
210 |
211 | Note that in very high-concurrency situations where you might have a large number of
212 | long-lived contexts each with a large number of associated breadcrumbs, there is potential
213 | for significant memory usage. 10,000 contexts with 10kB of breadcrumb data each will use
214 | around 120mB of memory. Most applications will be nowhere close to either of these numbers,
215 | but if yours might be, you can use the `maxBreadcrumbs` parameter to limit the amount of
216 | breadcrumb data each context will keep around.
217 |
218 | .. describe:: transport
219 |
220 | Override the default HTTP data transport handler.
221 |
222 | .. code-block:: javascript
223 |
224 | {
225 | transport: function (options) {
226 | // send data
227 | }
228 | }
229 |
230 | Please see the raven-node source code to see `how transports are implemented
231 | `__.
232 |
233 | .. describe:: maxReqQueueCount
234 |
235 | Controls how many requests can be maximally queued before bailing out and emitting an error. Defaults to `100`.
236 |
237 | .. describe:: stacktrace
238 |
239 | Attach stack trace to `captureMessage` calls by generatic "synthetic" error object and extracting all frames.
240 |
241 | Environment Variables
242 | ---------------------
243 |
244 | .. describe:: SENTRY_DSN
245 |
246 | Optionally declare the DSN to use for the client through the environment. Initializing the client in your app won't require setting the DSN.
247 |
248 | .. describe:: SENTRY_NAME
249 |
250 | Optionally set the server name for the client to use.
251 |
252 | .. describe:: SENTRY_RELEASE
253 |
254 | Optionally set the application release version for the client to use, this is usually a Git SHA hash.
255 |
256 | .. describe:: SENTRY_ENVIRONMENT
257 |
258 | Optionally set the environment name, e.g. "staging", "production". Sentry will default to the value of `NODE_ENV`, if present.
259 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. sentry:edition:: self
2 |
3 | raven-node
4 | ==========
5 |
6 | .. sentry:edition:: hosted, on-premise
7 |
8 | .. class:: platform-node
9 |
10 | Node.js
11 | =======
12 |
13 | raven-node is the official Node.js client for Sentry.
14 |
15 | **Note**: If you're using JavaScript in the browser, you'll need
16 | `raven-js `_.
17 |
18 | Installation
19 | ------------
20 |
21 | Raven is distributed via ``npm``:
22 |
23 | .. code-block:: bash
24 |
25 | $ npm install raven --save
26 |
27 | Configuring the Client
28 | ----------------------
29 |
30 | Next you need to initialize the Raven client and configure it to use your `Sentry DSN
31 | `_:
32 |
33 | .. code-block:: javascript
34 |
35 | var Raven = require('raven');
36 | Raven.config('___PUBLIC_DSN___').install();
37 |
38 | At this point, Raven is set up to capture and report any uncaught exceptions.
39 |
40 | You can optionally pass an object of configuration options as the 2nd argument to `Raven.config`. For
41 | more information, see :doc:`config`.
42 |
43 | Reporting Errors
44 | ----------------
45 |
46 | Raven's ``install`` method sets up a global handler to automatically capture any uncaught exceptions. You can also report errors manually with ``try...catch`` and
47 | a call to ``captureException``:
48 |
49 | .. code-block:: javascript
50 |
51 | try {
52 | doSomething(a[0]);
53 | } catch (e) {
54 | Raven.captureException(e);
55 | }
56 |
57 | You can also use ``wrap`` and ``context`` to have Raven wrap a function and automatically capture any exceptions it throws:
58 |
59 | .. code-block:: javascript
60 |
61 | Raven.context(function () {
62 | doSomething(a[0]);
63 | });
64 |
65 | For more information on reporting errors, see :doc:`usage`.
66 |
67 | Adding Context
68 | --------------
69 |
70 | Code run via ``wrap`` or ``context`` has an associated set of context data, and Raven provides methods for managing that data.
71 |
72 | You'll most commonly use this to associate the current user with an exception:
73 |
74 | .. code-block:: javascript
75 |
76 | Raven.context(function () {
77 | Raven.setContext({
78 | user: {
79 | email: 'matt@example.com',
80 | id: '123'
81 | }
82 | });
83 | // errors thrown here will be associated with matt
84 | });
85 | // errors thrown here will not be associated with matt
86 |
87 | This can also be used to set ``tags`` and ``extra`` keys for associated tags and extra data.
88 |
89 | You can update the context data with ``mergeContext`` or retrieve it with ``getContext``. When an exception is captured by a wrapper, the current context state will be passed as options to ``captureException``.
90 |
91 | See :ref:`raven-node-additional-context` for more.
92 |
93 | Breadcrumbs
94 | -----------
95 |
96 | Breadcrumbs are records of server and application lifecycle events that can be helpful in understanding the state of the application leading up to a crash.
97 |
98 | We can capture breadcrumbs and associate them with a context, and then send them along with any errors captured from that context:
99 |
100 | .. code-block:: javascript
101 |
102 | Raven.context(function () {
103 | Raven.captureBreadcrumb({
104 | message: 'Received payment confirmation',
105 | category: 'payment',
106 | data: {
107 | amount: 312,
108 | }
109 | });
110 | // errors thrown here will have breadcrumb attached
111 | });
112 |
113 | Raven can be configured to automatically capture breadcrubs for certain events including:
114 |
115 | * http/https requests
116 | * console log statements
117 | * postgres queries
118 |
119 | For more information, see :ref:`raven-recording-breadcrumbs`.
120 |
121 | Dealing with Minified Source Code
122 | ---------------------------------
123 |
124 | Raven and Sentry support `Source Maps
125 | `_. If
126 | you provide source maps in addition to your minified files that data
127 | becomes available in Sentry. For more information see
128 | :ref:`raven-node-sourcemaps`.
129 |
130 | Middleware and Integrations
131 | ---------------------------
132 |
133 | If you're using Node.js with a web server framework/library like Connect, Express, or Koa, it is recommended
134 | to configure one of Raven's server middleware integrations. See :doc:`integrations/index`.
135 |
136 | Deep Dive
137 | ---------
138 |
139 | For more detailed information about how to get most out of Raven there
140 | is additional documentation available that covers all the rest:
141 |
142 | .. toctree::
143 | :maxdepth: 2
144 | :titlesonly:
145 |
146 | config
147 | usage
148 | integrations/index
149 | sourcemaps
150 | typescript
151 | coffeescript
152 |
153 | Resources:
154 |
155 | * `Bug Tracker `_
156 | * `Github Project `_
157 |
--------------------------------------------------------------------------------
/docs/integrations/connect.rst:
--------------------------------------------------------------------------------
1 | Connect
2 | =======
3 |
4 | .. code-block:: javascript
5 |
6 | var connect = require('connect');
7 | var Raven = require('raven');
8 |
9 | // Must configure Raven before doing anything else with it
10 | Raven.config('___PUBLIC_DSN___').install();
11 |
12 | function mainHandler(req, res) {
13 | throw new Error('Broke!');
14 | }
15 |
16 | function onError(err, req, res, next) {
17 | // The error id is attached to `res.sentry` to be returned
18 | // and optionally displayed to the user for support.
19 | res.statusCode = 500;
20 | res.end(res.sentry + '\n');
21 | }
22 |
23 | connect(
24 | // The request handler be the first item
25 | Raven.requestHandler(),
26 |
27 | connect.bodyParser(),
28 | connect.cookieParser(),
29 | mainHandler,
30 |
31 | // The error handler must be before any other error middleware
32 | Raven.errorHandler(),
33 |
34 | // Optional fallthrough error handler
35 | onError,
36 | ).listen(3000);
37 |
--------------------------------------------------------------------------------
/docs/integrations/express.rst:
--------------------------------------------------------------------------------
1 | Express
2 | =======
3 |
4 | .. code-block:: javascript
5 |
6 | var app = require('express')();
7 | var Raven = require('raven');
8 |
9 | // Must configure Raven before doing anything else with it
10 | Raven.config('__DSN__').install();
11 |
12 | // The request handler must be the first middleware on the app
13 | app.use(Raven.requestHandler());
14 |
15 | app.get('/', function mainHandler(req, res) {
16 | throw new Error('Broke!');
17 | });
18 |
19 | // The error handler must be before any other error middleware
20 | app.use(Raven.errorHandler());
21 |
22 | // Optional fallthrough error handler
23 | app.use(function onError(err, req, res, next) {
24 | // The error id is attached to `res.sentry` to be returned
25 | // and optionally displayed to the user for support.
26 | res.statusCode = 500;
27 | res.end(res.sentry + '\n');
28 | });
29 |
30 | app.listen(3000);
31 |
--------------------------------------------------------------------------------
/docs/integrations/index.rst:
--------------------------------------------------------------------------------
1 | Integrations
2 | ============
3 |
4 | .. toctree::
5 | :maxdepth: 1
6 |
7 | connect
8 | express
9 | koa
10 | loopback
11 | sails
12 |
--------------------------------------------------------------------------------
/docs/integrations/koa.rst:
--------------------------------------------------------------------------------
1 | Koa
2 | ===
3 |
4 | .. code-block:: javascript
5 |
6 | var koa = require('koa');
7 | var Raven = require('raven');
8 |
9 | var app = koa();
10 | Raven.config('___PUBLIC_DSN___').install();
11 |
12 | app.on('error', function (err) {
13 | Raven.captureException(err, function (err, eventId) {
14 | console.log('Reported error ' + eventId);
15 | });
16 | });
17 |
18 | app.listen(3000);
19 |
--------------------------------------------------------------------------------
/docs/integrations/loopback.rst:
--------------------------------------------------------------------------------
1 | Loopback
2 | ========
3 |
4 | If you're using Loopback 2.x LTS, make sure
5 | you've migrated to `strong-error-handler
6 | `_, otherwise no
7 | errors will get to ``raven-node``.
8 |
9 | Configure ``raven-node`` as early as possible:
10 |
11 | .. code-block:: javascript
12 |
13 | // server/server.js
14 |
15 | const Raven = require('raven');
16 | Raven.config('__DSN__').install();
17 |
18 | Add ``Raven.errorHandler`` as a Loopback middleware:
19 |
20 | .. code-block:: json
21 |
22 | // server/middleware.json
23 |
24 | "final:after": {
25 | "raven#errorHandler": {},
26 | "strong-error-handler": {
27 | "debug": false,
28 | "log": false
29 | }
30 | }
31 |
32 | You're all set!
33 |
--------------------------------------------------------------------------------
/docs/integrations/sails.rst:
--------------------------------------------------------------------------------
1 | Sails
2 | =====
3 |
4 | .. code-block:: javascript
5 |
6 | // config/http.js
7 |
8 | var Raven = require('raven');
9 | Raven.config('__DSN__').install();
10 |
11 | module.exports.http = {
12 | middleware: {
13 | // Raven's handlers has to be added as a keys to http.middleware config object
14 | requestHandler: Raven.requestHandler(),
15 | errorHandler: Raven.errorHandler(),
16 |
17 | // And they have to be added in a correct order to middlewares list
18 | order: [
19 | // The request handler must be the very first one
20 | 'requestHandler',
21 | // ...more middlewares
22 | 'router',
23 | // The error handler must be after router, but before any other error middleware
24 | 'errorHandler',
25 | /// ...remaining middlewares
26 | ]
27 | }
28 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\raven-node.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\raven-node.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/sentry-doc-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "support_level": "production",
3 | "platforms": {
4 | "node": {
5 | "name": "Node.js",
6 | "type": "language",
7 | "doc_link": "",
8 | "wizard": [
9 | "index#installation",
10 | "index#configuring-the-client",
11 | "index#reporting-errors"
12 | ]
13 | },
14 | "node.express": {
15 | "name": "Express",
16 | "type": "framework",
17 | "doc_link": "integrations/express/",
18 | "wizard": [
19 | "index#installation",
20 | "integrations/express#express"
21 | ]
22 | },
23 | "node.koa": {
24 | "name": "Koa",
25 | "type": "framework",
26 | "doc_link": "integrations/koa/",
27 | "wizard": [
28 | "index#installation",
29 | "integrations/koa#koa"
30 | ]
31 | },
32 | "node.connect": {
33 | "name": "Connect",
34 | "type": "framework",
35 | "doc_link": "integrations/connect/",
36 | "wizard": [
37 | "index#installation",
38 | "integrations/connect#connect"
39 | ]
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/docs/sourcemaps.rst:
--------------------------------------------------------------------------------
1 | .. _raven-node-sourcemaps:
2 |
3 | Source Maps
4 | ===========
5 |
6 | Sentry supports un-minifying JavaScript via `Source Maps
7 | `_. This lets you
8 | view source code context obtained from stack traces in their original untransformed form, which is particularly useful for debugging minified code (e.g. UglifyJS), or transpiled code from a higher-level
9 | language (e.g. TypeScript, ES6).
10 |
11 | Generating a Source Map
12 | -----------------------
13 |
14 | Most modern JavaScript transpilers support source maps. Below are instructions for some common tools.
15 |
16 | Webpack
17 | ~~~~~~~
18 |
19 | Webpack is a powerful build tool that resolves and bundles your JavaScript modules into files fit for running in the
20 | browser. It also supports many different "loaders" which can convert higher-level languages like TypeScript and
21 | ES6/ES2015 into browser-compatible JavaScript.
22 |
23 | Webpack can be configured to output source maps by editing ``webpack.config.js``.
24 |
25 | ::
26 |
27 | module.exports = {
28 | // ... other config above ...
29 | target: 'node',
30 | devtool: 'source-map',
31 | entry: {
32 | "app": 'src/app.js'
33 | },
34 | output: {
35 | path: path.join(__dirname, 'dist'),
36 | filename: '[name].js'
37 | }
38 | };
39 |
40 |
41 | Making Source Maps Available to Sentry
42 | --------------------------------------
43 |
44 | Source maps for Node.js projects should be uploaded directly to Sentry.
45 |
46 | Uploading Source Maps to Sentry
47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48 |
49 | Sentry provides an abstraction called **Releases** which you can attach source artifacts to.
50 | The release API is intended to allow you to store source files (and sourcemaps) within Sentry.
51 |
52 | It can be easily done with a help of the ``sentry-webpack-plugin``, which internally uses our Sentry CLI.
53 |
54 | * Start by creating a new authentication token under **[Account] > API**.
55 | * Ensure you you have ``project:write`` selected under scopes.
56 | * Install ``@sentry/webpack-plugin`` using ``npm``
57 | * Create ``.sentryclirc`` file with necessary config (see Sentry Webpack Plugin docs below)
58 | * Update your ``webpack.config.json``
59 |
60 | ::
61 |
62 | const SentryPlugin = require('@sentry/webpack-plugin');
63 |
64 | module.exports = {
65 | // ... other config above ...
66 | plugins: [
67 | new SentryPlugin({
68 | release: process.env.RELEASE,
69 | include: './dist',
70 | ignore: ['node_modules', 'webpack.config.js'],
71 | })
72 | ]
73 | };
74 |
75 |
76 | You can take a look at `Sentry Webpack Plugin documentation `_
77 | for more information on how to configure the plugin.
78 |
79 | Additionally, you'll need to configure the client to send the ``release``:
80 |
81 | .. code-block:: javascript
82 |
83 | Raven.config('your-dsn', {
84 | release: process.env.RELEASE
85 | });
86 |
87 | Note: You dont *have* to use `RELEASE` environment variables. You can provide them in any way you want.
88 |
89 | Additional information can be found in the `Releases API documentation
90 | `_.
91 |
92 |
93 | Updating Raven configuration to support Source Maps
94 | ---------------------------------------------------
95 |
96 | In order for Sentry to understand how to resolve errors sources, we need to modify the data we send.
97 | Because Source Maps support is still in experimental phase, this task is not integrated into the core library itself.
98 | To do that however, we can normalize all urls using ``dataCallback`` method:
99 |
100 | .. code-block:: javascript
101 |
102 | var path = require('path');
103 |
104 | Raven.config('your-dsn', {
105 | // the rest of configuration
106 |
107 | dataCallback: function (data) {
108 | var stacktrace = data.exception && data.exception[0].stacktrace;
109 |
110 | if (stacktrace && stacktrace.frames) {
111 | stacktrace.frames.forEach(function(frame) {
112 | if (frame.filename.startsWith('/')) {
113 | frame.filename = 'app:///' + path.basename(frame.filename);
114 | }
115 | });
116 | }
117 |
118 | return data;
119 | }
120 | ).install();
121 |
122 | There's one very important thing to note here. This config assumes, that you'll bundle your application into a single file.
123 | That's why we are using `path.basename` to get the filename.
124 |
125 | If you are not doing this, eg. you are using TypeScript and upload all your compiled files separately to the server,
126 | then we need to be a little smarter about this.
127 | Please refer to `TypeScript usage docs `_ to see a more complex and detailed example.
128 |
--------------------------------------------------------------------------------
/docs/typescript.rst:
--------------------------------------------------------------------------------
1 | .. _raven-node-typescript:
2 |
3 | Source Maps
4 | ===========
5 |
6 | Please read `Source Maps docs `_ first to learn how to configure Raven SDK, upload artifacts to our servers or use Webpack (if you're willing to use `ts-loader` for your TypeScript compilation).
7 |
8 | Using Raven and Source Maps with TypeScript unfortunatelly requires slightly more configuration.
9 |
10 | There are two main reasons for this:
11 |
12 | 1) TypeScript compiles and outputs all files separately
13 | 2) SourceRoot is by default set to, well, source directory, which would require uploading artifacts from 2 separate directories and modification of source maps themselves
14 |
15 | We can still make it work with two additional steps, so let's do this.
16 |
17 | The first one is configuring TypeScript compiler in a way, in which we'll override `sourceRoot` and merge original sources with corresponding maps.
18 | The former is not required, but it'll help Sentry display correct filepaths, eg. `/lib/utils/helper.ts` instead of a full one like `/Users/Sentry/Projects/TSExample/lib/utils/helper.ts`.
19 | You can skip this option if you're fine with such a long names.
20 |
21 | Assuming you already have a `tsconfig.json` file similar to this:
22 |
23 | ::
24 |
25 | {
26 | "compilerOptions": {
27 | "target": "es6",
28 | "module": "commonjs",
29 | "allowJs": true,
30 | "moduleResolution": "node",
31 | "outDir": "dist"
32 | },
33 | "include": [
34 | "./src/**/*"
35 | ]
36 | }
37 |
38 | create a new one called `tsconfig.production.json` and paste the snippet below:
39 |
40 | ::
41 |
42 | {
43 | "extends": "./tsconfig",
44 | "compilerOptions": {
45 | "sourceMap": true,
46 | "inlineSources": true,
47 | "sourceRoot": "/"
48 | }
49 | }
50 |
51 | From now on, when you want to run the production build, that'll be uploaded you specify this very config, eg. `tsc -p tsconfig.production.json`.
52 | This will create necessary source maps and attach original sources to them instead of making us to upload them and modify source paths in our maps by hand.
53 |
54 | The second step is changing events frames, so that Sentry can link stacktraces with correct source files.
55 |
56 | This can be done using `dataCallback`, in a very similar manner as we do with a single entrypoint described in Source Maps docs, with one, very important difference.
57 | Instead of using `basename`, we have to somehow detect and pass the root directory of our project.
58 |
59 | Unfortunately, Node is very fragile in that manner and doesn't have a very reliable way to do this.
60 | The easiest and the most reliable way we found, is to store the `__dirname` or `process.cwd()` in the global variable and using it in other places of your app.
61 | This *has to be done* as the first thing in your code and from the entrypoint, otherwise the path will be incorrect.
62 |
63 | If you want, you can set this value by hand to something like `/var/www/html/some-app` if you can get this from some external source or you know it won't ever change.
64 |
65 | This can also be achieved by creating a separate file called `root.js` or similar that'll be placed in the same place as your entrypoint and exporting obtained value
66 | instead of exporting it globally.
67 |
68 | .. code-block:: javascript
69 |
70 | // index.js
71 |
72 | // This allows TypeScript to detect our global value
73 | declare global {
74 | namespace NodeJS {
75 | interface Global {
76 | __rootdir__: string;
77 | }
78 | }
79 | }
80 |
81 | global.__rootdir__ = __dirname || process.cwd();
82 |
83 | .. code-block:: javascript
84 |
85 | import * as path from 'path';
86 | const root = global.__rootdir__;
87 |
88 | Raven.config('your-dsn', {
89 | // the rest of configuration
90 |
91 | dataCallback: function (data) {
92 | var stacktrace = data.exception && data.exception[0].stacktrace;
93 |
94 | if (stacktrace && stacktrace.frames) {
95 | stacktrace.frames.forEach(function(frame) {
96 | if (frame.filename.startsWith('/')) {
97 | frame.filename = "app:///" + path.relative(root, frame.filename);
98 | }
99 | });
100 | }
101 |
102 | return data;
103 | }
104 | ).install();
105 |
106 | This config should be enough to make everything work and use TypeScript with Node and still being able to digest all original sources by Sentry.
107 |
108 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | Capturing Errors
5 | ----------------
6 |
7 | You can use ``captureException`` to manually report errors:
8 |
9 | .. code-block:: javascript
10 |
11 | try {
12 | throw new Error();
13 | } catch (e) {
14 | // You can get eventId either as the synchronous return value, or via the callback
15 | var eventId = Raven.captureException(e, function (sendErr, eventId) {
16 | // This callback fires once the report has been sent to Sentry
17 | if (sendErr) {
18 | console.error('Failed to send captured exception to Sentry');
19 | } else {
20 | console.log('Captured exception and send to Sentry successfully');
21 | }
22 | });
23 | }
24 |
25 | The recommended usage pattern, though, is to run your entire program inside a Raven context:
26 |
27 | .. code-block:: javascript
28 |
29 | var Raven = require('raven');
30 |
31 | Raven.config('___PUBLIC_DSN___').install();
32 | Raven.context(function () {
33 | // all your stuff goes here
34 | });
35 |
36 | Raven will automatically catch and report any unhandled errors originating inside this function
37 | (or anything it calls, etc), so you don't have to manually `captureException` everywhere. This
38 | also gives your code access to context methods. See below for more on contexts.
39 |
40 | .. _raven-node-additional-context:
41 |
42 | context/wrap
43 | ------------
44 |
45 | ``Raven.context`` allows you to wrap any function to be immediately
46 | executed. Behind the scenes, this uses `domains `__ to wrap, catch, and record any exceptions originating from the function.
47 |
48 | .. code-block:: javascript
49 |
50 | Raven.context(function () {
51 | doSomething(a[0])
52 | });
53 |
54 | ``Raven.wrap`` wraps a function in a similar way to ``Raven.context``, but
55 | instead of invoking the function, it returns another function. This is
56 | especially useful when passing around a callback.
57 |
58 | .. code-block:: javascript
59 |
60 | var doIt = function () {
61 | // doing cool stuff
62 | }
63 |
64 | setTimeout(Raven.wrap(doIt), 1000)
65 |
66 | We refer to code wrapped via ``Raven.context`` or ``Raven.wrap`` as being inside a context. Code inside a context
67 | has access to the ``setContext``, ``mergeContext``, and ``getContext`` methods for associating data with that context.
68 |
69 | .. code-block:: javascript
70 |
71 | Raven.setContext({
72 | user: {
73 | username: 'lewis'
74 | }
75 | });
76 |
77 | Raven.mergeContext({
78 | tags: {
79 | component: 'api'
80 | }
81 | });
82 |
83 | console.log(Raven.getContext());
84 | // { user: ..., tags: ... }
85 |
86 | A context most commonly corresponds to a request; if you're using our Express middleware, each request is automatically
87 | wrapped in its own context, so you can use Raven's context methods from inside any of your middleware or handlers.
88 | A context might also correspond to, say, a connection lifecycle or a job being handled in a worker process.
89 |
90 | Notable keys that you might set include ``user``, ``tags``, and ``extra``.
91 | These types of extra context data are detailed more under :ref:`raven-node-additional-data`.
92 |
93 | Since ``domains`` are not supported in native ``Promise`` until Node.js v8, version ``>=8.0.0`` is required if you want to have an access to the context in ``Promise`` rejections.
94 | When older version of Node.js is used, it'll just be skipped and globally set context will be used instead.
95 | Context for regular error handlers and ``context/wrap`` calls is working in every version, including v0.x.
96 |
97 | Tracking Users
98 | --------------
99 |
100 | While a user is logged in, you can tell Sentry to associate errors with
101 | user data. This is really just a particular use of the context methods described above:
102 |
103 | .. code-block:: javascript
104 |
105 | Raven.setContext({
106 | user: {
107 | email: 'matt@example.com',
108 | id: '123'
109 | }
110 | });
111 |
112 | This data is then included with any errors or messages, allowing you to see which users are affected by problems.
113 |
114 | Capturing Messages
115 | ------------------
116 |
117 | .. code-block:: javascript
118 |
119 | client.captureMessage('Broken!', function (err, eventId) {
120 | // The message has now been sent to Sentry
121 | });
122 |
123 |
124 | .. _raven-node-additional-data:
125 |
126 | Additional Data
127 | ---------------
128 |
129 | All optional attributes are passed as part of the options to ``captureException`` and ``captureMessage``.
130 |
131 | .. describe:: user
132 |
133 | User context for this event. Must be a mapping. Children can be any native JSON type.
134 |
135 | .. code-block:: javascript
136 |
137 | {
138 | user: { name: 'matt' }
139 | }
140 |
141 | If you're inside a context and your context data includes a ``user`` key, that data will be merged into this.
142 |
143 | .. describe:: request
144 |
145 | Alias: ``req``. The ``request`` object associated with this event, from a Node http server, Express, Koa, or similar.
146 | Will be parsed for request details and user context from ``request.user`` if present. It will only pull out the data
147 | that's handled by the server: ``headers``, ``method``, ``host``, ``protocol``, ``url``, ``query``, ``cookies``, ``body``, ``ip`` and ``user``.
148 |
149 | .. code-block:: javascript
150 |
151 | app.use(function (req, res, next) {
152 | if (someError) {
153 | Raven.captureException(someError, { req: req });
154 | }
155 | });
156 |
157 | Note that the ``Raven.requestHandler()`` Express middleware adds the ``req`` object to the context for you automatically, so you won't need to provide it manually.
158 |
159 | .. describe:: tags
160 |
161 | Tags to index with this event. Must be a mapping of strings.
162 |
163 | .. code-block:: javascript
164 |
165 | {
166 | tags: { key: 'value' }
167 | }
168 |
169 | If you're inside a context and your context data includes a `tags` key, that data will be merged into this.
170 | You can also set tags data globally to be merged with all events by passing a ``tags`` option to ``config``.
171 |
172 | .. describe:: extra
173 |
174 | Additional context for this event. Must be a mapping. Children can be any native JSON type.
175 |
176 | .. code-block:: javascript
177 |
178 | {
179 | extra: { key: 'value' }
180 | }
181 |
182 | If you're inside a context and your context data includes an `extra` key, that data will be merged into this.
183 | You can also set extra data globally to be merged with all events by passing an ``extra`` option to ``config``.
184 |
185 |
186 | .. describe:: fingerprint
187 |
188 | The fingerprint for grouping this event. Learn more how `Sentry groups errors `__.
189 |
190 | .. code-block:: javascript
191 |
192 | {
193 | // dont group events from the same NODE_ENV together
194 | fingerprint: ['{{ default }}', process.env.NODE_ENV]
195 | }
196 |
197 | .. describe:: level
198 |
199 | The level of the event. Defaults to ``error``.
200 |
201 | .. code-block:: javascript
202 |
203 | {
204 | level: 'warning'
205 | }
206 |
207 | Sentry is aware of the following levels:
208 |
209 | * debug (the least serious)
210 | * info
211 | * warning
212 | * error
213 | * fatal (the most serious)
214 |
215 | .. _raven-recording-breadcrumbs:
216 |
217 | Recording Breadcrumbs
218 | ---------------------
219 |
220 | Breadcrumbs are records of server and application lifecycle events that can be helpful in understanding the state of the application leading up to a crash.
221 |
222 | We can capture breadcrumbs and associate them with a context, and then send them along with any errors captured from that context:
223 |
224 | .. code-block:: javascript
225 |
226 | Raven.context(function () {
227 | Raven.captureBreadcrumb({
228 | message: 'Received payment confirmation',
229 | category: 'payment',
230 | data: {
231 | amount: 312,
232 | }
233 | });
234 | // errors thrown here will have breadcrumb attached
235 | });
236 |
237 | To learn more about what types of data can be collected via breadcrumbs, see the `breadcrumbs client API specification
238 | `_.
239 |
240 | Raven can be configured to automatically capture breadcrubs for certain events including:
241 |
242 | * http/https requests
243 | * console log statements
244 | * postgres queries
245 |
246 | Automatic breadcrumb collection is disabled by default. You can enable it with a config option:
247 |
248 | .. code-block:: javascript
249 |
250 | Raven.config('___PUBLIC_DSN___', {
251 | autoBreadcrumbs: true
252 | });
253 |
254 | Or just enable specific types of automatic breadcrumbs:
255 |
256 | .. code-block:: javascript
257 |
258 | Raven.config('___PUBLIC_DSN___', {
259 | autoBreadcrumbs: {
260 | http: true
261 | }
262 | });
263 |
264 | For more on configuring breadcrumbs, see :doc:`config`.
265 |
266 | Event IDs
267 | ---------
268 |
269 | To make referencing an event easy (both by the developer and customer), you can
270 | get an event ID from any captured message or exception. It's provided both as the
271 | synchronous return value of the capture method and as an argument to the callback:
272 |
273 | .. code-block:: javascript
274 |
275 | var eventId = Raven.captureException(e, function (sendErr, eventId2) {
276 | // eventId === eventId2
277 | });
278 |
279 | Promises
280 | --------
281 |
282 | By default, Raven does not capture unhandled promise rejections. You can have it do so automatically:
283 |
284 | .. code-block:: javascript
285 |
286 | Raven.config('___PUBLIC_DSN___', {
287 | captureUnhandledRejections: true
288 | }).install();
289 |
290 | Global Fatal Error Handler
291 | --------------------------
292 |
293 | The ``install`` method sets up a global listener for uncaught exceptions, and ``context`` and ``wrap`` can catch exceptions as well.
294 | These are situations where Raven catches what would otherwise be a fatal process-ending exception. A process should generally not
295 | continue to run after such events occur, (see `Node docs `_),
296 | so Raven has a concept of a "fatal error handler". When Raven catches an otherwise-fatal exception, it will capture the exception
297 | (send it to Sentry) and then call the fatal error handler.
298 |
299 | By default, the fatal error handler prints the error and then exits the process. If you want to do your own clean-up,
300 | pre-exit logging, or other shutdown procedures, you can provide your own fatal error handler as an argument to ``install()``.
301 |
302 | The fatal error handler callback will be the last thing called before the process should shut down.
303 | It can do anything necessary, including asynchronous operations, to make a best effort to clean up and shut down the process, but it should
304 | not throw, and it absolutely must not allow the process to keep running indefinitely. This means it should probably make an explicit ``process.exit()`` call.
305 |
306 | After catching a fatal exception, Raven will make a best-effort attempt to send it to Sentry before it calls the fatal exception handler.
307 | If sending fails, a ``sendErr`` error object will be passed, and otherwise the ``eventId`` will be provided. In either case, the error object
308 | resulting in the shutdown is passed as the first parameter.
309 |
310 | .. code-block:: javascript
311 |
312 | Raven.install(function (err, sendErr, eventId) {
313 | if (!sendErr) {
314 | console.log('Successfully sent fatal error with eventId ' + eventId + ' to Sentry:');
315 | console.error(err.stack);
316 | }
317 | console.log('This is thy sheath; there rust, and let me die.');
318 | process.exit(1);
319 | });
320 |
321 | Events
322 | ------
323 |
324 | If you want to know if an event was logged or errored out, Raven instances emit two events, `logged` and `error`:
325 |
326 | .. code-block:: javascript
327 |
328 | Raven.on('logged', function () {
329 | console.log('Yay, it worked!');
330 | });
331 |
332 | Raven.on('error', function (e) {
333 | // The event contains information about the failure:
334 | // e.reason -- raw response body
335 | // e.statusCode -- response status code
336 | // e.response -- raw http response object
337 |
338 | console.log('uh oh, couldnt record the event');
339 | });
340 |
341 | Raven.captureMessage('Boom');
342 |
343 | Configuring the HTTP Transport
344 | ------------------------------
345 |
346 | .. code-block:: javascript
347 |
348 | Raven.config('___PUBLIC_DSN___', {
349 | transport: new raven.transports.HTTPSTransport({rejectUnauthorized: false})
350 | });
351 |
352 | Disable Raven
353 | -------------
354 |
355 | Passing any falsey value as the DSN will disable sending events upstream:
356 |
357 | .. code-block:: javascript
358 |
359 | Raven.config(process.env.NODE_ENV === 'production' && '___PUBLIC_DSN___');
360 |
361 | Disable Console Alerts
362 | ----------------------
363 | Raven will print console alerts in situations where you're using a deprecated API
364 | or where behavior might be surprising, like if there's no DSN configured.
365 |
366 | These alerts are hopefully helpful during initial setup or in upgrading Raven versions,
367 | but once you have everything set up and going, we recommend disabling them:
368 |
369 | .. code-block:: javascript
370 |
371 | Raven.disableConsoleAlerts();
372 |
373 | Multiple Instances
374 | ------------------
375 | Normally there is just one instance of Raven:
376 |
377 | .. code-block:: javascript
378 |
379 | var Raven = require('raven');
380 | // Raven is already a Raven instance, and we do everything based on that instance
381 |
382 | This should be sufficient for almost all users, but for various reasons some users might like to have multiple instances.
383 | Additional instances can be created like this:
384 |
385 | .. code-block:: javascript
386 |
387 | var Raven2 = new Raven.Client();
388 |
389 |
390 | Dealing with Minified Source Code
391 | ---------------------------------
392 |
393 | Raven and Sentry support `Source Maps
394 | `_.
395 |
396 | We have provided some instructions to creating Source Maps over at :ref:`raven-node-sourcemaps`.
397 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./lib/client');
4 | module.exports.utils = require('./lib/utils');
5 |
6 | module.exports.transports = require('./lib/transports');
7 | module.exports.parsers = require('./lib/parsers');
8 |
9 | // To infinity and beyond
10 | Error.stackTraceLimit = Infinity;
11 |
--------------------------------------------------------------------------------
/lib/instrumentation/console.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 | var utils = require('../utils');
5 |
6 | module.exports = function(Raven, console, originals) {
7 | var wrapConsoleMethod = function(level) {
8 | if (!(level in console)) {
9 | return;
10 | }
11 |
12 | utils.fill(
13 | console,
14 | level,
15 | function(originalConsoleLevel) {
16 | var sentryLevel = level === 'warn' ? 'warning' : level;
17 |
18 | return function() {
19 | var args = [].slice.call(arguments);
20 |
21 | Raven.captureBreadcrumb({
22 | message: util.format.apply(null, args),
23 | level: sentryLevel,
24 | category: 'console'
25 | });
26 |
27 | originalConsoleLevel.apply(console, args);
28 | };
29 | },
30 | originals
31 | );
32 | };
33 |
34 | ['debug', 'info', 'warn', 'error', 'log'].forEach(wrapConsoleMethod);
35 |
36 | return console;
37 | };
38 |
--------------------------------------------------------------------------------
/lib/instrumentation/http.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var util = require('util');
3 | var utils = require('../utils');
4 |
5 | module.exports = function(Raven, http, originals) {
6 | var OrigClientRequest = http.ClientRequest;
7 | var ClientRequest = function(options, cb) {
8 | // Note: this won't capture a breadcrumb if a response never comes
9 | // It would be useful to know if that was the case, though, so
10 | // todo: revisit to see if we can capture sth indicating response never came
11 | // possibility: capture one breadcrumb for "req sent" and one for "res recvd"
12 | // seems excessive but solves the problem and *is* strictly more information
13 | // could be useful for weird response sequencing bug scenarios
14 | OrigClientRequest.call(this, options, cb);
15 |
16 | // We could just always reconstruct this from this.agent, this._headers, this.path, etc
17 | // but certain other http-instrumenting libraries (like nock, which we use for tests) fail to
18 | // maintain the guarantee that after calling OrigClientRequest, those fields will be populated
19 | if (typeof options === 'string') {
20 | this.__ravenBreadcrumbUrl = options;
21 | } else {
22 | var protocol = options.protocol || '';
23 | var hostname = options.hostname || options.host || '';
24 | // Don't log standard :80 (http) and :443 (https) ports to reduce the noise
25 | var port =
26 | !options.port || options.port === 80 || options.port === 443
27 | ? ''
28 | : ':' + options.port;
29 | var path = options.path || '/';
30 |
31 | this.__ravenBreadcrumbUrl = protocol + '//' + hostname + port + path;
32 | }
33 | };
34 | util.inherits(ClientRequest, OrigClientRequest);
35 |
36 | utils.fill(ClientRequest.prototype, 'emit', function(origEmit) {
37 | return function(evt, maybeResp) {
38 | if (evt === 'response' && this.__ravenBreadcrumbUrl) {
39 | if (!Raven.dsn || this.__ravenBreadcrumbUrl.indexOf(Raven.dsn.host) === -1) {
40 | Raven.captureBreadcrumb({
41 | type: 'http',
42 | category: 'http',
43 | data: {
44 | method: this.method,
45 | url: this.__ravenBreadcrumbUrl,
46 | status_code: maybeResp.statusCode
47 | }
48 | });
49 | }
50 | }
51 | return origEmit.apply(this, arguments);
52 | };
53 | });
54 |
55 | utils.fill(
56 | http,
57 | 'ClientRequest',
58 | function() {
59 | return ClientRequest;
60 | },
61 | originals
62 | );
63 |
64 | // http.request orig refs module-internal ClientRequest, not exported one, so
65 | // it still points at orig ClientRequest after our monkeypatch; these reimpls
66 | // just get that reference updated to use our new ClientRequest
67 | utils.fill(
68 | http,
69 | 'request',
70 | function() {
71 | return function(options, cb) {
72 | return new http.ClientRequest(options, cb);
73 | };
74 | },
75 | originals
76 | );
77 |
78 | utils.fill(
79 | http,
80 | 'get',
81 | function() {
82 | return function(options, cb) {
83 | var req = http.request(options, cb);
84 | req.end();
85 | return req;
86 | };
87 | },
88 | originals
89 | );
90 |
91 | return http;
92 | };
93 |
--------------------------------------------------------------------------------
/lib/instrumentation/instrumentor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var utils = require('../utils');
4 |
5 | var defaultOnConfig = {
6 | console: true,
7 | http: true
8 | };
9 |
10 | var defaultConfig = {
11 | console: false,
12 | http: false,
13 | pg: false
14 | };
15 |
16 | function instrument(Raven, config) {
17 | if (config === false) {
18 | return;
19 | } else if (config === true) {
20 | config = defaultOnConfig;
21 | } else {
22 | config = utils.extend({}, defaultConfig, config);
23 | }
24 |
25 | Raven.instrumentedOriginals = [];
26 | Raven.instrumentedModules = [];
27 |
28 | var Module = require('module');
29 | utils.fill(
30 | Module,
31 | '_load',
32 | function(origLoad) {
33 | return function(moduleId, parent, isMain) {
34 | var origModule = origLoad.apply(this, arguments);
35 | if (config[moduleId] && Raven.instrumentedModules.indexOf(moduleId) === -1) {
36 | Raven.instrumentedModules.push(moduleId);
37 | return require('./' + moduleId)(Raven, origModule, Raven.instrumentedOriginals);
38 | }
39 | return origModule;
40 | };
41 | },
42 | Raven.instrumentedOriginals
43 | );
44 |
45 | // special case: since console is built-in and app-level code won't require() it, do that here
46 | if (config.console) {
47 | require('console');
48 | }
49 |
50 | // observation: when the https module does its own require('http'), it *does not* hit our hooked require to instrument http on the fly
51 | // but if we've previously instrumented http, https *does* get our already-instrumented version
52 | // this is because raven's transports are required before this instrumentation takes place, which loads https (and http)
53 | // so module cache will have uninstrumented http; proactively loading it here ensures instrumented version is in module cache
54 | // alternatively we could refactor to load our transports later, but this is easier and doesn't have much drawback
55 | if (config.http) {
56 | require('http');
57 | }
58 | }
59 |
60 | function deinstrument(Raven) {
61 | if (!Raven.instrumentedOriginals) return;
62 | var original;
63 | // eslint-disable-next-line no-cond-assign
64 | while ((original = Raven.instrumentedOriginals.shift())) {
65 | var obj = original[0];
66 | var name = original[1];
67 | var orig = original[2];
68 | obj[name] = orig;
69 | }
70 | }
71 |
72 | module.exports = {
73 | instrument: instrument,
74 | deinstrument: deinstrument
75 | };
76 |
--------------------------------------------------------------------------------
/lib/instrumentation/pg.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = function(Raven, pg, originals) {
3 | // Using fill helper here is hard because of `this` binding
4 | var origQuery = pg.Connection.prototype.query;
5 | pg.Connection.prototype.query = function(text) {
6 | Raven.captureBreadcrumb({
7 | category: 'postgres',
8 | message: text
9 | });
10 | origQuery.call(this, text);
11 | };
12 | // todo thread this through
13 | // originals.push([pg.Connection.prototype, 'query', origQuery]);
14 | };
15 |
--------------------------------------------------------------------------------
/lib/parsers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var cookie = require('cookie');
4 | var urlParser = require('url');
5 | var stringify = require('../vendor/json-stringify-safe');
6 |
7 | var utils = require('./utils');
8 |
9 | module.exports.parseText = function parseText(message, kwargs) {
10 | kwargs = kwargs || {};
11 | kwargs.message = message;
12 |
13 | return kwargs;
14 | };
15 |
16 | module.exports.parseError = function parseError(err, kwargs, cb) {
17 | utils.parseStack(err, function(frames) {
18 | var name =
19 | ({}.hasOwnProperty.call(err, 'name') ? err.name : err.constructor.name) + '';
20 | if (typeof kwargs.message === 'undefined') {
21 | kwargs.message = name + ': ' + (err.message || '');
22 | }
23 | kwargs.exception = [
24 | {
25 | type: name,
26 | value: err.message,
27 | stacktrace: {
28 | frames: frames
29 | }
30 | }
31 | ];
32 |
33 | // Save additional error properties to `extra` under the error type (e.g. `extra.AttributeError`)
34 | var extraErrorProps;
35 | for (var key in err) {
36 | if (err.hasOwnProperty(key)) {
37 | if (key !== 'name' && key !== 'message' && key !== 'stack' && key !== 'domain') {
38 | extraErrorProps = extraErrorProps || {};
39 | extraErrorProps[key] = err[key];
40 | }
41 | }
42 | }
43 | if (extraErrorProps) {
44 | kwargs.extra = kwargs.extra || {};
45 | kwargs.extra[name] = extraErrorProps;
46 | }
47 |
48 | if (!kwargs.transaction && !kwargs.culprit) {
49 | for (var n = frames.length - 1; n >= 0; n--) {
50 | if (frames[n].in_app) {
51 | kwargs.transaction = utils.getTransaction(frames[n]);
52 | break;
53 | }
54 | }
55 | }
56 |
57 | cb(kwargs);
58 | });
59 | };
60 |
61 | module.exports.parseRequest = function parseRequest(req, parseUser) {
62 | var kwargs = {};
63 |
64 | // headers:
65 | // node, express: req.headers
66 | // koa: req.header
67 | var headers = req.headers || req.header || {};
68 |
69 | // method:
70 | // node, express, koa: req.method
71 | var method = req.method;
72 |
73 | // host:
74 | // express: req.hostname in > 4 and req.host in < 4
75 | // koa: req.host
76 | // node: req.headers.host
77 | var host = req.hostname || req.host || headers.host || '';
78 |
79 | // protocol:
80 | // node:
81 | // express, koa: req.protocol
82 | var protocol =
83 | req.protocol === 'https' || req.secure || (req.socket || {}).encrypted
84 | ? 'https'
85 | : 'http';
86 |
87 | // url (including path and query string):
88 | // node, express: req.originalUrl
89 | // koa: req.url
90 | var originalUrl = req.originalUrl || req.url;
91 |
92 | // absolute url
93 | var absoluteUrl = protocol + '://' + host + originalUrl;
94 |
95 | // query string:
96 | // node: req.url (raw)
97 | // express, koa: req.query
98 | var query = req.query || urlParser.parse(originalUrl || '', true).query;
99 |
100 | // cookies:
101 | // node, express, koa: req.headers.cookie
102 | var cookies = cookie.parse(headers.cookie || '');
103 |
104 | // body data:
105 | // node, express, koa: req.body
106 | var data = req.body;
107 | if (['GET', 'HEAD'].indexOf(method) === -1) {
108 | if (typeof data === 'undefined') {
109 | data = '';
110 | }
111 | }
112 |
113 | if (data && typeof data !== 'string' && {}.toString.call(data) !== '[object String]') {
114 | // Make sure the request body is a string
115 | data = stringify(data);
116 | }
117 |
118 | // http interface
119 | var http = {
120 | method: method,
121 | query_string: query,
122 | headers: headers,
123 | cookies: cookies,
124 | data: data,
125 | url: absoluteUrl
126 | };
127 |
128 | // expose http interface
129 | kwargs.request = http;
130 |
131 | // user: typically found on req.user in express/passport patterns
132 | // five cases for parseUser value:
133 | // absent: grab only id, username, email from req.user
134 | // false: capture nothing
135 | // true: capture all keys from req.user
136 | // array: provided whitelisted keys to grab from req.user
137 | // function :: req -> user: custom parsing function
138 | if (parseUser == null) parseUser = ['id', 'username', 'email'];
139 | if (parseUser) {
140 | var user = {};
141 | if (typeof parseUser === 'function') {
142 | user = parseUser(req);
143 | } else if (req.user) {
144 | if (parseUser === true) {
145 | for (var key in req.user) {
146 | if ({}.hasOwnProperty.call(req.user, key)) {
147 | user[key] = req.user[key];
148 | }
149 | }
150 | } else {
151 | parseUser.forEach(function(fieldName) {
152 | if ({}.hasOwnProperty.call(req.user, fieldName)) {
153 | user[fieldName] = req.user[fieldName];
154 | }
155 | });
156 | }
157 | }
158 |
159 | // client ip:
160 | // node: req.connection.remoteAddress
161 | // express, koa: req.ip
162 | var ip = req.ip || (req.connection && req.connection.remoteAddress);
163 | if (ip) {
164 | user.ip_address = ip;
165 | }
166 |
167 | kwargs.user = user;
168 | }
169 |
170 | return kwargs;
171 | };
172 |
--------------------------------------------------------------------------------
/lib/transports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var events = require('events');
4 | var util = require('util');
5 | var timeoutReq = require('timed-out');
6 |
7 | var http = require('http');
8 | var https = require('https');
9 |
10 | var agentOptions = {keepAlive: true, maxSockets: 100};
11 | var httpAgent = new http.Agent(agentOptions);
12 | var httpsAgent = new https.Agent(agentOptions);
13 |
14 | function Transport() {}
15 | util.inherits(Transport, events.EventEmitter);
16 |
17 | function HTTPTransport(options) {
18 | this.defaultPort = 80;
19 | this.transport = http;
20 | this.options = options || {};
21 | this.agent = httpAgent;
22 | }
23 | util.inherits(HTTPTransport, Transport);
24 | HTTPTransport.prototype.send = function(client, message, headers, eventId, cb) {
25 | var options = {
26 | hostname: client.dsn.host,
27 | path: client.dsn.path + 'api/' + client.dsn.project_id + '/store/',
28 | headers: headers,
29 | method: 'POST',
30 | port: client.dsn.port || this.defaultPort,
31 | ca: client.ca,
32 | agent: this.agent
33 | };
34 | for (var key in this.options) {
35 | if (this.options.hasOwnProperty(key)) {
36 | options[key] = this.options[key];
37 | }
38 | }
39 |
40 | // prevent off heap memory explosion
41 | var _name = this.agent.getName({host: client.dsn.host, port: client.dsn.port});
42 | var _requests = this.agent.requests[_name];
43 | if (_requests && Object.keys(_requests).length > client.maxReqQueueCount) {
44 | // other feedback strategy
45 | client.emit('error', new Error('client req queue is full..'));
46 | return;
47 | }
48 |
49 | var req = this.transport.request(options, function(res) {
50 | res.setEncoding('utf8');
51 | if (res.statusCode >= 200 && res.statusCode < 300) {
52 | client.emit('logged', eventId);
53 | cb && cb(null, eventId);
54 | } else {
55 | var reason = res.headers['x-sentry-error'];
56 | var e = new Error('HTTP Error (' + res.statusCode + '): ' + reason);
57 | e.response = res;
58 | e.statusCode = res.statusCode;
59 | e.reason = reason;
60 | e.sendMessage = message;
61 | e.requestHeaders = headers;
62 | e.eventId = eventId;
63 | client.emit('error', e);
64 | cb && cb(e);
65 | }
66 |
67 | // force the socket to drain
68 | var noop = function() {};
69 | res.on('data', noop);
70 | res.on('end', noop);
71 | });
72 |
73 | timeoutReq(req, client.sendTimeout * 1000);
74 |
75 | var cbFired = false;
76 | req.on('error', function(e) {
77 | client.emit('error', e);
78 | if (!cbFired) {
79 | cb && cb(e);
80 | cbFired = true;
81 | }
82 | });
83 | req.end(message);
84 | };
85 |
86 | function HTTPSTransport(options) {
87 | this.defaultPort = 443;
88 | this.transport = https;
89 | this.options = options || {};
90 | this.agent = httpsAgent;
91 | }
92 | util.inherits(HTTPSTransport, HTTPTransport);
93 |
94 | module.exports.http = new HTTPTransport();
95 | module.exports.https = new HTTPSTransport();
96 | module.exports.Transport = Transport;
97 | module.exports.HTTPTransport = HTTPTransport;
98 | module.exports.HTTPSTransport = HTTPSTransport;
99 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var url = require('url');
5 | var transports = require('./transports');
6 | var path = require('path');
7 | var lsmod = require('../vendor/node-lsmod');
8 | var stacktrace = require('stack-trace');
9 | var stringify = require('../vendor/json-stringify-safe');
10 |
11 | var ravenVersion = require('../package.json').version;
12 |
13 | var protocolMap = {
14 | http: 80,
15 | https: 443
16 | };
17 |
18 | var consoleAlerts = new Set();
19 |
20 | // Default Node.js REPL depth
21 | var MAX_SERIALIZE_EXCEPTION_DEPTH = 3;
22 | // 50kB, as 100kB is max payload size, so half sounds reasonable
23 | var MAX_SERIALIZE_EXCEPTION_SIZE = 50 * 1024;
24 | var MAX_SERIALIZE_KEYS_LENGTH = 40;
25 |
26 | function utf8Length(value) {
27 | return ~-encodeURI(value).split(/%..|./).length;
28 | }
29 |
30 | function jsonSize(value) {
31 | return utf8Length(JSON.stringify(value));
32 | }
33 |
34 | function isError(what) {
35 | return (
36 | Object.prototype.toString.call(what) === '[object Error]' || what instanceof Error
37 | );
38 | }
39 |
40 | module.exports.isError = isError;
41 |
42 | function isPlainObject(what) {
43 | return Object.prototype.toString.call(what) === '[object Object]';
44 | }
45 |
46 | module.exports.isPlainObject = isPlainObject;
47 |
48 | function serializeValue(value) {
49 | var maxLength = 40;
50 |
51 | if (typeof value === 'string') {
52 | return value.length <= maxLength ? value : value.substr(0, maxLength - 1) + '\u2026';
53 | } else if (
54 | typeof value === 'number' ||
55 | typeof value === 'boolean' ||
56 | typeof value === 'undefined'
57 | ) {
58 | return value;
59 | }
60 |
61 | var type = Object.prototype.toString.call(value);
62 |
63 | // Node.js REPL notation
64 | if (type === '[object Object]') return '[Object]';
65 | if (type === '[object Array]') return '[Array]';
66 | if (type === '[object Function]')
67 | return value.name ? '[Function: ' + value.name + ']' : '[Function]';
68 |
69 | return value;
70 | }
71 |
72 | function serializeObject(value, depth) {
73 | if (depth === 0) return serializeValue(value);
74 |
75 | if (isPlainObject(value)) {
76 | return Object.keys(value).reduce(function(acc, key) {
77 | acc[key] = serializeObject(value[key], depth - 1);
78 | return acc;
79 | }, {});
80 | } else if (Array.isArray(value)) {
81 | return value.map(function(val) {
82 | return serializeObject(val, depth - 1);
83 | });
84 | }
85 |
86 | return serializeValue(value);
87 | }
88 |
89 | function serializeException(ex, depth, maxSize) {
90 | if (!isPlainObject(ex)) return ex;
91 |
92 | depth = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_DEPTH : depth;
93 | maxSize = typeof depth !== 'number' ? MAX_SERIALIZE_EXCEPTION_SIZE : maxSize;
94 |
95 | var serialized = serializeObject(ex, depth);
96 |
97 | if (jsonSize(stringify(serialized)) > maxSize) {
98 | return serializeException(ex, depth - 1);
99 | }
100 |
101 | return serialized;
102 | }
103 |
104 | module.exports.serializeException = serializeException;
105 |
106 | function serializeKeysForMessage(keys, maxLength) {
107 | if (typeof keys === 'number' || typeof keys === 'string') return keys.toString();
108 | if (!Array.isArray(keys)) return '';
109 |
110 | keys = keys.filter(function(key) {
111 | return typeof key === 'string';
112 | });
113 | if (keys.length === 0) return '[object has no keys]';
114 |
115 | maxLength = typeof maxLength !== 'number' ? MAX_SERIALIZE_KEYS_LENGTH : maxLength;
116 | if (keys[0].length >= maxLength) return keys[0];
117 |
118 | for (var usedKeys = keys.length; usedKeys > 0; usedKeys--) {
119 | var serialized = keys.slice(0, usedKeys).join(', ');
120 | if (serialized.length > maxLength) continue;
121 | if (usedKeys === keys.length) return serialized;
122 | return serialized + '\u2026';
123 | }
124 |
125 | return '';
126 | }
127 |
128 | module.exports.serializeKeysForMessage = serializeKeysForMessage;
129 |
130 | module.exports.disableConsoleAlerts = function disableConsoleAlerts() {
131 | consoleAlerts = false;
132 | };
133 |
134 | module.exports.enableConsoleAlerts = function enableConsoleAlerts() {
135 | consoleAlerts = new Set();
136 | };
137 |
138 | module.exports.consoleAlert = function consoleAlert(msg) {
139 | if (consoleAlerts) {
140 | console.warn('raven@' + ravenVersion + ' alert: ' + msg);
141 | }
142 | };
143 |
144 | module.exports.consoleAlertOnce = function consoleAlertOnce(msg) {
145 | if (consoleAlerts && !consoleAlerts.has(msg)) {
146 | consoleAlerts.add(msg);
147 | console.warn('raven@' + ravenVersion + ' alert: ' + msg);
148 | }
149 | };
150 |
151 | module.exports.extend =
152 | Object.assign ||
153 | function(target) {
154 | for (var i = 1; i < arguments.length; i++) {
155 | var source = arguments[i];
156 | for (var key in source) {
157 | if (Object.prototype.hasOwnProperty.call(source, key)) {
158 | target[key] = source[key];
159 | }
160 | }
161 | }
162 | return target;
163 | };
164 |
165 | module.exports.getAuthHeader = function getAuthHeader(timestamp, apiKey, apiSecret) {
166 | var header = ['Sentry sentry_version=5'];
167 | header.push('sentry_timestamp=' + timestamp);
168 | header.push('sentry_client=raven-node/' + ravenVersion);
169 | header.push('sentry_key=' + apiKey);
170 | if (apiSecret) header.push('sentry_secret=' + apiSecret);
171 | return header.join(', ');
172 | };
173 |
174 | module.exports.parseDSN = function parseDSN(dsn) {
175 | if (!dsn) {
176 | // Let a falsey value return false explicitly
177 | return false;
178 | }
179 | try {
180 | var parsed = url.parse(dsn),
181 | response = {
182 | protocol: parsed.protocol.slice(0, -1),
183 | public_key: parsed.auth.split(':')[0],
184 | host: parsed.host.split(':')[0]
185 | };
186 |
187 | if (parsed.auth.split(':')[1]) {
188 | response.private_key = parsed.auth.split(':')[1];
189 | }
190 |
191 | if (~response.protocol.indexOf('+')) {
192 | response.protocol = response.protocol.split('+')[1];
193 | }
194 |
195 | if (!transports.hasOwnProperty(response.protocol)) {
196 | throw new Error('Invalid transport');
197 | }
198 |
199 | var index = parsed.pathname.lastIndexOf('/');
200 | response.path = parsed.pathname.substr(0, index + 1);
201 | response.project_id = parsed.pathname.substr(index + 1);
202 | response.port = ~~parsed.port || protocolMap[response.protocol] || 443;
203 | return response;
204 | } catch (e) {
205 | throw new Error('Invalid Sentry DSN: ' + dsn);
206 | }
207 | };
208 |
209 | module.exports.getTransaction = function getTransaction(frame) {
210 | if (frame.module || frame.function) {
211 | return (frame.module || '?') + ' at ' + (frame.function || '?');
212 | }
213 | return '';
214 | };
215 |
216 | var moduleCache;
217 | module.exports.getModules = function getModules() {
218 | if (!moduleCache) {
219 | moduleCache = lsmod();
220 | }
221 | return moduleCache;
222 | };
223 |
224 | module.exports.fill = function(obj, name, replacement, track) {
225 | var orig = obj[name];
226 | obj[name] = replacement(orig);
227 | if (track) {
228 | track.push([obj, name, orig]);
229 | }
230 | };
231 |
232 | var LINES_OF_CONTEXT = 7;
233 |
234 | function getFunction(line) {
235 | try {
236 | return (
237 | line.getFunctionName() ||
238 | line.getTypeName() + '.' + (line.getMethodName() || '')
239 | );
240 | } catch (e) {
241 | // This seems to happen sometimes when using 'use strict',
242 | // stemming from `getTypeName`.
243 | // [TypeError: Cannot read property 'constructor' of undefined]
244 | return '';
245 | }
246 | }
247 |
248 | var mainModule =
249 | ((require.main && require.main.filename && path.dirname(require.main.filename)) ||
250 | global.process.cwd()) + '/';
251 |
252 | function getModule(filename, base) {
253 | if (!base) base = mainModule;
254 |
255 | // It's specifically a module
256 | var file = path.basename(filename, '.js');
257 | filename = path.dirname(filename);
258 | var n = filename.lastIndexOf('/node_modules/');
259 | if (n > -1) {
260 | // /node_modules/ is 14 chars
261 | return filename.substr(n + 14).replace(/\//g, '.') + ':' + file;
262 | }
263 | // Let's see if it's a part of the main module
264 | // To be a part of main module, it has to share the same base
265 | n = (filename + '/').lastIndexOf(base, 0);
266 | if (n === 0) {
267 | var module = filename.substr(base.length).replace(/\//g, '.');
268 | if (module) module += ':';
269 | module += file;
270 | return module;
271 | }
272 | return file;
273 | }
274 |
275 | function readSourceFiles(filenames, cb) {
276 | // we're relying on filenames being de-duped already
277 | if (filenames.length === 0) return setTimeout(cb, 0, {});
278 |
279 | var sourceFiles = {};
280 | var numFilesToRead = filenames.length;
281 | return filenames.forEach(function(filename) {
282 | fs.readFile(filename, function(readErr, file) {
283 | if (!readErr) sourceFiles[filename] = file.toString().split('\n');
284 | if (--numFilesToRead === 0) cb(sourceFiles);
285 | });
286 | });
287 | }
288 |
289 | // This is basically just `trim_line` from https://github.com/getsentry/sentry/blob/master/src/sentry/lang/javascript/processor.py#L67
290 | function snipLine(line, colno) {
291 | var ll = line.length;
292 | if (ll <= 150) return line;
293 | if (colno > ll) colno = ll;
294 |
295 | var start = Math.max(colno - 60, 0);
296 | if (start < 5) start = 0;
297 |
298 | var end = Math.min(start + 140, ll);
299 | if (end > ll - 5) end = ll;
300 | if (end === ll) start = Math.max(end - 140, 0);
301 |
302 | line = line.slice(start, end);
303 | if (start > 0) line = '{snip} ' + line;
304 | if (end < ll) line += ' {snip}';
305 |
306 | return line;
307 | }
308 |
309 | function snipLine0(line) {
310 | return snipLine(line, 0);
311 | }
312 |
313 | function parseStack(err, cb) {
314 | if (!err) return cb([]);
315 |
316 | var stack = stacktrace.parse(err);
317 | if (!stack || !Array.isArray(stack) || !stack.length || !stack[0].getFileName) {
318 | // the stack is not the useful thing we were expecting :/
319 | return cb([]);
320 | }
321 |
322 | // Sentry expects the stack trace to be oldest -> newest, v8 provides newest -> oldest
323 | stack.reverse();
324 |
325 | var frames = [];
326 | var filesToRead = {};
327 | stack.forEach(function(line) {
328 | var frame = {
329 | filename: line.getFileName() || '',
330 | lineno: line.getLineNumber(),
331 | colno: line.getColumnNumber(),
332 | function: getFunction(line)
333 | };
334 |
335 | var isInternal =
336 | line.isNative() ||
337 | (frame.filename[0] !== '/' &&
338 | frame.filename[0] !== '.' &&
339 | frame.filename.indexOf(':\\') !== 1);
340 |
341 | // in_app is all that's not an internal Node function or a module within node_modules
342 | // note that isNative appears to return true even for node core libraries
343 | // see https://github.com/getsentry/raven-node/issues/176
344 | frame.in_app = !isInternal && frame.filename.indexOf('node_modules/') === -1;
345 |
346 | // Extract a module name based on the filename
347 | if (frame.filename) {
348 | frame.module = getModule(frame.filename);
349 | if (!isInternal) filesToRead[frame.filename] = true;
350 | }
351 |
352 | frames.push(frame);
353 | });
354 |
355 | return readSourceFiles(Object.keys(filesToRead), function(sourceFiles) {
356 | frames.forEach(function(frame) {
357 | if (frame.filename && sourceFiles[frame.filename]) {
358 | var lines = sourceFiles[frame.filename];
359 | try {
360 | frame.pre_context = lines
361 | .slice(Math.max(0, frame.lineno - (LINES_OF_CONTEXT + 1)), frame.lineno - 1)
362 | .map(snipLine0);
363 | frame.context_line = snipLine(lines[frame.lineno - 1], frame.colno);
364 | frame.post_context = lines
365 | .slice(frame.lineno, frame.lineno + LINES_OF_CONTEXT)
366 | .map(snipLine0);
367 | } catch (e) {
368 | // anomaly, being defensive in case
369 | // unlikely to ever happen in practice but can definitely happen in theory
370 | }
371 | }
372 | });
373 |
374 | cb(frames);
375 | });
376 | }
377 |
378 | // expose basically for testing because I don't know what I'm doing
379 | module.exports.parseStack = parseStack;
380 | module.exports.getModule = getModule;
381 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raven",
3 | "description": "A standalone (Node.js) client for Sentry",
4 | "keywords": [
5 | "debugging",
6 | "errors",
7 | "exceptions",
8 | "logging",
9 | "raven",
10 | "sentry"
11 | ],
12 | "version": "2.6.2",
13 | "repository": "git://github.com/getsentry/raven-node.git",
14 | "license": "BSD-2-Clause",
15 | "homepage": "https://github.com/getsentry/raven-node",
16 | "author": "Matt Robenolt ",
17 | "main": "index.js",
18 | "bin": {
19 | "raven": "./bin/raven"
20 | },
21 | "scripts": {
22 | "lint": "eslint . --quiet",
23 | "precommit": "lint-staged",
24 | "pretest": "npm install && npm run lint",
25 | "test": "NODE_ENV=test istanbul cover _mocha -- --reporter dot && NODE_ENV=test coffee ./test/run.coffee",
26 | "test-mocha": "NODE_ENV=test mocha",
27 | "test-full": "npm run test && cd test/instrumentation && ./run.sh"
28 | },
29 | "engines": {
30 | "node": ">= 4.0.0"
31 | },
32 | "dependencies": {
33 | "cookie": "0.3.1",
34 | "md5": "^2.2.1",
35 | "stack-trace": "0.0.10",
36 | "timed-out": "4.0.1",
37 | "uuid": "3.0.0"
38 | },
39 | "devDependencies": {
40 | "coffee-script": "~1.10.0",
41 | "connect": "*",
42 | "eslint": "^4.5.0",
43 | "eslint-config-prettier": "^2.3.0",
44 | "express": "*",
45 | "glob": "~3.1.13",
46 | "husky": "^0.14.3",
47 | "istanbul": "^0.4.3",
48 | "lint-staged": "^4.0.4",
49 | "mocha": "~3.1.2",
50 | "nock": "~9.0.0",
51 | "prettier": "^1.6.1",
52 | "should": "11.2.0",
53 | "sinon": "^3.3.0"
54 | },
55 | "prettier": {
56 | "singleQuote": true,
57 | "bracketSpacing": false,
58 | "printWidth": 90
59 | },
60 | "lint-staged": {
61 | "*.js": [
62 | "prettier --write",
63 | "git add"
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/test/exit/capture.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var dsn = 'https://public:private@app.getsentry.com/269';
4 |
5 | var nock = require('nock');
6 | var scope = nock('https://app.getsentry.com')
7 | .filteringRequestBody(/.*/, '*')
8 | .post('/api/269/store/', '*')
9 | .reply(200, 'OK');
10 |
11 | Raven.config(dsn).install();
12 |
13 | process.on('exit', function() {
14 | scope.done();
15 | console.log('exit test assertions complete');
16 | });
17 |
18 | setImmediate(function() {
19 | throw new Error('derp');
20 | });
21 |
--------------------------------------------------------------------------------
/test/exit/capture_callback.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | var nock = require('nock');
7 | var scope = nock('https://app.getsentry.com')
8 | .filteringRequestBody(/.*/, '*')
9 | .post('/api/269/store/', '*')
10 | .reply(200, 'OK');
11 |
12 | Raven.config(dsn).install(function(err, sendErr, eventId) {
13 | scope.done();
14 | assert.equal(sendErr, null);
15 | assert.ok(err instanceof Error);
16 | assert.equal(err.message, 'derp');
17 | console.log('exit test assertions complete');
18 | process.exit(20);
19 | });
20 |
21 | setImmediate(function() {
22 | throw new Error('derp');
23 | });
24 |
--------------------------------------------------------------------------------
/test/exit/capture_with_second_domain_error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | var nock = require('nock');
7 | var scope = nock('https://app.getsentry.com')
8 | .filteringRequestBody(/.*/, '*')
9 | .post('/api/269/store/', '*')
10 | .reply(200, 'OK');
11 |
12 | Raven.config(dsn).install();
13 |
14 | var uncaughts = 0;
15 | process.on('uncaughtException', function() {
16 | uncaughts++;
17 | });
18 |
19 | process.on('exit', function() {
20 | scope.done();
21 | assert.equal(uncaughts, 2);
22 | console.log('exit test assertions complete');
23 | });
24 |
25 | setImmediate(function() {
26 | throw new Error('derp');
27 | });
28 |
29 | Raven.context(function() {
30 | process.domain.on('error', function() {
31 | uncaughts++;
32 | });
33 | setImmediate(function() {
34 | throw new Error('herp');
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/test/exit/capture_with_second_error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | var nock = require('nock');
7 | var scope = nock('https://app.getsentry.com')
8 | .filteringRequestBody(/.*/, '*')
9 | .post('/api/269/store/', '*')
10 | .reply(200, 'OK');
11 |
12 | Raven.config(dsn).install();
13 |
14 | var uncaughts = 0;
15 | process.on('uncaughtException', function() {
16 | uncaughts++;
17 | });
18 |
19 | process.on('exit', function() {
20 | scope.done();
21 | assert.equal(uncaughts, 2);
22 | console.log('exit test assertions complete');
23 | });
24 |
25 | setImmediate(function() {
26 | setImmediate(function() {
27 | throw new Error('herp');
28 | });
29 | throw new Error('derp');
30 | });
31 |
--------------------------------------------------------------------------------
/test/exit/domain_capture.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var dsn = 'https://public:private@app.getsentry.com/269';
4 |
5 | var nock = require('nock');
6 | var scope = nock('https://app.getsentry.com')
7 | .filteringRequestBody(/.*/, '*')
8 | .post('/api/269/store/', '*')
9 | .reply(200, 'OK');
10 |
11 | Raven.config(dsn).install();
12 |
13 | process.on('exit', function() {
14 | scope.done();
15 | console.log('exit test assertions complete');
16 | });
17 |
18 | Raven.context(function() {
19 | setImmediate(function() {
20 | throw new Error('derp');
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/exit/domain_capture_callback.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | var nock = require('nock');
7 | var scope = nock('https://app.getsentry.com')
8 | .filteringRequestBody(/.*/, '*')
9 | .post('/api/269/store/', '*')
10 | .reply(200, 'OK');
11 |
12 | Raven.config(dsn).install(function(err, sendErr, eventId) {
13 | scope.done();
14 | assert.equal(sendErr, null);
15 | assert.ok(err instanceof Error);
16 | assert.equal(err.message, 'derp');
17 | console.log('exit test assertions complete');
18 | process.exit(20);
19 | });
20 |
21 | Raven.context(function() {
22 | setImmediate(function() {
23 | throw new Error('derp');
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/test/exit/domain_capture_with_second_error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | var nock = require('nock');
7 | var scope = nock('https://app.getsentry.com')
8 | .filteringRequestBody(/.*/, '*')
9 | .post('/api/269/store/', '*')
10 | .reply(200, 'OK');
11 |
12 | Raven.config(dsn).install();
13 |
14 | var uncaughts = 0;
15 |
16 | process.on('exit', function() {
17 | scope.done();
18 | assert.equal(uncaughts, 2);
19 | console.log('exit test assertions complete');
20 | });
21 |
22 | Raven.context(function() {
23 | process.domain.on('error', function() {
24 | uncaughts++;
25 | });
26 | setImmediate(function() {
27 | setImmediate(function() {
28 | throw new Error('herp');
29 | });
30 | throw new Error('derp');
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/exit/domain_throw_on_send.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | Raven.config(dsn).install(function(err, sendErr) {
7 | assert.ok(err instanceof Error);
8 | assert.ok(sendErr instanceof Error);
9 | assert.equal(err.message, 'derp');
10 | assert.equal(sendErr.message, 'foo');
11 | console.log('exit test assertions complete');
12 | process.exit(20);
13 | });
14 |
15 | Raven.transport.send = function() {
16 | throw new Error('foo');
17 | };
18 |
19 | Raven.context(function() {
20 | setImmediate(function() {
21 | throw new Error('derp');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/exit/throw_on_fatal.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var dsn = 'https://public:private@app.getsentry.com/269';
4 |
5 | var nock = require('nock');
6 | var scope = nock('https://app.getsentry.com')
7 | .filteringRequestBody(/.*/, '*')
8 | .post('/api/269/store/', '*')
9 | .reply(200, 'OK');
10 |
11 | Raven.disableConsoleAlerts();
12 | Raven.config(dsn).install(function() {
13 | setImmediate(function() {
14 | throw new Error('fatal derp');
15 | });
16 | });
17 |
18 | process.on('exit', function() {
19 | scope.done();
20 | console.log('exit test assertions complete');
21 | });
22 |
23 | setImmediate(function() {
24 | throw new Error('derp');
25 | });
26 |
--------------------------------------------------------------------------------
/test/exit/throw_on_send.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | var assert = require('assert');
4 | var dsn = 'https://public:private@app.getsentry.com/269';
5 |
6 | Raven.config(dsn).install(function(err, sendErr) {
7 | assert.ok(err instanceof Error);
8 | assert.ok(sendErr instanceof Error);
9 | assert.equal(err.message, 'derp');
10 | assert.equal(sendErr.message, 'foo');
11 | console.log('exit test assertions complete');
12 | process.exit(20);
13 | });
14 |
15 | Raven.transport.send = function() {
16 | throw new Error('foo');
17 | };
18 |
19 | process.emit('uncaughtException', new Error('derp'));
20 |
--------------------------------------------------------------------------------
/test/fixtures/file with spaces in path.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | throw new Error('boom');
3 | };
4 |
--------------------------------------------------------------------------------
/test/fixtures/long-line.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | var w = "this is a really long line to make sure that we will truncate it and not send the entire massive line to the server because we don't want to do that, we want to send just the beginning not the whole thing";
3 | var x = "this is a really long line but not as long as the last one"; throw new Error('boom'); var y = "we will continue here to make sure we grab the middle of this line and don't just grab the beginning or end";
4 | var z = "this is a really long line to make sure that we will truncate it and not send the entire massive line to the server because we don't want to do that, we want to send just the beginning not the whole thing";
5 | };
6 |
--------------------------------------------------------------------------------
/test/instrumentation/README.md:
--------------------------------------------------------------------------------
1 | # Instrumentation tests
2 |
3 | These are tests where we run our breadcrumb instrumentation on a module, then run the instrumented module's original test suite to make sure we didn't break anything.
4 |
5 | We have the following tests:
6 | - node core http (`node-http.test.js`)
7 |
8 | We may add in the future:
9 | - postgres, mysql, etc
10 | - other database drivers
11 | - any other node core modules we choose to instrument
12 |
13 | ### Usage:
14 | We don't run these by default as part of `npm test` since they require a heavy download and take ~a minute to run, but we run them in CI. You can run them locally with:
15 | ```
16 | npm run test-full
17 | ```
18 | Or:
19 | ```bash
20 | cd test/instrumentation
21 | ./run.sh
22 | ```
23 | This will check what version of node you have, grab the source of that version, then run against that http test suite.
24 |
--------------------------------------------------------------------------------
/test/instrumentation/http.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fs = require('fs');
3 | var path = require('path');
4 | var child_process = require('child_process');
5 |
6 | var nodeRoot = process.argv[2];
7 | var testRoot = path.join(nodeRoot, 'test/parallel');
8 | var testFiles = fs.readdirSync(testRoot).filter(function (filename) {
9 | return filename.indexOf('test-http') === 0;
10 | });
11 |
12 | var defaultFlags = [
13 | '--allow-natives-syntax',
14 | '--expose-gc',
15 | '--expose-internals'
16 | ];
17 |
18 | if (process.version >= 'v8') defaultFlags.push('--expose-http2');
19 |
20 | var failedTests = [];
21 | var numSuccesses = 0;
22 | testFiles.forEach(function (filename) {
23 | var testModulePath = path.join(testRoot, filename);
24 | var singleTestFlags = defaultFlags.concat([
25 | 'run-node-http-test.js',
26 | testModulePath
27 | ]);
28 |
29 | // this is the only test, that actually asserts the lack of http2 flag
30 | // therefore we have to remove it from the process we are about to run
31 | if (filename === 'test-http2-noflag.js') {
32 | singleTestFlags = singleTestFlags.filter(function (flag) {
33 | return flag !== '--expose-http2';
34 | });
35 | }
36 |
37 | try {
38 | child_process.execFileSync('node', singleTestFlags, { stdio: 'ignore' });
39 | console.log('✓ ' + filename);
40 | numSuccesses++;
41 | } catch (e) {
42 | // non-zero exit code -> test failure
43 | failedTests.push(filename);
44 | console.log('X ' + filename + ' - error!');
45 | }
46 | });
47 |
48 | console.log('Finished, failures: ' + failedTests.length + ', successes: ' + numSuccesses);
49 |
50 | // pipeline-flood is a harness issue with how it expects argvs
51 | // header response splitting is something about becoming more strict on allowed chars in headers
52 | // not worried about either one; they also fail without our instrumentation
53 | var knownFailures = [
54 | 'test-http-pipeline-flood.js',
55 | 'test-http-header-response-splitting.js'
56 | ];
57 |
58 | var didPass = failedTests.every(function (filename) {
59 | return knownFailures.indexOf(filename) !== -1;
60 | });
61 | if (!didPass) {
62 | console.log('Some unexpected failures, failing...');
63 | process.exit(1);
64 | } else {
65 | console.log('All failures were known/expected, passing...');
66 | process.exit(0);
67 | }
68 |
--------------------------------------------------------------------------------
/test/instrumentation/run-node-http-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | // We never actually report any errors, just getting Raven to play ball
4 | var sentryDsn = 'https://fake:dsn@app.getsentry.com/12345';
5 |
6 | Raven.disableConsoleAlerts();
7 | Raven.config(sentryDsn, {
8 | autoBreadcrumbs: {
9 | console: false,
10 | http: true,
11 | }
12 | }).install();
13 | process.removeAllListeners('uncaughtException');
14 |
15 | var testModulePath = process.argv[process.argv.length - 1];
16 | require(testModulePath);
17 |
--------------------------------------------------------------------------------
/test/instrumentation/run.sh:
--------------------------------------------------------------------------------
1 | # /bin/bash
2 | version=`node -v`
3 | versionMinusV=`echo $version | cut -c 2-`
4 | nodeRoot="node-$versionMinusV"
5 |
6 | if [ ! -d $nodeRoot ]; then
7 | url="https://codeload.github.com/nodejs/node/tar.gz/$version"
8 | curl $url -o "$version.tar.gz"
9 | tar -xf "$version.tar.gz"
10 | rm "$version.tar.gz"
11 | fi
12 | exec node http.test.js `pwd`/$nodeRoot
13 |
--------------------------------------------------------------------------------
/test/manual/README.md:
--------------------------------------------------------------------------------
1 | # Manual Tests
2 |
3 | ## How this works
4 | `express-patient.js` is an express app with a collection of endpoints that exercise various functionalities of Raven-node, including exception capturing, contexts, autobreadcrumbs, and the express middleware.
5 |
6 | It uses [memwatch-next](https://www.npmjs.com/package/memwatch-next) to record memory usage after each GC. `manager.js` does some child process stuff to have a fresh patient process for each test scenario, while poke-patient.sh uses apache bench to send a bunch of traffic so we can see what happens.
7 |
8 | ## Routes and what we test
9 | The raven express middleware is used on all endpoints, so each request constitutes its own Raven context.
10 | - `/hello`: just send a basic response without doing anything
11 | - `/context/basic`: `setContext` call
12 | - `/breadcrumbs/capture`: manual `captureBreadcrumb` call
13 | - `/breadcrumbs/auto/console`: console log with console autoBreadcrumbs enabled
14 | - `/breadcrumbs/auto/http`: send an http request with http autoBreadcrumbs enabled
15 | - uses nock to mock the response, not actual request
16 | - If the request has querystring param `doError=true`, we pass an error via Express's error handling mechanism with `next(new Error(responseText))` which will then be captured by the Raven express middleware error handler.
17 | - We test all 5 above cases with and without `doError=true`
18 |
19 | We also have a `/gc` endpoint for forcing a garbage collection; this is used at the end of each test scenario to see final memory usage.
20 |
21 | Note: there's a `/capture` endpoint which does a basic `captureException` call 1000 times. That's our current problem child requiring some more investigation on its memory usage.
22 |
23 | ## How to run it
24 | ```bash
25 | npm install memwatch-next
26 | node manager.js
27 | # in another tab send some traffic at it:
28 | curl localhost:3000/capture
29 | ```
30 |
31 | ## Why this can't be more automated
32 | Some objects can have long lifecycles or not be cleaned up by GC when you think they would be, and so it isn't straightforward to make the assertion "memory usage should have returned to baseline by now". Also, when the numbers look bad, it's pretty obvious to a trained eye that they're bad, but it can be hard to quantify an exact threshold of pass or fail.
33 |
34 | ## Interpreting results
35 | Starting the manager and then running `ab -c 5 -n 5000 /context/basic && sleep 1 && curl localhost:3000/gc` will get us this output:
36 |
37 | ```
38 | :[/Users/lewis/dev/raven-node/test/manual]#memleak-tests?$ node manager.js
39 | starting child
40 | patient is waiting to be poked on port 3000
41 | gc #1: min 0, max 0, est base 11639328, curr base 11639328
42 | gc #2: min 0, max 0, est base 11582672, curr base 11582672
43 | hit /context/basic for first time
44 | gc #3: min 16864536, max 16864536, est base 16864536, curr base 16864536
45 | gc #4: min 14830680, max 16864536, est base 14830680, curr base 14830680
46 | gc #5: min 14830680, max 16864536, est base 16013904, curr base 16013904
47 | hit /gc for first time
48 | gc #6: min 12115288, max 16864536, est base 12115288, curr base 12115288
49 | gc #7: min 11673824, max 16864536, est base 11673824, curr base 11673824
50 | ```
51 |
52 | This test stores some basic data in the request's Raven context, with the hope being for that context data to go out of scope and be garbage collected after the request is over. We can see that we start at a base of ~11.6MB, go up to ~16.8MB during the test, and then return to ~11.6MB. Everything checks out, no memory leak issue here.
53 |
54 | Back when we had a memory leak in `captureException`, if we started the manager and ran:
55 | ```shell
56 | ab -c 5 -n 5000 localhost:3000/context/basic?doError=true && sleep 5 && curl localhost:3000/gc
57 | sleep 5
58 | curl localhost:3000/gc
59 | sleep 10
60 | curl localhost:3000/gc
61 | sleep 15
62 | curl localhost:3000/gc
63 | ```
64 | we'd get this output:
65 |
66 | ```
67 | [/Users/lewis/dev/raven-node/test/manual]#memleak-tests?$ node manager.js
68 | starting child
69 | patient is waiting to be poked on port 3000
70 | gc #1: min 0, max 0, est base 11657056, curr base 11657056
71 | gc #2: min 0, max 0, est base 11599392, curr base 11599392
72 | hit /context/basic?doError=true for first time
73 | gc #3: min 20607752, max 20607752, est base 20607752, curr base 20607752
74 | gc #4: min 20607752, max 20969872, est base 20969872, curr base 20969872
75 | gc #5: min 19217632, max 20969872, est base 19217632, curr base 19217632
76 | gc #6: min 19217632, max 21025056, est base 21025056, curr base 21025056
77 | gc #7: min 19217632, max 21096656, est base 21096656, curr base 21096656
78 | gc #8: min 19085432, max 21096656, est base 19085432, curr base 19085432
79 | gc #9: min 19085432, max 22666768, est base 22666768, curr base 22666768
80 | gc #10: min 19085432, max 22666768, est base 22487320, curr base 20872288
81 | gc #11: min 19085432, max 22708656, est base 22509453, curr base 22708656
82 | gc #12: min 19085432, max 22708656, est base 22470302, curr base 22117952
83 | gc #13: min 19085432, max 22708656, est base 22440838, curr base 22175664
84 | gc #14: min 19085432, max 22829952, est base 22479749, curr base 22829952
85 | gc #15: min 19085432, max 25273504, est base 22759124, curr base 25273504
86 | gc #16: min 19085432, max 25273504, est base 22707814, curr base 22246024
87 | gc #17: min 19085432, max 33286216, est base 23765654, curr base 33286216
88 | gc #18: min 19085432, max 33286216, est base 23863713, curr base 24746248
89 | gc #19: min 19085432, max 33286216, est base 23685980, curr base 22086392
90 | gc #20: min 19085432, max 33286216, est base 23705022, curr base 23876400
91 | gc #21: min 19085432, max 33286216, est base 23769947, curr base 24354272
92 | gc #22: min 19085432, max 33286216, est base 23987724, curr base 25947720
93 | gc #23: min 19085432, max 33286216, est base 24636946, curr base 30479952
94 | gc #24: min 19085432, max 33286216, est base 24668561, curr base 24953096
95 | gc #25: min 19085432, max 33286216, est base 24750980, curr base 25492760
96 | gc #26: min 19085432, max 33286216, est base 24956242, curr base 26803600
97 | gc #27: min 19085432, max 33286216, est base 25127122, curr base 26665048
98 | gc #28: min 19085432, max 33286216, est base 25357309, curr base 27428992
99 | gc #29: min 19085432, max 33286216, est base 25519102, curr base 26975240
100 | gc #30: min 19085432, max 33286216, est base 25830428, curr base 28632368
101 | gc #31: min 19085432, max 33286216, est base 26113116, curr base 28657312
102 | gc #32: min 19085432, max 33286216, est base 26474999, curr base 29731952
103 | gc #33: min 19085432, max 41429616, est base 27970460, curr base 41429616
104 | gc #34: min 19085432, max 41429616, est base 29262386, curr base 40889728
105 | gc #35: min 19085432, max 41429616, est base 29402336, curr base 30661888
106 | gc #36: min 19085432, max 41429616, est base 29602979, curr base 31408768
107 | gc #37: min 19085432, max 42724544, est base 30915135, curr base 42724544
108 | gc #38: min 19085432, max 42724544, est base 31095390, curr base 32717688
109 | gc #39: min 19085432, max 42724544, est base 31907458, curr base 39216072
110 | gc #40: min 19085432, max 42724544, est base 32093021, curr base 33763088
111 | gc #41: min 19085432, max 42724544, est base 32281586, curr base 33978672
112 | gc #42: min 19085432, max 42724544, est base 32543090, curr base 34896632
113 | gc #43: min 19085432, max 42724544, est base 32743548, curr base 34547672
114 | gc #44: min 19085432, max 42724544, est base 33191109, curr base 37219160
115 | gc #45: min 19085432, max 42724544, est base 33659862, curr base 37878640
116 | gc #46: min 19085432, max 42724544, est base 34162262, curr base 38683864
117 | gc #47: min 19085432, max 42724544, est base 34624103, curr base 38780680
118 | gc #48: min 19085432, max 42724544, est base 35125267, curr base 39635752
119 | gc #49: min 19085432, max 42724544, est base 35547207, curr base 39344672
120 | gc #50: min 19085432, max 42724544, est base 35827942, curr base 38354560
121 | gc #51: min 19085432, max 42724544, est base 36185625, curr base 39404776
122 | gc #52: min 19085432, max 52995432, est base 37866605, curr base 52995432
123 | gc #53: min 19085432, max 52995432, est base 39230884, curr base 51509400
124 | gc #54: min 19085432, max 52995432, est base 39651220, curr base 43434248
125 | gc #55: min 19085432, max 52995432, est base 40010377, curr base 43242792
126 | gc #56: min 19085432, max 52995432, est base 40443827, curr base 44344880
127 | gc #57: min 19085432, max 52995432, est base 40979365, curr base 45799208
128 | gc #58: min 19085432, max 52995432, est base 41337723, curr base 44562952
129 | gc #59: min 19085432, max 57831608, est base 42987111, curr base 57831608
130 | hit /gc for first time
131 | gc #60: min 19085432, max 57831608, est base 42763791, curr base 40753920
132 | gc #61: min 19085432, max 57831608, est base 42427528, curr base 39401168
133 | gc #62: min 19085432, max 57831608, est base 42125779, curr base 39410040
134 | gc #63: min 19085432, max 57831608, est base 41850385, curr base 39371848
135 | gc #64: min 19085432, max 57831608, est base 41606578, curr base 39412320
136 | gc #65: min 19085432, max 57831608, est base 41386124, curr base 39402040
137 | ```
138 |
139 | This test, after storing some basic data in the request's Raven context, generates an error which Raven's express error handling middleware will capture. We can see that we started at a base of ~11.6MB, climbed steadily throughout the test to ~40-50MB toward the end, returned to ~39.4MB after the test ends, and were then still at ~39.4MB after 30 seconds and more GCing. This was worrysome, being 30MB over our baseline after 1000 captures. Something was up with capturing exceptions and we uncovered and fixed a memory leak as a result. Now the test returns to a baseline of ~13MB; the slight increase over 11.6MB is due to some warmup costs, but the marginal cost of additional capturing is zero (i.e. we return to that ~13MB baseline whether we do 1000 captures or 5000).
140 |
--------------------------------------------------------------------------------
/test/manual/context-memory.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Raven = require('../../');
3 | Raven.config('https://public:private@app.getsentry.com/12345').install();
4 |
5 | // We create a bunch of contexts, capture some breadcrumb data in all of them,
6 | // then watch memory usage. It'll go up to ~40 megs then after 10 or 20 seconds
7 | // gc will drop it back to ~5.
8 |
9 | console.log(process.memoryUsage());
10 | for (var i = 0; i < 10000; i++) {
11 | Raven.context(function () {
12 | Raven.captureBreadcrumb({ message: Array(1000).join('.') });
13 | setTimeout(function () {
14 | Raven.captureBreadcrumb({ message: Array(1000).join('a') });
15 | }, 2000);
16 | });
17 | }
18 |
19 | console.log(process.memoryUsage());
20 | setInterval(function () {
21 | console.log(process.memoryUsage());
22 | }, 1000);
23 |
--------------------------------------------------------------------------------
/test/manual/express-patient.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // make console log not print so when we hammer the console endpoint we don't flood the terminal
4 | console.log = function () {};
5 |
6 | var Raven = require('../../');
7 |
8 | var sentryDsn = 'https://public:private@app.getsentry.com/269';
9 | Raven.config(sentryDsn, {
10 | autoBreadcrumbs: true
11 | }).install();
12 |
13 | var util = require('util');
14 | var http = require('http');
15 | var nock = require('nock');
16 |
17 | // have to call this for each request :/ ref https://github.com/node-nock/nock#read-this---about-interceptors
18 | function nockRequest() {
19 | nock('https://app.getsentry.com')
20 | .filteringRequestBody(/.*/, '*')
21 | .post('/api/269/store/', '*')
22 | .reply(200, 'OK');
23 | }
24 |
25 | var memwatch = require('memwatch-next');
26 | memwatch.on('stats', function (stats) {
27 | process._rawDebug(util.format('gc #%d: min %d, max %d, est base %d, curr base %d',
28 | stats.num_full_gc, stats.min, stats.max, stats.estimated_base, stats.current_base
29 | ));
30 | });
31 |
32 | var express = require('express');
33 | var app = express();
34 |
35 | var hitBefore = {};
36 |
37 | app.use(Raven.requestHandler());
38 |
39 | app.use(function (req, res, next) {
40 | if (!hitBefore[req.url]) {
41 | hitBefore[req.url] = true;
42 | process._rawDebug('hit ' + req.url + ' for first time');
43 | }
44 | next();
45 | });
46 |
47 | app.get('/context/basic', function (req, res, next) {
48 | Raven.setContext({
49 | extra: {
50 | example: 'hey look we set some example context data yay',
51 | }
52 | });
53 | res.textToSend = 'hello there! we set some stuff to the context';
54 | next();
55 | });
56 |
57 | app.get('/breadcrumbs/capture', function (req, res, next) {
58 | Raven.captureBreadcrumb({
59 | message: 'Captured example breadcrumb',
60 | category: 'log',
61 | data: {
62 | example: 'hey look we captured this example breadcrumb yay'
63 | }
64 | });
65 | res.textToSend = 'hello there! we captured an example breadcrumb';
66 | next();
67 | });
68 |
69 | app.get('/breadcrumbs/auto/console', function (req, res, next) {
70 | console.log('hello there! i am printing to the console!');
71 | res.textToSend = 'hello there! we printed to the console';
72 | next();
73 | });
74 |
75 | app.get('/breadcrumbs/auto/http', function (req, res, next) {
76 | var scope = nock('http://www.example.com')
77 | .get('/hello')
78 | .reply(200, 'hello world');
79 |
80 | http.get('http://www.example.com/hello', function (nockRes) {
81 | scope.done();
82 | res.textToSend = 'hello there! we got hello world from example.com';
83 | next();
84 | }).on('error', next);
85 | });
86 |
87 | app.get('/hello', function (req, res, next) {
88 | res.textToSend = 'hello!';
89 | next();
90 | });
91 |
92 | app.get('/gc', function (req, res, next) {
93 | memwatch.gc();
94 | res.textToSend = 'collected garbage';
95 | next();
96 | });
97 |
98 | app.get('/shutdown', function (req, res, next) {
99 | setTimeout(function () {
100 | server.close(function () { // eslint-disable-line no-use-before-define
101 | process.exit();
102 | });
103 | }, 100);
104 | return res.send('shutting down');
105 | });
106 |
107 | app.get('/capture', function (req, res, next) {
108 | for (var i = 0; i < 1000; ++i) {
109 | nockRequest();
110 | Raven.captureException(new Error('oh no an exception to capture'));
111 | }
112 | memwatch.gc();
113 | res.textToSend = 'capturing an exception!';
114 | next();
115 | });
116 |
117 | app.get('/capture_large_source', function (req, res, next) {
118 | nockRequest();
119 |
120 | // largeModule.run recurses 1000 times, largeModule is a 5MB file
121 | // if we read the largeModule source once for each frame, we'll use a ton of memory
122 | var largeModule = require('./largeModule');
123 |
124 | try {
125 | largeModule.run();
126 | } catch (e) {
127 | Raven.captureException(e);
128 | }
129 |
130 | memwatch.gc();
131 | res.textToSend = 'capturing an exception!';
132 | next();
133 | });
134 |
135 | app.use(function (req, res, next) {
136 | if (req.query.doError) {
137 | nockRequest();
138 | return next(new Error(res.textToSend));
139 | }
140 | return res.send(res.textToSend);
141 | });
142 |
143 | app.use(Raven.errorHandler());
144 |
145 | app.use(function (err, req, res, next) {
146 | return res.status(500).send('oh no there was an error: ' + err.message);
147 | });
148 |
149 | var server = app.listen(3000, function () {
150 | process._rawDebug('patient is waiting to be poked on port 3000');
151 | memwatch.gc();
152 | });
153 |
--------------------------------------------------------------------------------
/test/manual/manager.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var child_process = require('child_process');
5 |
6 | var child;
7 |
8 | var serverPath = path.join(__dirname, 'express-patient.js');
9 |
10 | function startChild() {
11 | console.log('starting child');
12 | child = child_process.spawn('node', [serverPath]);
13 | child.stdout.pipe(process.stdout);
14 | child.stderr.pipe(process.stderr);
15 | child.on('exit', function () {
16 | console.log('child exited');
17 | startChild();
18 | });
19 | }
20 |
21 | startChild();
22 |
--------------------------------------------------------------------------------
/test/manual/poke-patient.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | gc() {
4 | sleep 2
5 | curl localhost:3000/gc
6 | }
7 |
8 | gc_restart() {
9 | gc
10 | sleep 2
11 | curl localhost:3000/shutdown
12 | sleep 2
13 | }
14 |
15 | curl localhost:3000/capture
16 | gc
17 | gc
18 | curl localhost:3000/capture
19 | gc
20 | gc_restart
21 |
22 | curl localhost:3000/capture_large_source
23 | gc
24 | gc
25 | gc_restart
26 |
27 | ab -c 5 -n 5000 localhost:3000/hello
28 | gc_restart
29 |
30 | ab -c 5 -n 5000 localhost:3000/context/basic
31 | gc_restart
32 |
33 | ab -c 5 -n 5000 localhost:3000/breadcrumbs/capture
34 | gc_restart
35 |
36 | ab -c 5 -n 5000 localhost:3000/breadcrumbs/auto/console
37 | gc_restart
38 |
39 | ab -c 5 -n 5000 localhost:3000/breadcrumbs/auto/http
40 | gc_restart
41 |
42 |
43 | ab -c 5 -n 2000 localhost:3000/hello?doError=true
44 | gc_restart
45 |
46 | ab -c 5 -n 2000 localhost:3000/context/basic?doError=true
47 | gc_restart
48 |
49 | ab -c 5 -n 2000 localhost:3000/breadcrumbs/capture?doError=true
50 | gc_restart
51 |
52 | ab -c 5 -n 2000 localhost:3000/breadcrumbs/auto/console?doError=true
53 | gc_restart
54 |
55 | ab -c 5 -n 2000 localhost:3000/breadcrumbs/auto/http?doError=true
56 | gc_restart
57 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require should
2 | --slow 20
--------------------------------------------------------------------------------
/test/raven.parsers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var util = require('util');
4 | var assert = require('assert');
5 | var raven = require('../');
6 |
7 | raven.parsers = require('../lib/parsers');
8 |
9 | describe('raven.parsers', function() {
10 | describe('#parseText()', function() {
11 | it('should parse some text without kwargs', function() {
12 | var parsed = raven.parsers.parseText('Howdy');
13 | parsed.message.should.equal('Howdy');
14 | });
15 |
16 | it('should parse some text with kwargs', function() {
17 | var parsed = raven.parsers.parseText('Howdy', {
18 | foo: 'bar'
19 | });
20 | parsed.message.should.equal('Howdy');
21 | parsed.foo.should.equal('bar');
22 | });
23 | });
24 |
25 | describe('#parseRequest()', function() {
26 | it('should parse a request object', function() {
27 | var mockReq = {
28 | method: 'GET',
29 | url: '/some/path?key=value',
30 | headers: {
31 | host: 'mattrobenolt.com'
32 | },
33 | body: '',
34 | cookies: {},
35 | socket: {
36 | encrypted: true
37 | },
38 | connection: {
39 | remoteAddress: '127.0.0.1'
40 | }
41 | };
42 | var parsed = raven.parsers.parseRequest(mockReq);
43 | parsed.should.have.property('request');
44 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
45 | parsed.user.ip_address.should.equal('127.0.0.1');
46 | });
47 |
48 | describe('`headers` detection', function() {
49 | it('should detect headers via `req.headers`', function() {
50 | var mockReq = {
51 | method: 'GET',
52 | hostname: 'mattrobenolt.com',
53 | url: '/some/path?key=value',
54 | headers: {
55 | foo: 'bar'
56 | }
57 | };
58 |
59 | var parsed = raven.parsers.parseRequest(mockReq);
60 |
61 | parsed.request.headers.should.eql({
62 | foo: 'bar'
63 | });
64 | });
65 |
66 | it('should detect headers via `req.header`', function() {
67 | var mockReq = {
68 | method: 'GET',
69 | hostname: 'mattrobenolt.com',
70 | url: '/some/path?key=value',
71 | header: {
72 | foo: 'bar'
73 | }
74 | };
75 |
76 | var parsed = raven.parsers.parseRequest(mockReq);
77 |
78 | parsed.request.headers.should.eql({
79 | foo: 'bar'
80 | });
81 | });
82 | });
83 |
84 | describe('`method` detection', function() {
85 | it('should detect method via `req.method`', function() {
86 | var mockReq = {
87 | method: 'GET',
88 | hostname: 'mattrobenolt.com',
89 | url: '/some/path?key=value'
90 | };
91 |
92 | var parsed = raven.parsers.parseRequest(mockReq);
93 |
94 | parsed.request.method.should.equal('GET');
95 | });
96 | });
97 |
98 | describe('`host` detection', function() {
99 | it('should detect host via `req.hostname`', function() {
100 | var mockReq = {
101 | method: 'GET',
102 | hostname: 'mattrobenolt.com',
103 | url: '/some/path?key=value'
104 | };
105 |
106 | var parsed = raven.parsers.parseRequest(mockReq);
107 |
108 | parsed.request.url.should.equal('http://mattrobenolt.com/some/path?key=value');
109 | });
110 |
111 | it('should detect host via deprecated `req.host`', function() {
112 | var mockReq = {
113 | method: 'GET',
114 | host: 'mattrobenolt.com',
115 | url: '/some/path?key=value'
116 | };
117 |
118 | var parsed = raven.parsers.parseRequest(mockReq);
119 |
120 | parsed.request.url.should.equal('http://mattrobenolt.com/some/path?key=value');
121 | });
122 |
123 | it('should detect host via `req.header.host`', function() {
124 | var mockReq = {
125 | method: 'GET',
126 | header: {
127 | host: 'mattrobenolt.com'
128 | },
129 | url: '/some/path?key=value'
130 | };
131 |
132 | var parsed = raven.parsers.parseRequest(mockReq);
133 |
134 | parsed.request.url.should.equal('http://mattrobenolt.com/some/path?key=value');
135 | });
136 |
137 | it('should detect host via `req.headers.host`', function() {
138 | var mockReq = {
139 | method: 'GET',
140 | headers: {
141 | host: 'mattrobenolt.com'
142 | },
143 | url: '/some/path?key=value'
144 | };
145 |
146 | var parsed = raven.parsers.parseRequest(mockReq);
147 |
148 | parsed.request.url.should.equal('http://mattrobenolt.com/some/path?key=value');
149 | });
150 |
151 | it('should fallback to if host is not available', function() {
152 | var mockReq = {
153 | method: 'GET',
154 | url: '/some/path?key=value'
155 | };
156 |
157 | var parsed = raven.parsers.parseRequest(mockReq);
158 |
159 | parsed.request.url.should.equal('http:///some/path?key=value');
160 | });
161 | });
162 |
163 | describe('`protocol` detection', function() {
164 | it('should detect protocol via `req.protocol`', function() {
165 | var mockReq = {
166 | method: 'GET',
167 | url: '/some/path?key=value',
168 | headers: {
169 | host: 'mattrobenolt.com'
170 | },
171 | protocol: 'https',
172 | socket: {
173 | encrypted: false
174 | }
175 | };
176 |
177 | var parsed = raven.parsers.parseRequest(mockReq);
178 |
179 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
180 | });
181 |
182 | it('should detect protocol via `req.secure`', function() {
183 | var mockReq = {
184 | method: 'GET',
185 | url: '/some/path?key=value',
186 | headers: {
187 | host: 'mattrobenolt.com'
188 | },
189 | secure: true,
190 | socket: {
191 | encrypted: false
192 | }
193 | };
194 |
195 | var parsed = raven.parsers.parseRequest(mockReq);
196 |
197 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
198 | });
199 |
200 | it('should detect protocol via `req.socket.encrypted`', function() {
201 | var mockReq = {
202 | method: 'GET',
203 | url: '/some/path?key=value',
204 | headers: {
205 | host: 'mattrobenolt.com'
206 | },
207 | socket: {
208 | encrypted: true
209 | }
210 | };
211 |
212 | var parsed = raven.parsers.parseRequest(mockReq);
213 |
214 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
215 | });
216 | });
217 |
218 | describe('`cookie` detection', function() {
219 | it('should parse `req.headers.cookie`', function() {
220 | var mockReq = {
221 | method: 'GET',
222 | url: '/some/path?key=value',
223 | headers: {
224 | host: 'mattrobenolt.com',
225 | cookie: 'foo=bar'
226 | }
227 | };
228 |
229 | var parsed = raven.parsers.parseRequest(mockReq);
230 | parsed.request.cookies.should.eql({
231 | foo: 'bar'
232 | });
233 | });
234 |
235 | it('should parse `req.header.cookie`', function() {
236 | var mockReq = {
237 | method: 'GET',
238 | url: '/some/path?key=value',
239 | header: {
240 | host: 'mattrobenolt.com',
241 | cookie: 'foo=bar'
242 | }
243 | };
244 |
245 | var parsed = raven.parsers.parseRequest(mockReq);
246 | parsed.request.cookies.should.eql({
247 | foo: 'bar'
248 | });
249 | });
250 | });
251 |
252 | describe('`query` detection', function() {
253 | it('should detect query via `req.query`', function() {
254 | var mockReq = {
255 | method: 'GET',
256 | hostname: 'mattrobenolt.com',
257 | url: '/some/path?key=value',
258 | query: {
259 | some: 'key'
260 | }
261 | };
262 |
263 | var parsed = raven.parsers.parseRequest(mockReq);
264 |
265 | parsed.request.query_string.should.eql({
266 | some: 'key'
267 | });
268 | });
269 |
270 | it('should detect query via `req.url`', function() {
271 | var mockReq = {
272 | method: 'GET',
273 | hostname: 'mattrobenolt.com',
274 | url: '/some/path?foo=bar'
275 | };
276 |
277 | // using assert instead of should here because query_string has no prototype
278 | // https://github.com/nodejs/node/pull/6289
279 | var parsed = raven.parsers.parseRequest(mockReq);
280 | var expected = {foo: 'bar'};
281 | assert.deepEqual(parsed.request.query_string, expected);
282 | });
283 | });
284 |
285 | describe('`ip` detection', function() {
286 | it('should detect ip via `req.ip`', function() {
287 | var mockReq = {
288 | method: 'GET',
289 | url: '/some/path?key=value',
290 | headers: {
291 | hostname: 'mattrobenolt.com'
292 | },
293 | ip: '127.0.0.1'
294 | };
295 |
296 | var parsed = raven.parsers.parseRequest(mockReq);
297 |
298 | parsed.user.ip_address.should.equal('127.0.0.1');
299 | });
300 |
301 | it('should detect ip via `req.connection.remoteAddress`', function() {
302 | var mockReq = {
303 | method: 'GET',
304 | url: '/some/path?key=value',
305 | headers: {
306 | hostname: 'mattrobenolt.com'
307 | },
308 | connection: {
309 | remoteAddress: '127.0.0.1'
310 | }
311 | };
312 |
313 | var parsed = raven.parsers.parseRequest(mockReq);
314 |
315 | parsed.user.ip_address.should.equal('127.0.0.1');
316 | });
317 | });
318 |
319 | describe('`url` detection', function() {
320 | it('should detect url via `req.originalUrl`', function() {
321 | var mockReq = {
322 | method: 'GET',
323 | protocol: 'https',
324 | hostname: 'mattrobenolt.com',
325 | originalUrl: '/some/path?key=value'
326 | };
327 |
328 | var parsed = raven.parsers.parseRequest(mockReq);
329 |
330 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
331 | });
332 |
333 | it('should detect url via `req.url`', function() {
334 | var mockReq = {
335 | method: 'GET',
336 | protocol: 'https',
337 | hostname: 'mattrobenolt.com',
338 | url: '/some/path?key=value'
339 | };
340 |
341 | var parsed = raven.parsers.parseRequest(mockReq);
342 |
343 | parsed.request.url.should.equal('https://mattrobenolt.com/some/path?key=value');
344 | });
345 | });
346 |
347 | describe('`body` detection', function() {
348 | it('should detect body via `req.body`', function() {
349 | var mockReq = {
350 | method: 'GET',
351 | hostname: 'mattrobenolt.com',
352 | url: '/some/path?key=value',
353 | body: 'foo=bar'
354 | };
355 |
356 | var parsed = raven.parsers.parseRequest(mockReq);
357 |
358 | parsed.request.data.should.equal('foo=bar');
359 | });
360 |
361 | it('should fallback to if body is not available', function() {
362 | var mockReq = {
363 | method: 'POST',
364 | hostname: 'mattrobenolt.com',
365 | url: '/some/path?key=value'
366 | };
367 |
368 | var parsed = raven.parsers.parseRequest(mockReq);
369 |
370 | parsed.request.data.should.equal('');
371 | });
372 |
373 | it('should not fallback to if GET', function() {
374 | var mockReq = {
375 | method: 'GET',
376 | hostname: 'mattrobenolt.com',
377 | url: '/some/path?key=value'
378 | };
379 |
380 | var parsed = raven.parsers.parseRequest(mockReq);
381 |
382 | (typeof parsed.request.data === 'undefined').should.be.ok;
383 | });
384 |
385 | it('should make sure that body is a string', function() {
386 | var mockReq = {
387 | method: 'POST',
388 | hostname: 'mattrobenolt.com',
389 | url: '/some/path?key=value',
390 | body: {
391 | foo: true
392 | }
393 | };
394 |
395 | var parsed = raven.parsers.parseRequest(mockReq);
396 | parsed.request.data.should.equal('{"foo":true}');
397 | });
398 | });
399 |
400 | describe('`user` detection', function() {
401 | it('should assign req.user to kwargs', function() {
402 | var mockReq = {
403 | method: 'POST',
404 | hostname: 'example.org',
405 | url: '/some/path?key=value',
406 | user: {
407 | username: 'janedoe',
408 | email: 'hello@janedoe.com'
409 | }
410 | };
411 |
412 | var parsed = raven.parsers.parseRequest(mockReq);
413 | parsed.should.have.property('user', {
414 | username: 'janedoe',
415 | email: 'hello@janedoe.com'
416 | });
417 | });
418 |
419 | it('should add ip address to user if available', function() {
420 | var mockReq = {
421 | method: 'POST',
422 | hostname: 'example.org',
423 | url: '/some/path?key=value',
424 | ip: '127.0.0.1',
425 | user: {
426 | username: 'janedoe',
427 | email: 'hello@janedoe.com'
428 | }
429 | };
430 |
431 | var parsed = raven.parsers.parseRequest(mockReq);
432 | parsed.should.have.property('user', {
433 | username: 'janedoe',
434 | email: 'hello@janedoe.com',
435 | ip_address: '127.0.0.1'
436 | });
437 | });
438 |
439 | describe('parseUser option', function() {
440 | var mockReq = {
441 | method: 'POST',
442 | hostname: 'example.org',
443 | url: '/some/path?key=value',
444 | ip: '127.0.0.1',
445 | user: {
446 | username: 'janedoe',
447 | email: 'hello@janedoe.com',
448 | random: 'random'
449 | }
450 | };
451 |
452 | it('should limit to default whitelisted fields', function() {
453 | var parsed = raven.parsers.parseRequest(mockReq);
454 | parsed.should.have.property('user', {
455 | username: 'janedoe',
456 | email: 'hello@janedoe.com',
457 | ip_address: '127.0.0.1'
458 | });
459 | });
460 |
461 | it('should limit to provided whitelisted fields array', function() {
462 | var parsed = raven.parsers.parseRequest(mockReq, ['username', 'random']);
463 | parsed.should.have.property('user', {
464 | username: 'janedoe',
465 | random: 'random',
466 | ip_address: '127.0.0.1'
467 | });
468 | });
469 |
470 | it('should parse all fields when passed true', function() {
471 | var parsed = raven.parsers.parseRequest(mockReq, true);
472 | parsed.should.have.property('user', {
473 | username: 'janedoe',
474 | email: 'hello@janedoe.com',
475 | random: 'random',
476 | ip_address: '127.0.0.1'
477 | });
478 | });
479 |
480 | it('should parse nothing when passed false', function() {
481 | var parsed = raven.parsers.parseRequest(mockReq, false);
482 | parsed.should.not.have.property('user');
483 | });
484 |
485 | it('should take a custom parsing function', function() {
486 | var parsed = raven.parsers.parseRequest(mockReq, function(req) {
487 | return {user: req.user.username};
488 | });
489 | parsed.should.have.property('user', {
490 | user: 'janedoe',
491 | ip_address: '127.0.0.1'
492 | });
493 | });
494 | });
495 | });
496 | });
497 |
498 | describe('#parseError()', function() {
499 | it('should parse plain Error object', function(done) {
500 | raven.parsers.parseError(new Error(), {}, function(parsed) {
501 | parsed.message.should.equal('Error: ');
502 | parsed.should.have.property('exception');
503 | parsed.exception[0].type.should.equal('Error');
504 | parsed.exception[0].value.should.equal('');
505 | parsed.exception[0].stacktrace.should.have.property('frames');
506 | done();
507 | });
508 | });
509 |
510 | it('should parse Error with message', function(done) {
511 | raven.parsers.parseError(new Error('Crap'), {}, function(parsed) {
512 | parsed.message.should.equal('Error: Crap');
513 | parsed.should.have.property('exception');
514 | parsed.exception[0].type.should.equal('Error');
515 | parsed.exception[0].value.should.equal('Crap');
516 | parsed.exception[0].stacktrace.should.have.property('frames');
517 | done();
518 | });
519 | });
520 |
521 | it('should parse TypeError with message', function(done) {
522 | raven.parsers.parseError(new TypeError('Crap'), {}, function(parsed) {
523 | parsed.message.should.equal('TypeError: Crap');
524 | parsed.should.have.property('exception');
525 | parsed.exception[0].type.should.equal('TypeError');
526 | parsed.exception[0].value.should.equal('Crap');
527 | parsed.exception[0].stacktrace.should.have.property('frames');
528 | done();
529 | });
530 | });
531 |
532 | it('should parse thrown Error', function(done) {
533 | try {
534 | throw new Error('Derp');
535 | } catch (e) {
536 | raven.parsers.parseError(e, {}, function(parsed) {
537 | parsed.message.should.equal('Error: Derp');
538 | parsed.should.have.property('exception');
539 | parsed.exception[0].type.should.equal('Error');
540 | parsed.exception[0].value.should.equal('Derp');
541 | parsed.exception[0].stacktrace.should.have.property('frames');
542 | done();
543 | });
544 | }
545 | });
546 |
547 | it('should allow specifying a custom `culprit`', function(done) {
548 | try {
549 | throw new Error('Foobar');
550 | } catch (e) {
551 | raven.parsers.parseError(
552 | e,
553 | {
554 | culprit: 'foobar'
555 | },
556 | function(parsed) {
557 | parsed.culprit.should.equal('foobar');
558 | done();
559 | }
560 | );
561 | }
562 | });
563 |
564 | it('should allow specifying a custom `transaction`', function(done) {
565 | try {
566 | throw new Error('Foobar');
567 | } catch (e) {
568 | raven.parsers.parseError(
569 | e,
570 | {
571 | transaction: 'foobar'
572 | },
573 | function(parsed) {
574 | parsed.transaction.should.equal('foobar');
575 | done();
576 | }
577 | );
578 | }
579 | });
580 |
581 | it('should have a string stack after parsing', function(done) {
582 | try {
583 | throw new Error('Derp');
584 | } catch (e) {
585 | raven.parsers.parseError(e, {}, function(parsed) {
586 | e.stack.should.be.a.String;
587 | done();
588 | });
589 | }
590 | });
591 |
592 | it('should parse caught real error', function(done) {
593 | /* eslint new-cap:0 */
594 | try {
595 | var o = {};
596 | o['...'].Derp();
597 | } catch (e) {
598 | raven.parsers.parseError(e, {}, function(parsed) {
599 | parsed.message.should.containEql('TypeError');
600 | parsed.message.should.containEql('Derp');
601 | parsed.should.have.property('exception');
602 | parsed.exception[0].type.should.equal('TypeError');
603 | parsed.exception[0].value.should.containEql('Derp');
604 | parsed.exception[0].stacktrace.should.have.property('frames');
605 | done();
606 | });
607 | }
608 | });
609 |
610 | it('should parse an error with additional information', function(done) {
611 | try {
612 | var err = new Error('boom');
613 | err.prop = 'value';
614 | throw err;
615 | } catch (e) {
616 | raven.parsers.parseError(e, {}, function(parsed) {
617 | parsed.should.have.property('exception');
618 | parsed.exception[0].stacktrace.should.have.property('frames');
619 | parsed.should.have.property('extra');
620 | parsed.extra.should.have.property('Error');
621 | parsed.extra.Error.should.have.property('prop');
622 | parsed.extra.Error.prop.should.equal('value');
623 | done();
624 | });
625 | }
626 | });
627 |
628 | it('should read name from an Error constructor', function(done) {
629 | var err = new Error();
630 | raven.parsers.parseError(err, {}, function(parsed) {
631 | parsed.message.should.equal('Error: ');
632 | parsed.exception[0].type.should.equal('Error');
633 | done();
634 | });
635 | });
636 |
637 | it('should read name from an Error constructor for custom errors', function(done) {
638 | // https://gist.github.com/justmoon/15511f92e5216fa2624b
639 | function CustomError(message) {
640 | Error.captureStackTrace(this, this.constructor);
641 | this.name = this.constructor.name;
642 | this.message = message;
643 | }
644 |
645 | util.inherits(CustomError, Error);
646 |
647 | var err = new CustomError('boom');
648 | raven.parsers.parseError(err, {}, function(parsed) {
649 | parsed.message.should.equal('CustomError: boom');
650 | parsed.exception[0].type.should.equal('CustomError');
651 | done();
652 | });
653 | });
654 |
655 | it('should allow for overriding name for built-in errors', function(done) {
656 | var err = new Error();
657 | err.name = 'foo';
658 | raven.parsers.parseError(err, {}, function(parsed) {
659 | parsed.message.should.equal('foo: ');
660 | parsed.exception[0].type.should.equal('foo');
661 | done();
662 | });
663 | });
664 |
665 | it('should allow for overriding name for custom errors', function(done) {
666 | // https://gist.github.com/justmoon/15511f92e5216fa2624b
667 | function CustomError(message) {
668 | Error.captureStackTrace(this, this.constructor);
669 | this.name = 'foo';
670 | this.message = message;
671 | }
672 |
673 | util.inherits(CustomError, Error);
674 |
675 | var err = new CustomError('boom');
676 | raven.parsers.parseError(err, {}, function(parsed) {
677 | parsed.message.should.equal('foo: boom');
678 | parsed.exception[0].type.should.equal('foo');
679 | done();
680 | });
681 | });
682 |
683 | it('should parse Error with non-string type', function(done) {
684 | var err = new Error();
685 | err.name = {};
686 | raven.parsers.parseError(err, {}, function(parsed) {
687 | parsed.message.should.equal('[object Object]: ');
688 | parsed.exception[0].type.should.equal('[object Object]');
689 | parsed.exception[0].value.should.equal('');
690 | done();
691 | });
692 | });
693 | });
694 | });
695 |
--------------------------------------------------------------------------------
/test/raven.transports.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var transports = require('../lib/transports');
4 |
5 | describe('transports', function() {
6 | it('should have an http/s agent with correct config attached by default', function() {
7 | var http = transports.http;
8 | http.agent.should.exist;
9 | http.agent.keepAlive.should.equal(true);
10 | http.agent.maxSockets.should.equal(100);
11 |
12 | var https = transports.https;
13 | https.agent.should.exist;
14 | https.agent.keepAlive.should.equal(true);
15 | https.agent.maxSockets.should.equal(100);
16 | });
17 |
18 | it('should emit error when requests queued over the limit', function(done) {
19 | var http = transports.http;
20 | var _cachedAgent = http.options.agent;
21 |
22 | var requests = {};
23 | for (var i = 0; i < 10; i++) {
24 | requests[i] = 'req';
25 | }
26 |
27 | http.agent = Object.assign({}, _cachedAgent, {
28 | getName: function() {
29 | return 'foo:123';
30 | },
31 | requests: {
32 | 'foo:123': requests
33 | }
34 | });
35 |
36 | http.send({
37 | dsn: {
38 | host: 'foo',
39 | port: 123
40 | },
41 | maxReqQueueCount: 5,
42 | emit: function(event, body) {
43 | event.should.equal('error');
44 | body.message.should.equal('client req queue is full..');
45 | http.options.agent = _cachedAgent;
46 | done();
47 | }
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/test/raven.utils.js:
--------------------------------------------------------------------------------
1 | /* eslint max-len:0, no-undefined:0 */
2 | 'use strict';
3 |
4 | var versionRegexp = /^v(\d+)\.(\d+)\.(\d+)$/i;
5 | var majorVersion = parseInt(versionRegexp.exec(process.version)[1], 10);
6 |
7 | var raven = require('../');
8 |
9 | var _oldConsoleWarn = console.warn;
10 |
11 | function mockConsoleWarn() {
12 | console.warn = function() {
13 | console.warn._called = true;
14 | ++console.warn._callCount;
15 | };
16 | console.warn._called = false;
17 | console.warn._callCount = 0;
18 | }
19 |
20 | function restoreConsoleWarn() {
21 | console.warn = _oldConsoleWarn;
22 | }
23 |
24 | describe('raven.utils', function() {
25 | describe('#parseDSN()', function() {
26 | it('should parse hosted Sentry DSN without path', function() {
27 | var dsn = raven.utils.parseDSN(
28 | 'https://8769c40cf49c4cc58b51fa45d8e2d166:296768aa91084e17b5ac02d3ad5bc7e7@app.getsentry.com/269'
29 | );
30 | var expected = {
31 | protocol: 'https',
32 | public_key: '8769c40cf49c4cc58b51fa45d8e2d166',
33 | private_key: '296768aa91084e17b5ac02d3ad5bc7e7',
34 | host: 'app.getsentry.com',
35 | path: '/',
36 | project_id: '269',
37 | port: 443
38 | };
39 | dsn.should.eql(expected);
40 | });
41 |
42 | it('should parse http not on hosted Sentry with path', function() {
43 | var dsn = raven.utils.parseDSN(
44 | 'http://8769c40cf49c4cc58b51fa45d8e2d166:296768aa91084e17b5ac02d3ad5bc7e7@mysentry.com/some/other/path/269'
45 | );
46 | var expected = {
47 | protocol: 'http',
48 | public_key: '8769c40cf49c4cc58b51fa45d8e2d166',
49 | private_key: '296768aa91084e17b5ac02d3ad5bc7e7',
50 | host: 'mysentry.com',
51 | path: '/some/other/path/',
52 | project_id: '269',
53 | port: 80
54 | };
55 | dsn.should.eql(expected);
56 | });
57 |
58 | it('should parse DSN with non-standard port', function() {
59 | var dsn = raven.utils.parseDSN(
60 | 'https://8769c40cf49c4cc58b51fa45d8e2d166:296768aa91084e17b5ac02d3ad5bc7e7@mysentry.com:8443/some/other/path/269'
61 | );
62 | var expected = {
63 | protocol: 'https',
64 | public_key: '8769c40cf49c4cc58b51fa45d8e2d166',
65 | private_key: '296768aa91084e17b5ac02d3ad5bc7e7',
66 | host: 'mysentry.com',
67 | path: '/some/other/path/',
68 | project_id: '269',
69 | port: 8443
70 | };
71 | dsn.should.eql(expected);
72 | });
73 |
74 | it('should return false for a falsey dns', function() {
75 | raven.utils.parseDSN(false).should.eql(false);
76 | raven.utils.parseDSN('').should.eql(false);
77 | });
78 |
79 | it('show throw an Error on invalid transport protocol', function() {
80 | (function() {
81 | raven.utils.parseDSN(
82 | 'noop://8769c40cf49c4cc58b51fa45d8e2d166:296768aa91084e17b5ac02d3ad5bc7e7@mysentry.com:1234/some/other/path/269'
83 | );
84 | }.should.throw());
85 | });
86 |
87 | it('should ignore a sub-transport protocol', function() {
88 | var dsn = raven.utils.parseDSN(
89 | 'gevent+https://8769c40cf49c4cc58b51fa45d8e2d166:296768aa91084e17b5ac02d3ad5bc7e7@mysentry.com:8443/some/other/path/269'
90 | );
91 | var expected = {
92 | protocol: 'https',
93 | public_key: '8769c40cf49c4cc58b51fa45d8e2d166',
94 | private_key: '296768aa91084e17b5ac02d3ad5bc7e7',
95 | host: 'mysentry.com',
96 | path: '/some/other/path/',
97 | project_id: '269',
98 | port: 8443
99 | };
100 | dsn.should.eql(expected);
101 | });
102 |
103 | it('should parse DSN without private key', function() {
104 | var dsn = raven.utils.parseDSN(
105 | 'https://8769c40cf49c4cc58b51fa45d8e2d166@mysentry.com:8443/some/other/path/269'
106 | );
107 | var expected = {
108 | protocol: 'https',
109 | public_key: '8769c40cf49c4cc58b51fa45d8e2d166',
110 | host: 'mysentry.com',
111 | path: '/some/other/path/',
112 | project_id: '269',
113 | port: 8443
114 | };
115 | dsn.should.eql(expected);
116 | });
117 | });
118 |
119 | describe('#parseAuthHeader()', function() {
120 | it('should parse all parameters', function() {
121 | var timestamp = 12345,
122 | apiKey = 'abc',
123 | apiSecret = 'xyz';
124 | var expected =
125 | 'Sentry sentry_version=5, sentry_timestamp=12345, sentry_client=raven-node/' +
126 | raven.version +
127 | ', sentry_key=abc, sentry_secret=xyz';
128 | raven.utils.getAuthHeader(timestamp, apiKey, apiSecret).should.equal(expected);
129 | });
130 |
131 | it('should skip sentry_secret if apiSecret not provided', function() {
132 | var timestamp = 12345,
133 | apiKey = 'abc';
134 | var expected =
135 | 'Sentry sentry_version=5, sentry_timestamp=12345, sentry_client=raven-node/' +
136 | raven.version +
137 | ', sentry_key=abc';
138 | raven.utils.getAuthHeader(timestamp, apiKey).should.equal(expected);
139 | });
140 | });
141 |
142 | describe('#parseStack()', function() {
143 | // needs new tests with a mock callsite object
144 | it('shouldnt barf on an invalid stack', function() {
145 | var parseStack = raven.utils.parseStack;
146 | var callback = function(frames) {
147 | frames.length.should.equal(0);
148 | };
149 | parseStack('lol', callback);
150 | parseStack(void 0, callback);
151 | parseStack([], callback);
152 | parseStack(
153 | [
154 | {
155 | lol: 1
156 | }
157 | ],
158 | callback
159 | );
160 | });
161 |
162 | it('should extract context from last stack line', function(done) {
163 | var parseStack = raven.utils.parseStack;
164 | var callback = function(frames) {
165 | var frame = frames.pop();
166 |
167 | /* verify that the frame has the properties: pre_context, context_line
168 | and post_context, which are valid for in-app stack lines only.*/
169 | frame.pre_context.should.be.an.instanceOf(Array);
170 | frame.context_line.should.be.type('string');
171 | frame.context_line.trim().should.endWith('undeclared_function();');
172 | frame.post_context.should.be.an.instanceOf(Array);
173 |
174 | frame.in_app.should.be.true;
175 | done();
176 | };
177 | try {
178 | // eslint-disable-next-line no-undef
179 | undeclared_function();
180 | } catch (e) {
181 | parseStack(e, callback);
182 | }
183 | });
184 |
185 | it('should trim long source line in surrounding source context', function(done) {
186 | var parseStack = raven.utils.parseStack;
187 | var callback = function(frames) {
188 | var frame = frames.pop();
189 | frame.in_app.should.be.true;
190 |
191 | var lineBefore = frame.pre_context[frame.pre_context.length - 1].trim();
192 | lineBefore.should.not.startWith('{snip}');
193 | lineBefore.should.endWith('{snip}');
194 |
195 | var lineOf = frame.context_line.trim();
196 | lineOf.should.startWith('{snip}');
197 | lineOf.should.endWith('{snip}');
198 | lineOf.length.should.equal(154); // 140 limit + 7 for `{snip} ` and ` {snip}`
199 | lineOf.should.containEql("throw new Error('boom');");
200 |
201 | var lineAfter = frame.post_context[0].trim();
202 | lineAfter.should.not.startWith('{snip}');
203 | lineAfter.should.endWith('{snip}');
204 | done();
205 | };
206 | try {
207 | require('./fixtures/long-line')();
208 | } catch (e) {
209 | parseStack(e, callback);
210 | }
211 | });
212 |
213 | it('should treat windows files as being in app: in_app should be true', function(done) {
214 | var parseStack = raven.utils.parseStack;
215 | var callback = function(frames) {
216 | var frame = frames.pop();
217 | frame.filename.should.be.type('string');
218 | frame.filename.should.startWith('C:\\');
219 | frame.in_app.should.be.true;
220 | done();
221 | };
222 | var err = new Error('some error message');
223 | // get first line of err stack (line after err message)
224 | var firstFileLine = err.stack.split('\n')[1];
225 | // replace first occurrence of "/" with C:\ to mock windows style
226 | var winFirstFileLine = firstFileLine.replace(/[/]/, 'C:\\');
227 | // replace all remaining "/" with "\"
228 | winFirstFileLine = winFirstFileLine.replace(/[/]/g, '\\');
229 | // create a "win-style" stack replacing the first err.stack line with our above win-style line
230 | err.stack = err.stack.replace(firstFileLine, winFirstFileLine);
231 | parseStack(err, callback);
232 | });
233 |
234 | it('should mark node core library frames as not being in app', function(done) {
235 | var qsStringify = require('querystring').stringify;
236 | var parseStack = raven.utils.parseStack;
237 |
238 | var callback = function(frames) {
239 | var frame1 = frames.pop();
240 | frame1.in_app.should.be.false;
241 | frame1.filename.should.equal('querystring.js');
242 |
243 | var frame2 = frames.pop();
244 | frame2.in_app.should.be.false;
245 | frame2.filename.should.equal('querystring.js');
246 |
247 | done();
248 | };
249 |
250 | try {
251 | // Incomplete surrogate pair will cause qs.encode (used by qs.stringify) to throw
252 | qsStringify({a: '\uDCA9'});
253 | } catch (e) {
254 | parseStack(e, callback);
255 | }
256 | });
257 |
258 | it('should not read the same source file multiple times when getting source context lines', function(done) {
259 | var fs = require('fs');
260 | var origReadFile = fs.readFile;
261 | var filesRead = [];
262 |
263 | fs.readFile = function(file) {
264 | filesRead.push(file);
265 | origReadFile.apply(this, arguments);
266 | };
267 |
268 | function parseCallback(frames) {
269 | // first two frames will both be from this file, but we should have only read this file once
270 | var frame1 = frames.pop();
271 | var frame2 = frames.pop();
272 | frame1.context_line.trim().should.endWith("throw new Error('error');");
273 | frame2.context_line.trim().should.endWith('nestedThrow();');
274 | frame1.filename.should.equal(frame2.filename);
275 |
276 | var uniqueFilesRead = filesRead.filter(function(filename, idx, arr) {
277 | return arr.indexOf(filename) === idx;
278 | });
279 | filesRead.length.should.equal(uniqueFilesRead.length);
280 |
281 | fs.readFile = origReadFile;
282 | done();
283 | }
284 |
285 | function nestedThrow() {
286 | throw new Error('error');
287 | }
288 |
289 | try {
290 | nestedThrow();
291 | } catch (e) {
292 | raven.utils.parseStack(e, parseCallback);
293 | }
294 | });
295 |
296 | it('should handle spaces in paths', function(done) {
297 | var parseStack = raven.utils.parseStack;
298 | var callback = function(frames) {
299 | var frame = frames.pop();
300 | frame.module.should.endWith('file with spaces in path');
301 | frame.filename.should.endWith('file with spaces in path.js');
302 | done();
303 | };
304 | try {
305 | require('./fixtures/file with spaces in path')();
306 | } catch (e) {
307 | parseStack(e, callback);
308 | }
309 | });
310 | });
311 |
312 | describe('#getTransaction()', function() {
313 | it('should handle empty', function() {
314 | raven.utils.getTransaction({}).should.eql('');
315 | });
316 |
317 | it('should handle missing module', function() {
318 | raven.utils
319 | .getTransaction({
320 | function: 'foo'
321 | })
322 | .should.eql('? at foo');
323 | });
324 |
325 | it('should handle missing function', function() {
326 | raven.utils
327 | .getTransaction({
328 | module: 'foo'
329 | })
330 | .should.eql('foo at ?');
331 | });
332 |
333 | it('should work', function() {
334 | raven.utils
335 | .getTransaction({
336 | module: 'foo',
337 | function: 'bar'
338 | })
339 | .should.eql('foo at bar');
340 | });
341 | });
342 |
343 | describe('#getModule()', function() {
344 | it('should identify a node_module', function() {
345 | var filename = '/home/x/node_modules/foo/bar/baz.js';
346 | raven.utils.getModule(filename).should.eql('foo.bar:baz');
347 | });
348 |
349 | it('should identify a main module', function() {
350 | var filename = '/home/x/foo/bar/baz.js';
351 | raven.utils.getModule(filename, '/home/x/').should.eql('foo.bar:baz');
352 | });
353 |
354 | it('should fallback to just filename', function() {
355 | var filename = '/home/lol.js';
356 | raven.utils.getModule(filename).should.eql('lol');
357 | });
358 | });
359 |
360 | describe('#serializeException()', function() {
361 | it('return [Object] when reached depth=0', function() {
362 | var actual = raven.utils.serializeException(
363 | {
364 | a: 42,
365 | b: 'asd',
366 | c: true
367 | },
368 | 0
369 | );
370 | var expected = '[Object]';
371 |
372 | actual.should.eql(expected);
373 | });
374 |
375 | it('should serialize one level deep with depth=1', function() {
376 | var actual = raven.utils.serializeException(
377 | {
378 | a: 42,
379 | b: 'asd',
380 | c: true,
381 | d: undefined,
382 | e:
383 | 'very long string that is definitely over 120 characters, which is default for now but can be changed anytime because why not?',
384 | f: {foo: 42},
385 | g: [1, 'a', true],
386 | h: function() {}
387 | },
388 | 1
389 | );
390 | var expected = {
391 | a: 42,
392 | b: 'asd',
393 | c: true,
394 | d: undefined,
395 | e: 'very long string that is definitely ove\u2026',
396 | f: '[Object]',
397 | g: '[Array]',
398 | // Node < 6 is not capable of pulling function name from unnamed object methods
399 | h: majorVersion < 6 ? '[Function]' : '[Function: h]'
400 | };
401 |
402 | actual.should.eql(expected);
403 | });
404 |
405 | it('should serialize arbitrary number of depths', function() {
406 | var actual = raven.utils.serializeException(
407 | {
408 | a: 42,
409 | b: 'asd',
410 | c: true,
411 | d: undefined,
412 | e:
413 | 'very long string that is definitely over 40 characters, which is default for now but can be changed',
414 | f: {
415 | foo: 42,
416 | bar: {
417 | foo: 42,
418 | bar: {
419 | bar: {
420 | bar: {
421 | bar: 42
422 | }
423 | }
424 | },
425 | baz: ['hello']
426 | },
427 | baz: [1, 'a', true]
428 | },
429 | g: [1, 'a', true],
430 | h: function bar() {}
431 | },
432 | 5
433 | );
434 | var expected = {
435 | a: 42,
436 | b: 'asd',
437 | c: true,
438 | d: undefined,
439 | e: 'very long string that is definitely ove\u2026',
440 | f: {
441 | foo: 42,
442 | bar: {
443 | foo: 42,
444 | bar: {
445 | bar: {
446 | bar: '[Object]'
447 | }
448 | },
449 | baz: ['hello']
450 | },
451 | baz: [1, 'a', true]
452 | },
453 | g: [1, 'a', true],
454 | h: '[Function: bar]'
455 | };
456 |
457 | actual.should.eql(expected);
458 | });
459 |
460 | it('should reduce depth if payload size was exceeded', function() {
461 | var actual = raven.utils.serializeException(
462 | {
463 | a: {
464 | a: '50kB worth of payload pickle rick',
465 | b: '50kB worth of payload pickle rick'
466 | },
467 | b: '50kB worth of payload pickle rick'
468 | },
469 | 2,
470 | 100
471 | );
472 | var expected = {
473 | a: '[Object]',
474 | b: '50kB worth of payload pickle rick'
475 | };
476 |
477 | actual.should.eql(expected);
478 | });
479 |
480 | it('should reduce depth only one level at the time', function() {
481 | var actual = raven.utils.serializeException(
482 | {
483 | a: {
484 | a: {
485 | a: {
486 | a: [
487 | '50kB worth of payload pickle rick',
488 | '50kB worth of payload pickle rick',
489 | '50kB worth of payload pickle rick'
490 | ]
491 | }
492 | },
493 | b: '50kB worth of payload pickle rick'
494 | },
495 | b: '50kB worth of payload pickle rick'
496 | },
497 | 4,
498 | 200
499 | );
500 | var expected = {
501 | a: {
502 | a: {
503 | a: {
504 | a: '[Array]'
505 | }
506 | },
507 | b: '50kB worth of payload pickle rick'
508 | },
509 | b: '50kB worth of payload pickle rick'
510 | };
511 |
512 | actual.should.eql(expected);
513 | });
514 |
515 | it('should fallback to [Object] if cannot reduce payload size enough', function() {
516 | var actual = raven.utils.serializeException(
517 | {
518 | a: '50kB worth of payload pickle rick',
519 | b: '50kB worth of payload pickle rick',
520 | c: '50kB worth of payload pickle rick',
521 | d: '50kB worth of payload pickle rick'
522 | },
523 | 1,
524 | 100
525 | );
526 | var expected = '[Object]';
527 |
528 | actual.should.eql(expected);
529 | });
530 | });
531 |
532 | describe('#serializeKeysForMessage()', function() {
533 | it('should fit as many keys as possible in default limit of 40', function() {
534 | var actual = raven.utils.serializeKeysForMessage([
535 | 'pickle',
536 | 'rick',
537 | 'morty',
538 | 'snuffles',
539 | 'server',
540 | 'request'
541 | ]);
542 | var expected = 'pickle, rick, morty, snuffles, server\u2026';
543 | actual.should.eql(expected);
544 | });
545 |
546 | it('shouldnt append ellipsis if have enough space', function() {
547 | var actual = raven.utils.serializeKeysForMessage(['pickle', 'rick', 'morty']);
548 | var expected = 'pickle, rick, morty';
549 | actual.should.eql(expected);
550 | });
551 |
552 | it('should default to no-keys message if empty array provided', function() {
553 | var actual = raven.utils.serializeKeysForMessage([]);
554 | var expected = '[object has no keys]';
555 | actual.should.eql(expected);
556 | });
557 |
558 | it('should leave first key as is, if its too long for the limit', function() {
559 | var actual = raven.utils.serializeKeysForMessage([
560 | 'imSuchALongKeyThatIDontEvenFitInTheLimitOf40Characters',
561 | 'pickle'
562 | ]);
563 | var expected = 'imSuchALongKeyThatIDontEvenFitInTheLimitOf40Characters';
564 | actual.should.eql(expected);
565 | });
566 |
567 | it('should with with provided maxLength', function() {
568 | var actual = raven.utils.serializeKeysForMessage(['foo', 'bar', 'baz'], 10);
569 | var expected = 'foo, bar\u2026';
570 | actual.should.eql(expected);
571 | });
572 |
573 | it('handles incorrect input', function() {
574 | raven.utils.serializeKeysForMessage({}).should.eql('');
575 | raven.utils.serializeKeysForMessage(false).should.eql('');
576 | raven.utils.serializeKeysForMessage(undefined).should.eql('');
577 | raven.utils.serializeKeysForMessage(42).should.eql('42');
578 | raven.utils.serializeKeysForMessage('foo').should.eql('foo');
579 | });
580 | });
581 |
582 | describe('isError', function() {
583 | it('should work as advertised', function() {
584 | function RavenError(message) {
585 | this.name = 'RavenError';
586 | this.message = message;
587 | }
588 | RavenError.prototype = new Error();
589 | RavenError.prototype.constructor = RavenError;
590 |
591 | raven.utils.isError(new Error()).should.be.true;
592 | raven.utils.isError(new RavenError()).should.be.true;
593 | raven.utils.isError({}).should.be.false;
594 | raven.utils.isError({
595 | message: 'A fake error',
596 | stack: 'no stack here'
597 | }).should.be.false;
598 | raven.utils.isError('').should.be.false;
599 | raven.utils.isError(true).should.be.false;
600 | });
601 |
602 | it('should work with errors from different contexts, eg. vm module', function(done) {
603 | var vm = require('vm');
604 | var script = new vm.Script("throw new Error('this is the error')");
605 |
606 | try {
607 | script.runInNewContext();
608 | } catch (e) {
609 | raven.utils.isError(e).should.be.true;
610 | done();
611 | }
612 | });
613 | });
614 |
615 | describe('#consoleAlert()', function() {
616 | it('should call console.warn if enabled', function() {
617 | mockConsoleWarn();
618 | raven.utils.consoleAlert('foo');
619 | raven.utils.consoleAlert('foo');
620 | console.warn._called.should.eql(true);
621 | console.warn._callCount.should.eql(2);
622 | restoreConsoleWarn();
623 | });
624 |
625 | it('should be disabled after calling disableConsoleAlerts', function() {
626 | mockConsoleWarn();
627 | raven.utils.disableConsoleAlerts();
628 | raven.utils.consoleAlert('foo');
629 | console.warn._called.should.eql(false);
630 | console.warn._callCount.should.eql(0);
631 | raven.utils.enableConsoleAlerts();
632 | restoreConsoleWarn();
633 | });
634 |
635 | it('should be disabled after calling disableConsoleAlerts, even after previous successful calls', function() {
636 | mockConsoleWarn();
637 | raven.utils.consoleAlert('foo');
638 | console.warn._called.should.eql(true);
639 | console.warn._callCount.should.eql(1);
640 | raven.utils.disableConsoleAlerts();
641 | raven.utils.consoleAlert('foo');
642 | console.warn._callCount.should.eql(1);
643 | raven.utils.enableConsoleAlerts();
644 | restoreConsoleWarn();
645 | });
646 | });
647 |
648 | describe('#consoleAlertOnce()', function() {
649 | it('should call console.warn if enabled, but only once with the same message', function() {
650 | mockConsoleWarn();
651 | raven.utils.consoleAlertOnce('foo');
652 | console.warn._called.should.eql(true);
653 | console.warn._callCount.should.eql(1);
654 | raven.utils.consoleAlertOnce('foo');
655 | console.warn._callCount.should.eql(1);
656 | restoreConsoleWarn();
657 | });
658 |
659 | it('should be disable after calling disableConsoleAlerts', function() {
660 | mockConsoleWarn();
661 | raven.utils.disableConsoleAlerts();
662 | raven.utils.consoleAlertOnce('foo');
663 | console.warn._called.should.eql(false);
664 | console.warn._callCount.should.eql(0);
665 | raven.utils.enableConsoleAlerts();
666 | restoreConsoleWarn();
667 | });
668 | });
669 | });
670 |
--------------------------------------------------------------------------------
/test/run.coffee:
--------------------------------------------------------------------------------
1 | Mocha = require('mocha')
2 | should = require('should')
3 |
4 | fs = require('fs')
5 | path = require('path')
6 |
7 | mocha = new Mocha
8 |
9 | ['test', 'test/vendor'].forEach (dir) ->
10 | fs.readdirSync(dir).filter (file) ->
11 | file.substr(-3) == '.js';
12 | .forEach (file) ->
13 | mocha.addFile path.join(dir, file)
14 |
15 | mocha.run (failures) ->
16 | process.on 'exit', () ->
17 | process.exit(failures);
18 |
--------------------------------------------------------------------------------
/test/vendor/json-stringify-safe.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Sinon = require('sinon');
4 | var assert = require('assert');
5 | var stringify = require('../../vendor/json-stringify-safe');
6 |
7 | function jsonify(obj) {
8 | return JSON.stringify(obj, null, 2);
9 | }
10 |
11 | describe('Stringify', function() {
12 | it('must stringify circular objects', function() {
13 | var obj = {name: 'Alice'};
14 | obj.self = obj;
15 | var json = stringify(obj, null, 2);
16 | assert.deepEqual(json, jsonify({name: 'Alice', self: '[Circular ~]'}));
17 | });
18 |
19 | it('must stringify circular objects with intermediaries', function() {
20 | var obj = {name: 'Alice'};
21 | obj.identity = {self: obj};
22 | var json = stringify(obj, null, 2);
23 | assert.deepEqual(json, jsonify({name: 'Alice', identity: {self: '[Circular ~]'}}));
24 | });
25 |
26 | it('must stringify circular objects deeper', function() {
27 | var obj = {name: 'Alice', child: {name: 'Bob'}};
28 | obj.child.self = obj.child;
29 |
30 | assert.deepEqual(
31 | stringify(obj, null, 2),
32 | jsonify({
33 | name: 'Alice',
34 | child: {name: 'Bob', self: '[Circular ~.child]'}
35 | })
36 | );
37 | });
38 |
39 | it('must stringify circular objects deeper with intermediaries', function() {
40 | var obj = {name: 'Alice', child: {name: 'Bob'}};
41 | obj.child.identity = {self: obj.child};
42 |
43 | assert.deepEqual(
44 | stringify(obj, null, 2),
45 | jsonify({
46 | name: 'Alice',
47 | child: {name: 'Bob', identity: {self: '[Circular ~.child]'}}
48 | })
49 | );
50 | });
51 |
52 | it('must stringify circular objects in an array', function() {
53 | var obj = {name: 'Alice'};
54 | obj.self = [obj, obj];
55 |
56 | assert.deepEqual(
57 | stringify(obj, null, 2),
58 | jsonify({
59 | name: 'Alice',
60 | self: ['[Circular ~]', '[Circular ~]']
61 | })
62 | );
63 | });
64 |
65 | it('must stringify circular objects deeper in an array', function() {
66 | var obj = {name: 'Alice', children: [{name: 'Bob'}, {name: 'Eve'}]};
67 | obj.children[0].self = obj.children[0];
68 | obj.children[1].self = obj.children[1];
69 |
70 | assert.deepEqual(
71 | stringify(obj, null, 2),
72 | jsonify({
73 | name: 'Alice',
74 | children: [
75 | {name: 'Bob', self: '[Circular ~.children.0]'},
76 | {name: 'Eve', self: '[Circular ~.children.1]'}
77 | ]
78 | })
79 | );
80 | });
81 |
82 | it('must stringify circular arrays', function() {
83 | var obj = [];
84 | obj.push(obj);
85 | obj.push(obj);
86 | var json = stringify(obj, null, 2);
87 | assert.deepEqual(json, jsonify(['[Circular ~]', '[Circular ~]']));
88 | });
89 |
90 | it('must stringify circular arrays with intermediaries', function() {
91 | var obj = [];
92 | obj.push({name: 'Alice', self: obj});
93 | obj.push({name: 'Bob', self: obj});
94 |
95 | assert.deepEqual(
96 | stringify(obj, null, 2),
97 | jsonify([
98 | {name: 'Alice', self: '[Circular ~]'},
99 | {name: 'Bob', self: '[Circular ~]'}
100 | ])
101 | );
102 | });
103 |
104 | it('must stringify repeated objects in objects', function() {
105 | var obj = {};
106 | var alice = {name: 'Alice'};
107 | obj.alice1 = alice;
108 | obj.alice2 = alice;
109 |
110 | assert.deepEqual(
111 | stringify(obj, null, 2),
112 | jsonify({
113 | alice1: {name: 'Alice'},
114 | alice2: {name: 'Alice'}
115 | })
116 | );
117 | });
118 |
119 | it('must stringify repeated objects in arrays', function() {
120 | var alice = {name: 'Alice'};
121 | var obj = [alice, alice];
122 | var json = stringify(obj, null, 2);
123 | assert.deepEqual(json, jsonify([{name: 'Alice'}, {name: 'Alice'}]));
124 | });
125 |
126 | it('must call given decycler and use its output', function() {
127 | var obj = {};
128 | obj.a = obj;
129 | obj.b = obj;
130 |
131 | var decycle = Sinon.spy(function() {
132 | return decycle.callCount;
133 | });
134 | var json = stringify(obj, null, 2, decycle);
135 | assert.deepEqual(json, jsonify({a: 1, b: 2}, null, 2));
136 |
137 | assert.strictEqual(decycle.callCount, 2);
138 | assert.strictEqual(decycle.thisValues[0], obj);
139 | assert.strictEqual(decycle.args[0][0], 'a');
140 | assert.strictEqual(decycle.args[0][1], obj);
141 | assert.strictEqual(decycle.thisValues[1], obj);
142 | assert.strictEqual(decycle.args[1][0], 'b');
143 | assert.strictEqual(decycle.args[1][1], obj);
144 | });
145 |
146 | it('must call replacer and use its output', function() {
147 | var obj = {name: 'Alice', child: {name: 'Bob'}};
148 |
149 | var replacer = Sinon.spy(bangString);
150 | var json = stringify(obj, replacer, 2);
151 | assert.deepEqual(json, jsonify({name: 'Alice!', child: {name: 'Bob!'}}));
152 |
153 | assert.strictEqual(replacer.callCount, 4);
154 | assert.strictEqual(replacer.args[0][0], '');
155 | assert.strictEqual(replacer.args[0][1], obj);
156 | assert.strictEqual(replacer.thisValues[1], obj);
157 | assert.strictEqual(replacer.args[1][0], 'name');
158 | assert.strictEqual(replacer.args[1][1], 'Alice');
159 | assert.strictEqual(replacer.thisValues[2], obj);
160 | assert.strictEqual(replacer.args[2][0], 'child');
161 | assert.strictEqual(replacer.args[2][1], obj.child);
162 | assert.strictEqual(replacer.thisValues[3], obj.child);
163 | assert.strictEqual(replacer.args[3][0], 'name');
164 | assert.strictEqual(replacer.args[3][1], 'Bob');
165 | });
166 |
167 | it('must call replacer after describing circular references', function() {
168 | var obj = {name: 'Alice'};
169 | obj.self = obj;
170 |
171 | var replacer = Sinon.spy(bangString);
172 | var json = stringify(obj, replacer, 2);
173 | assert.deepEqual(json, jsonify({name: 'Alice!', self: '[Circular ~]!'}));
174 |
175 | assert.strictEqual(replacer.callCount, 3);
176 | assert.strictEqual(replacer.args[0][0], '');
177 | assert.strictEqual(replacer.args[0][1], obj);
178 | assert.strictEqual(replacer.thisValues[1], obj);
179 | assert.strictEqual(replacer.args[1][0], 'name');
180 | assert.strictEqual(replacer.args[1][1], 'Alice');
181 | assert.strictEqual(replacer.thisValues[2], obj);
182 | assert.strictEqual(replacer.args[2][0], 'self');
183 | assert.strictEqual(replacer.args[2][1], '[Circular ~]');
184 | });
185 |
186 | it('must call given decycler and use its output for nested objects', function() {
187 | var obj = {};
188 | obj.a = obj;
189 | obj.b = {self: obj};
190 |
191 | var decycle = Sinon.spy(function() {
192 | return decycle.callCount;
193 | });
194 | var json = stringify(obj, null, 2, decycle);
195 | assert.deepEqual(json, jsonify({a: 1, b: {self: 2}}));
196 |
197 | assert.strictEqual(decycle.callCount, 2);
198 | assert.strictEqual(decycle.args[0][0], 'a');
199 | assert.strictEqual(decycle.args[0][1], obj);
200 | assert.strictEqual(decycle.args[1][0], 'self');
201 | assert.strictEqual(decycle.args[1][1], obj);
202 | });
203 |
204 | it("must use decycler's output when it returned null", function() {
205 | var obj = {a: 'b'};
206 | obj.self = obj;
207 | obj.selves = [obj, obj];
208 |
209 | function decycle() {
210 | return null;
211 | }
212 | assert.deepEqual(
213 | stringify(obj, null, 2, decycle),
214 | jsonify({
215 | a: 'b',
216 | self: null,
217 | selves: [null, null]
218 | })
219 | );
220 | });
221 |
222 | it("must use decycler's output when it returned undefined", function() {
223 | var obj = {a: 'b'};
224 | obj.self = obj;
225 | obj.selves = [obj, obj];
226 |
227 | function decycle() {}
228 | assert.deepEqual(
229 | stringify(obj, null, 2, decycle),
230 | jsonify({
231 | a: 'b',
232 | selves: [null, null]
233 | })
234 | );
235 | });
236 |
237 | it('must throw given a decycler that returns a cycle', function() {
238 | var obj = {};
239 | obj.self = obj;
240 | var err;
241 | function identity(key, value) {
242 | return value;
243 | }
244 | try {
245 | stringify(obj, null, 2, identity);
246 | } catch (ex) {
247 | err = ex;
248 | }
249 | assert.ok(err instanceof TypeError);
250 | });
251 |
252 | it('must stringify error objects, including extra properties', function() {
253 | var obj = new Error('Wubba Lubba Dub Dub');
254 | obj.reason = new TypeError("I'm pickle Riiick!");
255 |
256 | // Testing whole stack is reduntant, we just need to know that it has been stringified
257 | // And that it includes our first frame
258 | var stringified = stringify(obj, null, 2);
259 | assert.equal(obj.stack.indexOf('Error: Wubba Lubba Dub Dub') === 0, true);
260 | assert.equal(obj.reason.stack.indexOf("TypeError: I'm pickle Riiick!") === 0, true);
261 |
262 | // Skip stack, as it has been tested above
263 | delete obj.stack;
264 | delete obj.reason.stack;
265 | stringified = stringify(obj, null, 2);
266 |
267 | assert.deepEqual(
268 | stringified,
269 | jsonify({
270 | message: 'Wubba Lubba Dub Dub',
271 | name: 'Error',
272 | reason: {
273 | message: "I'm pickle Riiick!",
274 | name: 'TypeError'
275 | }
276 | })
277 | );
278 | });
279 |
280 | it('must stringify error objects with circular references', function() {
281 | var obj = new Error('Wubba Lubba Dub Dub');
282 | obj.reason = obj;
283 |
284 | // Skip stack, as it has been tested above
285 | delete obj.stack;
286 | var stringified = stringify(obj, null, 2);
287 |
288 | assert.deepEqual(
289 | stringified,
290 | jsonify({
291 | message: 'Wubba Lubba Dub Dub',
292 | name: 'Error',
293 | reason: '[Circular ~]'
294 | })
295 | );
296 | });
297 |
298 | describe('.getSerialize', function() {
299 | it('must stringify circular objects', function() {
300 | var obj = {a: 'b'};
301 | obj.circularRef = obj;
302 | obj.list = [obj, obj];
303 |
304 | var json = JSON.stringify(obj, stringify.getSerialize(), 2);
305 | assert.deepEqual(
306 | json,
307 | jsonify({
308 | a: 'b',
309 | circularRef: '[Circular ~]',
310 | list: ['[Circular ~]', '[Circular ~]']
311 | })
312 | );
313 | });
314 |
315 | // This is the behavior as of Mar 3, 2015.
316 | // The serializer function keeps state inside the returned function and
317 | // so far I'm not sure how to not do that. JSON.stringify's replacer is not
318 | // called _after_ serialization.
319 | xit('must return a function that could be called twice', function() {
320 | var obj = {name: 'Alice'};
321 | obj.self = obj;
322 |
323 | var json;
324 | var serializer = stringify.getSerialize();
325 |
326 | json = JSON.stringify(obj, serializer, 2);
327 | assert.deepEqual(json, jsonify({name: 'Alice', self: '[Circular ~]'}));
328 |
329 | json = JSON.stringify(obj, serializer, 2);
330 | assert.deepEqual(json, jsonify({name: 'Alice', self: '[Circular ~]'}));
331 | });
332 | });
333 | });
334 |
335 | function bangString(key, value) {
336 | return typeof value === 'string' ? value + '!' : value;
337 | }
338 |
--------------------------------------------------------------------------------
/vendor/json-stringify-safe.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*
4 | json-stringify-safe
5 | Like JSON.stringify, but doesn't throw on circular references.
6 |
7 | Originally forked from https://github.com/isaacs/json-stringify-safe
8 | version 5.0.1 on 2017-09-21 and modified to handle Errors serialization.
9 | Tests for this are in test/vendor.
10 |
11 | ISC license: https://github.com/isaacs/json-stringify-safe/blob/master/LICENSE
12 | */
13 |
14 | exports = module.exports = stringify;
15 | exports.getSerialize = serializer;
16 |
17 | function stringify(obj, replacer, spaces, cycleReplacer) {
18 | return JSON.stringify(obj, serializer(replacer, cycleReplacer), spaces);
19 | }
20 |
21 | // https://github.com/ftlabs/js-abbreviate/blob/fa709e5f139e7770a71827b1893f22418097fbda/index.js#L95-L106
22 | function stringifyError(value) {
23 | var err = {
24 | // These properties are implemented as magical getters and don't show up in for in
25 | stack: value.stack,
26 | message: value.message,
27 | name: value.name
28 | };
29 |
30 | for (var i in value) {
31 | if (Object.prototype.hasOwnProperty.call(value, i)) {
32 | err[i] = value[i];
33 | }
34 | }
35 |
36 | return err;
37 | }
38 |
39 | function serializer(replacer, cycleReplacer) {
40 | var stack = [];
41 | var keys = [];
42 |
43 | if (cycleReplacer == null) {
44 | cycleReplacer = function(key, value) {
45 | if (stack[0] === value) {
46 | return '[Circular ~]';
47 | }
48 | return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']';
49 | };
50 | }
51 |
52 | return function(key, value) {
53 | if (stack.length > 0) {
54 | var thisPos = stack.indexOf(this);
55 | ~thisPos ? stack.splice(thisPos + 1) : stack.push(this);
56 | ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key);
57 |
58 | if (~stack.indexOf(value)) {
59 | value = cycleReplacer.call(this, key, value);
60 | }
61 | } else {
62 | stack.push(value);
63 | }
64 |
65 | return replacer == null
66 | ? value instanceof Error ? stringifyError(value) : value
67 | : replacer.call(this, key, value);
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/vendor/node-lsmod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Original repository: https://github.com/defunctzombie/node-lsmod/
4 | //
5 | // [2018-02-09] @kamilogorek - Handle scoped packages structure
6 |
7 | // builtin
8 | var fs = require('fs');
9 | var path = require('path');
10 |
11 | // node 0.6 support
12 | fs.existsSync = fs.existsSync || path.existsSync;
13 |
14 | // mainPaths are the paths where our mainprog will be able to load from
15 | // we store these to avoid grabbing the modules that were loaded as a result
16 | // of a dependency module loading its dependencies, we only care about deps our
17 | // mainprog loads
18 | var mainPaths = (require.main && require.main.paths) || [];
19 |
20 | module.exports = function() {
21 | var paths = Object.keys(require.cache || []);
22 |
23 | // module information
24 | var infos = {};
25 |
26 | // paths we have already inspected to avoid traversing again
27 | var seen = {};
28 |
29 | paths.forEach(function(p) {
30 | /* eslint-disable consistent-return */
31 |
32 | var dir = p;
33 |
34 | (function updir() {
35 | var orig = dir;
36 | dir = path.dirname(orig);
37 |
38 | if (/@[^/]+$/.test(dir)) {
39 | dir = path.dirname(dir);
40 | }
41 |
42 | if (!dir || orig === dir || seen[orig]) {
43 | return;
44 | } else if (mainPaths.indexOf(dir) < 0) {
45 | return updir();
46 | }
47 |
48 | var pkgfile = path.join(orig, 'package.json');
49 | var exists = fs.existsSync(pkgfile);
50 |
51 | seen[orig] = true;
52 |
53 | // travel up the tree if no package.json here
54 | if (!exists) {
55 | return updir();
56 | }
57 |
58 | try {
59 | var info = JSON.parse(fs.readFileSync(pkgfile, 'utf8'));
60 | infos[info.name] = info.version;
61 | } catch (e) {}
62 | })();
63 |
64 | /* eslint-enable consistent-return */
65 | });
66 |
67 | return infos;
68 | };
69 |
--------------------------------------------------------------------------------