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

Ce86d68173d477a17396b5e611468f52?s=128

Matt Robenolt

May 28, 2014
Tweet

Transcript

  1. JSConf US May 28th 2014 Matt Robenolt Everything is broken,

    and I don’t know why. Help! Help! Help! Help! Help!
  2. Hello < me irl

  3. Lead Operations Engineer

  4. Core Contributor

  5. If an exception happens in production and nobody sees the

    Chrome debug console, did it really happen?
  6. Post-mortem debugging

  7. Ideally, we’d like to know that something is broken before

    our users complain to us.
  8. Humans are actually pretty bad at writing tests.

  9. … or any tests at all.

  10. QA processes are faulty.

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

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

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

  14. How can we be proactive about this?

  15. We should capture our errors, and log as much information

    automatically so we can debug.
  16. window.onerror

  17. window.onerror onNOPE

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

  19. function(message,url,lineNumber)

  20. In reality: “Script error.”

  21. No real Error object.

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

    lineNumber: 10
  23. BUT…

  24. Your code is probably minified.

  25. Line 1 is your entire application.

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

    etc.
  27. If the script is on another domain, it’s subject to

    CORS. Meaning… “Script error.”
  28. Literally no idea what happened. No message, no url, no

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

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

  31. new Error('lol')

  32. What is better about an Error object?

  33. A stack trace!

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

  35. A stack trace is a record of function calls until

    now within the current call stack.
  36. stack trace or gtfo

  37. $ node

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

  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.<anonymous> (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)
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  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.<anonymous> (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
  48. > typeof new Error('lol').stack

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

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

    v8 stack trace
  51. // awesome.js (function() { ! var things = [ {foo:

    'bar'} ]; ! function showThing(index) { console.log(things[index].foo); } ! showThing(1); ! })();
  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.<anonymous> (/jsconf/awesome.js:14:2)
  53. 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

  54. 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
  55. $ uglifyjs awesome.js -m (function(){var o=[{foo:"bar"}];function n(n) {console.log(o[n].foo)}n(1)})();

  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.<anonymous> (/jsconf/awesome.min.js:1:137)
  57. source maps

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

    the original source.
  59. Translates app.min.js, line 1, col 125 app.js, line 9, col

    30
  60. Translates symbol “n” symbol “showThing”

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

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

  63. Column numbers are rare.

  64. window.onerror only has line number.

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

  66. Internet Explorer… lol * exercise for the reader

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

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

  69. Error.prepareStackTrace

  70. We can tell V8 how to transform raw CallSite objects

    into the Error.prototype.stack property.
  71. … or not parse them at all.

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

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

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

    collect them?
  75. try…catch

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

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

    logError(error) throw error; }
  78. But this approach is not very scalable.

  79. You must predict when your code is going to break.

    If we don’t, it bubbles up to window.onerror.
  80. WRAP ALL THE THINGS!

  81. function wrap(func) { function wrapped() { try { return func.apply(this,

    arguments) } catch(e) { logError(e) throw e } } return wrapped }
  82. wrap(function() { ! var things = [ {foo: 'bar'} ];

    ! function showThing(index) { console.log(things[index].foo); } ! showThing(1); ! })();
  83. Meet Sentry + raven.js getsentry.com

  84. We do all of these terrible things for you so

    you don’t have to.
  85. Raven.js monkey patches native objects and instruments popular libraries.

  86. Raven then parses the Error and reports the information to

    the central Sentry server.
  87. Sentry will fetch your source maps and give you a

    nice clean view of your error.
  88. None
  89. Please make this better!

  90. Exception handling should be simple.

  91. I want to help.

  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

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