├── LICENSE ├── logbook.js.coffee ├── logbook.js └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 David Verhasselt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /logbook.js.coffee: -------------------------------------------------------------------------------- 1 | class window.Logbook 2 | options: 3 | name: "logbook" 4 | initMessage: "" 5 | maxSize: 5000 6 | passThrough: true 7 | 8 | constructor: (options = {}) -> 9 | return unless @supports_html5_storage() 10 | 11 | for key, value of options 12 | @options[key] = value 13 | 14 | @window_onerror_orig = window.onerror 15 | 16 | if window.console && @options.passThrough 17 | @console_orig = window.console 18 | @console_log_orig = window.console.log 19 | @console_error_orig = window.console.error 20 | 21 | window.console ||= {} 22 | window.console.log = @console_log 23 | window.console.error = @console_error 24 | window.onerror = @window_onerror 25 | 26 | @log "\ninitialized", @options.initMessage 27 | 28 | logbook: => 29 | localStorage[@options.name] 30 | 31 | save_logbook: (value) => 32 | localStorage[@options.name] = @truncate_to_size(value) 33 | 34 | log: (action, params...) => 35 | @write action + " @ " + new Date() + ": " + params.join(", ") + "\n" 36 | 37 | write: (line) => 38 | @save_logbook(@logbook() + line) 39 | 40 | console_log: (params...) => 41 | @log "console.log", params... 42 | @console_log_orig.apply(@console_orig, params) if @console_log_orig 43 | 44 | console_error: (params...) => 45 | @log "console.error", params... 46 | @console_error_orig.apply(@console_orig, params) if @console_error_orig 47 | 48 | window_onerror: (message, url, line) => 49 | @log "exception", "#{url} (#{line}): #{message}" 50 | @window_onerror_orig?(message, url, line) 51 | 52 | truncate_to_size: (value) => 53 | lines = value.split "/n" 54 | 55 | while value.length > @options.maxSize 56 | lines.shift() 57 | value = lines.join "/n" 58 | 59 | value 60 | 61 | supports_html5_storage: -> 62 | try 63 | window.localStorage != null 64 | catch e 65 | false 66 | -------------------------------------------------------------------------------- /logbook.js: -------------------------------------------------------------------------------- 1 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 2 | __slice = [].slice; 3 | 4 | window.Logbook = (function() { 5 | Logbook.prototype.options = { 6 | name: "logbook", 7 | initMessage: "", 8 | maxSize: 5000, 9 | passThrough: true 10 | }; 11 | 12 | function Logbook(options) { 13 | var key, value; 14 | if (options == null) { 15 | options = {}; 16 | } 17 | this.truncate_to_size = __bind(this.truncate_to_size, this); 18 | this.window_onerror = __bind(this.window_onerror, this); 19 | this.console_error = __bind(this.console_error, this); 20 | this.console_log = __bind(this.console_log, this); 21 | this.write = __bind(this.write, this); 22 | this.log = __bind(this.log, this); 23 | this.save_logbook = __bind(this.save_logbook, this); 24 | this.logbook = __bind(this.logbook, this); 25 | if (!this.supports_html5_storage()) { 26 | return; 27 | } 28 | for (key in options) { 29 | value = options[key]; 30 | this.options[key] = value; 31 | } 32 | this.window_onerror_orig = window.onerror; 33 | if (window.console && this.options.passThrough) { 34 | this.console_orig = window.console; 35 | this.console_log_orig = window.console.log; 36 | this.console_error_orig = window.console.error; 37 | } 38 | window.console || (window.console = {}); 39 | window.console.log = this.console_log; 40 | window.console.error = this.console_error; 41 | window.onerror = this.window_onerror; 42 | this.log("\ninitialized", this.options.initMessage); 43 | } 44 | 45 | Logbook.prototype.logbook = function() { 46 | return localStorage[this.options.name]; 47 | }; 48 | 49 | Logbook.prototype.save_logbook = function(value) { 50 | return localStorage[this.options.name] = this.truncate_to_size(value); 51 | }; 52 | 53 | Logbook.prototype.log = function() { 54 | var action, params; 55 | action = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 56 | return this.write(action + " @ " + new Date() + ": " + params.join(", ") + "\n"); 57 | }; 58 | 59 | Logbook.prototype.write = function(line) { 60 | return this.save_logbook(this.logbook() + line); 61 | }; 62 | 63 | Logbook.prototype.console_log = function() { 64 | var params; 65 | params = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 66 | this.log.apply(this, ["console.log"].concat(__slice.call(params))); 67 | if (this.console_log_orig) { 68 | return this.console_log_orig.apply(this.console_orig, params); 69 | } 70 | }; 71 | 72 | Logbook.prototype.console_error = function() { 73 | var params; 74 | params = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 75 | this.log.apply(this, ["console.error"].concat(__slice.call(params))); 76 | if (this.console_error_orig) { 77 | return this.console_error_orig.apply(this.console_orig, params); 78 | } 79 | }; 80 | 81 | Logbook.prototype.window_onerror = function(message, url, line) { 82 | this.log("exception", "" + url + " (" + line + "): " + message); 83 | return typeof this.window_onerror_orig === "function" ? this.window_onerror_orig(message, url, line) : void 0; 84 | }; 85 | 86 | Logbook.prototype.truncate_to_size = function(value) { 87 | var lines; 88 | lines = value.split("/n"); 89 | while (value.length > this.options.maxSize) { 90 | lines.shift(); 91 | value = lines.join("/n"); 92 | } 93 | return value; 94 | }; 95 | 96 | Logbook.prototype.supports_html5_storage = function() { 97 | var e; 98 | try { 99 | return window.localStorage !== null; 100 | } catch (_error) { 101 | e = _error; 102 | return false; 103 | } 104 | }; 105 | 106 | return Logbook; 107 | 108 | })(); 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logbook.js 2 | ========== 3 | 4 | Save console.log, console.error and thrown exceptions to localStorage for later use in debugging of customer's sites. 5 | 6 | Description 7 | ----------- 8 | 9 | Logbook.js automatically saves any `console.log` or `console.error` calls as well as any exceptions thrown as a logbook to a LocalStorage object. This way when you're debugging what went wrong for a user, you can easily access the logbook to see what actions the user was doing, or what exceptions were thrown. Here's an example logbook: 10 | 11 | ``` 12 | console.log @ Thu Nov 07 2013 13:51:13 GMT+0200 (EET): Flash installed 13 | 14 | initialized @ Thu Nov 07 2013 13:51:24 GMT+0200 (EET): 15 | console.log @ Thu Nov 07 2013 13:51:24 GMT+0200 (EET): 3 16 | console.log @ Thu Nov 07 2013 13:51:25 GMT+0200 (EET): State now allow_camera_hint 17 | console.log @ Thu Nov 07 2013 13:51:25 GMT+0200 (EET): Flash installed 18 | console.error @ Thu Nov 07 2013 13:52:36 GMT+0200 (EET): BLAAAA 19 | 20 | initialized @ Thu Nov 07 2013 13:53:17 GMT+0200 (EET): 21 | console.log @ Thu Nov 07 2013 13:53:17 GMT+0200 (EET): 3 22 | console.log @ Thu Nov 07 2013 13:53:18 GMT+0200 (EET): State now allow_camera_hint 23 | console.log @ Thu Nov 07 2013 13:53:18 GMT+0200 (EET): Flash installed 24 | exception @ Thu Nov 07 2013 13:53:35 GMT+0200 (EET): http://localhost:3000/assets/application.js?body=1 (45): Uncaught ReferenceError: windows is not defined 25 | console.log @ Thu Nov 07 2013 13:54:05 GMT+0200 (EET): State now showcase 26 | console.log @ Thu Nov 07 2013 13:54:05 GMT+0200 (EET): Selecting , 27 | console.log @ Thu Nov 07 2013 13:54:05 GMT+0200 (EET): Done selecting , 28 | initialized @ Thu Nov 07 2013 13:54:16 GMT+0200 (EET): 29 | exception @ Thu Nov 07 2013 13:55:53 GMT+0200 (EET): http://localhost:3000/assets/application.js?body=1 (45): Uncaught ReferenceError: myObject is not defined 30 | 31 | initialized @ Thu Nov 07 2013 13:56:35 GMT+0200 (EET): http://localhost:3000/photos 32 | ``` 33 | 34 | Every line has an action (e.g. `console.log`), a timestamp, and a message. The message is whatever was passed as parameters. When Logbook is first initialized on a page, it logs an `Initialized` message. When exceptions are thrown, the action is `exception` and the message includes the file URL, line, and exception message. 35 | 36 | Usage 37 | ===== 38 | 39 | Initialization 40 | -------------- 41 | 42 | Run Logbook like this: 43 | 44 | ```javascript 45 | var logbook = new Logbook() 46 | ``` 47 | 48 | You can pass these options: 49 | 50 | * `passThrough`: boolean (default true). Set wether you want the default console.log, .error or exception handler to be called as well. If false, nothing will be logged to the console anymore, good for running in production. 51 | * `initMessage`: string (default blank). Set whatever message you'd like to be logged first as the "initialized" line. A handy use is putting the currently URL here, so you know what page the user was on. 52 | * `maxSize`: integer (default 50000). LocalStorage has a maximum size depending on the browser, but mostly between 2.5MB and 5MB. If you want to keep the log under a certain size because you need LocalStorage for different things as well, set this to however long you want it to get, maximum. Any old lines will be removed until the logbook is less than this size. 53 | * `name`: string (default 'logbook'): The name of the LocalStorage object. 54 | 55 | For example: 56 | 57 | ```javascript 58 | var logbook = new Logbook({maxSize: 10000, initMessage: window.location, passThrough: false}); 59 | ``` 60 | 61 | Writing 62 | ======= 63 | 64 | ```javascript 65 | console.log("A log message", 1, 2, 3) 66 | console.error("An error") 67 | logbook.log("Custom Action", "this is a custom action", "any params work") 68 | ``` 69 | 70 | Reading 71 | ======= 72 | 73 | To read the logbook back out, use `logbook.logbook()`. An example use case is to fill a hidden input on a feedback form with this information. 74 | 75 | ```javascript 76 | document.getElementById("logbook-field").value = logbook.logbook() 77 | ``` 78 | 79 | Author 80 | ====== 81 | 82 | [David Verhasselt](http://davidverhasselt.com) - david@crowdway.com 83 | --------------------------------------------------------------------------------