diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 8d534311..2ee10c42 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -3,7 +3,10 @@ "LbryClaimAddress": "LBRY_CLAIM_ADDRESS" }, "Database": { - "username": "MYSQL_USERNAME", - "password": "MYSQL_PASSWORD" + "Username": "MYSQL_USERNAME", + "Password": "MYSQL_PASSWORD" + }, + "Logging": { + "SlackWebHook": "SLACK_WEB_HOOK" } } \ No newline at end of file diff --git a/config/default.json b/config/default.json index 85515097..cdab2d23 100644 --- a/config/default.json +++ b/config/default.json @@ -6,9 +6,9 @@ "GoogleId": "none" }, "Database": { - "database": "lbry", - "username": "none", - "password": "none" + "Database": "lbry", + "Username": "none", + "Password": "none" }, "Logging": { "LogLevel": "none" diff --git a/config/development.json b/config/development.json index e21d54ea..839e5f35 100644 --- a/config/development.json +++ b/config/development.json @@ -9,6 +9,8 @@ "MySqlConnectionUri": "none" }, "Logging": { - "LogLevel": "silly" + "LogLevel": "silly", + "SlackErrorChannel": "#staging_speech-errors", + "SlackInfoChannel": "none" } } \ No newline at end of file diff --git a/config/loggerSetup.js b/config/loggerConfig.js similarity index 84% rename from config/loggerSetup.js rename to config/loggerConfig.js index ffd4c970..4efbbb02 100644 --- a/config/loggerSetup.js +++ b/config/loggerConfig.js @@ -12,6 +12,10 @@ module.exports = (winston, logLevel) => { ], }); + // winston.on('error', (err) => { + // console.log('unhandled exception in winston >> ', err); + // }); + winston.error('Level 0'); winston.warn('Level 1'); winston.info('Level 2'); diff --git a/config/production.json b/config/production.json index 34d9bfca..caa0b328 100644 --- a/config/production.json +++ b/config/production.json @@ -9,6 +9,8 @@ "MySqlConnectionUri": "none" }, "Logging": { - "LogLevel": "verbose" + "LogLevel": "verbose", + "SlackErrorChannel": "#speech-errors", + "SlackInfoChannel": "#speech-logs" } } diff --git a/config/slackLoggerConfig.js b/config/slackLoggerConfig.js new file mode 100644 index 00000000..5f49e001 --- /dev/null +++ b/config/slackLoggerConfig.js @@ -0,0 +1,27 @@ +const config = require('config'); +const SLACK_WEB_HOOK = config.get('Logging.SlackWebHook'); +const SLACK_ERROR_CHANNEL = config.get('Logging.SlackErrorChannel'); +const SLACK_INFO_CHANNEL = config.get('Logging.SlackInfoChannel'); +const winstonSlackWebHook = require('winston-slack-webhook').SlackWebHook; + +module.exports = (winston) => { + // add a transport for errors + winston.add(winstonSlackWebHook, { + name : 'slack-errors-transport', + level : 'error', + webhookUrl: SLACK_WEB_HOOK, + channel : SLACK_ERROR_CHANNEL, + username : 'spee.ch', + iconEmoji : ':face_with_head_bandage:', + }); + winston.add(winstonSlackWebHook, { + name : 'slack-info-transport', + level : 'info', + webhookUrl: SLACK_WEB_HOOK, + channel : SLACK_INFO_CHANNEL, + username : 'spee.ch', + iconEmoji : ':nerd_face:', + }); + // send test message + winston.error('Testing slack logging... slack logging is online.'); +}; diff --git a/controllers/statsController.js b/controllers/statsController.js index 915c9d81..3ee05e77 100644 --- a/controllers/statsController.js +++ b/controllers/statsController.js @@ -35,7 +35,7 @@ module.exports = { }); }) .catch(error => { - logger.error('Sequelize error', error); + logger.error('Sequelize error >>', error); }); }, sendGoogleAnalytics (action, headers, ip, originalUrl) { @@ -137,7 +137,7 @@ module.exports = { resolve({ records: resultHashTable, totals: { totalServe, totalPublish, totalShow, totalCount, totalSuccess, totalFailure }, percentSuccess }); }) .catch(error => { - logger.error('sequelize error', error); + logger.error('sequelize error >>', error); reject(error); }); }); @@ -161,7 +161,7 @@ module.exports = { resolve(results); }) .catch(error => { - logger.error('sequelize error', error); + logger.error('sequelize error >>', error); reject(error); }); }); diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js index 0bf168e3..d1ac23cc 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -1,32 +1,38 @@ const logger = require('winston'); const { postToStats } = require('../controllers/statsController.js'); +function useObjectPropertiesIfNoKeys (err) { + if (Object.keys(err).length === 0) { + let newErrorObject = {}; + Object.getOwnPropertyNames(err).forEach((key) => { + newErrorObject[key] = err[key]; + }); + return newErrorObject; + } + return err; +} + module.exports = { handleRequestError (action, originalUrl, ip, error, res) { - logger.error('Request Error >>', error); + logger.error('Request Error:', useObjectPropertiesIfNoKeys(error)); + postToStats(action, originalUrl, ip, null, null, error); if (error.response) { - postToStats(action, originalUrl, ip, null, null, error.response.data.error.messsage); res.status(error.response.status).send(error.response.data.error.message); } else if (error.code === 'ECONNREFUSED') { - postToStats(action, originalUrl, ip, null, null, 'Connection refused. The daemon may not be running.'); res.status(503).send('Connection refused. The daemon may not be running.'); } else if (error.message) { - postToStats(action, originalUrl, ip, null, null, error); res.status(400).send(error.message); } else { - postToStats(action, originalUrl, ip, null, null, error); res.status(400).send(error); } }, handlePublishError (error) { + logger.error('Publish Error:', useObjectPropertiesIfNoKeys(error)); if (error.code === 'ECONNREFUSED') { - logger.error('Publish Error:', 'Connection refused. The daemon may not be running.'); return 'Connection refused. The daemon may not be running.'; } else if (error.response.data.error) { - logger.error('Publish Error:', error.response.data.error); return error.response.data.error.message; } else { - logger.error('Unhandled Publish Error:', error.message); return error; } }, diff --git a/helpers/lbryApi.js b/helpers/lbryApi.js index 84290053..3777c593 100644 --- a/helpers/lbryApi.js +++ b/helpers/lbryApi.js @@ -110,11 +110,12 @@ module.exports = { if (data.result) { resolve(data.result.download_directory); } else { - reject(new Error('Successfully connected to lbry daemon, but unable to retrieve the download directory.')); + // reject(new Error('Successfully connected to lbry daemon, but unable to retrieve the download directory.')); + return new Error('Successfully connected to lbry daemon, but unable to retrieve the download directory.'); } }) - .catch((error) => { - logger.error('Unable to retrieve daemon download directory. Restart spee.ch once the daemon is ready. Using default "/home/lbry/Downloads".', error); + .catch(error => { + logger.error('Lbrynet Error:', error); resolve('/home/lbry/Downloads/'); }); }); diff --git a/helpers/serveHelpers.js b/helpers/serveHelpers.js index b270865f..d5dbdaf1 100644 --- a/helpers/serveHelpers.js +++ b/helpers/serveHelpers.js @@ -11,7 +11,7 @@ function createOpenGraphInfo ({ fileType, claimId, name, fileName, fileExt }) { module.exports = { serveFile ({ fileName, fileType, filePath }, res) { - logger.info(`serving file ${fileName}`); + logger.verbose(`serving file ${fileName}`); // set default options let options = { headers: { diff --git a/models/index.js b/models/index.js index 0d670107..e4e870c5 100644 --- a/models/index.js +++ b/models/index.js @@ -6,9 +6,9 @@ const config = require('config'); const db = {}; const logger = require('winston'); -const database = config.get('Database.database'); -const username = config.get('Database.username'); -const password = config.get('Database.password'); +const database = config.get('Database.Database'); +const username = config.get('Database.Username'); +const password = config.get('Database.Password'); const sequelize = new Sequelize(database, username, password, { host : 'localhost', dialect: 'mysql', @@ -52,7 +52,7 @@ function getLongClaimIdFromShortClaimId (name, shortId) { .then(result => { switch (result.length) { case 0: - return reject(new Error('That is an invalid Short Claim Id')); + throw new Error('That is an invalid Short Claim Id'); default: // note results must be sorted return resolve(result[0].claimId); } @@ -179,7 +179,7 @@ db['getShortClaimIdFromLongClaimId'] = (claimId, claimName) => { .then(result => { switch (result.length) { case 0: - return reject(new Error('That is an invalid claim name')); + throw new Error('That is an invalid claim name'); default: return resolve(sortResult(result, claimId)); } @@ -198,7 +198,7 @@ db['getShortChannelIdFromLongChannelId'] = (channelName, longChannelId) => { .then(result => { switch (result.length) { case 0: - return reject(new Error('That is an invalid channel name')); + throw new Error('That is an invalid channel name'); default: return resolve(sortResult(result, longChannelId)); } @@ -238,7 +238,7 @@ db['resolveClaim'] = (name, claimId) => { case 1: return resolve(result[0]); default: - return new Error('more than one entry matches that name and claimID'); + throw new Error('more than one entry matches that name and claimID'); } }) .catch(error => { @@ -255,7 +255,7 @@ db['getClaimIdByLongChannelId'] = (channelId, claimName) => { .then(result => { switch (result.length) { case 0: - return reject(new Error('There is no such claim for that channel')); + throw new Error('There is no such claim for that channel'); default: return resolve(result[0].claimId); } diff --git a/package.json b/package.json index 06a7de6c..22f9872b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "socket.io": "^2.0.1", "socketio-file-upload": "^0.6.0", "universal-analytics": "^0.4.13", - "winston": "^2.3.1" + "winston": "^2.3.1", + "winston-slack-webhook": "billbitt/winston-slack-webhook" }, "devDependencies": { "eslint": "3.19.0", diff --git a/speech.js b/speech.js index 259d0837..21dff58c 100644 --- a/speech.js +++ b/speech.js @@ -14,7 +14,8 @@ const db = require('./models'); // require our models for syncing // configure logging const logLevel = config.get('Logging.LogLevel'); -require('./config/loggerSetup.js')(logger, logLevel); +require('./config/loggerConfig.js')(logger, logLevel); +require('./config/slackLoggerConfig.js')(logger); // trust the proxy to get ip address for us app.enable('trust proxy'); @@ -134,7 +135,7 @@ app.set('view engine', 'handlebars'); db.sequelize .sync() // sync sequelize .then(() => { // get the download directory from the daemon - logger.info('Retrieving daemon download directory'); + logger.info('Retrieving daemon download directory...'); return getDownloadDirectory(); }) .then(hostedContentPath => { @@ -154,5 +155,5 @@ db.sequelize }); }) .catch((error) => { - logger.error('Startup Error >>', error); + logger.error(`Startup Error >> ${error.message}`, error); });