From 2188b5a6c0ded29cf3d0a349bddbc56c5d5ad4c2 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Wed, 15 Jan 2014 23:26:18 -0600 Subject: [PATCH 01/15] chore(logging): add logging via Winston and wrapped it with a custom logging module --- .gitignore | 2 ++ package.json | 3 ++- src/logging.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/logging.js diff --git a/.gitignore b/.gitignore index bb5ec66ebf..4a6eb3925d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ newrelic_agent.log .bower-tmp .bower-registry .bower-cache + +*.log src/*/*.map src/*/*/*.map test/*.js diff --git a/package.json b/package.json index 338485de34..ac9ffa0831 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "passport": "~0.1.18", "passport-facebook": "~1.0.2", "newrelic": "~1.3.0", - "connect-ratelimit": "0.0.6" + "connect-ratelimit": "0.0.6", + "winston": "~0.7.2" }, "private": true, "subdomain": "habitrpg", diff --git a/src/logging.js b/src/logging.js new file mode 100644 index 0000000000..32ff5ef883 --- /dev/null +++ b/src/logging.js @@ -0,0 +1,34 @@ +var nconf = require('nconf'); +var winston = require('winston'); + +var logger; +if (logger == null) { + // We currently don't support logging on Heroku + if (nconf.get('NODE_ENV') != 'production') { + logger = new (winston.Logger)({ + transports: [ + new (winston.transports.Console)({colorize: true}), + new (winston.transports.File)({ filename: 'habitrpg.log' }) + // TODO: Add email, loggly, or mongodb transports + ] + }); + } +} + +// A custom log function that wraps Winston. Makes it easy to instrument code +// and still possible to replace Winston in the future. +module.exports.log = function(/* variable args */) { + logger.log.apply(logger, arguments); +}; + +module.exports.info = function(/* variable args */) { + logger.info.apply(logger, arguments); +}; + +module.exports.warn = function(/* variable args */) { + logger.warn.apply(logger, arguments); +}; + +module.exports.error = function(/* variable args */) { + winston.error(arguments); +}; From 441c3cf1d74ce990322998231acf339385f728b6 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Sat, 1 Feb 2014 02:00:49 -0600 Subject: [PATCH 02/15] chore(logging): ignore *.log when running nodemon to avoid loop --- .nodemonignore | 1 + Gruntfile.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.nodemonignore b/.nodemonignore index f4e0b53926..d104457dfc 100644 --- a/.nodemonignore +++ b/.nodemonignore @@ -6,4 +6,5 @@ Gruntfile.js CHANGELOG.md .idea* .git* +*.log newrelic_agent.log diff --git a/Gruntfile.js b/Gruntfile.js index e5fa1ed3ed..0d7247aa83 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -78,7 +78,7 @@ module.exports = function(grunt) { nodemon: { dev: { - ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*'] + ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*', '*.log'] } }, From 4494600d92875a479426deb87ef40b5d33943271 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Sat, 1 Feb 2014 02:01:19 -0600 Subject: [PATCH 03/15] chore(logging): replace console.* with new logging module --- src/controllers/challenges.js | 3 ++- src/controllers/user.js | 5 +++-- src/middleware.js | 21 +++++++++++---------- src/routes/apiv1.js | 5 +++-- src/seed.js | 5 +++-- src/server.js | 9 +++++---- src/utils.js | 11 ++++++----- 7 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js index bb1eff241d..4dd06e87ac 100644 --- a/src/controllers/challenges.js +++ b/src/controllers/challenges.js @@ -7,6 +7,7 @@ var shared = require('habitrpg-shared'); var User = require('./../models/user').model; var Group = require('./../models/group').model; var Challenge = require('./../models/challenge').model; +var logging = require('./../logging'); var csv = require('express-csv'); var api = module.exports; @@ -213,7 +214,7 @@ api.update = function(req, res){ // Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers if (before.isOutdated(req.body)) { User.find({_id: {$in: saved.members}}, function(err, users){ - console.log('Challenge updated, sync to subscribers'); + logging.info('Challenge updated, sync to subscribers'); if (err) throw err; _.each(users, function(user){ saved.syncToUser(user); diff --git a/src/controllers/user.js b/src/controllers/user.js index f0f1f72cbe..a44f5937da 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -12,6 +12,7 @@ var sanitize = validator.sanitize; var User = require('./../models/user').model; var Group = require('./../models/group').model; var Challenge = require('./../models/challenge').model; +var logging = require('./../logging'); var acceptablePUTPaths; var api = module.exports; @@ -349,7 +350,7 @@ api.buyGemsPaypalIPN = function(req, res, next) { user.balance += 5; //user.purchased.ads = true; user.save(); - console.log('PayPal transaction completed and user updated'); + logging.info('PayPal transaction completed and user updated'); }); } }); @@ -518,4 +519,4 @@ api.batchUpdate = function(req, res, next) { res.json(200, {_v: response._v}); } }); -}; \ No newline at end of file +}; diff --git a/src/middleware.js b/src/middleware.js index 19ada70aaf..781b6f7202 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -4,6 +4,7 @@ var fs = require('fs'); var path = require('path'); var User = require('./models/user').model var limiter = require('connect-ratelimit'); +var logging = require('./logging'); module.exports.apiThrottle = function(app) { app.use(limiter({ @@ -16,7 +17,7 @@ module.exports.apiThrottle = function(app) { } } })).use(function(req,res,next){ - //console.log(res.ratelimit); + //logging.info(res.ratelimit); if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'}); next(); }); @@ -92,15 +93,15 @@ var getManifestFiles = function(page){ var css = ''; _.each(files.css, function(file){ - css += ''; + css += ''; }); if(nconf.get('NODE_ENV') === 'production'){ - return css + ''; + return css + ''; }else{ var results = css; _.each(files.js, function(file){ - results += ''; + results += ''; }); return results; } @@ -184,16 +185,16 @@ var getUserLanguage = function(req, callback){ } }); }else{ - return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()})); + return callback(null, _.find(avalaibleLanguages, {code: getFromBrowser()})); } } module.exports.locals = function(req, res, next) { getUserLanguage(req, function(err, language){ - if(err) return res.json(500, {err: err}); + if(err) return res.json(500, {err: err}); language.momentLang = (momentLangs[language.code] || undefined); - + res.locals.habitrpg = { NODE_ENV: nconf.get('NODE_ENV'), BASE_URL: nconf.get('BASE_URL'), @@ -209,11 +210,11 @@ module.exports.locals = function(req, res, next) { var string = translations[language.code][stringName]; if(!string) return _.template(translations[language.code].stringNotFound, {string: stringName}); - return vars === undefined ? string : _.template(string, vars); + return vars === undefined ? string : _.template(string, vars); }, siteVersion: siteVersion } - next(); + next(); }); -} \ No newline at end of file +} diff --git a/src/routes/apiv1.js b/src/routes/apiv1.js index 53207631d2..ce09e8a24a 100644 --- a/src/routes/apiv1.js +++ b/src/routes/apiv1.js @@ -6,6 +6,7 @@ var icalendar = require('icalendar'); var api = require('./../controllers/user'); var auth = require('./../controllers/auth'); var middleware = require('../middleware'); +var logging = require('./../logging'); /* ---------- Deprecated API ------------*/ @@ -78,7 +79,7 @@ var batchUpdate = function(req, res, next) { req.body = action.data; res.send = res.json = function(code, data) { if (_.isNumber(code) && code >= 400) { - console.error({ + logging.error({ code: code, data: data }); @@ -168,4 +169,4 @@ router.get('*', deprecated); router.post('*', deprecated); router.put('*', deprecated); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/src/seed.js b/src/seed.js index becd3cf94b..111dfee1df 100644 --- a/src/seed.js +++ b/src/seed.js @@ -1,6 +1,7 @@ require('coffee-script') // for habitrpg-shared var nconf = require('nconf'); var utils = require('./utils'); +var logging = require('./logging'); utils.setupConfig(); var async = require('async'); var mongoose = require('mongoose'); @@ -15,7 +16,7 @@ async.waterfall([ Group.findById('habitrpg', cb); }, function(tavern, cb){ - console.log({tavern:tavern,cb:cb}); + logging.info({tavern:tavern,cb:cb}); if (!tavern) { tavern = new Group({ _id: 'habitrpg', @@ -31,6 +32,6 @@ async.waterfall([ } ],function(err){ if (err) throw err; - console.log("Done initializing database"); + logging.info("Done initializing database"); mongoose.disconnect(); }) diff --git a/src/server.js b/src/server.js index 8d95852ad0..d428beae8f 100644 --- a/src/server.js +++ b/src/server.js @@ -3,6 +3,7 @@ var cluster = require("cluster"); var _ = require('lodash'); var nconf = require('nconf'); var utils = require('./utils'); +var logging = require('./logging'); utils.setupConfig(); var isProd = nconf.get('NODE_ENV') === 'production'; @@ -16,7 +17,7 @@ if (cluster.isMaster && (isDev || isProd)) { cluster.on('exit', function(worker, code, signal) { var w = cluster.fork(); // replace the dead worker - console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); + logging.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); }); } else { @@ -40,7 +41,7 @@ if (cluster.isMaster && (isDev || isProd)) { require('./models/challenge'); mongoose.connect(nconf.get('NODE_DB_URI'), {auto_reconnect:true}, function(err) { if (err) throw err; - console.info('Connected with Mongoose'); + logging.info('Connected with Mongoose'); }); @@ -141,8 +142,8 @@ if (cluster.isMaster && (isDev || isProd)) { server.on('request', app); server.listen(app.get("port"), function() { - return console.log("Express server listening on port " + app.get("port")); + return logging.info("Express server listening on port " + app.get("port")); }); module.exports = server; -} \ No newline at end of file +} diff --git a/src/utils.js b/src/utils.js index eacb9e7c59..f4020694d6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ var nodemailer = require('nodemailer'); var nconf = require('nconf'); var crypto = require('crypto'); var path = require("path"); +var logging = require('./logging'); module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport("SMTP",{ @@ -12,8 +13,8 @@ module.exports.sendEmail = function(mailData) { } }); smtpTransport.sendMail(mailData, function(error, response){ - if(error) console.log(error); - else console.log("Message sent: " + response.message); + if(error) logging.error(error); + else logging.info("Message sent: " + response.message); smtpTransport.close(); // shut down the connection pool, no more messages }); } @@ -48,7 +49,7 @@ module.exports.setupConfig = function(){ // // * https://developers.google.com/chrome-developer-tools/docs/heap-profiling // // * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101 // agent = require('webkit-devtools-agent'); -// console.log("To debug memory leaks:" + +// logging.info("To debug memory leaks:" + // "\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" + // "\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0"); // } @@ -75,9 +76,9 @@ module.exports.errorHandler = function(err, req, res, next) { subject: "HabitRPG Error", text: stack }); - console.error(stack); + logging.error(stack); var message = err.message ? err.message : err; message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); res.json(500,{err:message}); //res.end(err.message); process.exit(0); -} \ No newline at end of file +} From ae847facda284658db0da87536f773d141e41a35 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Sat, 1 Feb 2014 02:08:32 -0600 Subject: [PATCH 04/15] chore(logging): fix logging.error and make sure logger exists --- src/logging.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/logging.js b/src/logging.js index 32ff5ef883..9de74ebc72 100644 --- a/src/logging.js +++ b/src/logging.js @@ -18,17 +18,21 @@ if (logger == null) { // A custom log function that wraps Winston. Makes it easy to instrument code // and still possible to replace Winston in the future. module.exports.log = function(/* variable args */) { - logger.log.apply(logger, arguments); + if (logger) + logger.log.apply(logger, arguments); }; module.exports.info = function(/* variable args */) { - logger.info.apply(logger, arguments); + if (logger) + logger.info.apply(logger, arguments); }; module.exports.warn = function(/* variable args */) { - logger.warn.apply(logger, arguments); + if (logger) + logger.warn.apply(logger, arguments); }; module.exports.error = function(/* variable args */) { - winston.error(arguments); + if (logger) + logger.error.apply(logger, arguments); }; From 71f4371d9cf5170c110115f919dc51a7efbc51ac Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Sat, 1 Feb 2014 02:46:22 -0600 Subject: [PATCH 05/15] chore(logging): add Newrelic and Email transports for Winston This will send an email on errors logged with winston and thus removes the old error emailing code. --- config.json.example | 3 +++ package.json | 4 +++- src/logging.js | 18 ++++++++++++++++-- src/server.js | 3 +-- src/utils.js | 16 ++++++---------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/config.json.example b/config.json.example index 634d22c7a7..f63a71703e 100644 --- a/config.json.example +++ b/config.json.example @@ -11,6 +11,9 @@ "SMTP_USER":"user@domain.com", "SMTP_PASS":"password", "SMTP_SERVICE":"Gmail", + "SMTP_HOST":"smtp.gmail.com", + "SMTP_PORT": 587, + "SMTP_TLS": true, "STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111", "STRIPE_PUB_KEY":"22223333444455556666777788889999", "PAYPAL_MERCHANT":"paypal-merchant@gmail.com", diff --git a/package.json b/package.json index ac9ffa0831..172b6e48c9 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "passport-facebook": "~1.0.2", "newrelic": "~1.3.0", "connect-ratelimit": "0.0.6", - "winston": "~0.7.2" + "winston": "~0.7.2", + "winston-mail": "~0.2.7", + "winston-newrelic": "~0.1.4" }, "private": true, "subdomain": "habitrpg", diff --git a/src/logging.js b/src/logging.js index 9de74ebc72..d54bedfa3e 100644 --- a/src/logging.js +++ b/src/logging.js @@ -1,10 +1,11 @@ var nconf = require('nconf'); var winston = require('winston'); +require('winston-mail').Mail; +require('winston-newrelic'); var logger; + if (logger == null) { - // We currently don't support logging on Heroku - if (nconf.get('NODE_ENV') != 'production') { logger = new (winston.Logger)({ transports: [ new (winston.transports.Console)({colorize: true}), @@ -12,6 +13,19 @@ if (logger == null) { // TODO: Add email, loggly, or mongodb transports ] }); + if (nconf.get('NODE_ENV') == 'production') { + logger.add(winston.transport.newrelic, {}); + logger.add(winston.transports.Mail, { + to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), + from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", + subject: "HabitRPG Error", + host: nconf.get('SMTP_HOST'), + port: nconf.get('SMTP_PORT'), + tls: nconf.get('SMTP_TLS'), + username: nconf.get('SMTP_USER'), + password: nconf.get('SMTP_PASS'), + level: 'error' + }); } } diff --git a/src/server.js b/src/server.js index d428beae8f..728da524ed 100644 --- a/src/server.js +++ b/src/server.js @@ -3,9 +3,8 @@ var cluster = require("cluster"); var _ = require('lodash'); var nconf = require('nconf'); var utils = require('./utils'); -var logging = require('./logging'); utils.setupConfig(); - +var logging = require('./logging'); var isProd = nconf.get('NODE_ENV') === 'production'; var isDev = nconf.get('NODE_ENV') === 'development'; diff --git a/src/utils.js b/src/utils.js index f4020694d6..0fa0bcc68f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,17 +2,17 @@ var nodemailer = require('nodemailer'); var nconf = require('nconf'); var crypto = require('crypto'); var path = require("path"); -var logging = require('./logging'); module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport("SMTP",{ - service: nconf.get('SMTP_SERVICE'), + service: nconf.get('SMTP_SERVICE'), auth: { - user: nconf.get('SMTP_USER'), - pass: nconf.get('SMTP_PASS') + user: nconf.get('SMTP_USER'), + pass: nconf.get('SMTP_PASS') } }); smtpTransport.sendMail(mailData, function(error, response){ + var logging = require('./logging'); if(error) logging.error(error); else logging.info("Message sent: " + response.message); smtpTransport.close(); // shut down the connection pool, no more messages @@ -49,6 +49,7 @@ module.exports.setupConfig = function(){ // // * https://developers.google.com/chrome-developer-tools/docs/heap-profiling // // * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101 // agent = require('webkit-devtools-agent'); +// var logging = require('./logging'); // logging.info("To debug memory leaks:" + // "\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" + // "\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0"); @@ -70,12 +71,7 @@ module.exports.errorHandler = function(err, req, res, next) { "\n\nheaders: " + JSON.stringify(req.headers) + "\n\nbody: " + JSON.stringify(req.body) + (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); - module.exports.sendEmail({ - from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", - to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), - subject: "HabitRPG Error", - text: stack - }); + var logging = require('./logging'); logging.error(stack); var message = err.message ? err.message : err; message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); From d581986dd9fc041951c1f390f846eba278c8641c Mon Sep 17 00:00:00 2001 From: deilann Date: Sat, 1 Feb 2014 13:14:51 -0800 Subject: [PATCH 06/15] changing order of stats in character build --- views/options/profile.jade | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/views/options/profile.jade b/views/options/profile.jade index fb0ae74794..15986489a1 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -160,22 +160,23 @@ script(id='partials/options.profile.stats.html', type='text/ng-template') i.icon-download.icon-white   =env.t('distributePoints') - tr - td= env.t('allocateStr') + ' {{user.stats.str}}' - td - a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) + tr td= env.t('allocateCon') + ' {{user.stats.con}}' td a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateConPop')) + + tr + td= env.t('allocateInt') + ' {{user.stats.int}}' + td + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) + tr td= env.t('allocatePer') + ' {{user.stats.per}}' td a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocatePerPop')) + tr - td= env.t('allocateInt') + ' {{user.stats.int}}' + td= env.t('allocateStr') + ' {{user.stats.str}}' td - a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateIntPop')) + + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")', popover-trigger='mouseenter', popover-placement='right', popover=env.t('allocateStrPop')) + + div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "span4" : "span6"') include ../shared/profiles/achievements From dc7a6175e35a479ea95c3de45973d4ebb207455c Mon Sep 17 00:00:00 2001 From: deilann Date: Sat, 1 Feb 2014 19:05:25 -0800 Subject: [PATCH 07/15] correcting quest abort message --- public/js/controllers/groupsCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index ddb36c824f..6488ef6e90 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -383,7 +383,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } $scope.questAbort = function(){ - if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, and you'll lose your quest scroll.")) return; + if (!confirm("Are you sure you want to abort this mission? It will abort it for everyone in your party, all progress will be lost.")) return; if (!confirm("Are you double sure? Make sure they won't hate you forever!")) return; $rootScope.party.$questAbort(); } From 2e3ee3f97cbc32dabe4329335a97c9efde09aeeb Mon Sep 17 00:00:00 2001 From: deilann Date: Sun, 2 Feb 2014 13:22:24 -0800 Subject: [PATCH 08/15] removes progress bar of unfeedable pets --- views/options/inventory/stable.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/options/inventory/stable.jade b/views/options/inventory/stable.jade index 328be90ebb..d85fc484fc 100644 --- a/views/options/inventory/stable.jade +++ b/views/options/inventory/stable.jade @@ -54,7 +54,7 @@ script(type='text/ng-template', id='partials/options.inventory.pets.html') menu div(ng-repeat='potion in Content.hatchingPotions', popover-trigger='mouseenter', popover='{{potion.text}} {{egg.text}}', popover-placement='bottom', ng-init='pet = egg.key+"-"+potion.key') button(class="pet-button Pet-{{pet}}", ng-if='user.items.pets[pet]>0', ng-class='{active: user.items.currentPet == pet, selectableInventory: selectedFood}', ng-click='choosePet(egg.key, potion.key)') - .progress(ng-class='{"progress-success": user.items.pets[pet]<50}') + .progress(ng-show='!user.items.mounts[pet]', ng-class='{"progress-success": user.items.pets[pet]<50}') .bar(style="width: {{user.items.pets[pet]/.5}}%;") button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]') .PixelPaw From e99118b6a77aa9ea220967120e59d90ba82cb7f8 Mon Sep 17 00:00:00 2001 From: Lorian Date: Sun, 2 Feb 2014 16:35:36 -0800 Subject: [PATCH 09/15] Edited appliedTags call to actually call the correct function; now hovering over a task's tag icon displays a list of that task's tags. --- views/shared/tasks/task.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade index 5fd8d64baa..23d0855cfd 100644 --- a/views/shared/tasks/task.jade +++ b/views/shared/tasks/task.jade @@ -15,7 +15,8 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s a.badge(ng-if='task.checklist[0]', ng-class='{"badge-success":checklistCompletion(task.checklist) == task.checklist.length}', ng-click='collapseChecklist(task)', style='margin:1px') {{checklistCompletion(task.checklist)}}/{{task.checklist.length}} - i.icon-tags(tooltip='{{appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)') + // tags + i.icon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)') // edit a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit')) i.icon-pencil(ng-hide='task._editing') From 4f911a68d805742e6744383948eea6f224f2b0ea Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Sun, 2 Feb 2014 21:19:01 -0600 Subject: [PATCH 10/15] fix(buffs): Move help bubble to left of special buffs Corrects an issue where the popover text for Stealth and Chilling Frost inherited the "strong" formatting of the buff name label, and improves visual consistency with the attribute readout above them. --- views/shared/profiles/stats.jade | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/views/shared/profiles/stats.jade b/views/shared/profiles/stats.jade index ea9a921c01..51606f2cbb 100644 --- a/views/shared/profiles/stats.jade +++ b/views/shared/profiles/stats.jade @@ -63,15 +63,16 @@ table.table.table-striped |: {{profile.stats.buffs[k] || 0}}  tr(ng-if='profile.stats.buffs.stealth') td + i.icon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay')) +   strong=env.t('stealth') - |: {{profile.stats.buffs.stealth}}  - i.icon-question-sign(popover-title=env.t('stealth'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('stealthNewDay')) + |: {{profile.stats.buffs.stealth}} td tr(ng-if='profile.stats.buffs.streaks') td + i.icon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText')) +   strong=env.t('streaksFrozen') - | - i.icon-question-sign(popover-title=env.t('streaksFrozen'), popover-trigger='mouseenter', popover-placement='right', popover=env.t('streaksFrozenText')) td h4(ng-show='user.flags.dropsEnabled')=env.t('pets') From 85eb0aa38a472633d32aa00bbe6202c0efa03faa Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Sun, 2 Feb 2014 17:41:29 -0600 Subject: [PATCH 11/15] chore(cluster): disconnect worker on error, then kill after 30 sec (cc @lefnire): This disconnects the process, sets up a kill timer, and then sends error emails and responses back to the client. This should stop new clients from connecting to the dead worker, allow connections already established to finish, and cause the master to fork a new worker to pick up the slack immediately. --- src/server.js | 7 ++++--- src/utils.js | 21 +++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/server.js b/src/server.js index 8d95852ad0..54971e719e 100644 --- a/src/server.js +++ b/src/server.js @@ -12,9 +12,9 @@ if (cluster.isMaster && (isDev || isProd)) { // Fork workers. _.times(require('os').cpus().length, function(){ cluster.fork(); - }) + }); - cluster.on('exit', function(worker, code, signal) { + cluster.on('disconnect', function(worker, code, signal) { var w = cluster.fork(); // replace the dead worker console.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); }); @@ -135,6 +135,7 @@ if (cluster.isMaster && (isDev || isProd)) { app.use('/api/v1', require('./routes/apiv1').middleware); app.use('/export', require('./routes/dataexport').middleware); + app.use(utils.crashWorker(server)); app.use(utils.errorHandler); require('./routes/apiv2.coffee')(swagger, v2); @@ -145,4 +146,4 @@ if (cluster.isMaster && (isDev || isProd)) { }); module.exports = server; -} \ No newline at end of file +} diff --git a/src/utils.js b/src/utils.js index eacb9e7c59..dc99aafd23 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ var nodemailer = require('nodemailer'); var nconf = require('nconf'); var crypto = require('crypto'); var path = require("path"); +var cluster = require("cluster"); module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport("SMTP",{ @@ -59,6 +60,23 @@ module.exports.setupConfig = function(){ if (nconf.get('NODE_ENV') === 'production') require('newrelic'); }; +module.exports.crashWorker = function(server) { + return function(err, req, res, next) { + if (!cluster.isMaster) { + // make sure we close down within 30 seconds + var killtimer = setTimeout(function() { + process.exit(1); + }, 30000); + // But don't keep the process open just for that! + killtimer.unref(); + // stop taking new requests. + server.close(); + cluster.worker.disconnect(); + } + next(err); + }; +} + module.exports.errorHandler = function(err, req, res, next) { // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER) @@ -79,5 +97,4 @@ module.exports.errorHandler = function(err, req, res, next) { var message = err.message ? err.message : err; message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); res.json(500,{err:message}); //res.end(err.message); - process.exit(0); -} \ No newline at end of file +} From e966cc956a55955fec65ce639334c0728bad8702 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Mon, 3 Feb 2014 00:13:51 -0600 Subject: [PATCH 12/15] chore(logging): only log to console and file on dev (cc @lefnire) --- src/logging.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/logging.js b/src/logging.js index d54bedfa3e..f519370c07 100644 --- a/src/logging.js +++ b/src/logging.js @@ -6,15 +6,9 @@ require('winston-newrelic'); var logger; if (logger == null) { - logger = new (winston.Logger)({ - transports: [ - new (winston.transports.Console)({colorize: true}), - new (winston.transports.File)({ filename: 'habitrpg.log' }) - // TODO: Add email, loggly, or mongodb transports - ] - }); + logger = new (winston.Logger)({}); if (nconf.get('NODE_ENV') == 'production') { - logger.add(winston.transport.newrelic, {}); + logger.add(winston.transport.newrelic, {}); logger.add(winston.transports.Mail, { to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", @@ -26,6 +20,9 @@ if (logger == null) { password: nconf.get('SMTP_PASS'), level: 'error' }); + } else { + logger.add(winston.transports.Console, {colorize:true}); + logger.add(winston.transports.File, {filename: 'habitrpg.log'}); } } From f8796e9028d4f4cd2b5c5ede1734d2876d174dc9 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 2 Feb 2014 23:17:30 -0700 Subject: [PATCH 13/15] feat(winter): remove purchasable winter hair colors, keep available if they purchased during event --- views/options/profile.jade | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/views/options/profile.jade b/views/options/profile.jade index 15986489a1..c238ea79ec 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -30,22 +30,15 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') | 2 / = ' ' + env.t('locked') menu(type='list') + // For special events code, see commit dfa27b3 + // Color li.customize-menu menu(label=env.t('color')) each v,k in {'c8c8c8':'white','903a00':'brown','cfb853':'blond','ec720f':'red','2e2e2e':'black'} button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:##{k};', ng-click='set({"preferences.hair.color": "#{v}"})') - - // Special Events - li.customize-menu.well.limited-edition - .label.label-info.pull-right(popover=env.t('limited31Jan'), popover-title=env.t('limitedEdition'), popover-placement='right', popover-trigger='mouseenter') - =env.t('limitedEdition') - | - i.icon.icon-question-sign - menu(label=env.t('winterColors')) each color in ['candycane','frost','winternight','holly'] - button(type='button', ng-class='{locked: !user.purchased.hair.color.#{color}}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")') - button.btn.btn-small.btn-primary(ng-hide='user.purchased.hair.color.candycane && user.purchased.hair.color.frost && user.purchased.hair.color.winternight && user.purchased.hair.color.holly', ng-click='unlock("hair.color.candycane,hair.color.frost,hair.color.winternight,hair.color.holly")')!= env.t('unlockSet5') + ' ' + button(type='button', ng-if='user.purchased.hair.color.#{color}', class='customize-option hair_bangs_1_#{color}', style='width: 40px; height: 40px;', ng-click='unlock("hair.color.#{color}")') h5=env.t('bodyHair') // Bangs From 83b3739f4671a08466e057242f936140d5c739ef Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 2 Feb 2014 23:25:00 -0700 Subject: [PATCH 14/15] fix(winston): typo --- src/logging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging.js b/src/logging.js index f519370c07..f8e56da7cf 100644 --- a/src/logging.js +++ b/src/logging.js @@ -8,7 +8,7 @@ var logger; if (logger == null) { logger = new (winston.Logger)({}); if (nconf.get('NODE_ENV') == 'production') { - logger.add(winston.transport.newrelic, {}); + logger.add(winston.transports.newrelic, {}); logger.add(winston.transports.Mail, { to: nconf.get('ADMIN_EMAIL') || nconf.get('SMTP_USER'), from: "HabitRPG <" + nconf.get('SMTP_USER') + ">", From e3b5a3978ab1eeeec1b73132ab9a220e9d1eee65 Mon Sep 17 00:00:00 2001 From: Cole Gleason Date: Mon, 3 Feb 2014 01:25:54 -0600 Subject: [PATCH 15/15] chore(logging): change disconnect message level from error to info --- src/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.js b/src/server.js index 5afb0fd248..aa734c7d3a 100644 --- a/src/server.js +++ b/src/server.js @@ -16,7 +16,7 @@ if (cluster.isMaster && (isDev || isProd)) { cluster.on('disconnect', function(worker, code, signal) { var w = cluster.fork(); // replace the dead worker - logging.error('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); + logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); }); } else {