<|start_filename|>test/winston-mail.js<|end_filename|>
/**
 * @module 'test-winston-mail'
 * @fileoverview test file to test winston-mail transport
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
const winstonMail = require('../src/transports/winston-mail').Mail;
const { LEVEL, MESSAGE } = require('triple-beam');
const shortid = require('shortid');
const assert = require('assert');
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
require('dotenv-extended').load();


describe('Mail Transport', () => {
	it('should throw error if no to', () => {
		expect(()=> {
			let transport = new winstonMail();
		}).to.throw('super-logger(winston-email) requires \'to\' property');
	});

	it('should throw error if no transportOptions', () => {
		expect(()=> {
			let transport = new winstonMail({to: process.env.MAIL_TO});
		}).to.throw('super-logger(winston-email) requires \'transportOptions\'');
	});

	it('should throw error if wrong transportOptions', () => {
		expect(()=> {
			let transport = new winstonMail({
				to: process.env.MAIL_TO,
				transportOptions: 'fake wrong data',
			});
		}).to.throw();
	});

	it('should call callback if level is below transport level', () => {
		let transport = new winstonMail({
			transportOptions: {
				host: process.env.MAIL_HOST,
				port: process.env.MAIL_PORT,
				auth: {
					user: process.env.MAIL_USER,
					pass: process.env.MAIL_PASSWORD,
				},
			},
			to: process.env.MAIL_TO,
			from: process.env.MAIL_USER,
			level: 'info',
		});
		let info = {
			[LEVEL]: 'debug',
			level: 'debug',
			message: 'foo',
			context: 'TEST',
			logblock: 'test-block-' + shortid.generate(),
		};
		var cbSpy = sinon.spy();
		info[MESSAGE] = JSON.stringify(info);
		let result = transport.log(info, cbSpy);
		expect(cbSpy.called).to.be.true;
		assert(true, result);
	});

	require('./abstract-transport')({
		name: '',
		Transport: winstonMail,
		settings: {
			transportOptions: {
				host: process.env.MAIL_HOST,
				port: process.env.MAIL_PORT,
				auth: {
					user: process.env.MAIL_USER,
					pass: <PASSWORD>.MAIL_PASSWORD,
				},
			},
			to: process.env.MAIL_TO,
			from: process.env.MAIL_USER,
		},
	});
});


<|start_filename|>test/winston-mongodb.js<|end_filename|>
/**
 * @module 'test-winston-mail'
 * @fileoverview test file to test winston-mongodb transport
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
const winstonMongo = require('../src/transports/winston-mongodb').MongoDB;
const test_suite = require('./abstract-transport');
const mongodb = require('mongodb');
const assert = require('assert');
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
require('dotenv-extended').load();


describe('MongoDb Transport', () => {
	let dbUrl = 'mongodb://localhost/test';
	var stub = sinon.stub(console, 'error');
	it('{db: url} should console error on wrong auth db settings', (done) => {
		let transport = new winstonMongo({
			db: 'test',
		});
		setTimeout(() => {
			expect( console.error.called ).to.be.true;
			expect( console.error.calledWith('super-logger(mongodb): error initialising logger'))
				.to.be.true;
			done();
		}, 1000);
	});

	it('{db: promise} should console error on wrong auth db settings', (done) => {
		let transport = new winstonMongo({
			db: mongodb.MongoClient.connect('test'),
		});
		setTimeout(() => {
			expect( console.error.called).to.be.true;
			expect( console.error.calledWith('super-logger(mongodb): error initialising logger from promise'))
				.to.be.true;
			done();
		}, 1000);
	});

	describe('.query()', () => {
		it('should be present', () => {
			let transport = new winstonMongo({
				db: dbUrl,
			});
			assert.ok(transport.query);
			assert.equal('function', typeof transport.query);
		});

		it('should call callback (without options)', (done) => {
			let transport = new winstonMongo({
				db: dbUrl,
			});
			var cbSpy = sinon.spy();
			transport.query(cbSpy);
			setTimeout(() => {
				expect(cbSpy.called).to.be.true;
				done();
			}, 1000);
		});

		it('should call callback (with options)', (done) => {
			let transport = new winstonMongo({
				db: dbUrl,
			});
			var cbSpy = sinon.spy();
			transport.query({level: 'debug'}, cbSpy);
			setTimeout(() => {
				expect(cbSpy.called).to.be.true;
				done();
			}, 1000);
		});
	});

	test_suite({
		name: '{db: url}',
		Transport: winstonMongo,
		settings: {db: dbUrl},
	});

	test_suite({
		name: '{db: promise}',
		Transport: winstonMongo,
		settings: {
			db: mongodb.MongoClient.connect(dbUrl),
		},
	});
});


<|start_filename|>src/log.js<|end_filename|>
/**
 * @module log
 * @fileoverview log class to create a log instance
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

// dependency modules
const shortid = require('shortid');
const _ = require('lodash');
const isHtml = require('is-html');
const fs = require('fs');
const circular = require('circular');
// our own modules
const ansi = require('./helpers/ansi.js');
const logTypes = require('./helpers/logTypes');
const {levelFromResStatus} = require('./helpers/levelsSettings');

/**
 * Log class
 */
class Log {
	constructor() {
		const Logger = require('./logger');
		const logger = new Logger();
		this.logger = logger.logger;
		this.logDir = logger.logDir;
	}

	//add wrapper functions for levels
	debug (...args) {
		return this.logger.debug(...args);
	}
	info (...args) {
		return this.logger.info(...args);
	}
	notice (...args) {
		return this.logger.notice(...args);
	}
	warning (...args) {
		return this.logger.warning(...args);
	}
	error (...args) {
		return this.logger.error(...args);
	}
	critical (...args) {
		return this.logger.critical(...args);
	}
	alert (...args) {
		return this.logger.alert(...args);
	}
	emergency (...args) {
		return this.logger.emergency(...args);
	}

	/**
	 * Log on a call request:
	 * log instantly the called route with its params, query, body and method.
	 * @param  {string}  url        url called
	 * @param  {string}  method     method used
	 * @param  {object}  form       body sent to request
	 * @param  {boolean} [api=true] is the url called part of a private API
	 * @param  {string}  logblock		logblock value
	 */
	_callRequestLogging(url, method, form, api = true, logblock){
		if(!url || !method){
			throw new Error('Url and method are required for your request logging.');
		}
		method = method.toUpperCase();
		let uid = shortid.generate();
		let urlName = (url.split('?'))[0]
			.replace(/(^\w+:|^)\/\//, '')
			.replace(/\//g, '-');
		logblock = logblock || `${urlName}-${method}-${uid}`;
		let logMeta = {
			type: logTypes.REST_CLIENT,
			logblock,
			context: 'REQUEST',
		};

		//log request call
		let apiCallMsg = 'API ';
		let msg = 'Request %s';
		if(api){
			msg = apiCallMsg + msg;
		}
		this.logger.info(msg, ansi.blue(`${url}: ${method}`), logMeta);

		//log request query
		let queryString = (url.split('?'))[1];
		const getQueryParameters = (str) => {
			str = str.replace(/(^&)|(&$)/g, '');
			return str.replace(/(^\?)/, '')
				.split('&')
				.map(function (n) {
					return n = n.split('='), this[n[0]] = n[1], this;
				}.bind({}))[0];
		};

		let query = queryString ? getQueryParameters(queryString) : {};
		if(!_.isEmpty(query)){
			let logMetaQuery = Object.assign({}, logMeta, {query});
			this.logger.info('Query params: ', logMetaQuery);
		}

		//log the request body
		if(!_.isEmpty(form)){
			let logMetaBody = Object.assign({}, logMeta, {form});
			this.logger.info('Body request: ', logMetaBody);
		}

	}

	/**
	 * wrapper for callRequestLogging
	 * @param  {array} args
	 */
	callRequestLogging(...args) {
		this._callRequestLogging(...args);
	}

	/**
	 * Log a request call response:
	 * log after request the error, status and body response
	 * @param  {string}  method       method used
	 * @param  {object}  err          error on request
	 * @param  {object}  httpResponse httpResponse
	 * @param  {object}  body         body Response
	 * @param  {boolean} [api=true]   is the url called part of a private API
	 * @param  {boolean} [json=false] is the body response a json
	 * @param  {string}  logblock		logblock value
	 */
	_endRequestLogging(url, method, err, httpResponse, body, api = true, json = false, logblock){
		if(!url || !method){
			throw new Error('Url and method are required for your request logging.');
		}
		method = method.toUpperCase();
		let uid = shortid.generate();
		let urlName = (url.split('?'))[0]
			.replace(/(^\w+:|^)\/\//, '')
			.replace(/\//g, '-');

		logblock = logblock || `${urlName}-${method}-${uid}`;
		let logMeta = {
			type: logTypes.REST_CLIENT,
			logblock,
			context: 'REQUEST',
		};
		//log request error
		if(!_.isEmpty(err)){
			let logMetaError = Object.assign({}, logMeta, {err});
			this.logger.error('Request fail on: ', logMetaError);
		} else {
			//log request call
			let statusCode = (httpResponse && httpResponse.statusCode) ?
				httpResponse.statusCode : 500;
			let level = levelFromResStatus(statusCode);
			let msg = api ? '%s API Response %s ' : '%s Response %s ';
			let status = (err || statusCode >= 300 || statusCode < 200) ?
				ansi.red.bold(`[Error] ${statusCode}`) :
				ansi.green.bold(`[Success] ${statusCode}`);
			this.logger.log(level, msg, status, ansi.blue(`${url}: ${method}`), logMeta);
			//log file containing html body
			if (isHtml(body)) {
				let data = body.toString();
				let path = `${this.logDir}/${uid}.html`;
				fs.writeFile(path, data, 'utf8', (rerr) => {
					if (rerr) {
						this.logger.error('Error on write error file', Object.assign({}, logMeta, {err: rerr}));
					}
				});
				this.logger.info('Body Response saved in a HTML file: %s', path, logMeta);
				//log body
			} else if (typeof body === 'string') {
				if (json) {
					try {
						body = JSON.parse(body);
						let logMetaBody = Object.assign({}, logMeta, body);
						this.logger.info('Body Response', logMetaBody);
					} catch (e) {
						let logMetaBodyError = Object.assign({}, logMeta, {body});
						//log error if body can't be parsed to json object
						this.logger.error('Parsing body response to object fail: ', logMetaBodyError);
						this.logger.info('Body Response: %s', body, logMeta);
					}
				} else {
					this.logger.info('Body Response: %s', body.toString(), logMeta);
				}
				//log body if string or object
			} else if (typeof body === 'object') {
				let logMetaBody = Object.assign({}, logMeta, { body: JSON.stringify(body, circular()) });
				this.logger.info('Body Response', logMetaBody);
			} else {
				body = body.toString();
				this.logger.info('Body Response: %s', body, logMeta);
			}
		}
	}

	/**
	 * wrapper for endRequestLogging
	 * @param  {array} args
	 */
	endRequestLogging(...args) {
		this._endRequestLogging(...args);
	}
}

module.exports = Log;


<|start_filename|>src/api/server.js<|end_filename|>
/**
 * @module server
 * @fileoverview Express server manager for logging api
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

const _ = require('lodash');
const { routes } = require('./routes');

/**
 * launching express server for api Logging
 * @param  {object}  logger         logger instance
 * @param  {object}  api.appExpress express app
 * @param  {string}  api.logPrefix  routes prifix for listing logs
 * @param  {integer} api.port       port for the express app
 */
module.exports = (logger, api = {}) => {
	let routesPrefix = api.logPrefix || '/';
	let logFilesPrefix = routesPrefix + '/log-files';
	const express = require('express');
	// pass express app
	if (api.appExpress && !_.isEmpty(api.appExpress)) {
		api.appExpress.use(logFilesPrefix, express.static(logger.logDir));
		routes(logger, api.appExpress, routesPrefix);
		// create express app
	} else if (api.port) {
		let app = express();
		app.use(logFilesPrefix, express.static(logger.logDir));
		routes(logger, app, routesPrefix);
		app.listen(api.port, () => {
			console.log('Logging server is working on ', api.port);
		});
	} else {
		throw new Error('You need to pass a port or an existing express app');
	}
};


<|start_filename|>server.js<|end_filename|>
const _ = require('lodash');
const Logger = require('./src/logger');
/*
  Gettings args
 */
let port = process.argv[2];
if(_.isEmpty(port)){
	throw 'You need to specify a port for your super-logger api';
}
let dbString = process.argv[3];
if(_.isEmpty(dbString)){
	throw 'You need to specify you database string connection for your super-logger app';
}
let logPrefix = process.argv[4] || '/logs';
let collection = process.argv[5] || 'log';

/*
 Create fake logger
 */
const fakeLogger = new Logger();
fakeLogger.init({
	logDir: './fakeLogs',
	api: {
		port,
		logPrefix,
	},
	dbSettings: {
		db: dbString,
		collection,
	},
});


<|start_filename|>test/abstract-log.js<|end_filename|>
/**
 * @module 'test-abstract-log'
 * @fileoverview test file to test log
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */



const {levels} = require('../src/helpers/levelsSettings');
const _ = require('lodash');
const request = require('request');

const chai = require('chai');
const expect = chai.expect;
const assert = require('assert');

require('dotenv-extended').load();
process.setMaxListeners(0);

module.exports = (options) => {
	const name = options.name;
	const logger = options.logger;
	const logging = options.type;

	describe(name + ' level wrappers', () => {
		it('should contain log wrapeprs', () => {
			function hasMethod (obj, name) {
				const desc = Object.getOwnPropertyDescriptor (obj, name);
				return typeof desc.value === 'function';
			}
			function getInstanceMethodNames (obj) {
				let array = [];
				let proto = Object.getPrototypeOf (obj);
				let stop = false;
				while (proto && !stop) {
					Object.getOwnPropertyNames (proto).forEach (name => {
						if( name == 'emergency') stop = true;
						if (name !== 'constructor') {
							if (hasMethod (proto, name)) {
								array.push (name);
							}
						}
					});
					proto = Object.getPrototypeOf (proto);
				}
				return array;
			}
			let methods = getInstanceMethodNames(logging);
			let expectedMethods = [];
			for (let level in levels) {
				expectedMethods.push(level);
			}
			let result = _.intersection(expectedMethods, methods);
			expect(result).to.be.eql(expectedMethods);
		});
	});

	describe(name + '.callRequestLogging()', () => {
		it('should be present', () => {
			assert.ok(logging.callRequestLogging);
			assert.equal('function', typeof logging.callRequestLogging);
		});

		it('should throw error in method or url missing', () => {
			expect(() => {
				logging.callRequestLogging();
			}).to.throw();
		});
	});

	describe(name + '.endRequestLogging()', () => {
		it('should be present', () => {
			assert.ok(logging.endRequestLogging);
			assert.equal('function', typeof logging.endRequestLogging);
		});

		it('should throw error in method or url missing', () => {
			expect(() => {
				logging.endRequestLogging();
			}).to.throw();
		});

		it('should not throw error in circular objects', (done) => {
			let url = 'http://validate.jsontest.com/?json=%5BJSON-code-to-validate%5D';
			request.get(url, (err, httpResponse, body) => {
				body = {test: body}; //create circular object
				expect(() => {
					logging.endRequestLogging(url, 'get', err, httpResponse, body);
				}).to.not.throw();
				done();
			});
		});
	});


};


<|start_filename|>src/api/routes.js<|end_filename|>
/**
 * @module routes
 * @fileoverview Express server routes declaration
 * @license MIT
 * @author <NAME> <<EMAIL>>
 */

const moment = require('moment');

/**
 * api routes to get logs
 * @param  {object} logger logger instance
 * @param  {object} app    express app
 * @param  {string} routesPrefix routes prefix
 */
module.exports.routes = (logger, app, routesPrefix) => {
	// Main route
	const mainRoute = `${routesPrefix}`;
	app.get(mainRoute, (req, res) => {
		let { context, content, logblock, type, level, page, until, from, order, source } = req.query;

		let validUntil = until && moment(until).isValid();
		let _until = validUntil ? moment(until).toDate() : moment().toDate();

		let validFrom = from && moment(from).isValid() &&  moment(until).isAfter(moment(from));
		let _from = validFrom ? moment(from).toDate() : moment(until).subtract(30, 'days').toDate();

		order = order || -1;

		page = page || 0;
		const pageSize = 10;
		const start = page * pageSize;
		const options = {
			from: _from,
			until: _until,
			limit: pageSize,
			start,
			content,
			context,
			logblock,
			type,
			level,
			source,
			order: parseInt(order) >= 0 ? 'asc' : 'desc',
			fields: ['content', 'timestamp', 'context', 'logblock', 'type', 'level', 'source'],
		};
		logger._listLog(options)
			.then(results => {
				res.json(results);
			})
			.catch(err => {
				console.log(err);
				res.send(err);
			});
	});
	// Grouped by logblock route
	const groupedRoute = `${routesPrefix}/by-block`;
	app.get(groupedRoute, (req, res) => {
		let { content, context, logblock, type, level, page, source } = req.query;
		page = page || 0;
		const pageSize = 10;
		const start = page * pageSize;
		const options = {
			from: new Date() - (30 * 24 * 60 * 60 * 1000),
			until: new Date(),
			limit: pageSize,
			start,
			content,
			context,
			logblock,
			type,
			level,
			source,
			order: 'desc',
			fields: ['content', 'timestamp', 'context', 'type', 'level', 'source'],
			group: 'logblock',
		};
		logger._listLog(options)
			.then(results => {
				res.json(results);
			})
			.catch(err => {
				console.log(err);
				res.send(err);
			});
	});
};


<|start_filename|>src/transports/winston-mongodb.js<|end_filename|>
/**
 * @module 'winston-mongodb'
 * @fileoverview Winston transport for logging into MongoDB
 * @license MIT
 * @author <EMAIL> (<NAME>)
 * @author <EMAIL> (<NAME>)
 * @author <EMAIL> (<NAME>)
 */
const util = require('util');
const os = require('os');
const mongodb = require('mongodb');
const _ = require('lodash');
const { LEVEL } = require('triple-beam');
const Stream = require('stream').Stream;
const logTypes = require('../helpers/logTypes');
const helpers = require('./helpers');

let Transport = require('winston-transport');
Transport.prototype.normalizeQuery = function (options) {  //
	options = options || {};

	// limit
	options.rows = options.rows || options.limit || 10;

	// starting row offset
	options.start = options.start || 0;

	// now
	options.until = options.until || new Date();
	if (typeof options.until !== 'object') {
		options.until = new Date(options.until);
	}

	// now - 24
	options.from = options.from || (options.until - (24 * 60 * 60 * 1000));
	if (typeof options.from !== 'object') {
		options.from = new Date(options.from);
	}

	// 'asc' or 'desc'
	options.order = options.order || 'desc';

	// which fields to select
	options.fields = options.fields;

	return options;
};
Transport.prototype.formatResults = function (results, options) {
	return results;
};

/**
 * Constructor for the MongoDB transport object.
 * @constructor
 * @param {Object} options
 * @param {string=info} options.level Level of messages that this transport
 * should log.
 * @param {boolean=false} options.silent Boolean flag indicating whether to
 * suppress output.
 * @param {string|Object} options.db MongoDB connection uri or preconnected db
 * object.
 * @param {Object} options.options MongoDB connection parameters
 * (optional, defaults to `{poolSize: 2, autoReconnect: true}`).
 * @param {string=logs} options.collection The name of the collection you want
 * to store log messages in.
 * @param {boolean=false} options.storeHost Boolean indicating if you want to
 * store machine hostname in logs entry, if set to true it populates MongoDB
 * entry with 'hostname' field, which stores os.hostname() value.
 * @param {string} options.label Label stored with entry object if defined.
 * @param {string} options.name Transport instance identifier. Useful if you
 * need to create multiple MongoDB transports.
 * @param {boolean=false} options.capped In case this property is true,
 * winston-mongodb will try to create new log collection as capped.
 * @param {number=10000000} options.cappedSize Size of logs capped collection
 * in bytes.
 * @param {number} options.cappedMax Size of logs capped collection in number
 * of documents.
 * @param {boolean=false} options.tryReconnect Will try to reconnect to the
 * database in case of fail during initialization. Works only if `db` is
 * a string.
 * @param {boolean=false} options.decolorize Will remove color attributes from
 * the log entry message.
 * @param {number} options.expireAfterSeconds Seconds before the entry is removed. Do not use if capped is set.
 */
let MongoDB = exports.MongoDB = function(options) {
	Transport.call(this, options);
	options = (options || {});
	if (!options.db) {
		throw new Error('You should provide db to log to.');
	}
	this.on('error', (err) => {
		console.error('super-logger(winston-mongodb), error on logging in database: ', err);
	});
	this.name = options.name || 'mongodb';
	this.db = options.db;
	this.options = options.options;
	if (!this.options) {
		this.options = {
			poolSize: 2,
			autoReconnect: true,
		};
	}
	this.collection = (options.collection || 'log');
	this.silent = options.silent;
	this.storeHost = options.storeHost;
	this.label = options.label;
	this.capped = options.capped;
	this.cappedSize = (options.cappedSize || 10000000);
	this.cappedMax = options.cappedMax;
	this.decolorize = options.decolorize;
	this.expireAfterSeconds = !this.capped && options.expireAfterSeconds;
	if (this.storeHost) {
		this.hostname = os.hostname();
	}
	this._opQueue = [];
	let self = this;

	function setupDatabaseAndEmptyQueue(client) {
		return createCollection(client.db()).then(db=>{
			self.logDb = db;
			processOpQueue();
		}, err=>{
			client.close();
			console.error('super-logger(mongodb), initialization error: ', err);
		});
	}
	function processOpQueue() {
		self._opQueue.forEach(operation=>
			self[operation.method].apply(self, operation.args));
		delete self._opQueue;
	}
	function createCollection(db) {
		let opts = self.capped ?
			{capped: true, size: self.cappedSize, max: self.cappedMax} : {};
		return db.createCollection(self.collection, opts).then(col=>{
			const ttlIndexName = 'timestamp_1';
			let indexOpts = {name: ttlIndexName, background: true};
			if (self.expireAfterSeconds) {
				indexOpts.expireAfterSeconds = self.expireAfterSeconds;
			}
			return col.indexInformation({full: true}).then(info=>{
				info = info.filter(i=>i.name === ttlIndexName);
				if (info.length === 0) { // if its a new index then create it
					return col.createIndex({timestamp: -1}, indexOpts);
				} else { // if index existed with the same name check if expireAfterSeconds param has changed
					if (info[0].expireAfterSeconds !== undefined &&
              info[0].expireAfterSeconds !== self.expireAfterSeconds) {
						return col.dropIndex(ttlIndexName)
							.then(()=>col.createIndex({timestamp: -1}, indexOpts))
							.catch(err=>{console.error('super-logger(mongodb), fail to drop index: ', err);});
					}
				}
			})
				.catch(err => {console.error('super-logger(mongodb), fail to create index: ', err);});
		})
			.then(()=>{
				return db;
			})
			.catch(err => {
				console.error('super-logger(mongodb), fail to create collection: ', err);
			});
	}
	function connectToDatabase(logger) {
		return mongodb.MongoClient.connect(
			logger.db, Object.assign(logger.options, { useNewUrlParser: true})
		).then(setupDatabaseAndEmptyQueue, err=>{
			console.error('super-logger(mongodb): error initialising logger', err);
		});
	}

	if ('string' === typeof this.db) {
		connectToDatabase(this);
	} else if ('function' === typeof this.db.then) {
		this.db.then(setupDatabaseAndEmptyQueue,
			err=>{console.error('super-logger(mongodb): error initialising logger from promise', err);});
	} else { // preconnected object
		setupDatabaseAndEmptyQueue(this.db);
	}
};


/**
 * Inherit from `winston.Transport`.
 */
util.inherits(MongoDB, Transport);


/**
 * Define a getter so that `winston.transports.MongoDB`
 * is available and thus backwards compatible.
 */
Transport.MongoDB = MongoDB;


/**
 * Closes MongoDB connection so using process would not hang up.
 * Used by winston Logger.close on transports.
 */
MongoDB.prototype.close = function() {
	if (!this.logDb) {
		return;
	}
	this.logDb.close().then(()=>this.logDb = null).catch(err=>{
		console.error('Winston MongoDB transport encountered on error during closing.', err);
	});
};


/**
 * Core logging method exposed to Winston. Metadata is optional.
 * @param {object} info **Optional** Additional metadata to attach
 * @param {Function} cb Continuation to respond to when complete.
 */
MongoDB.prototype.log = function(info, cb) {
	if (!this.logDb) {
		this._opQueue.push({method: 'log', args: arguments});
		return true;
	}
	if(!cb) {
		cb = () => {};
	}

	let meta;
	if (info.splat) {
		meta = Object.assign({}, info.meta);
	} else {
		meta = Object.assign({}, info);
		delete meta.message;
		delete meta.level;
	}
	if(meta.noMongoLog) return true;

	meta.context = meta.context || 'GENERAL';
	let type = meta.type || logTypes.BASE;

	// Avoid reentrancy that can be not assumed by database code.
	// If database logs, better not to call database itself in the same call.
	process.nextTick(()=>{
		if (this.silent) {
			cb(null, true);
			return;
		}
		let entry = {
			timestamp: new Date(),
			level: info[LEVEL],
			context: meta.context,
			logblock: meta.logblock,
			type,
		};
		delete meta.context;
		delete meta.noMongoLog;
		delete meta.logblock;
		delete meta.type;
		if(meta.source){
			entry.source = meta.source;
			delete meta.source;
		}
		let message = info.splat ? util.format(info.message, ...info.splat): info.message;
		entry.content = this.decolorize ? message.replace(/\u001b\[[0-9]{1,2}m/g, '') : message;
		entry.content += ' ';
		let metaObject = helpers.prepareMetaData(meta);
		entry.content += _.isEmpty(metaObject) ? '': JSON.stringify(metaObject);
		if (this.storeHost) {
			entry.hostname = this.hostname;
		}
		if (this.label) {
			entry.label = this.label;
		}

		this.logDb.collection(this.collection).insertOne(entry, (err) => {
			if (err) {
				this.emit('error', err);
				cb(err);
			} else {
				this.emit('logged', info);
				cb(null, true);
			}
		});
	});
};


/**
 * Query the transport. Options object is optional.
 * @param {Object=} opt_options Loggly-like query options for this instance.
 * @param {Function} cb Continuation to respond to when complete.
 * @return {*}
 */
MongoDB.prototype.query = function(opt_options, cb) {
	if (!this.logDb) {
		this._opQueue.push({method: 'query', args: arguments});
		return;
	}
	if ('function' === typeof opt_options) {
		cb = opt_options;
		opt_options = {};
	}
	let options = this.normalizeQuery(opt_options);
	// filter by time
	let query = {
		timestamp: {$gte: options.from, $lte: options.until},
	};
	// filter by content
	if(options.content){
		Object.assign(query, {content: {$regex : `.*${options.content}.*`}});
	}
	// filter by context
	if (options.context) {
		Object.assign(query, { context: { $regex: `.*${options.context}.*` } });
	}
	// filter by logblock
	if(options.logblock){
		Object.assign(query, {logblock: {$regex : `.*${options.logblock}.*`}});
	}
	// filter by type
	if(options.type){
		Object.assign(query, {type: options.type});
	}
	// filter by source
	if (options.source) {
		Object.assign(query, {source: { $regex: `.*${options.source}.*` }});
	}
	// filter by level
	if(options.level){
		Object.assign(query, {level: options.level});
	}
	let opt = {
		skip: options.start,
		limit: options.limit,
		sort: {timestamp: options.order === 'desc' ? -1 : 1},
	};
	if (options.fields) {
		opt.fields = options.fields;
	}
	if (options.group) {
		const fullQuery = [
			{ $match : query },
			{ $group : { _id : '$logblock', logs: { $push: '$$ROOT' } } },
		];
		this.logDb.collection(this.collection).aggregate(fullQuery, opt).toArray().then(docs=>{
			cb(null, docs);
		}).catch(cb);
	} else {
		this.logDb.collection(this.collection).find(query, opt).toArray().then(docs=>{
			cb(null, docs);
		}).catch(cb);
	}
};


/**
 * Returns a log stream for this transport. Options object is optional.
 * This will only work with a capped collection.
 * @param {Object} options Stream options for this instance.
 * @param {Stream} stream Pass in a pre-existing stream.
 * @return {Stream}
 */
MongoDB.prototype.stream = function(options, stream) {
	options = options || {};
	stream = stream || new Stream();
	let start = options.start;
	if (!this.logDb) {
		this._opQueue.push({method: 'stream', args: [options, stream]});
		return stream;
	}
	stream.destroy = function() {
		this.destroyed = true;
	};
	if (start === -1) {
		start = null;
	}
	let col = this.logDb.collection(this.collection);
	if (start != null) {
		col.find({}, {skip: start}).toArray().then(docs=>{
			docs.forEach(doc=>{
				if (!options.includeIds) {
					delete doc._id;
				}
				stream.emit('log', doc);
			});
			delete options.start;
			this.stream(options, stream);
		}).catch(err=>stream.emit('error', err));
		return stream;
	}
	if (stream.destroyed) {
		return stream;
	}
	col.isCapped().then(capped=>{
		if (!capped) {
			return this.streamPoll(options, stream);
		}
		let cursor = col.find({}, {tailable: true});
		stream.destroy = function() {
			this.destroyed = true;
			cursor.destroy();
		};
		cursor.on('data', doc=>{
			if (!options.includeIds) {
				delete doc._id;
			}
			stream.emit('log', doc);
		});
		cursor.on('error', err=>stream.emit('error', err));
	}).catch(err=>stream.emit('error', err));
	return stream;
};


/**
 * Returns a log stream for this transport. Options object is optional.
 * @param {Object} options Stream options for this instance.
 * @param {Stream} stream Pass in a pre-existing stream.
 * @return {Stream}
 */
MongoDB.prototype.streamPoll = function(options, stream) {
	options = options || {};
	stream = stream || new Stream();
	let self = this;
	let start = options.start;
	let last;
	if (!this.logDb) {
		this._opQueue.push({method: 'streamPoll', args: [options, stream]});
		return stream;
	}
	if (start === -1) {
		start = null;
	}
	if (start == null) {
		last = new Date(new Date() - 1000);
	}
	stream.destroy = function() {
		this.destroyed = true;
	};
	(function check() {
		let query = last ? {timestamp: {$gte: last}} : {};
		self.logDb.collection(self.collection).find(query).toArray().then(docs=>{
			if (stream.destroyed) {
				return;
			}
			if (!docs.length) {
				return next();
			}
			if (start == null) {
				docs.forEach(doc=>{
					if (!options.includeIds) {
						delete doc._id;
					}
					stream.emit('log', doc);
				});
			} else {
				docs.forEach(doc=>{
					if (!options.includeIds) {
						delete doc._id;
					}
					if (!start) {
						stream.emit('log', doc);
					} else {
						start -= 1;
					}
				});
			}
			last = new Date(docs.pop().timestamp);
			next();
		}).catch(err=>{
			if (stream.destroyed) {
				return;
			}
			next();
			stream.emit('error', err);
		});
		function next() {
			setTimeout(check, 2000);
		}
	})();
	return stream;
};


<|start_filename|>src/helpers/levelsSettings.js<|end_filename|>
/**
 * @module levelsSettings
 * @fileoverview Levels settings: levels definitions with colors and express Statuses
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

//levels definitions
module.exports.levels = {
	debug: 7,  	   	//debug-level messages
	info:	6, 		    //informational messages
	notice: 5, 		  //normal but significant condition
	warning: 4, 		//warning conditions
	error: 3, 		  //error conditions
	critical: 2, 		//critical conditions
	alert:	1, 	    //action must be taken immediately
	emergency: 0, 	//system is unusable
};

module.exports.lowestLevel = 'debug';

module.exports.colors = {
	debug: 'green',
	info:	'blue',
	notice: 'grey',
	warning: 'yellow',
	error: 'red',
	critical: 'red',
	alert:	'red',
	emergency: 'red',
};

module.exports.levelFromStatus = (res) =>{
	var level = '';
	if (res.statusCode >= 100) {
		level = 'debug';
	}
	if (res.statusCode >= 400) {
		level = 'warning';
	}
	if (res.statusCode >= 500) {
		level = 'error';
	}
	return level;
};


module.exports.levelFromResStatus = (status) =>{
	var level = '';
	switch(status){
	case 200:
		level = 'debug';
		break;
	case 400:
		level = 'warning';
		break;
	case 500:
		level = 'error';
		break;
	default:
		level = 'info';
	}
	return level;
};


<|start_filename|>test/winston-console.js<|end_filename|>
/**
 * @module 'test-winston-console'
 * @fileoverview test file to test winston-console transport
 * @license MIT
 * @author <EMAIL> (<NAME>)
 * @author <EMAIL> (<NAME>)
 */

const path = require('path');
const assume = require('assume');
const { LEVEL, MESSAGE } = require('triple-beam');
const winston = require('winston');
const shortid = require('shortid');
const winstonConsole = require('../src/transports/winston-console');
const stdMocks = require('std-mocks');

const {levels} = require('../src/helpers/levelsSettings');

const transports = {
	defaults: new winstonConsole(),
	noStderr: new winstonConsole({ stderrLevels: [] }),
	debugStdout: new winstonConsole({ debugStdout: true }),
	stderrLevels: new winstonConsole({
		stderrLevels: ['info', 'warn'],
	}),
};

/**
 * Returns a function that asserts the `transport` has the specified
 * `stderrLevels`.
 *
 * @param  {TransportStream} transport Transport to assert against
 * @param  {Array} stderrLevels Set of levels assumed to exist
 * @return {function} Assertion function to execute comparison
 */
function assertStderrLevels(transport, stderrLevels) {
	return () => {
		assume(JSON.stringify(Object.keys(transport.stderrLevels).sort()))
			.equals(JSON.stringify(stderrLevels.sort()));
	};
}

describe('Console transport', () => {
	describe('with defaults', () => {
		it('should set stderrLevels to [\'error\', \'debug\'] by default', assertStderrLevels(
			transports.defaults,
			['error', 'debug']
		));

		it('logs all levels (EXCEPT error and debug) to stdout', () => {
			stdMocks.use();
			transports.defaults.levels = levels;
			Object.keys(levels)
				.forEach((level) => {
					const info = {
						[LEVEL]: level,
						message: `This is level ${level}`,
						level,
						context: 'TEST',
						logblock:'test-block-' + shortid.generate(),
					};

					info[MESSAGE] = JSON.stringify(info);
					transports.defaults.log(info);
				});

			stdMocks.restore();
			var output = stdMocks.flush();
			assume(output.stderr).is.an('array');
			assume(output.stderr).length(2);
			assume(output.stdout).is.an('array');
			assume(output.stdout).length(6);
		});
	});

	describe('throws an appropriate error when', () => {
		it('if both debugStdout and stderrLevels are set { debugStdout, stderrLevels }', () => {
			assume(() => {
				let throwing = new winstonConsole({
					stderrLevels: ['foo', 'bar'],
					debugStdout: true,
				});
			}).throws(/Cannot set debugStdout and stderrLevels/);
		});

		it('if stderrLevels is set, but not an Array { stderrLevels: \'Not an Array\' }', () => {
			assume(() => {
				let throwing = new winstonConsole({
					stderrLevels: 'Not an Array',
					debugStdout: false,
				});
			}).throws(/Cannot set stderrLevels to type other than Array/);
		});

		it('if stderrLevels contains non-string elements { stderrLevels: [\'good\', /^invalid$/, \'valid\']', () => {
			assume(() => {
				let throwing = new winstonConsole({
					stderrLevels: ['good', /^invalid$/, 'valid'],
					debugStdout: false,
				});
			}).throws(/Cannot have non-string elements in stderrLevels Array/);
		});
	});

	it('{ stderrLevels: [\'info\', \'warn\'] } logs to them appropriately', assertStderrLevels(
		transports.stderrLevels,
		['info', 'warn']
	));

	require('./abstract-transport')({
		name: '',
		Transport: winstonConsole,
	});
});


<|start_filename|>src/logblock.js<|end_filename|>
/**
 * @module logblock
 * @fileoverview logblock class to create a logblock instance
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

// dependency modules
const shortid = require('shortid');
const StackTrace = require('stack-trace');
const _ = require('lodash');
// our own modules
const Log = require('./log');

/**
 * Logblock class
 */
class Logblock extends Log {
	/**
	 * @param {string} data.name 		logblock name
	 * @param {string} data.context log context
	 * @param {string} data.source  log source
	 */
	constructor(data) {
		super();
		let name, context, source;
		if (!_.isEmpty(data)) {
			if (typeof data == 'string') {
				name = data;
			} else {
				name = data.name;
				context = data.context;
				source = data.source;
			}
		}
		let logblockId = shortid.generate();
		if (!_.isEmpty(name)) {
			name = name.replace('module.exports.', '');
			this.name = `${name}-${logblockId}`;
		} else {
			let strace = StackTrace.get()[1];
			let logblockName = strace.getMethodName() || strace.getFunctionName() || strace.getFileName();
			this.name = `${logblockName}-${logblockId}`;
		}
		if (context) this.context = context;
		if (source) this.source = source;
	}

	/**
	 * set log args
	 * @param {array} args log args
	 * @return {array} return args with new meta
	 */
	_setLogMeta(args){
		let context = this.context;
		let source = this.source;
		let meta = args[args.length - 1];
		if (typeof meta == 'object') {
			args[args.length - 1].logblock = meta.logblock || this.name;
			args[args.length - 1].context = meta.context || context;
			args[args.length - 1].source = meta.source || source;
		} else {
			args[args.length] = {
				logblock: this.name,
				context,
				source,
			};
		}
		return args;
	}

	//add wrapper functions for levels
	debug (...args) {
		args = this._setLogMeta(args);
		return super.debug(...args);
	}
	info (...args) {
		args = this._setLogMeta(args);
		return super.info(...args);
	}
	notice (...args) {
		args = this._setLogMeta(args);
		return super.notice(...args);
	}
	warning (...args) {
		args = this._setLogMeta(args);
		return super.warning(...args);
	}
	error (...args) {
		args = this._setLogMeta(args);
		return super.error(...args);
	}
	critical (...args) {
		args = this._setLogMeta(args);
		return super.critical(...args);
	}
	alert (...args) {
		args = this._setLogMeta(args);
		return super.alert(...args);
	}
	emergency (...args) {
		args = this._setLogMeta(args);
		return super.emergency(...args);
	}

	/**
	 * End Request Logging wrapper
	 * @param  {string}  method       method used
	 * @param  {object}  err          error on request
	 * @param  {object}  httpResponse httpResponse
	 * @param  {object}  body         body Response
	 * @param  {boolean} [api=true]   is the url called part of a private API
	 * @param  {boolean} [json=false] is the body response a json
	 * @param  {string}  logblock		logblock value
	 */
	endRequestLogging(url, method, err, httpResponse, body, api = true, json = false) {
		super._endRequestLogging(url, method, err, httpResponse, body, api, json, this.name);
	}

	/**
	 * Call Request Logging wrapper
	 * @param  {string}  url        url called
	 * @param  {string}  method     method used
	 * @param  {object}  form       body sent to request
	 * @param  {boolean} [api=true] is the url called part of a private API
	 * @param  {string}  logblock		logblock value
	 */
	callRequestLogging(url, method, form, api = true) {
		super._callRequestLogging(url, method, form, api, this.name);
	}
}

module.exports = Logblock;


<|start_filename|>src/logger.js<|end_filename|>
/**
 * @module logger
 * @fileoverview logger class to create a logger instance
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

// winston helpers
const { createLogger, format, addColors } = require('winston');
const { combine, prettyPrint, colorize} = format;
// dependency modules
const shortid = require('shortid');
const _ = require('lodash');
const fs = require('fs');
const EventEmitter = require('events');
const StackTrace = require('stack-trace');
// our own modules
const winstonMongo = require('./transports/winston-mongodb').MongoDB;
const winstonMail = require('./transports/winston-mail').Mail;
const winstonConsole = require('./transports/winston-console');
const logTypes = require('./helpers/logTypes');
const {levels, lowestLevel, colors, levelFromStatus} = require('./helpers/levelsSettings');
const server = require('./api/server');
const ansi = require('./helpers/ansi.js');
const Logblock = require('./logblock');
const Log = require('./log');


let instance = null;
class Logger {
	constructor() {
		if (!instance) {
			this.logTypes = logTypes;
			instance = this;
		}
		return instance;
	}

	/**
   * clear:
   * destroy logger on next instantiation
   */
	clear(_this) {
		let self = this || _this;
		//process.removeListener('uncaughtException', self.logExceptions);
		if (self.mailTransport) self.mailTransport.removeAllListeners();
		if (self.dbTransport) self.dbTransport.removeAllListeners();
		instance = null;
	}

	/**
   * init logger:
   * create winston logger with console, mongodb and mail transports,
   * and add colors to log console levels
   * @param  {object} options logger mongo transport options
   */
	init (options){
		this.options = (options || {});

		//create a log directory
		if(!this.options.logDir){
			throw new Error('You need to specify a log directory in logDir property');
		}
		this.logDir = this.options.logDir;
		if (!fs.existsSync(this.logDir)){
			fs.mkdirSync(this.logDir);
		}

		this.level = process.env.LOG_LEVEL || lowestLevel;

		//create console transport
		const consoleTransport = new winstonConsole({
			level: this.level,
			format: combine(
				colorize(),
				prettyPrint(),
				format.splat(),
				format.simple()
			),
			handleExceptions: false,
		});
		this.consoleTransport = consoleTransport;
		let transports = [consoleTransport];

		//create mongo transport if wanted
		if(!_.isEmpty(this.options.dbSettings)) {
			this.dbLevel = process.env.DB_LOG_LEVEL || lowestLevel;

			let dbSettings = this.options.dbSettings;

			const mongoTransport = new winstonMongo(Object.assign({}, dbSettings, {
				level: this.dbLevel,
				keepAlive: 1000,
				safe: true,
				nativeParser: true,
				decolorize: true,
				handleExceptions: false,
			}));
			this.dbTransport = mongoTransport;

			transports.push(mongoTransport);
		}

		//create email transport if wanted
		if(!_.isEmpty(this.options.mailSettings)) {
			this.mailLevel = process.env.MAIL_LOG_LEVEL || lowestLevel;

			Object.assign(this.options.mailSettings, {
				level: this.mailLevel,
				handleExceptions: false,
			});
			const mailTransport = new winstonMail(this.options.mailSettings);
			this.mailTransport = mailTransport;

			transports.unshift(mailTransport);
		}
		//create winston logger
		this.logger = createLogger({
			levels,
			transports,
			exitOnError: false,
		});
		//add level colors
		addColors({
			levels,
			colors,
		});

		//set log
		let log = new Log();
		this.Log = log;
		//set logblock
		this.Logblock = Logblock;


		if (this.dbTransport && process.env.APP_ENV != 'test') {
			//launch express logging api
			server(this, this.options.api);
		}

		//log uncaughtExceptions
		if(process.env.APP_ENV != 'test')
			process.once('uncaughtException', err => this._logExceptions(this, err));
	}

	/**
   * log uncaught exceptions
   */
	_logExceptions(self, err) {
		console.log(err);
		let noMongoLog = false;
		if (err instanceof Error && err.name == 'MongoError') {
			noMongoLog = true;
		}
		let msg = 'Server is down.';
		let logExpected = 1;
		let logEmitted = 0;
		let exitEmitter = new EventEmitter();
		exitEmitter.on('exit', () => {
			if(logEmitted == logExpected)
				process.exit(1);
		});

		if (self.mailTransport) {
			logExpected++;
			self.mailTransport.on('logged', info => {
				console.log('super-logger: mailTransport message', info);
				if (info.message == msg) {
					logEmitted++;
					process.nextTick(() => { exitEmitter.emit('exit'); });
				}
			});
		}
		if (self.dbTransport && !noMongoLog) {
			logExpected++;
			self.dbTransport.on('logged', info => {
				if (info.message == msg) {
					logEmitted++;
					process.nextTick(() => { exitEmitter.emit('exit'); });
				}
			});
		}

		self.logger.emergency(msg, {
			context: 'GENERAL',
			logblock: 'uncaughtException-' + shortid.generate(),
			err,
			noMongoLog,
		});

		self.consoleTransport.on('logged', info => {
			if (info.message == msg) {
				logEmitted++;
				process.nextTick(() => { exitEmitter.emit('exit'); });
			}
		});

	}

	/**
   * expresse Middleware:
   * a Middleware for express to log called routes in a morgan style logging
   */
	expressLogging() {
		const self = this;
		return (req, res, next) => {
			let currentUrl = req.originalUrl || req.url,
				startTime = new Date(),
				uid = shortid.generate();
			let logblock = `${req.url}-${req.method}-${uid}`;
			let logMeta = {
				context: 'EXPRESS',
				type: this.logTypes.REST_SERVER,
				logblock,
			};
			//log on call
			self.logger.info('EXPRESS CALL %s: %s', currentUrl, req.method, logMeta);
			//log params
			if (!_.isEmpty(req.params)) {
				let logMetaParams = Object.assign({}, logMeta, req.params);
				self.logger.info('Params: ', logMetaParams);
			}

			//log query
			if (!_.isEmpty(req.query)) {
				let logMetaQuery = Object.assign({}, logMeta, req.query);
				self.logger.info('Query: ', logMetaQuery);
			}

			//log body
			if (!_.isEmpty(req.body)) {
				let logMetaBody = Object.assign({}, logMeta, req.body);
				self.logger.info('Body Request: ', logMetaBody);
			} else {
				self.logger.info('Body Request is empty ', logMeta);
			}
			let end = res.end;
			res.end = function (chunk, encoding) {
				//log response status and delay
				res.responseTime = (new Date()) - startTime;
				res.end = end;
				res.end(chunk, encoding);
				req.url = req.originalUrl || req.url;
				let statusColor = 'green';
				if (res.statusCode >= 500) statusColor = 'red';
				else if (res.statusCode >= 400) statusColor = 'yellow';
				else if (res.statusCode >= 300) statusColor = 'cyan';
				let msg = ansi.grey(`${req.method} ${req.url}`) +
          ansi[statusColor](` ${res.statusCode} `) +
          ansi.grey(`${res.responseTime}ms`);
				self.logger.log(levelFromStatus(res), msg, logMeta);
				//log response body
				if (!_.isEmpty(res.body)) {
					let logMetaResponseBody = Object.assign({}, logMeta, res.body);
					self.logger.info('Response Body: ', logMetaResponseBody);
				}
			};
			next();
		};
	}

	/**
   * log websocket event
   * @param  {object} io the io socket object
   */
	wsLogging(io){
		io.on('connection', (socket) => {
			let me = socket;
			let onevent = socket.onevent;
			socket.onevent = function(packet) {
				let args = packet.data || [];
				onevent.call(this, packet); // original call
				packet.data = ['*'].concat(args);
				onevent.call(this, packet); // additional call to catch-all
			};
			socket.on('*', (e, data) => {
				let uid = shortid.generate();
				let logblock = `${e}-${uid}`;
				let logMeta = {
					type: this.logTypes.WS,
					logblock,
					context: 'WEBSOCKET',
				};
				this.logger.info('The event %s has been called!', ansi.cyan(e), logMeta);
				Object.assign(logMeta, {data});
				this.logger.info('Event Body: ', logMeta);
			});
		});
	}

	/**
   * Mongo query to get saved logs
   * @param  {Object} [options={}] query options
   */
	_listLog(options = {}){
		return new Promise ((resolve, reject) => {
			this.dbTransport.query(options, (err, results) => {
				if(err) {
					reject(err);
				} else {
					resolve(results);
				}
			});
		});
	}


}

module.exports = Logger;


<|start_filename|>src/transports/winston-console.js<|end_filename|>
/**
  * @module 'winston-console'
  * @fileoverview Transport for outputting to the console
  * @license MIT
  * @author <EMAIL> (<NAME>)
  * @author <EMAIL> (<NAME>)
 */

const os = require('os');
const util = require('util');
const moment = require('moment');
const { LEVEL } = require('triple-beam');
const TransportStream = require('winston-transport');
const helpers = require('./helpers');
const {colors} = require('../helpers/levelsSettings');
const _ = require('lodash');
const ansi = require('../helpers/ansi.js');

//
// ### function Console (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Console transport object responsible
// for persisting log messages and metadata to a terminal or TTY.
//
var Console = module.exports = function (options) {
	options = options || {};
	TransportStream.call(this, options);
	this.stderrLevels = getStderrLevels(options.stderrLevels, options.debugStdout);
	this.eol = os.EOL;

	//
	// Convert stderrLevels into an Object for faster key-lookup times than an Array.
	//
	// For backwards compatibility, stderrLevels defaults to ['error', 'debug']
	// or ['error'] depending on whether options.debugStdout is true.
	//
	function getStderrLevels(levels, debugStdout) {
		var defaultMsg = 'Cannot have non-string elements in stderrLevels Array';
		if (debugStdout) {
			if (levels) {
				//
				// Don't allow setting both debugStdout and stderrLevels together,
				// since this could cause behaviour a programmer might not expect.
				//
				throw new Error('Cannot set debugStdout and stderrLevels together');
			}

			return stringArrayToSet(['error'], defaultMsg);
		}

		if (!levels) {
			return stringArrayToSet(['error', 'debug'], defaultMsg);
		} else if (!(Array.isArray(levels))) {
			throw new Error('Cannot set stderrLevels to type other than Array');
		}

		return stringArrayToSet(levels, defaultMsg);
	}
};

//
// Inherit from `winston.Transport`.
//
util.inherits(Console, TransportStream);

//
// Expose the name of this Transport on the prototype
//
Console.prototype.name = 'console';

//
// ### function log (info)
// #### @info {Object} **Optional** Additional metadata to attach
// Core logging method exposed to Winston.
//
Console.prototype.log = function (info, callback) {
	var self = this;

	let meta;
	if (info.splat) {
		meta = Object.assign({}, info.meta);
	} else {
		meta = Object.assign({}, info);
		delete meta.message;
		delete meta.level;
	}
	meta.context = meta.context || 'GENERAL';

	setImmediate(function () {
		self.emit('logged', info);
	});

	let extras = helpers.prepareMetaData({context: meta.context, logblock: meta.logblock});
	delete meta.logblock;
	delete meta.context;
	delete meta.noMongoLog;
	delete meta.type;
	delete meta.splat;
	let message = moment().format('YYYY/MM/DD_HH:mm:ss') + ' ';
	message += ansi[colors[info[LEVEL]]](info[LEVEL]) + ': ';
	message += info.message.replace(/^\s+|\s+$/g, '');

	let metaObject = helpers.prepareMetaData(meta);
	message += _.isEmpty(metaObject) ? '': '\n';
	message += _.isEmpty(metaObject) ? '': JSON.stringify(metaObject);
	message += _.isEmpty(extras) ? '': '\n';
	message += _.isEmpty(extras) ? '': JSON.stringify(extras);

	if (this.stderrLevels[info[LEVEL]]) {
		process.stderr.write(message + this.eol + this.eol);
    if (callback) { callback(); } // eslint-disable-line
		return;
	}

	process.stdout.write(message + this.eol + this.eol);
  if (callback) { callback(); } // eslint-disable-line
};

//
// ### function stringArrayToSet (array)
// #### @strArray {Array} Array of Set-elements as strings.
// #### @errMsg {string} **Optional** Custom error message thrown on invalid input.
// Returns a Set-like object with strArray's elements as keys (each with the value true).
//
function stringArrayToSet(strArray, errMsg) {
	errMsg = errMsg || 'Cannot make set from Array with non-string elements';

	return strArray.reduce(function (set, el) {
		if (typeof el !== 'string') { throw new Error(errMsg); }
		set[el] = true;
		return set;
	}, {});
}


<|start_filename|>test/log.js<|end_filename|>
/**
 * @module test-log
 * @fileoverview test file to test log class
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

const Logger = require('../src/logger');

describe('log', () => {
	let logger = new Logger();
	let options = {
		logDir: './logs',
	};
	logger.init(options);
	let logging = logger.Log;

	beforeEach(() => {
		logger = new Logger();
	});

	require('./abstract-log')({
		name: 'log',
		type: logging,
		logger,
	});

	afterEach(() => {
		logger.clear(logger);
	});
});


<|start_filename|>src/transports/winston-mail.js<|end_filename|>
/**
  * @module 'winston-email'
  * @fileoverview Transport to send to email
  * @license MIT
  * @author <EMAIL> (<NAME>)
 */

const util = require('util');
const os = require('os');
const { LEVEL } = require('triple-beam');
const moment = require('moment');
const _ = require('lodash');
const nodemailer = require('nodemailer');
const {lowestLevel, levels} = require('../helpers/levelsSettings');
const helpers = require('./helpers');
let Transport = require('winston-transport');

/**
 * @constructs Mail
 * @param {object} options Hash of options.
 */
let Mail = exports.Mail = function(options) {
	options = options || {};
	Transport.call(this, options);

	if (!options.to) {
		throw new Error('super-logger(winston-email) requires \'to\' property');
	}

	if (!options.transportOptions) {
		throw new Error('super-logger(winston-email) requires \'transportOptions\'');
	}

	this.to = options.to;
	this.from = options.from || 'super-logger@' + os.hostname();
	this.subject = options.subject || '';
	this.html = options.html || false; // Send mail in html format
	this.formatter = options.formatter || false;
	this.level = options.level;

	this.transporter = nodemailer.createTransport(
		options.transportOptions
	);
};

/**
 * Inherit from `winston.Transport`.
 */
util.inherits(Mail, Transport);

/**
 * Define a getter so that `winston.transports.Mail`
 * is available and thus backwards compatible.
 */
Transport.Mail = Mail;

/**
 * Core logging method exposed to Winston. Metadata is optional.
 * @param {object} info **Optional** Additional metadata to attach
 * @param {Function} cb Continuation to respond to when complete.
 */
Mail.prototype.log = function(info, cb) {
	var self = this;
	let level = info[LEVEL];
	if (levels[level] != levels[lowestLevel] && levels[level] >= levels[this.level]){
		if(cb) cb(null, true);
		return;
	}

	let body;
	let env  = (process.env.APP_ENV || 'prod').toUpperCase();
	let subject = `${env} ${level} ${this.subject}: `;
	if (this.formatter) {
		body = this.formatter(info);
	} else {
		let meta;
		if (info.splat) {
			meta = Object.assign({}, info.meta);
		} else {
			meta = Object.assign({}, info);
			delete meta.message;
			delete meta.level;
		}
		meta.context = meta.context || 'GENERAL';

		let extras = helpers.prepareMetaData({context: meta.context, logblock: meta.logblock});
		delete meta.logblock;
		delete meta.context;
		delete meta.noMongoLog;
		delete meta.type;
		delete meta.splat;
		let message = moment().format('YYYY/MM/DD_HH:mm:ss') + ' ';
		message += info[LEVEL] + ': ';
		let msgSubject = info.splat ? util.format(info.message, ...info.splat): info.message;
		subject += msgSubject;
		message += msgSubject;
		message = message.replace(/\u001b\[[0-9]{1,2}m/g, '');
		let metaObject = helpers.prepareMetaData(meta, true);

		message += _.isEmpty(metaObject) ? '': '\n';
		message += _.isEmpty(metaObject) ? '': JSON.stringify(metaObject);
		message += _.isEmpty(extras) ? '': '\n';
		message += _.isEmpty(extras) ? '': JSON.stringify(extras);
		body = message;
	}

	var mailOptions = {
		from: this.from,
		to: this.to,
		subject: subject.replace(/\u001b\[[0-9]{1,2}m/g, ''),
		text: body,
	};

	// Send mail as html.
	if (this.html) {
		mailOptions.html = body;
	}

	// don't send mail in test env
	if (process.env.APP_ENV == 'test') {
		self.emit('logged', info);
		if(cb) cb(null, true);
		return true;
	}
	// send mail with defined transport object
	this.transporter.sendMail(mailOptions, (error, data) => {
		if (error) {
			self.emit('error', error);
			return console.error(error);
		}
		self.emit('logged', info);
		try {
			if(cb) cb(null, true);
			return true;
		} catch (e) {
			console.error('super-logger(winston-email), Failed to send Email: ', e);
		}
	});
};


<|start_filename|>test/routes.js<|end_filename|>
/**
 * @module test-logger
 * @fileoverview test file to test routes
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

const express = require('express');
const Logger = require('../src/logger');
const moment = require('moment');
const { routes } = require('../src/api/routes');
const winstonMongo = require('../src/transports/winston-mongodb').MongoDB;

const supertest = require('supertest');
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;

require('dotenv-extended').load();

describe('api routes', () => {
	let logger, request;
	before(() => {
		logger = new Logger();
		let port = 3025;
		logger.init({
			dbSettings: {
				db: 'mongodb://localhost/test',
			},
			api:{ port },
			logDir: './logs',
		});
		let app = express();
		app.listen(port);
		request = supertest(app);
		routes(logger, app, '/test');
	});


	it('should return logs from database', (done) => {
		const page = 0;
		const pageSize = 10;
		const start = page * pageSize;
		let fields = ['content', 'timestamp', 'context', 'logblock', 'type', 'level'];
		const options = {
			from: moment().subtract(30, 'days').toDate(),
			until: moment().toDate(),
			limit: pageSize,
			start,
			order: 'desc',
			fields,
		};
		logger._listLog(options)
			.then(results => {
				request
					.get('/test')
					.expect(200)
					.expect('Content-Type', /json/)
					.end(function(err, res) {
						expect(err).to.be.null;
						expect(res.body).to.be.an('array').that.have.lengthOf(results.length);
						res.body.every((log, i) => {
							fields.push('_id');
							expect(log).to.have.all.keys(fields);
							expect(JSON.stringify(log))
								.to.be.equal(JSON.stringify(results[i]));
						});
						done();
					});
			});
	});

	it('should return logs by block from database', (done) => {
		const page = 0;
		const pageSize = 10;
		const start = page * pageSize;
		let fields = ['content', 'timestamp', 'context', 'type', 'level'];
		const options = {
			from: moment().subtract(30, 'days').toDate(),
			until: moment().toDate(),
			limit: pageSize,
			start,
			order: 'asc',
			fields,
			group: 'logblock',
		};
		logger._listLog(options)
			.then(results => {
				request
					.get('/test/by-block')
					.expect(200)
					.expect('Content-Type', /json/)
					.end(function(err, res) {
						expect(err).to.be.null;
						expect(res.body).to.be.an('array').that.have.lengthOf(results.length);
						res.body.every(i => expect(i).to.have.all.keys('logs', '_id'));
						fields.push('_id');
						fields.push('logblock');
						res.body[0].logs.every((log, i) => {
							expect(log.logblock).to.be.equal(res.body[0].logs[0].logblock);
							expect(log).to.have.all.keys(fields);
							expect(JSON.stringify(log))
								.to.be.equal(JSON.stringify(results[0].logs[i]));
						});
						done();
					});
			});
	});

});


<|start_filename|>src/transports/helpers.js<|end_filename|>
/**
 * @module helpers
 * @fileoverview Helpers for winston-mongodb
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
'use strict';
const util = require('util');
const ObjectID = require('mongodb').ObjectID;
const cycle = require('cycle');


//
// ### function clone (obj)
// #### @obj {Object} Object to clone.
// Helper method for deep cloning pure JSON objects
// i.e. JSON objects that are either literals or objects (no Arrays, etc)
//
function clone (obj) {
	if (obj instanceof Error) {
		// With potential custom Error objects, this might not be exactly correct,
		// but probably close-enough for purposes of this lib.
		var copy = { message: obj.message };
		Object.getOwnPropertyNames(obj).forEach(function (key) {
			copy[key] = obj[key];
		});

		return copy;
	}
	else if (!(obj instanceof Object)) {
		return obj;
	}
	else if (obj instanceof Date) {
		return new Date(obj.getTime());
	}
	return cloneRefType(cycle.decycle(obj));
}

function cloneRefType(obj) {
	//
	// We only need to clone reference types (Object)
	//
	var copy = Array.isArray(obj) ? [] : {};

	for (var i in obj) {
		if (Array.isArray(obj[i])) {
			copy[i] = obj[i].slice(0);
		}
		else if (obj[i] instanceof Buffer) {
			copy[i] = obj[i].slice(0);
		}
		else if (typeof obj[i] != 'function') {
			copy[i] = obj[i] instanceof Object ? clone(obj[i]) : obj[i];
		}
		else if (typeof obj[i] === 'function') {
			copy[i] = obj[i];
		}
	}

	return copy;
}

/**
 * Prepares metadata to store into database.
 * @param {*} meta Metadata
 * @return {*}
 */
exports.prepareMetaData = (meta, pretty = false) =>{
	if (typeof meta === 'object' && meta !== null) {
		// Convert meta to string if it is an error.
		if (meta instanceof Error) {
			meta = {
				message: meta.message,
				name: meta.name,
				stack: meta.stack,
			};
		}
		meta = makeObjectNonCircular(meta);
		cleanFieldNames(meta);
	}
	meta = clone(meta);

	if (pretty && meta !== null && meta !== undefined && (typeof meta !== 'object' || Object.keys(meta).length > 0))
		meta = util.inspect(meta, {depth: 5}); // Add some pretty printing.

	return meta;
};

/**
 * Removes unexpected characters from metadata field names.
 * @param {Object} object Object to clean
 */
function cleanFieldNames(object) {
	for (let field in object) {
		if (!Object.prototype.hasOwnProperty.call(object, field)) {
			continue;
		}
		let value = object[field];
		if (field.includes('.') || field.includes('$')) {
			delete object[field];
			object[field.replace(/\./g, '[dot]').replace(/\$/g, '[$]')] = value;
		}
		if (typeof value === 'object') {
			cleanFieldNames(value);
		}
	}
}


/**
 * Cleans object from circular references, replaces them with string
 * '[Circular]'
 * @param {Object} node Current object or its leaf
 * @param {Array=} opt_parents Object's parents
 */
function makeObjectNonCircular(node, opt_parents) {
	opt_parents = opt_parents || [];
	opt_parents.push(node);
	let copy = Array.isArray(node) ? [] : {};
	if (node instanceof Error) {
		// This is needed because Error's message, name and stack isn't accessible when cycling through properties
		copy = {message: node.message, name: node.name, stack: node.stack};
	}
	for (let key in node) {
		if (!Object.prototype.hasOwnProperty.call(node, key)) {
			continue;
		}
		let value = node[key];
		if (typeof value === 'object' && !(value instanceof ObjectID) && !(value instanceof Date)) {
			if (opt_parents.indexOf(value) === -1) {
				copy[key] = makeObjectNonCircular(value, opt_parents);
			} else {
				copy[key] = '[Circular]';
			}
		} else {
			copy[key] = value;
		}
	}
	opt_parents.pop();
	return copy;
}


<|start_filename|>test/logblock.js<|end_filename|>
/**
 * @module test-logger
 * @fileoverview test file to test logblock
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
const Logger = require('../src/logger');
const {levels} = require('../src/helpers/levelsSettings');
const Logblock = require('../src/logblock');
const StackTrace = require('stack-trace');

const assert = require('assert');
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;


describe('logblock', () => {
	let logger = new Logger();
	let options = {
		logDir: './logs',
	};
	logger.init(options);
	let logging = new logger.Logblock('test-logblock');

	beforeEach(() => {
		logger = new Logger();
		logger.init(options);
	});

	describe('constructor', () => {
		it('should be able to accept no params', () => {
			let logblock, strace;
			expect(() => {
				strace = StackTrace.get()[0];
				logblock = new logger.Logblock();
			}).to.not.throw();
			let logblockName = strace.getMethodName() || strace.getFunctionName() || strace.getFileName();
			expect(logblock.name.includes(logblockName)).to.be.true;
		});

		it('should be able to accept string params', () => {
			let logblock;
			let logblockName = 'test-logblock';
			expect(() => {
				logblock = new logger.Logblock(logblockName);
			}).to.not.throw();
			expect(logblock.name.includes(logblockName)).to.be.true;
		});

		it('should able to accept object params', () => {
			let logblock;
			let logblockName = 'test-logblock';
			expect(() => {
				logblock = new logger.Logblock({
					name: logblockName,
					context: 'TEST',
					source: 'TEST',
				});
			}).to.not.throw();
			expect(logblock.name.includes(logblockName)).to.be.true;
			expect('TEST').to.be.equal(logblock.context);
			expect('TEST').to.be.equal(logblock.source);
		});
	});
	describe('._setLogMeta()', () => {
		it('should return array of args with constructor value (without meta)', () => {
			let name = 'test-logblock',
				context = 'TEST',
				source = 'TEST';
			let logblock = new logger.Logblock({
				name,
				context,
				source,
			});
			let result = logblock._setLogMeta(['text']);
			expect(result).to.be.an('array');
			expect(result[1].context).to.be.equal(context);
			expect(result[1].source).to.be.equal(source);
			expect(result[1].logblock.includes(name)).to.be.true;
		});

		it('should return array of args with constructor value (with meta)', () => {
			let name = 'test-logblock',
				context = 'TEST',
				source = 'TEST';
			let logblock = new logger.Logblock({
				name,
				context,
				source,
			});
			let result = logblock._setLogMeta(['text', {context: 'TEST2'}]);
			expect(result).to.be.an('array');
			expect(result[1].context).to.not.be.equal(context);
			expect(result[1].source).to.be.equal(source);
			expect(result[1].logblock.includes(name)).to.be.true;
		});
	});

	require('./abstract-log')({
		name: 'logblock',
		type: logging,
		logger,
	});

	afterEach(() => {
		logger.clear(logger);
	});
});


<|start_filename|>src/helpers/ansi.js<|end_filename|>
/**
 * @module ansi
 * @fileoverview getting ansi color depending on what the terminal supports
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
const chalk = require('chalk');
const supportsColor = require('supports-color');

let ansi = new chalk.constructor({level: 0});
if (supportsColor.stderr.has16m) {
	ansi = new chalk.constructor({level: 3});
} else if (supportsColor.stdout.has256) {
	ansi = new chalk.constructor({level: 2});
} else if (supportsColor.stdout) {
	ansi = new chalk.constructor({level: 1});
}

module.exports = ansi;


<|start_filename|>test/server.js<|end_filename|>
/**
 * @module test-logger
 * @fileoverview test file to test server
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */
const server = require('../src/api/server');
const express = require('express');
const Logger = require('../src/logger');

const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;


describe('api server', () => {
	let logger;
	before(() => {
		logger = new Logger();
		logger.init({
			logDir: './logs',
		});
	});

	it('should throw an error on no api settings', () => {
		expect(() => {
			server(logger);
		}).to.throw('You need to pass a port or an existing express app');
	});

	it('should calls app routes if express app passed', () => {
		let app = express();
		let routeSpy = sinon.stub(app, 'get');
		server(logger, {
			appExpress: app,
			logPrefix: '/logs',
		});
		expect(routeSpy.called).to.be.true;
	});

	it('should throw error listen to port if app is already listening and port is passed', () => {
		let port = 3052;
		let app = express();
		app.listen(port);
		expect(() => {
			server(null, {
				logPrefix: '/logs',
				port: 3052,
			});
		}).to.throw();
	});
});


<|start_filename|>test/abstract-transport.js<|end_filename|>
/**
 * @module 'test-winston-abstract-transport'
 * @fileoverview test file to test winston-transport
 * @license MIT
 * @author <EMAIL> (<NAME>)
 * @author <EMAIL> (<NAME>)
 */

const assert = require('assert');
const { LEVEL, MESSAGE } = require('triple-beam');
const shortid = require('shortid');

module.exports = (options) => {
	options = options || {};

	var Transport = options.Transport;
	var name = Transport.name || options.name;
	var settings = options.settings || {};
	var instance;
	beforeEach(() => {
		instance = new Transport(settings);
	});

	describe(name + ' .log()', () => {
		it('should be present', () => {
			assert.ok(instance.log);
			assert.equal('function', typeof instance.log);
		});

		it('(with no callback) should return true', () => {
			var info = {
				[LEVEL]: 'debug',
				level: 'debug',
				message: 'foo',
				context: 'TEST',
				logblock: 'test-block-' + shortid.generate(),
			};

			info[MESSAGE] = JSON.stringify(info);
			var result = instance.log(info);
			assert(true, result);
		});

		it('(with callback) should return true', (done) => {
			var info = {
				[LEVEL]: 'debug',
				level: 'debug',
				message: 'foo',
				context: 'TEST',
				logblock: 'test-block-' + shortid.generate(),
			};

			info[MESSAGE] = JSON.stringify(info);
			var result = instance.log(info, () => {
				assert(true, result);
				done();
			});
		});
	});

	describe(name + ' events', () => {
		it('should emit the "logged" event', (done) => {
			instance.once('logged', (info) => {
				done();
			});

			var info = {
				[LEVEL]: 'debug',
				level: 'debug',
				message: 'foo',
				context: 'TEST',
				logblock: 'test-block-' + shortid.generate(),
			};

			info[MESSAGE] = JSON.stringify(info);
			instance.log(info);
		});
	});

	afterEach((done) => {
		if (options.afterEach) {
			return options.afterEach(options, done);
		}
		done();
	});

	after((done) => {
		if (options.afterEach) {
			return options.afterEach(options, done);
		}
		done();
	});
};


<|start_filename|>src/helpers/logTypes.js<|end_filename|>
/**
 * @module logTypes
 * @fileoverview log types
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

//log types definitions
module.exports = {
	BASE: 0, //basic logging
	REST_SERVER: 1, //morgan like logging
	REST_CLIENT: 2, //requests logging
	WS: 3, //ws event calls logging
};


<|start_filename|>test/logger.js<|end_filename|>
/**
 * @module test-logger
 * @fileoverview test file to test logger class
 * @license MIT
 * @author <EMAIL> (<NAME>)
 */

const express = require('express');
const bodyParser = require('body-parser');
const Logger = require('../src/logger');
const logTypes = require('../src/helpers/logTypes');
const {levels, lowestLevel, levelFromResStatus} = require('../src/helpers/levelsSettings');
const request = require('request');
const _ = require('lodash');

const sinon = require('sinon');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;

require('dotenv-extended').load();
process.setMaxListeners(0);
describe('logger', () => {
	let app, api, logger;
	let mailSettings = {
		transportOptions: {
			host: process.env.MAIL_HOST,
			port: process.env.MAIL_PORT,
			auth: {
				user: process.env.MAIL_USER,
				pass: <PASSWORD>,
			},
		},
		to: process.env.MAIL_TO,
		from: process.env.MAIL_USER,
	};
	let dbSettings = {
		db: 'mongodb://localhost/test',
	};

	beforeEach(() => {
		logger = new Logger();
	});

	before(() => {
		app = express();
		app.use(bodyParser.urlencoded({
			extended: true,
		}));
		app.use(bodyParser.json());

		api = {
			appExpress: app,
			logPrefix: '/logs',
		};
	});

	describe('logger initiation', () => {
		describe('sucessful use cases of logger initiation', () => {
			let options;

			beforeEach(() => {
				expect(logger).to.have.property('logTypes');
				expect(logger.logTypes).to.eql(logTypes);
			});

			it('should succeed to init logger with no db or mail transport', () => {
				options = {
					logDir: './logs',
					api,
				};
				logger.init(options);

				expect(logger).to.not.have.property('dbLevel');
				expect(logger).to.not.have.property('dbTransport');
				expect(logger).to.not.have.property('mailLevel');
			});

			it('should succeed to init logger with db transport', () => {
				options = {
					logDir: './logs',
					dbSettings,
					api,
				};
				logger.init(options);

				expect(logger).to.have.property('dbLevel');
				if(process.env.DB_LOG_LEVEL) {
					expect(logger.dbLevel).to.equal(process.env.DB_LOG_LEVEL);
				} else {
					expect(logger.dbLevel).to.equal(lowestLevel);
				}
				expect(logger).to.have.property('dbTransport');

				expect(logger).to.not.have.property('mailLevel');
			});

			it('should succeed to init logger with mail transport', () => {
				options = {
					logDir: './logs',
					api,
					mailSettings,
				};
				logger.init(options);

				expect(logger).to.not.have.property('dbLevel');
				expect(logger).to.not.have.property('dbTransport');

				expect(logger).to.have.property('mailLevel');
				if(process.env.MAIL_LOG_LEVEL) {
					expect(logger.mailLevel).to.equal(process.env.MAIL_LOG_LEVEL);
				} else {
					expect(logger.mailLevel).to.equal(lowestLevel);
				}
			});

			afterEach(() => {
				expect(logger).to.have.property('options');
				expect(logger.options).to.eql(options);

				expect(logger).to.have.property('logDir');
				expect(logger.logDir).to.eql(options.logDir);

				expect(logger).to.have.property('level');
				if(process.env.LOG_LEVEL) {
					expect(logger.level).to.equal(process.env.LOG_LEVEL);
				} else {
					expect(logger.level).to.equal(lowestLevel);
				}
			});
		});

		describe('failing use cases of logger initiation', () => {
			it('should throw an error on no logDir', () => {
				let options = {};
				expect(() => {
					logger.init(options);
				}).to.throw();
			});
		});
	});

	describe('logger clear', () => {
		it('should make winston logger undefined', () => {
			logger.init({
				logDir: './logs',
				api,
			});
			logger.clear(logger);
			logger = new Logger();
			expect(logger.logger).to.be.undefined;
		});
	});

	describe('logger handle Exceptions', () => {
		it('should not log to mongodb transport', (done) => {
			logger.init({
				logDir: './logs',
				dbSettings,
				api,
			});
			process.exit = () => { };
			var eventSpy = sinon.spy();
			setTimeout(() => {
				expect(eventSpy.called).to.be.false;
				done();
			}, 1000);
			logger.dbTransport.on('logged', eventSpy);
			var e = new Error('test');
			e.name = 'MongoError';
			logger._logExceptions(logger, e);
		});

		it('should exit immediatly if no mail transport exist on MongoError', (done) => {
			logger.init({
				logDir: './logs',
				dbSettings,
				api,
			});
			let logEmitted = 0;
			process.exit = () => {
				expect(logEmitted).to.be.equal(0);
				done();
			};
			logger.dbTransport.on('logged', info => {
				logEmitted++;
			});
			var e = new Error('test');
			e.name = 'MongoError';
			logger._logExceptions(logger, e);
		});

		it('should exit after logging if mail and db transport exist', (done) => {
			logger.init({
				logDir: './logs',
				dbSettings,
				mailSettings,
				api,
			});
			let logEmitted = 0;
			process.exit = () => {
				expect(logEmitted).to.be.equal(2);
				done();
			};
			logger.mailTransport.on('logged', info => {
				logEmitted++;
			});
			logger.dbTransport.on('logged', info => {
				logEmitted++;
			});
			logger._logExceptions(logger, 'error');
		});
	});

	// describe('logger expressLogging middleware', () => {
	//   it()
	// });

	describe('logger websocket logging', () => {
		it('should throw an error if not io socket object is passed', () => {
			//logger.clear(logger);
			logger.init({
				logDir: './logs',
				api,
			});
			let io = 'I\'m not a socket object';
			expect(() => {
				logger.wsLogging(io);
			}).to.throw();
		});
	});

	describe('logger listing log from database', () => {
		it('should throw error if no mongo transport', () => {
			//logger.clear(logger);
			logger.init({
				logDir: './logs',
				api,
			});
			expect(logger._listLog()).to.be.rejected;
		});

		it('should resolve an array', (done) => {
			//logger.clear(logger);
			logger.init({
				logDir: './logs',
				dbSettings,
				api,
			});
			logger._listLog()
				.then((res) => {
					expect(res).to.be.an('array');
					done();
				});
		});
	});

	afterEach(() => {
		logger.clear(logger);
	});
});
