├── .travis.yml ├── example ├── basic.js ├── basic2.js └── advanced.js ├── .gitignore ├── test └── test.js ├── package.json ├── README.md └── lib └── crashreporter.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.5" 4 | -------------------------------------------------------------------------------- /example/basic.js: -------------------------------------------------------------------------------- 1 | require('../lib/crashreporter'); 2 | throw new Error('foo'); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.gz 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | lib-cov 9 | logs 10 | node_modules 11 | npm-debug.log 12 | pids 13 | results 14 | -------------------------------------------------------------------------------- /example/basic2.js: -------------------------------------------------------------------------------- 1 | require('../lib/crashreporter').configure({ 2 | outDir: (__dirname + '/../bin/crash'), 3 | exitOnCrash: true, 4 | maxCrashFile: 3 5 | }); 6 | throw new Error('foo'); 7 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var 2 | assert = require('assert'), 3 | crashreporter = require('../lib/crashreporter'); 4 | 5 | describe('CrashReporter', function(){ 6 | describe('When creating a CrashReporter with no param', function(){ 7 | it('should connect to uncaughtException event', function(){ 8 | assert.deepEqual(process.listeners('uncaughtException').length, 2); 9 | }); 10 | }); 11 | }); -------------------------------------------------------------------------------- /example/advanced.js: -------------------------------------------------------------------------------- 1 | require('../lib/crashreporter').configure({ 2 | mailEnabled: true, 3 | mailTransportName: 'SMTP', 4 | mailTransportConfig: { 5 | service: 'Gmail', 6 | auth: { 7 | user: "yourmail@gmail.com", 8 | pass: "yourpass" 9 | } 10 | }, 11 | mailSubject: 'advanced.js crashreporter test', 12 | mailFrom: 'crashreporter ', 13 | mailTo: 'yourmail@gmail.com' 14 | }); 15 | throw new Error('foo'); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crashreporter", 3 | "version": "1.1.0", 4 | "description": "Dump node context into a file on process crash, send it by mail", 5 | "directories": { 6 | "example": "example", 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "mkdirp": "^0.5.1", 11 | "nodemailer": "^2.5.0" 12 | }, 13 | "devDependencies": { 14 | "mocha": "x.x.x" 15 | }, 16 | "scripts": { 17 | "test": "mocha" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+ssh://git@github.com/sdolard/node-crashreporter.git" 22 | }, 23 | "author": "sdolard@gmail.com, joaquinicolas", 24 | "license": "BSD-2-Clause", 25 | "bugs": { 26 | "url": "https://github.com/sdolard/node-crashreporter/issues" 27 | }, 28 | "homepage": "https://github.com/sdolard/node-crashreporter", 29 | "main": "lib/crashreporter.js", 30 | "keywords": [ 31 | "crash", 32 | "report", 33 | "mail" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-crashreporter 2 | 3 | node-crashreporter [![Build Status](https://travis-ci.org/sdolard/node-crashreporter.png?branch=master)](https://travis-ci.org/sdolard/node-crashreporter)[![Build Dependency](https://david-dm.org/sdolard/node-crashreporter.png)](https://david-dm.org/sdolard/node-crashreporter) 4 | 5 | Dump node context into a file on process crash, send it by mail 6 | 7 | ## Installing crashreporter 8 | 9 | ```bash 10 | [sudo] npm install crashreporter 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Basic 16 | 17 | ```javascript 18 | require('crashreporter'); 19 | ``` 20 | 21 | ### More 22 | 23 | ```javascript 24 | require('crashreporter').configure({ 25 | outDir: [your out directory], // default to cwd 26 | exitOnCrash: [true|false] // if you want that crash reporter exit(1) for you, default to true, 27 | maxCrashFile: [number] // older files will be removed up, default 5 files are kept 28 | }); 29 | ``` 30 | 31 | ### Advanced: send mail 32 | 33 | See for support 34 | 35 | ```javascript 36 | require('crashreporter').configure({ 37 | mailEnabled: true, 38 | mailTransportName: 'SMTP', 39 | mailTransportConfig: { 40 | service: 'Gmail', 41 | auth: { 42 | user: "yourmail@gmail.com", 43 | pass: "yourpass" 44 | } 45 | }, 46 | mailSubject: 'advanced.js crashreporter test', 47 | mailFrom: 'crashreporter ', 48 | mailTo: 'yourmail@gmail.com' 49 | }); 50 | ``` 51 | 52 | ### Hidden Attributes 53 | 54 | Hidden Attribute hide items in the email. 55 | 56 | ``` 57 | require('crashreporter').configure({ 58 | ... 59 | hiddenAttributes: ['versions', 'error'], 60 | ... 61 | }); 62 | ``` 63 | 64 | #### Options 65 | 66 | * dateTime 67 | * execPath 68 | * argv 69 | * currentDirectory 70 | * env 71 | * currentDirectory 72 | * env 73 | * pid 74 | * gid 75 | * uid 76 | * processTitle 77 | * uptime 78 | * arch 79 | * versions 80 | * memoryUsage 81 | * requireCache 82 | * activeHandle 83 | * activeRequest 84 | * stack 85 | 86 | ## File 87 | 88 | ### Name 89 | 90 | crash_YYYY-MM-DD_HH-mm-ss_zzz_UTC.txt 91 | 92 | ### Dump 93 | 94 | ```bash 95 | Date time: 96 | ----------------------- 97 | execPath: 98 | ----------------------- 99 | argv: 100 | ----------------------- 101 | current directory: 102 | ----------------------- 103 | env: 104 | ----------------------- 105 | gid: 106 | ----------------------- 107 | uid: 108 | ----------------------- 109 | pid: 110 | ----------------------- 111 | process title: 112 | ----------------------- 113 | uptime (sec): 114 | ----------------------- 115 | arch: x 116 | ----------------------- 117 | versions: 118 | ----------------------- 119 | memory usage: 120 | ----------------------- 121 | require.cache: 122 | ----------------------- 123 | active handle: 124 | ----------------------- 125 | active request: 126 | ----------------------- 127 | stack 128 | ``` 129 | -------------------------------------------------------------------------------- /lib/crashreporter.js: -------------------------------------------------------------------------------- 1 | 2 | var 3 | path = require('path'), 4 | util = require('util'), 5 | fs = require('fs'), 6 | mkdirp = require('mkdirp'), 7 | 8 | crashReporterSingleton = (function() { 9 | function CrashReporter(config) { 10 | this.configure(config); 11 | process.on('uncaughtException', this.onUncaughtException.bind(this)); 12 | } 13 | 14 | /** 15 | * 16 | * @param config 17 | * @example : { 18 | mailEnabled: true, 19 | mailTransportName: 'SMTP', 20 | mailTransportConfig: { 21 | service: 'Gmail', 22 | auth: { 23 | user: 'youremail', // Your email id 24 | pass: 'yourpass' // Your password 25 | } 26 | }, 27 | mailSubject: 'advanced.js crashreporter test', 28 | mailFrom: 'crashreporter ', 29 | mailTo: 'to@email.com' 30 | } 31 | */ 32 | CrashReporter.prototype.configure = function(config) { 33 | config = config || {}; 34 | this.outDir = path.normalize(config.outDir || process.cwd()); 35 | this.exitOnCrash = config.exitOnCrash === undefined ? true : config.exitOnCrash; 36 | this.maxCrashFile = config.maxCrashFile > 0 ? config.maxCrashFile : 5; 37 | 38 | this.mailEnabled = config.mailEnabled; 39 | this.mailTransportName = config.mailTransportName; 40 | this.mailTransportConfig = config.mailTransportConfig; 41 | this.mailSubject = config.mailSubject || 'crashreporter'; 42 | this.mailFrom = config.mailFrom; 43 | this.mailTo = config.mailTo; 44 | 45 | this.hiddenAttributes = config.hiddenAttributes || []; 46 | 47 | this.testOutdir(); 48 | }; 49 | 50 | CrashReporter.prototype.checkHiddenAttribute = function (name) { 51 | return this.hiddenAttributes.indexOf(name) !== -1; 52 | } 53 | 54 | CrashReporter.prototype.onUncaughtException = function(err) { 55 | this.error = err.stack || err; 56 | var 57 | now = new Date(), 58 | fileName = path.resolve(util.format('%s/crash_%s-%s-%s_%s-%s-%s_%s_UTC.txt', 59 | this.outDir, 60 | now.getUTCFullYear(), 61 | ("0" + (now.getUTCMonth() + 1)).slice(-2), 62 | ("0" + (now.getUTCDate())).slice(-2), 63 | ("0" + now.getUTCHours()).slice(-2), 64 | ("0" + now.getUTCMinutes()).slice(-2), 65 | ("0" + now.getUTCSeconds()).slice(-2), 66 | ("00" + now.getUTCMilliseconds()).slice(-3))), 67 | sep = '\n-----------------------\n', 68 | content = [], 69 | data; 70 | if (!this.checkHiddenAttribute('dateTime')) content.push('Date time: ' + now); 71 | if (!this.checkHiddenAttribute('execPath')) content.push('execPath: ' + process.execPath); 72 | if (!this.checkHiddenAttribute('argv')) content.push('argv: ' + process.argv.join(', ')); 73 | if (!this.checkHiddenAttribute('currentDirectory')) content.push('current directory: ' + process.cwd()); 74 | if (!this.checkHiddenAttribute('env')) content.push('env:\n' + util.inspect(process.env)); 75 | if (process.platform !== 'win32') { 76 | if (!this.checkHiddenAttribute('gid')) content.push('gid: ' + process.getgid()); 77 | if (!this.checkHiddenAttribute('uid')) content.push('uid: ' + process.getuid()); 78 | } 79 | if (!this.checkHiddenAttribute('pid')) content.push('pid: ' + process.pid); 80 | if (!this.checkHiddenAttribute('processTitle')) content.push('process title: ' + process.title); 81 | if (!this.checkHiddenAttribute('uptime')) content.push('uptime (sec): ' + process.uptime()); 82 | if (!this.checkHiddenAttribute('arch')) content.push('arch: ' + process.arch); 83 | if (!this.checkHiddenAttribute('versions')) content.push('versions:\n' + util.inspect(process.versions)); 84 | if (!this.checkHiddenAttribute('memoryUsage')) content.push('memory usage:\n' + util.inspect(process.memoryUsage())); 85 | if (!this.checkHiddenAttribute('requireCache')) content.push('require.cache:\n' + Object.keys(require.cache).join('\n')); 86 | if (!this.checkHiddenAttribute('activeHandle')) content.push('active handle:\n' + util.inspect(process._getActiveHandles())); 87 | if (!this.checkHiddenAttribute('activeRequest')) content.push('active request:\n' + util.inspect(process._getActiveRequests())); 88 | if (!this.checkHiddenAttribute('stack')) content.push(this.error); 89 | data = content.join(sep); 90 | if (fs.writeFileSync(fileName, data) === undefined) { 91 | console.error('Crash written in: %s', fileName); 92 | } else { 93 | console.error('Failed to write crash in: %s', fileName); 94 | } 95 | 96 | console.error(this.error); 97 | 98 | this.deleteOlderCrashFile(); 99 | 100 | if (!this.sendMail(data)) { // should always be the last call 101 | this.exit(); 102 | } 103 | }; 104 | 105 | CrashReporter.prototype.sendMail = function(plaintextBody) { 106 | if (!this.mailEnabled) { 107 | return false; 108 | } 109 | var 110 | nodemailer = require('nodemailer'), 111 | // create reusable transport method (opens pool of SMTP connections) 112 | mailTransport = nodemailer.createTransport(this.mailTransportConfig), 113 | 114 | // setup e-mail data with unicode symbols 115 | mailOptions = { 116 | from: this.mailFrom, // sender address 117 | to: this.mailTo, // list of receivers 118 | subject: this.mailSubject, // Subject line 119 | text: plaintextBody // plaintext body 120 | }; 121 | 122 | // send mail with defined transport object 123 | mailTransport.sendMail(mailOptions, function(error, response){ 124 | if(error){ 125 | this.crashExit(error); 126 | }else{ 127 | console.error("Message sent: " + response.message); 128 | } 129 | 130 | // if you don't want to use this transport object anymore, uncomment following line 131 | mailTransport.close(); // shut down the connection pool, no more messages 132 | 133 | this.exit(); 134 | }.bind(this)); 135 | return true; 136 | }; 137 | 138 | CrashReporter.prototype.exit = function() { 139 | if (this.exitOnCrash) { 140 | process.nextTick(function() { 141 | process.exit(1); // should exit after all 'uncaughtException' event calls 142 | }); 143 | } 144 | }; 145 | 146 | CrashReporter.prototype.crashExit = function(error, exit) { 147 | exit = exit !== false; 148 | console.log(exit); 149 | console.error('\n-------------------\ncrashreporter Error\n-------------------'); 150 | console.error(error.stack || error); 151 | if (exit) { 152 | process.exit(1); 153 | } 154 | }; 155 | 156 | CrashReporter.prototype.testOutdir = function() { 157 | var 158 | err, 159 | statSync; 160 | 161 | try { 162 | statSync = fs.statSync(this.outDir); 163 | } catch(e) { 164 | if(e.code === 'ENOENT') { 165 | try { 166 | mkdirp.sync(this.outDir); 167 | return; 168 | } catch(er) { 169 | this.crashExit(er); 170 | } 171 | } else { 172 | this.crashExit(e); 173 | } 174 | } 175 | 176 | if (statSync.isDirectory()) { 177 | return; 178 | } 179 | 180 | err = new Error('Invalid outDir: ' + this.outDir); 181 | err.code = 'EINVALIDOUTDIR'; 182 | this.crashExit(err); 183 | }; 184 | 185 | CrashReporter.prototype.deleteOlderCrashFile = function() { 186 | var 187 | files, 188 | crashfiles = [], 189 | crashfilesRe = /^crash_(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})_(\d{3})_UTC.txt$/, 190 | match; 191 | 192 | try { 193 | files = fs.readdirSync(this.outDir); 194 | files.forEach(function(file) { 195 | match = file.match(crashfilesRe); 196 | if (match !== null) { 197 | crashfiles.push({ 198 | file: path.resolve(this.outDir,file), 199 | date : new Date( 200 | match[1],//year 201 | parseInt(match[2], 10) - 1,//month 202 | match[3],//day 203 | match[4],//hour 204 | match[5],//minute 205 | match[6],//second 206 | match[7] //millisecond 207 | ) 208 | }); 209 | } 210 | }, this); 211 | 212 | if (crashfiles.length <= this.maxCrashFile) { 213 | return; 214 | } 215 | 216 | crashfiles = crashfiles.sort(function(a,b){ 217 | if (a.date > b.date) { 218 | return 1; 219 | } 220 | if (a.date < b.date) { 221 | return -1; 222 | } 223 | return 0; 224 | }); 225 | crashfiles = crashfiles.slice(0, crashfiles.length - this.maxCrashFile); 226 | crashfiles.forEach(function(file){ 227 | try { 228 | fs.unlinkSync(file.file); 229 | } catch(e) { 230 | this.crashExit(e, false); 231 | // we continue, cleanup is not essential 232 | } 233 | }); 234 | } catch (err) { 235 | this.crashExit(err, false); 236 | // we continue, cleanup is not essential 237 | } 238 | }; 239 | 240 | if (!crashReporterSingleton) { 241 | exports._singleton = crashReporterSingleton = new CrashReporter(); 242 | } 243 | return crashReporterSingleton; 244 | }()); 245 | 246 | exports.configure = function (config) { 247 | crashReporterSingleton.configure(config); 248 | }; 249 | --------------------------------------------------------------------------------