Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Everything is broken, and I don't know why.

Everything is broken, and I don't know why.

JSConf US
May 28th 2014

Video: https://www.youtube.com/watch?v=e4eE5VeO1_o

Matt Robenolt

May 28, 2014
Tweet

More Decks by Matt Robenolt

Other Decks in Technology

Transcript

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

    View Slide

  2. Hello
    < me irl

    View Slide

  3. Lead Operations Engineer

    View Slide

  4. Core Contributor

    View Slide

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

    View Slide

  6. Post-mortem debugging

    View Slide

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

    View Slide

  8. Humans are actually pretty
    bad at writing tests.

    View Slide

  9. … or any tests at all.

    View Slide

  10. QA processes are faulty.

    View Slide

  11. Bugs will still get into
    production no matter what.

    View Slide

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

    View Slide

  13. “It works for me. #wontfix”
    - Me

    View Slide

  14. How can we be proactive
    about this?

    View Slide

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

    View Slide

  16. window.onerror

    View Slide

  17. window.onerror
    onNOPE

    View Slide

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

    View Slide

  19. function(message,url,lineNumber)

    View Slide

  20. In reality: “Script error.”

    View Slide

  21. No real Error object.

    View Slide

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

    View Slide

  23. BUT…

    View Slide

  24. Your code is probably
    minified.

    View Slide

  25. Line 1 is your entire
    application.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. new Error('lol')

    View Slide

  32. What is better about an
    Error object?

    View Slide

  33. A stack trace!

    View Slide

  34. A stack trace!
    … if you’re lucky.

    View Slide

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

    View Slide

  36. stack trace or gtfo

    View Slide

  37. $ node

    View Slide

  38. $ node
    > throw new Error('lol')

    View Slide

  39. 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)

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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

    View Slide

  45. 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

    View Slide

  46. 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

    View Slide

  47. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. $ 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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. $ 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)

    View Slide

  57. source maps

    View Slide

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

    View Slide

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

    View Slide

  60. Translates
    symbol “n”
    symbol “showThing”

    View Slide

  61. A source map requires 3
    things to be useful.

    View Slide

  62. 1. Filename
    2. Line number
    3. Column number

    View Slide

  63. Column numbers are rare.

    View Slide

  64. window.onerror only has
    line number.

    View Slide

  65. FireFox only has line
    number.
    [email protected]://localhost:8000/awesome.js:9

    View Slide

  66. Internet Explorer… lol
    * exercise for the reader

    View Slide

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

    View Slide

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

    View Slide

  69. Error.prepareStackTrace

    View Slide

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

    View Slide

  71. … or not parse them at all.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  75. try…catch

    View Slide

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

    View Slide

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

    View Slide

  78. But this approach is not
    very scalable.

    View Slide

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

    View Slide

  80. WRAP ALL THE THINGS!

    View Slide

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

    View Slide

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

    View Slide

  83. Meet Sentry + raven.js
    getsentry.com

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  88. View Slide

  89. Please make this better!

    View Slide

  90. Exception handling
    should be simple.

    View Slide

  91. I want to help.

    View Slide

  92. 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

    View Slide

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

    View Slide