From d31f261918f2a06364954b07ab040b1a1ac7c08b Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 11 Mar 2017 17:21:08 +0200 Subject: [PATCH] SUpport logging to syslog and multiple processes --- README.md | 2 +- config/default.js | 14 ++++++++ config/development.js | 31 ++--------------- logger.js | 40 ++++++++++++++++++++++ package.json | 3 ++ server.js | 77 ++++++++++++++++++++++++++++--------------- worker.js | 54 ++++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 56 deletions(-) create mode 100644 logger.js create mode 100644 worker.js diff --git a/README.md b/README.md index 059f4e9a..f74a9d6a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Yes, it does. You can run the server and get a working IMAP server for mail stor ### What are the killer features? -1. Start as many instances as you want. You can start multiple Wild Duck instances in different machines and as long as they share the same MongoDB and Redis settings, users can connect to any instances. This is very different from more traditional IMAP servers where a single user always needs to connect (or proxied) to the same IMAP server. Wild Duck keeps all required state information in MongoDB, so it does not matter which IMAP instance you use. +1. Start as many instances as you want. You can start multiple Wild Duck instances in different machines and as long as they share the same MongoDB and Redis settings, users can connect to any instances. This is very different from more traditional IMAP servers where a single user always needs to connect (or be proxied) to the same IMAP server. Wild Duck keeps all required state information in MongoDB, so it does not matter which IMAP instance you use. 2. Super easy to tweak. The entire codebase is pure JavaScript, so there's nothing to compile or anything platform specific. If you need to tweak something then change the code, restart the app and you're ready to go. If it works on one machine then most probably it works in every other machine as well. 3. Works almost on any OS including Windows. At least if you get MongoDB and Redis ([Windows fork](https://github.com/MSOpenTech/redis)) running first. 4. Focus on internationalization, ie. supporting email addresses with non-ascii characters diff --git a/config/default.js b/config/default.js index ba508270..bc91d38e 100644 --- a/config/default.js +++ b/config/default.js @@ -5,7 +5,21 @@ module.exports = { level: 'silly' }, + // downgrade process user after binding to ports + //user: 'wildduck', + //group: 'wildduck', + + // log to syslog if true + syslog: false, + + // process title and syslog ident + ident: 'wildduck', + + // how many processes to start + processes: 1, + mongo: 'mongodb://127.0.0.1:27017/wildduck', + redis: { host: 'localhost', port: 6379, diff --git a/config/development.js b/config/development.js index fcfafdf5..76eb9d12 100644 --- a/config/development.js +++ b/config/development.js @@ -5,33 +5,6 @@ module.exports = { level: 'silly' }, - mongo: 'mongodb://127.0.0.1:27017/wildduck', - redis: { - host: 'localhost', - port: 6379, - db: 3 - }, - - imap: { - port: 9998, - host: '127.0.0.1' - }, - - lmtp: { - enabled: true, - port: 3424, - host: '0.0.0.0', - maxMB: 25 - }, - - smtp: { - enabled: true, - port: 3525, - host: '0.0.0.0', - maxMB: 25 - }, - - api: { - port: 8380 - } + syslog: false, + processes: 5 }; diff --git a/logger.js b/logger.js new file mode 100644 index 00000000..64315cb5 --- /dev/null +++ b/logger.js @@ -0,0 +1,40 @@ +'use strict'; + +const config = require('config'); +const log = require('npmlog'); + +let syslog; +try { + // might not be installed + syslog = require('modern-syslog'); // eslint-disable-line global-require +} catch (E) { + // just ignore +} + +if (config.syslog && syslog) { + syslog.open(config.ident, syslog.option.LOG_PID, syslog.level.LOG_INFO); + + let logger = data => { + data.messageRaw[0] = '(' + data.prefix + ') ' + data.messageRaw[0]; + return data.messageRaw; + }; + + switch (log.level) { + /* eslint-disable no-fallthrough */ + case 'silly': + log.on('log.silly', data => syslog.debug(...logger(data))); + case 'verbose': + log.on('log.verbose', data => syslog.info(...logger(data))); + case 'info': + log.on('log.info', data => syslog.notice(...logger(data))); + case 'http': + log.on('log.http', data => syslog.note(...logger(data))); + case 'warn': + log.on('log.warn', data => syslog.warn(...logger(data))); + case 'error': + log.on('log.error', data => syslog.error(...logger(data))); + /* eslint-enable no-fallthrough */ + } + + log.level = 'silent'; // disable normal log stream +} diff --git a/package.json b/package.json index 43cd990d..b32e6f5e 100644 --- a/package.json +++ b/package.json @@ -42,5 +42,8 @@ "repository": { "type": "git", "url": "git://github.com/wildduck-email/wildduck.git" + }, + "optionalDependencies": { + "modern-syslog": "^1.1.4" } } diff --git a/server.js b/server.js index ffb6c01e..76434950 100644 --- a/server.js +++ b/server.js @@ -1,36 +1,61 @@ +/* eslint global-require:0 */ + 'use strict'; let config = require('config'); let log = require('npmlog'); -let imap = require('./imap'); -let lmtp = require('./lmtp'); -let smtp = require('./smtp'); -let api = require('./api'); +let packageData = require('./package.json'); log.level = config.log.level; +require('./logger'); -imap((err, imap) => { - if (err) { - log.error('App', 'Failed to start IMAP server'); - return process.exit(1); +let printLogo = () => { + log.info('App', '.##...##..######..##......#####...#####...##..##...####...##..##.'); + log.info('App', '.##...##....##....##......##..##..##..##..##..##..##..##..##.##..'); + log.info('App', '.##.#.##....##....##......##..##..##..##..##..##..##......####...'); + log.info('App', '.#######....##....##......##..##..##..##..##..##..##..##..##.##..'); + log.info('App', '..##.##...######..######..#####...#####....####....####...##..##.'); + log.info('App', ' --- v' + packageData.version + ' ---'); +}; + +if (!config.processes || config.processes <= 1) { + printLogo(); + if (config.ident) { + process.title = config.ident; } - lmtp(imap, err => { - if (err) { - log.error('App', 'Failed to start LMTP server'); - return process.exit(1); + // single process mode, do not fork anything + require('./worker.js'); +} else { + let cluster = require('cluster'); + + if (cluster.isMaster) { + printLogo(); + + if (config.ident) { + process.title = config.ident + ' master'; } - smtp(imap, err => { - if (err) { - log.error('App', 'Failed to start SMTP server'); - return process.exit(1); - } - api(imap, err => { - if (err) { - log.error('App', 'Failed to start API server'); - return process.exit(1); - } - log.info('App', 'All servers started, ready to process some mail'); - }); + + log.info('App', `Master [${process.pid}] is running`); + + let forkWorker = () => { + let worker = cluster.fork(); + log.info('App', `Forked worker ${worker.process.pid}`); + }; + + // Fork workers. + for (let i = 0; i < config.processes; i++) { + forkWorker(); + } + + cluster.on('exit', worker => { + log.info('App', `Worker ${worker.process.pid} died`); + setTimeout(forkWorker, 1000); }); - }); -}); + } else { + if (config.ident) { + process.title = config.ident + ' worker'; + } + + require('./worker.js'); + } +} diff --git a/worker.js b/worker.js new file mode 100644 index 00000000..60cd7880 --- /dev/null +++ b/worker.js @@ -0,0 +1,54 @@ +'use strict'; + +let config = require('config'); +let log = require('npmlog'); +let imap = require('./imap'); +let lmtp = require('./lmtp'); +let smtp = require('./smtp'); +let api = require('./api'); + +imap((err, imap) => { + if (err) { + log.error('App', 'Failed to start IMAP server'); + return process.exit(1); + } + lmtp(imap, err => { + if (err) { + log.error('App', 'Failed to start LMTP server'); + return process.exit(1); + } + smtp(imap, err => { + if (err) { + log.error('App', 'Failed to start SMTP server'); + return process.exit(1); + } + api(imap, err => { + if (err) { + log.error('App', 'Failed to start API server'); + return process.exit(1); + } + log.info('App', 'All servers started, ready to process some mail'); + + // downgrade user if needed + if (config.group) { + try { + process.setgid(config.group); + log.info('App', 'Changed group to "%s" (%s)', config.group, process.getgid()); + } catch (E) { + log.error('App', 'Failed to change group to "%s" (%s)', config.group, E.message); + return process.exit(1); + } + } + if (config.user) { + try { + process.setuid(config.user); + log.info('App', 'Changed user to "%s" (%s)', config.user, process.getuid()); + } catch (E) { + log.error('App', 'Failed to change user to "%s" (%s)', config.user, E.message); + return process.exit(1); + } + } + }); + }); + }); +});