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

Record.Replay.Reproduce - Dealing with JS errors in modern web apps

Record.Replay.Reproduce - Dealing with JS errors in modern web apps

#Agentconf 2017
http://agent.sh/

Mats Bryntse

January 21, 2017
Tweet

More Decks by Mats Bryntse

Other Decks in Programming

Transcript

  1. Who is Mats Bryntse? • From Stockholm • Working with

    javascript past 10 years • Founder of Bryntum • Scheduler, Gantt & kanban UI components • Testing tools, logging tools • @bryntum • www.bryntum.com !
  2. Javascript error basics • Javascript errors are unhandled exceptions in

    your code base • Or in the frameworks you use • Doesn’t matter where errors happen, poor user impression • With JS codebases in the size of MBs, cannot ignore error handling + logging • Good news - it’s easy
  3. Two scenarios for the end user: • Error is captured

    by you. User notified • Or…… • Nothing happens for user, will probably try same action again. • If you’re lucky, user will contact you ✉ ✊
  4. The ErrorEvent constructor • When an error happens, an ´error´

    event is fired on the window object • ErrorEvent.message • ErrorEvent.filename • ErrorEvent.lineno • ErrorEvent.colno // Normal browsers only • ErrorEvent.error // Contains stack. Normal browsers only
  5. Global error handler // Old school window.onerror = function(message, source,

    lineno, colno, error) { … }; window.addEventListener(‘error’, function(event) { // event.message // event.filename // event.lineno // event.colno // good browsers only // event.error (has stack property, in good browsers) }, true); …or by listening to the window ‘error’ event. Listen with capture=true to also get notified of resource load errors Errors are easily caught in window.onerror
  6. You can throw your own Errors too // Bad, no

    call stack will be available throw ‘My own error’; // preferred throw new Error(‘This will have a call stack’); try { // Code written after drinking beer goes here } catch(e) { // Oops, log error } finally { // always called } • throw a String • Or better, an Error instance (=> callstack) • try/catch/finally
  7. Script error. • For errors originating in foreign origin scripts

    • No stack, no line, no col, no error message. Nothing. Nada. Nichts. • Solution - add a crossOrigin attribute to external script tags • And add a CORS header: Access-Control-Allow-Origin: * <script src=“https://other.io/file.js” crossOrigin="anonymous"></script>
 <script>
 window.onerror = function (message, url, line, column, error) {
 console.log(message); // Script error if you forget crossOrigin.
 }
 </script>
  8. Debug context wish list 1. Error message 2. File /

    line number 3. Call stack 4. Screenshot 5. Step by step description 6. Log of user / browser session activity 7. Seeing the user reproduce the error 8. Live breakpoint in production environment 9. Live breakpoint on my localhost, in my fav browser
  9. Error at Object.module.exports.request (/home/vagrant/src/kumascript/lib/kumascript/caching.js:366:17) at attempt (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:180:24) at ks_utils.Class.get (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:194:9)

    at /home/vagrant/src/kumascript/lib/kumascript/macros.js:282:24 at /home/vagrant/src/kumascript/node_modules/async/lib/async.js:118:13 at Array.forEach (native) at _each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:39:24) at Object.async.each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:117:9) at ks_utils.Class.reloadTemplates (/home/vagrant/src/kumascript/lib/kumascript/macros.js:281:19) at ks_utils.Class.process (/home/vagrant/src/kumascript/lib/kumascript/macros.js:217:15) “A live breakpoint is worth a 1000 callstacks”
  10. Email ping pong - Enterprise version Error in web app

    Reports to own support Your company User realises it’s an error 01010 10110 11110 User Dear User, /Depressed dev. Can’t reproduce, need more info. Sincerely yours,
  11. Roll your own logger • Pros: Simple, get basic error

    info. Awareness • Cons: Lots of code to scan through
  12. Using a 3rd party logger • Pros: Tons of data,

    call stack, console logs, ajax, user activity. Enables you to search for the error • Cons: Slow, tons of data to parse, manual work, code to review
  13. 1. Create single table db • date, message, file, line,

    callstack etc CREATE TABLE `error` ( `msg` char(60), `callstack` char(1000), … ) ENGINE=InnoDB DEFAULT CHARSET=utf8
  14. 2. PHP script to receive error data and store it

    in DB <?php // LOG TO DB
 $link = getLink();
 
 $command = "call insert_error('$msg', ‘$url', ‘$stack’, …); 
 $result = mysqli_query($link, $command);
 

  15. 3. Setup client side logging • Log message, file, line,

    stack etc.. • Add any extra meta relevant for your debugging (userId/name/…) // Poor mans error logger window.onerror = function log(msg) { new Image().src = "log.php?msg=" + encodeURIComponent(msg); } throw new Error("Ooops");
  16. Manual error logging, things to consider • Store error logs

    in a database on a non-production server • Throttle logging on client side + server side • Probably we only care about the first error on a page
  17. Previous error handling at Bryntum • Web site visitors are

    test monkeys unknowingly === free help • Errors logged in a DB • Emails sent to devs = At best, useful for finding simple bugs
  18. Error handling at Bryntum • What we had was pretty

    good, not great • Lots of time spent playing detective, looking at callstacks • Just error message, filename, callstack isn’t enough to rapidly locate root cause • We would like to know more… ,
  19. Function arguments Know how the crashing function was called function

    getUserInfo(id) { var user = this.store.getById(id); // => null return user.getInfo(); // Cannot call getInfo of null } getUserInfo(-1); // crashes, would be neat to know input args
  20. + environment data collected Many things to consider… • OS

    • Browser • Window size • Touch support • Window blur/focus events • Date + Timezone • Language • Failed ajax requests • Cookie state • Network connectivity events
  21. Notifying the affected user • Optional popup for the user

    that triggered the error • Shows status of the error (New, Reproduced, Fixed)
  22. Cuts 99% of communication out • No need for QA

    / end users to email devs with crash reports, step by step • No need for devs to notify QA that bug is fixed
  23. Installing the logger in your web app window.logger = new

    RC.Logger({
 applicationId : 'yourToken',
 recordUserActions : true,
 showFeedbackButton : true,
 logAjaxRequests : true,
 captureScreenshot : true,
 environment : 'prod',
 user : {
 email : '[email protected]',
 name : 'Anton'
 }
 });

  24. Technical details • Recorder: 100% vanilla JS • Screenshots: HTML2Canvas

    • Dashboard: Ext JS • Replay studio powered by Siesta
  25. BETA open at http://app.therootcause.io Summing up: • Fix your external

    script tags. Never see “Script error”. Ever. • Don’t rely on users reporting bugs • Choose an automated error reporting tool (invite code r2d2)