Slide 1

Slide 1 text

JSConf US May 28th 2014 Matt Robenolt Everything is broken, and I don’t know why. Help! Help! Help! Help! Help!

Slide 2

Slide 2 text

Hello < me irl

Slide 3

Slide 3 text

Lead Operations Engineer

Slide 4

Slide 4 text

Core Contributor

Slide 5

Slide 5 text

If an exception happens in production and nobody sees the Chrome debug console, did it really happen?

Slide 6

Slide 6 text

Post-mortem debugging

Slide 7

Slide 7 text

Ideally, we’d like to know that something is broken before our users complain to us.

Slide 8

Slide 8 text

Humans are actually pretty bad at writing tests.

Slide 9

Slide 9 text

… or any tests at all.

Slide 10

Slide 10 text

QA processes are faulty.

Slide 11

Slide 11 text

Bugs will still get into production no matter what.

Slide 12

Slide 12 text

“It doesn’t work for me.” - Angry user

Slide 13

Slide 13 text

“It works for me. #wontfix” - Me

Slide 14

Slide 14 text

How can we be proactive about this?

Slide 15

Slide 15 text

We should capture our errors, and log as much information automatically so we can debug.

Slide 16

Slide 16 text

window.onerror

Slide 17

Slide 17 text

window.onerror onNOPE

Slide 18

Slide 18 text

window.onerror is literally the worst way to capture an exception.

Slide 19

Slide 19 text

function(message,url,lineNumber)

Slide 20

Slide 20 text

In reality: “Script error.”

Slide 21

Slide 21 text

No real Error object.

Slide 22

Slide 22 text

message: “TypeError: Cannot read property 'foo' of undefined” url: “http://example.com/foo.js” lineNumber: 10

Slide 23

Slide 23 text

BUT…

Slide 24

Slide 24 text

Your code is probably minified.

Slide 25

Slide 25 text

Line 1 is your entire application.

Slide 26

Slide 26 text

Your JavaScript is likely being hosted on another domain. CDN, etc.

Slide 27

Slide 27 text

If the script is on another domain, it’s subject to CORS. Meaning… “Script error.”

Slide 28

Slide 28 text

Literally no idea what happened. No message, no url, no lineNumber.

Slide 29

Slide 29 text

Though, newer engines are getting better: Gecko 31 and Chrome 28.

Slide 30

Slide 30 text

function(message,url,lineNumber, columnNumber,error)

Slide 31

Slide 31 text

new Error('lol')

Slide 32

Slide 32 text

What is better about an Error object?

Slide 33

Slide 33 text

A stack trace!

Slide 34

Slide 34 text

A stack trace! … if you’re lucky.

Slide 35

Slide 35 text

A stack trace is a record of function calls until now within the current call stack.

Slide 36

Slide 36 text

stack trace or gtfo

Slide 37

Slide 37 text

$ node

Slide 38

Slide 38 text

$ node > throw new Error('lol')

Slide 39

Slide 39 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10)

Slide 40

Slide 40 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) name

Slide 41

Slide 41 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) message

Slide 42

Slide 42 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) stack trace

Slide 43

Slide 43 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) frame

Slide 44

Slide 44 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) caller

Slide 45

Slide 45 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) source

Slide 46

Slide 46 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) line number

Slide 47

Slide 47 text

Error: lol at repl:1:7 at REPLServer.self.eval (repl.js:110:21) at repl.js:249:20 at REPLServer.self.eval (repl.js:122:7) at Interface. (repl.js:239:12) at Interface.EventEmitter.emit (events.js:95:17) at Interface._onLine (readline.js:202:10) at Interface._line (readline.js:531:8) at Interface._ttyWrite (readline.js:760:14) at ReadStream.onkeypress (readline.js:99:10) column number

Slide 48

Slide 48 text

> typeof new Error('lol').stack

Slide 49

Slide 49 text

> typeof new Error('lol').stack 'string'

Slide 50

Slide 50 text

/at (?:(.+)\s+)?\(?(?:(. +?):(\d+):(\d+)|([^)]+)) \)?/ * regular expression to parse a v8 stack trace

Slide 51

Slide 51 text

// awesome.js (function() { ! var things = [ {foo: 'bar'} ]; ! function showThing(index) { console.log(things[index].foo); } ! showThing(1); ! })();

Slide 52

Slide 52 text

$ node awesome.js ! /jsconf/awesome.js:9 console.log(things[index].foo); ^ TypeError: Cannot read property 'foo' of undefined at showThing (/jsconf/awesome.js:9:30) at /jsconf/awesome.js:12:3 at Object. (/jsconf/awesome.js:14:2)

Slide 53

Slide 53 text

FireFox ! TypeError: things[index] is undefined ! showThing@http://localhost:8000/awesome.js:9 @http://localhost:8000/awesome.js:12 @http://localhost:8000/awesome.js:2

Slide 54

Slide 54 text

Safari ! TypeError: 'undefined' is not an object (evaluating ‘things[index].foo') ! showThing@http://localhost:8000/awesome.js:9:30 http://localhost:8000/awesome.js:12:16 global code@http://localhost:8000/awesome.js:14:2

Slide 55

Slide 55 text

$ uglifyjs awesome.js -m (function(){var o=[{foo:"bar"}];function n(n) {console.log(o[n].foo)}n(1)})();

Slide 56

Slide 56 text

$ node awesome.min.js ! /jsconf/awesome.min.js:1 o=[{foo:"bar"}];function n(n){console.log(o[n].foo)}n(1) ^ TypeError: Cannot read property 'foo' of undefined at n (/jsconf/awesome.min.js:1:125) at /jsconf/awesome.min.js:1:131 at Object. (/jsconf/awesome.min.js:1:137)

Slide 57

Slide 57 text

source maps

Slide 58

Slide 58 text

A source map maps up minified tokens to positions in the original source.

Slide 59

Slide 59 text

Translates app.min.js, line 1, col 125 app.js, line 9, col 30

Slide 60

Slide 60 text

Translates symbol “n” symbol “showThing”

Slide 61

Slide 61 text

A source map requires 3 things to be useful.

Slide 62

Slide 62 text

1. Filename 2. Line number 3. Column number

Slide 63

Slide 63 text

Column numbers are rare.

Slide 64

Slide 64 text

window.onerror only has line number.

Slide 65

Slide 65 text

FireFox only has line number. showThing@http://localhost:8000/awesome.js:9

Slide 66

Slide 66 text

Internet Explorer… lol * exercise for the reader

Slide 67

Slide 67 text

Error.prototype.stack being a string sucks. We can do better.

Slide 68

Slide 68 text

V8’s CallSite API code.google.com/p/v8/wiki/JavaScriptStackTraceApi

Slide 69

Slide 69 text

Error.prepareStackTrace

Slide 70

Slide 70 text

We can tell V8 how to transform raw CallSite objects into the Error.prototype.stack property.

Slide 71

Slide 71 text

… or not parse them at all.

Slide 72

Slide 72 text

Error.prepareStackTrace = function(error, frames) { return frames; }

Slide 73

Slide 73 text

var frame = e.stack[0] frame.getFunctionName() frame.getLineNumber() frame.getColumnNumber()

Slide 74

Slide 74 text

So Error objects are pretty cool. Now how do we collect them?

Slide 75

Slide 75 text

try…catch

Slide 76

Slide 76 text

try { doStuff() } catch(error) { // log it first logError(error) throw error; }

Slide 77

Slide 77 text

try { doStuff() } catch(error) { // log it first logError(error) throw error; }

Slide 78

Slide 78 text

But this approach is not very scalable.

Slide 79

Slide 79 text

You must predict when your code is going to break. If we don’t, it bubbles up to window.onerror.

Slide 80

Slide 80 text

WRAP ALL THE THINGS!

Slide 81

Slide 81 text

function wrap(func) { function wrapped() { try { return func.apply(this, arguments) } catch(e) { logError(e) throw e } } return wrapped }

Slide 82

Slide 82 text

wrap(function() { ! var things = [ {foo: 'bar'} ]; ! function showThing(index) { console.log(things[index].foo); } ! showThing(1); ! })();

Slide 83

Slide 83 text

Meet Sentry + raven.js getsentry.com

Slide 84

Slide 84 text

We do all of these terrible things for you so you don’t have to.

Slide 85

Slide 85 text

Raven.js monkey patches native objects and instruments popular libraries.

Slide 86

Slide 86 text

Raven then parses the Error and reports the information to the central Sentry server.

Slide 87

Slide 87 text

Sentry will fetch your source maps and give you a nice clean view of your error.

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

Please make this better!

Slide 90

Slide 90 text

Exception handling should be simple.

Slide 91

Slide 91 text

I want to help.

Slide 92

Slide 92 text

References getsentry.com github.com/getsentry/raven-js github.com/getsentry/sentry github.com/defunctzombie/browser-stacks github.com/stacktracejs/stacktrace.js github.com/btford/zone.js code.google.com/p/v8/wiki/JavaScriptStackTraceApi

Slide 93

Slide 93 text

Questions? I have answers. ^ github.com/mattrobenolt @mattrobenolt some