From a5a172918e939915c1f1ac21ea4e8db74825bbbd Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Fri, 12 Aug 2016 13:25:24 -0400 Subject: [PATCH] Refactor logging to provide common logger from LoggerAdapter (#2478) * Refactor logging to provide common logger from LoggerAdapter Move logger logic de WinstonLoggerAdapter Further improvements in configuration Use logger instead of getLogger - Removes PLog module Reverts name changes nits * Adds additional logging levels as requirements * Adds tests for logging configuration * removes flaky test * investigate... * further investigation * Adds silent option to disable console output * Restores logs with VERBOSE in tests * Expose controller instead of adapter, reduces method requirements for adapter * Shuffles initializations around * Fix doc * Load cloudCode last to make sure the logger is available * Adds test to make sure we can load an adapter from npm module * extract defaults * Adds defaultMongoURI to defaults * fix defaults values * Proper error for PG failures * Disable flaky test --- spec/AdapterLoader.spec.js | 13 ++ spec/InstallationsRouter.spec.js | 8 +- spec/Logger.spec.js | 53 ++++++++- spec/Subscription.spec.js | 25 ++-- spec/WinstonLoggerAdapter.spec.js | 4 +- spec/helper.js | 3 +- src/Adapters/Files/GridStoreAdapter.js | 5 +- src/Adapters/Logger/LoggerAdapter.js | 10 +- src/Adapters/Logger/WinstonLogger.js | 100 ++++++++++++++++ src/Adapters/Logger/WinstonLoggerAdapter.js | 56 ++------- .../Storage/Mongo/MongoStorageAdapter.js | 3 +- src/Controllers/DatabaseController.js | 2 + src/Controllers/LoggerController.js | 32 +++++ src/Controllers/SchemaCache.js | 3 +- src/LiveQuery/Client.js | 4 +- src/LiveQuery/PLog.js | 5 - src/LiveQuery/ParseCloudCodePublisher.js | 4 +- src/LiveQuery/ParseLiveQueryServer.js | 69 +++++------ src/LiveQuery/ParseWebSocketServer.js | 4 +- src/LiveQuery/SessionTokenCache.js | 8 +- src/LiveQuery/Subscription.js | 8 +- src/ParseServer.js | 94 +++++++-------- src/Routers/FunctionsRouter.js | 2 +- src/cli/cli-definitions.js | 19 +++ src/defaults.js | 31 +++++ src/index.js | 10 +- src/logger.js | 112 +++--------------- src/triggers.js | 2 +- 28 files changed, 396 insertions(+), 293 deletions(-) create mode 100644 src/Adapters/Logger/WinstonLogger.js delete mode 100644 src/LiveQuery/PLog.js create mode 100644 src/defaults.js diff --git a/spec/AdapterLoader.spec.js b/spec/AdapterLoader.spec.js index 250a8a7c..8c2e1aef 100644 --- a/spec/AdapterLoader.spec.js +++ b/spec/AdapterLoader.spec.js @@ -45,6 +45,19 @@ describe("AdapterLoader", ()=>{ done(); }); + it("should instantiate an adapter from npm module", (done) => { + var adapter = loadAdapter({ + module: 'parse-server-fs-adapter' + }); + + expect(typeof adapter).toBe('object'); + expect(typeof adapter.createFile).toBe('function'); + expect(typeof adapter.deleteFile).toBe('function'); + expect(typeof adapter.getFileData).toBe('function'); + expect(typeof adapter.getFileLocation).toBe('function'); + done(); + }); + it("should instantiate an adapter from function/Class", (done) => { var adapter = loadAdapter({ adapter: FilesAdapter diff --git a/spec/InstallationsRouter.spec.js b/spec/InstallationsRouter.spec.js index 60965ff9..b2725a09 100644 --- a/spec/InstallationsRouter.spec.js +++ b/spec/InstallationsRouter.spec.js @@ -5,7 +5,7 @@ var InstallationsRouter = require('../src/Routers/InstallationsRouter').Installa var config = new Config('test'); -describe('InstallationsRouter', () => { +describe_only_db(['mongo'])('InstallationsRouter', () => { it('uses find condition from request.body', (done) => { var androidDeviceRequest = { 'installationId': '12345678-abcd-abcd-abcd-123456789abc', @@ -71,6 +71,9 @@ describe('InstallationsRouter', () => { var results = res.response.results; expect(results.length).toEqual(1); done(); + }).catch((err) => { + fail(JSON.stringify(err)); + done(); }); }); @@ -172,6 +175,9 @@ describe('InstallationsRouter', () => { expect(response.results.length).toEqual(0); expect(response.count).toEqual(2); done(); + }).catch((err) => { + fail(JSON.stringify(err)); + done(); }); }); }); diff --git a/spec/Logger.spec.js b/spec/Logger.spec.js index 37f8f871..a131e0d5 100644 --- a/spec/Logger.spec.js +++ b/spec/Logger.spec.js @@ -1,4 +1,4 @@ -var logger = require('../src/logger'); +var logging = require('../src/Adapters/Logger/WinstonLogger'); var winston = require('winston'); class TestTransport extends winston.Transport { @@ -9,10 +9,55 @@ class TestTransport extends winston.Transport { describe('Logger', () => { it('should add transport', () => { - const testTransport = new (TestTransport)({}); + const testTransport = new (TestTransport)({ + name: 'test' + }); spyOn(testTransport, 'log'); - logger.addTransport(testTransport); - logger.logger.info('hi'); + logging.addTransport(testTransport); + expect(Object.keys(logging.logger.transports).length).toBe(4); + logging.logger.info('hi'); expect(testTransport.log).toHaveBeenCalled(); + logging.removeTransport(testTransport); + expect(Object.keys(logging.logger.transports).length).toBe(3); + }); + + it('should have files transports', (done) => { + reconfigureServer().then(() => { + let transports = logging.logger.transports; + let transportKeys = Object.keys(transports); + expect(transportKeys.length).toBe(3); + done(); + }); + }); + + it('should disable files logs', (done) => { + reconfigureServer({ + logsFolder: null + }).then(() => { + let transports = logging.logger.transports; + let transportKeys = Object.keys(transports); + expect(transportKeys.length).toBe(1); + done(); + }); + }); + + it('should enable JSON logs', (done) => { + // Force console transport + reconfigureServer({ + logsFolder: null, + jsonLogs: true, + silent: false + }).then(() => { + let spy = spyOn(process.stdout, 'write'); + logging.logger.info('hi', {key: 'value'}); + expect(process.stdout.write).toHaveBeenCalled(); + var firstLog = process.stdout.write.calls.first().args[0]; + expect(firstLog).toEqual(JSON.stringify({key: 'value', level: 'info', message: 'hi' })+'\n'); + return reconfigureServer({ + jsonLogs: false + }); + }).then(() => { + done(); + }); }); }); diff --git a/spec/Subscription.spec.js b/spec/Subscription.spec.js index a9f35020..20f1aa5b 100644 --- a/spec/Subscription.spec.js +++ b/spec/Subscription.spec.js @@ -1,10 +1,10 @@ var Subscription = require('../src/LiveQuery/Subscription').Subscription; - +let logger; describe('Subscription', function() { beforeEach(function() { - var mockError = jasmine.createSpy('error'); - jasmine.mockLibrary('../src/LiveQuery/PLog', 'error', mockError); + logger = require('../src/logger').logger; + spyOn(logger, 'error').and.callThrough(); }); it('can be initialized', function() { @@ -62,8 +62,7 @@ describe('Subscription', function() { var subscription = new Subscription('className', { key : 'value' }, 'hash'); subscription.deleteClientSubscription(1, 1); - var PLog =require('../src/LiveQuery/PLog'); - expect(PLog.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalled(); }); it('can delete nonexistent request for one client', function() { @@ -71,8 +70,7 @@ describe('Subscription', function() { subscription.addClientSubscription(1, 1); subscription.deleteClientSubscription(1, 2); - var PLog =require('../src/LiveQuery/PLog'); - expect(PLog.error).toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalled(); expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.get(1)).toEqual([1]); }); @@ -83,8 +81,7 @@ describe('Subscription', function() { subscription.addClientSubscription(1, 2); subscription.deleteClientSubscription(1, 2); - var PLog =require('../src/LiveQuery/PLog'); - expect(PLog.error).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.get(1)).toEqual([1]); }); @@ -96,8 +93,7 @@ describe('Subscription', function() { subscription.deleteClientSubscription(1, 1); subscription.deleteClientSubscription(1, 2); - var PLog =require('../src/LiveQuery/PLog'); - expect(PLog.error).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); expect(subscription.clientRequestIds.size).toBe(0); }); @@ -111,13 +107,8 @@ describe('Subscription', function() { subscription.deleteClientSubscription(2, 1); subscription.deleteClientSubscription(2, 2); - var PLog =require('../src/LiveQuery/PLog'); - expect(PLog.error).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.get(1)).toEqual([1]); }); - - afterEach(function(){ - jasmine.restoreLibrary('../src/LiveQuery/PLog', 'error'); - }); }); diff --git a/spec/WinstonLoggerAdapter.spec.js b/spec/WinstonLoggerAdapter.spec.js index ee2a72d4..fa813756 100644 --- a/spec/WinstonLoggerAdapter.spec.js +++ b/spec/WinstonLoggerAdapter.spec.js @@ -8,7 +8,7 @@ describe('info logs', () => { it("Verify INFO logs", (done) => { var winstonLoggerAdapter = new WinstonLoggerAdapter(); - winstonLoggerAdapter.info('testing info logs', () => { + winstonLoggerAdapter.log('info', 'testing info logs', () => { winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, @@ -29,7 +29,7 @@ describe('info logs', () => { describe('error logs', () => { it("Verify ERROR logs", (done) => { var winstonLoggerAdapter = new WinstonLoggerAdapter(); - winstonLoggerAdapter.error('testing error logs', () => { + winstonLoggerAdapter.log('error', 'testing error logs', () => { winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, diff --git a/spec/helper.js b/spec/helper.js index e9095d61..37d169f9 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -45,6 +45,7 @@ var defaultConfiguration = { webhookKey: 'hook', masterKey: 'test', fileKey: 'test', + silent: !process.env.VERBOSE, push: { 'ios': { cert: 'prodCert.pem', @@ -352,8 +353,6 @@ global.describe_only_db = db => { } } -// LiveQuery test setting -require('../src/LiveQuery/PLog').logLevel = 'NONE'; var libraryCache = {}; jasmine.mockLibrary = function(library, name, mock) { var original = require(library)[name]; diff --git a/src/Adapters/Files/GridStoreAdapter.js b/src/Adapters/Files/GridStoreAdapter.js index d7844a0b..a2584dc4 100644 --- a/src/Adapters/Files/GridStoreAdapter.js +++ b/src/Adapters/Files/GridStoreAdapter.js @@ -8,14 +8,13 @@ import { MongoClient, GridStore, Db} from 'mongodb'; import { FilesAdapter } from './FilesAdapter'; - -const DefaultMongoURI = 'mongodb://localhost:27017/parse'; +import defaults from '../../defaults'; export class GridStoreAdapter extends FilesAdapter { _databaseURI: string; _connectionPromise: Promise; - constructor(mongoDatabaseURI = DefaultMongoURI) { + constructor(mongoDatabaseURI = defaults.DefaultMongoURI) { super(); this._databaseURI = mongoDatabaseURI; this._connect(); diff --git a/src/Adapters/Logger/LoggerAdapter.js b/src/Adapters/Logger/LoggerAdapter.js index 9fda8eab..4a065589 100644 --- a/src/Adapters/Logger/LoggerAdapter.js +++ b/src/Adapters/Logger/LoggerAdapter.js @@ -3,15 +3,13 @@ // Allows you to change the logger mechanism // // Adapter classes must implement the following functions: -// * info(obj1 [, obj2, .., objN]) -// * error(obj1 [, obj2, .., objN]) -// * query(options, callback) +// * log() {} +// * query(options, callback) /* optional */ // Default is WinstonLoggerAdapter.js export class LoggerAdapter { - info() {} - error() {} - query(options, callback) {} + constructor(options) {} + log(level, message, /* meta */) {} } export default LoggerAdapter; diff --git a/src/Adapters/Logger/WinstonLogger.js b/src/Adapters/Logger/WinstonLogger.js new file mode 100644 index 00000000..2015908b --- /dev/null +++ b/src/Adapters/Logger/WinstonLogger.js @@ -0,0 +1,100 @@ +import winston from 'winston'; +import fs from 'fs'; +import path from 'path'; +import DailyRotateFile from 'winston-daily-rotate-file'; +import _ from 'lodash'; +import defaults from '../../defaults'; + +const logger = new winston.Logger(); +const additionalTransports = []; + +function updateTransports(options) { + let transports = Object.assign({}, logger.transports); + if (options) { + let silent = options.silent; + delete options.silent; + if (_.isNull(options.dirname)) { + delete transports['parse-server']; + delete transports['parse-server-error']; + } else if (!_.isUndefined(options.dirname)) { + transports['parse-server'] = new (DailyRotateFile)( + Object.assign({ + filename: 'parse-server.info', + name: 'parse-server', + }, options)); + transports['parse-server-error'] = new (DailyRotateFile)( + Object.assign({ + filename: 'parse-server.err', + name: 'parse-server-error', + level: 'error' + }, options)); + } + + transports.console = new (winston.transports.Console)( + Object.assign({ + colorize: true, + name: 'console', + silent + }, options)); + } + // Mount the additional transports + additionalTransports.forEach((transport) => { + transports[transport.name] = transport; + }); + logger.configure({ + transports: _.values(transports) + }); +} + +export function configureLogger({ + logsFolder = defaults.logsFolder, + jsonLogs = defaults.jsonLogs, + logLevel = winston.level, + verbose = defaults.verbose, + silent = defaults.silent } = {}) { + + if (verbose) { + logLevel = 'verbose'; + } + + winston.level = logLevel; + const options = {}; + + if (logsFolder) { + if (!path.isAbsolute(logsFolder)) { + logsFolder = path.resolve(process.cwd(), logsFolder); + } + try { + fs.mkdirSync(logsFolder); + } catch (exception) {} + } + options.dirname = logsFolder; + options.level = logLevel; + options.silent = silent; + + if (jsonLogs) { + options.json = true; + options.stringify = true; + } + updateTransports(options); +} + +export function addTransport(transport) { + additionalTransports.push(transport); + updateTransports(); +} + +export function removeTransport(transport) { + let transportName = typeof transport == 'string' ? transport : transport.name; + let transports = Object.assign({}, logger.transports); + delete transports[transportName]; + logger.configure({ + transports: _.values(transports) + }); + _.remove(additionalTransports, (transport) => { + return transport.name === transportName; + }); +} + +export { logger, addTransport, configureLogger, removeTransport }; +export default logger; diff --git a/src/Adapters/Logger/WinstonLoggerAdapter.js b/src/Adapters/Logger/WinstonLoggerAdapter.js index d3728f28..c1397594 100644 --- a/src/Adapters/Logger/WinstonLoggerAdapter.js +++ b/src/Adapters/Logger/WinstonLoggerAdapter.js @@ -1,63 +1,23 @@ import { LoggerAdapter } from './LoggerAdapter'; -import { logger, addTransport } from '../../logger'; +import { logger, addTransport, configureLogger } from './WinstonLogger'; const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; -const CACHE_TIME = 1000 * 60; - -let currentDate = new Date(); - -let simpleCache = { - timestamp: null, - from: null, - until: null, - order: null, - data: [], - level: 'info', -}; // returns Date object rounded to nearest day let _getNearestDay = (date) => { return new Date(date.getFullYear(), date.getMonth(), date.getDate()); } -// returns Date object of previous day -let _getPrevDay = (date) => { - return new Date(date - MILLISECONDS_IN_A_DAY); -} - -// returns the iso formatted file name -let _getFileName = () => { - return _getNearestDay(currentDate).toISOString() -} - -// check for valid cache when both from and util match. -// cache valid for up to 1 minute -let _hasValidCache = (from, until, level) => { - if (String(from) === String(simpleCache.from) && - String(until) === String(simpleCache.until) && - new Date() - simpleCache.timestamp < CACHE_TIME && - level === simpleCache.level) { - return true; - } - return false; -} - -// check that log entry has valid time stamp based on query -let _isValidLogEntry = (from, until, entry) => { - var _entry = JSON.parse(entry), - timestamp = new Date(_entry.timestamp); - return timestamp >= from && timestamp <= until - ? true - : false -}; - export class WinstonLoggerAdapter extends LoggerAdapter { - info() { - return logger.info.apply(undefined, arguments); + constructor(options) { + super(); + if (options) { + configureLogger(options); + } } - error() { - return logger.error.apply(undefined, arguments); + log() { + return logger.log.apply(logger, arguments); } addTransport(transport) { diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 4bdb575b..44c116ad 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -17,7 +17,6 @@ let mongodb = require('mongodb'); let MongoClient = mongodb.MongoClient; const MongoSchemaCollectionName = '_SCHEMA'; -const DefaultMongoURI = 'mongodb://localhost:27017/parse'; const storageAdapterAllCollections = mongoAdapter => { return mongoAdapter.connect() @@ -86,7 +85,7 @@ export class MongoStorageAdapter { database; constructor({ - uri = DefaultMongoURI, + uri = defaults.DefaultMongoURI, collectionPrefix = '', mongoOptions = {}, }) { diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 9f439433..f0dbb902 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -878,6 +878,8 @@ DatabaseController.prototype.addPointerPermissions = function(schema, className, } } +// TODO: create indexes on first creation of a _User object. Otherwise it's impossible to +// have a Parse app without it having a _User collection. DatabaseController.prototype.performInitizalization = function() { const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._User } }; diff --git a/src/Controllers/LoggerController.js b/src/Controllers/LoggerController.js index 7cfbe41e..5b1946e9 100644 --- a/src/Controllers/LoggerController.js +++ b/src/Controllers/LoggerController.js @@ -16,7 +16,35 @@ export const LogOrder = { } export class LoggerController extends AdaptableController { + + log(level, args) { + args = [].concat(level, [...args]); + this.adapter.log.apply(this.adapter, args); + } + info() { + return this.log('info', arguments); + } + + error() { + return this.log('error', arguments); + } + + warn() { + return this.log('warn', arguments); + } + + verbose() { + return this.log('verbose', arguments); + } + + debug() { + return this.log('debug', arguments); + } + + silly() { + return this.log('silly', arguments); + } // check that date input is valid static validDateTime(date) { if (!date) { @@ -60,6 +88,10 @@ export class LoggerController extends AdaptableController { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not availabe'); } + if (typeof this.adapter.query !== 'function') { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + 'Querying logs is not supported with this adapter'); + } options = LoggerController.parseOptions(options); return this.adapter.query(options); } diff --git a/src/Controllers/SchemaCache.js b/src/Controllers/SchemaCache.js index 7a56f107..a72fc021 100644 --- a/src/Controllers/SchemaCache.js +++ b/src/Controllers/SchemaCache.js @@ -3,11 +3,12 @@ const SCHEMA_CACHE_PREFIX = "__SCHEMA"; const ALL_KEYS = "__ALL_KEYS"; import { randomString } from '../cryptoUtils'; +import defaults from '../defaults'; export default class SchemaCache { cache: Object; - constructor(cacheController, ttl = 30) { + constructor(cacheController, ttl = defaults.schemaCacheTTL) { this.ttl = ttl; if (typeof ttl == 'string') { this.ttl = parseInt(ttl); diff --git a/src/LiveQuery/Client.js b/src/LiveQuery/Client.js index 72e4a9d3..8d844290 100644 --- a/src/LiveQuery/Client.js +++ b/src/LiveQuery/Client.js @@ -1,5 +1,5 @@ -import PLog from './PLog'; import Parse from 'parse/node'; +import logger from '../logger'; import type { FlattenedObjectData } from './Subscription'; export type Message = { [attr: string]: any }; @@ -37,7 +37,7 @@ class Client { } static pushResponse(parseWebSocket: any, message: Message): void { - PLog.verbose('Push Response : %j', message); + logger.verbose('Push Response : %j', message); parseWebSocket.send(message); } diff --git a/src/LiveQuery/PLog.js b/src/LiveQuery/PLog.js deleted file mode 100644 index 8ae8f691..00000000 --- a/src/LiveQuery/PLog.js +++ /dev/null @@ -1,5 +0,0 @@ -import { addGroup } from '../logger'; - -let PLog = addGroup('parse-live-query-server'); - -module.exports = PLog; diff --git a/src/LiveQuery/ParseCloudCodePublisher.js b/src/LiveQuery/ParseCloudCodePublisher.js index ac5e9d34..b50a5082 100644 --- a/src/LiveQuery/ParseCloudCodePublisher.js +++ b/src/LiveQuery/ParseCloudCodePublisher.js @@ -1,5 +1,5 @@ import { ParsePubSub } from './ParsePubSub'; -import PLog from './PLog'; +import logger from '../logger'; class ParseCloudCodePublisher { parsePublisher: Object; @@ -20,7 +20,7 @@ class ParseCloudCodePublisher { // Request is the request object from cloud code functions. request.object is a ParseObject. _onCloudCodeMessage(type: string, request: any): void { - PLog.verbose('Raw request from cloud code current : %j | original : %j', request.object, request.original); + logger.verbose('Raw request from cloud code current : %j | original : %j', request.object, request.original); // We need the full JSON which includes className let message = { currentParseObject: request.object._toFullJSON() diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index 0d59211f..044c6536 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -3,7 +3,7 @@ import Parse from 'parse/node'; import { Subscription } from './Subscription'; import { Client } from './Client'; import { ParseWebSocketServer } from './ParseWebSocketServer'; -import PLog from './PLog'; +import logger from '../logger'; import RequestSchema from './RequestSchema'; import { matchesQuery, queryHash } from './QueryTools'; import { ParsePubSub } from './ParsePubSub'; @@ -25,15 +25,14 @@ class ParseLiveQueryServer { this.subscriptions = new Map(); config = config || {}; - // Set LogLevel - PLog.level = config.logLevel || 'INFO'; + // Store keys, convert obj to map let keyPairs = config.keyPairs || {}; this.keyPairs = new Map(); for (let key of Object.keys(keyPairs)) { this.keyPairs.set(key, keyPairs[key]); } - PLog.verbose('Support key pairs', this.keyPairs); + logger.verbose('Support key pairs', this.keyPairs); // Initialize Parse Parse.Object.disableSingleInstance(); @@ -62,7 +61,7 @@ class ParseLiveQueryServer { // Register message handler for subscriber. When publisher get messages, it will publish message // to the subscribers and the handler will be called. this.subscriber.on('message', (channel, messageStr) => { - PLog.verbose('Subscribe messsage %j', messageStr); + logger.verbose('Subscribe messsage %j', messageStr); let message = JSON.parse(messageStr); this._inflateParseObject(message); if (channel === 'afterSave') { @@ -70,7 +69,7 @@ class ParseLiveQueryServer { } else if (channel === 'afterDelete') { this._onAfterDelete(message); } else { - PLog.error('Get message %s from unknown channel %j', message, channel); + logger.error('Get message %s from unknown channel %j', message, channel); } }); @@ -100,16 +99,16 @@ class ParseLiveQueryServer { // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. // Message.originalParseObject is the original ParseObject. _onAfterDelete(message: any): void { - PLog.verbose('afterDelete is triggered'); + logger.verbose('afterDelete is triggered'); let deletedParseObject = message.currentParseObject.toJSON(); let className = deletedParseObject.className; - PLog.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id); - PLog.verbose('Current client number : %d', this.clients.size); + logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id); + logger.verbose('Current client number : %d', this.clients.size); let classSubscriptions = this.subscriptions.get(className); if (typeof classSubscriptions === 'undefined') { - PLog.error('Can not find subscriptions under this class ' + className); + logger.error('Can not find subscriptions under this class ' + className); return; } for (let subscription of classSubscriptions.values()) { @@ -131,7 +130,7 @@ class ParseLiveQueryServer { } client.pushDelete(requestId, deletedParseObject); }, (error) => { - PLog.error('Matching ACL error : ', error); + logger.error('Matching ACL error : ', error); }); } } @@ -141,7 +140,7 @@ class ParseLiveQueryServer { // Message is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes. // Message.originalParseObject is the original ParseObject. _onAfterSave(message: any): void { - PLog.verbose('afterSave is triggered'); + logger.verbose('afterSave is triggered'); let originalParseObject = null; if (message.originalParseObject) { @@ -149,12 +148,12 @@ class ParseLiveQueryServer { } let currentParseObject = message.currentParseObject.toJSON(); let className = currentParseObject.className; - PLog.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id); - PLog.verbose('Current client number : %d', this.clients.size); + logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id); + logger.verbose('Current client number : %d', this.clients.size); let classSubscriptions = this.subscriptions.get(className); if (typeof classSubscriptions === 'undefined') { - PLog.error('Can not find subscriptions under this class ' + className); + logger.error('Can not find subscriptions under this class ' + className); return; } for (let subscription of classSubscriptions.values()) { @@ -192,7 +191,7 @@ class ParseLiveQueryServer { originalACLCheckingPromise, currentACLCheckingPromise ).then((isOriginalMatched, isCurrentMatched) => { - PLog.verbose('Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', + logger.verbose('Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s', originalParseObject, currentParseObject, isOriginalSubscriptionMatched, @@ -220,7 +219,7 @@ class ParseLiveQueryServer { let functionName = 'push' + type; client[functionName](requestId, currentParseObject); }, (error) => { - PLog.error('Matching ACL error : ', error); + logger.error('Matching ACL error : ', error); }); } } @@ -232,12 +231,12 @@ class ParseLiveQueryServer { if (typeof request === 'string') { request = JSON.parse(request); } - PLog.verbose('Request: %j', request); + logger.verbose('Request: %j', request); // Check whether this request is a valid request, return error directly if not if (!tv4.validate(request, RequestSchema['general']) || !tv4.validate(request, RequestSchema[request.op])) { Client.pushError(parseWebsocket, 1, tv4.error.message); - PLog.error('Connect message error %s', tv4.error.message); + logger.error('Connect message error %s', tv4.error.message); return; } @@ -253,15 +252,15 @@ class ParseLiveQueryServer { break; default: Client.pushError(parseWebsocket, 3, 'Get unknown operation'); - PLog.error('Get unknown operation', request.op); + logger.error('Get unknown operation', request.op); } }); parseWebsocket.on('disconnect', () => { - PLog.log('Client disconnect: %d', parseWebsocket.clientId); + logger.info('Client disconnect: %d', parseWebsocket.clientId); let clientId = parseWebsocket.clientId; if (!this.clients.has(clientId)) { - PLog.error('Can not find client %d on disconnect', clientId); + logger.error('Can not find client %d on disconnect', clientId); return; } @@ -285,8 +284,8 @@ class ParseLiveQueryServer { } } - PLog.verbose('Current clients %d', this.clients.size); - PLog.verbose('Current subscriptions %d', this.subscriptions.size); + logger.verbose('Current clients %d', this.clients.size); + logger.verbose('Current subscriptions %d', this.subscriptions.size); }); } @@ -331,14 +330,14 @@ class ParseLiveQueryServer { _handleConnect(parseWebsocket: any, request: any): any { if (!this._validateKeys(request, this.keyPairs)) { Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); - PLog.error('Key in request is not valid'); + logger.error('Key in request is not valid'); return; } let client = new Client(this.clientId, parseWebsocket); parseWebsocket.clientId = this.clientId; this.clientId += 1; this.clients.set(parseWebsocket.clientId, client); - PLog.log('Create new client: %d', parseWebsocket.clientId); + logger.info('Create new client: %d', parseWebsocket.clientId); client.pushConnect(); } @@ -361,7 +360,7 @@ class ParseLiveQueryServer { // If we can not find this client, return error to client if (!parseWebsocket.hasOwnProperty('clientId')) { Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before subscribing'); - PLog.error('Can not find this client, make sure you connect to server before subscribing'); + logger.error('Can not find this client, make sure you connect to server before subscribing'); return; } let client = this.clients.get(parseWebsocket.clientId); @@ -400,15 +399,15 @@ class ParseLiveQueryServer { client.pushSubscribe(request.requestId); - PLog.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId); - PLog.verbose('Current client number: %d', this.clients.size); + logger.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId); + logger.verbose('Current client number: %d', this.clients.size); } _handleUnsubscribe(parseWebsocket: any, request: any): any { // If we can not find this client, return error to client if (!parseWebsocket.hasOwnProperty('clientId')) { Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before unsubscribing'); - PLog.error('Can not find this client, make sure you connect to server before unsubscribing'); + logger.error('Can not find this client, make sure you connect to server before unsubscribing'); return; } let requestId = request.requestId; @@ -416,7 +415,7 @@ class ParseLiveQueryServer { if (typeof client === 'undefined') { Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId + '. Make sure you connect to live query server before unsubscribing.'); - PLog.error('Can not find this client ' + parseWebsocket.clientId); + logger.error('Can not find this client ' + parseWebsocket.clientId); return; } @@ -424,7 +423,7 @@ class ParseLiveQueryServer { if (typeof subscriptionInfo === 'undefined') { Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId + '. Make sure you subscribe to live query server before unsubscribing.'); - PLog.error('Can not find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId); + logger.error('Can not find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId); return; } @@ -446,14 +445,10 @@ class ParseLiveQueryServer { client.pushUnsubscribe(request.requestId); - PLog.verbose('Delete client: %d | subscription: %d', parseWebsocket.clientId, request.requestId); + logger.verbose('Delete client: %d | subscription: %d', parseWebsocket.clientId, request.requestId); } } -ParseLiveQueryServer.setLogLevel = function(logLevel) { - PLog.logLevel = logLevel; -} - export { ParseLiveQueryServer } diff --git a/src/LiveQuery/ParseWebSocketServer.js b/src/LiveQuery/ParseWebSocketServer.js index e9722306..b7c331c4 100644 --- a/src/LiveQuery/ParseWebSocketServer.js +++ b/src/LiveQuery/ParseWebSocketServer.js @@ -1,4 +1,4 @@ -import PLog from './PLog'; +import logger from '../logger'; let typeMap = new Map([['disconnect', 'close']]); @@ -9,7 +9,7 @@ export class ParseWebSocketServer { let WebSocketServer = require('ws').Server; let wss = new WebSocketServer({ server: server }); wss.on('listening', () => { - PLog.log('Parse LiveQuery Server starts running'); + logger.info('Parse LiveQuery Server starts running'); }); wss.on('connection', (ws) => { onConnect(new ParseWebSocket(ws)); diff --git a/src/LiveQuery/SessionTokenCache.js b/src/LiveQuery/SessionTokenCache.js index 07d9d627..57f84720 100644 --- a/src/LiveQuery/SessionTokenCache.js +++ b/src/LiveQuery/SessionTokenCache.js @@ -1,6 +1,6 @@ import Parse from 'parse/node'; import LRU from 'lru-cache'; -import PLog from './PLog'; +import logger from '../logger'; class SessionTokenCache { cache: Object; @@ -18,16 +18,16 @@ class SessionTokenCache { } let userId = this.cache.get(sessionToken); if (userId) { - PLog.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken); + logger.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken); return Parse.Promise.as(userId); } return Parse.User.become(sessionToken).then((user) => { - PLog.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken); + logger.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken); let userId = user.id; this.cache.set(sessionToken, userId); return Parse.Promise.as(userId); }, (error) => { - PLog.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error); + logger.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error); return Parse.Promise.error(error); }); } diff --git a/src/LiveQuery/Subscription.js b/src/LiveQuery/Subscription.js index e3b63daf..56cf27b2 100644 --- a/src/LiveQuery/Subscription.js +++ b/src/LiveQuery/Subscription.js @@ -1,5 +1,5 @@ -import {matchesQuery, queryHash} from './QueryTools'; -import PLog from './PLog'; +import {matchesQuery, queryHash} from './QueryTools'; +import logger from '../logger'; export type FlattenedObjectData = { [attr: string]: any }; export type QueryData = { [attr: string]: any }; @@ -29,13 +29,13 @@ class Subscription { deleteClientSubscription(clientId: number, requestId: number): void { let requestIds = this.clientRequestIds.get(clientId); if (typeof requestIds === 'undefined') { - PLog.error('Can not find client %d to delete', clientId); + logger.error('Can not find client %d to delete', clientId); return; } let index = requestIds.indexOf(requestId); if (index < 0) { - PLog.error('Can not find client %d subscription %d to delete', clientId, requestId); + logger.error('Can not find client %d subscription %d to delete', clientId, requestId); return; } requestIds.splice(index, 1); diff --git a/src/ParseServer.js b/src/ParseServer.js index 243908db..c6290f69 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -13,8 +13,8 @@ if (!global._babelPolyfill) { require('babel-polyfill'); } -import { logger, - configureLogger } from './logger'; +import defaults from './defaults'; +import * as logging from './logger'; import AppCache from './cache'; import Config from './Config'; import parseServerPackage from '../package.json'; @@ -94,13 +94,16 @@ class ParseServer { appId = requiredParameter('You must provide an appId!'), masterKey = requiredParameter('You must provide a masterKey!'), appName, - analyticsAdapter = undefined, + analyticsAdapter, filesAdapter, push, loggerAdapter, - jsonLogs, - logsFolder, - databaseURI, + jsonLogs = defaults.jsonLogs, + logsFolder = defaults.logsFolder, + verbose = defaults.verbose, + logLevel = defaults.level, + silent = defaults.silent, + databaseURI = defaults.DefaultMongoURI, databaseOptions, databaseAdapter, cloud, @@ -110,15 +113,15 @@ class ParseServer { dotNetKey, restAPIKey, webhookKey, - fileKey = undefined, + fileKey, facebookAppIds = [], - enableAnonymousUsers = true, - allowClientClassCreation = true, + enableAnonymousUsers = defaults.enableAnonymousUsers, + allowClientClassCreation = defaults.allowClientClassCreation, oauth = {}, serverURL = requiredParameter('You must provide a serverURL!'), - maxUploadSize = '20mb', - verifyUserEmails = false, - preventLoginWithUnverifiedEmail = false, + maxUploadSize = defaults.maxUploadSize, + verifyUserEmails = defaults.verifyUserEmails, + preventLoginWithUnverifiedEmail = defaults.preventLoginWithUnverifiedEmail, emailVerifyTokenValidityDuration, cacheAdapter, emailAdapter, @@ -130,17 +133,16 @@ class ParseServer { passwordResetSuccess: undefined }, liveQuery = {}, - sessionLength = 31536000, // 1 Year in seconds - expireInactiveSessions = true, - verbose = false, - revokeSessionOnPasswordReset = true, - schemaCacheTTL = 5, // cache for 5s + sessionLength = defaults.sessionLength, // 1 Year in seconds + expireInactiveSessions = defaults.expireInactiveSessions, + revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset, + schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s __indexBuildCompletionCallbackForTests = () => {}, }) { // Initialize the node client SDK automatically Parse.initialize(appId, javascriptKey || 'unused', masterKey); Parse.serverURL = serverURL; - if ((databaseOptions || databaseURI || collectionPrefix !== '') && databaseAdapter) { + if ((databaseOptions || (databaseURI && databaseURI != defaults.DefaultMongoURI) || collectionPrefix !== '') && databaseAdapter) { throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/connectionPrefix.'; } else if (!databaseAdapter) { databaseAdapter = new MongoStorageAdapter({ @@ -156,49 +158,34 @@ class ParseServer { throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.'; } - if (logsFolder) { - configureLogger({logsFolder, jsonLogs}); - } - - if (cloud) { - addParseCloud(); - if (typeof cloud === 'function') { - cloud(Parse) - } else if (typeof cloud === 'string') { - require(path.resolve(process.cwd(), cloud)); - } else { - throw "argument 'cloud' must either be a string or a function"; - } - } - - if (verbose || process.env.VERBOSE || process.env.VERBOSE_PARSE_SERVER) { - configureLogger({level: 'silly', jsonLogs}); - } + const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent }); + const loggerController = new LoggerController(loggerControllerAdapter, appId); + logging.setLogger(loggerController); const filesControllerAdapter = loadAdapter(filesAdapter, () => { return new GridStoreAdapter(databaseURI); }); + const filesController = new FilesController(filesControllerAdapter, appId); + // Pass the push options too as it works with the default const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push || {}); - const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter); - const emailControllerAdapter = loadAdapter(emailAdapter); - const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId}); - const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter); - // We pass the options and the base class for the adatper, // Note that passing an instance would work too - const filesController = new FilesController(filesControllerAdapter, appId); const pushController = new PushController(pushControllerAdapter, appId, push); - const loggerController = new LoggerController(loggerControllerAdapter, appId); + + const emailControllerAdapter = loadAdapter(emailAdapter); const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails }); - const liveQueryController = new LiveQueryController(liveQuery); + + const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId}); const cacheController = new CacheController(cacheControllerAdapter, appId); - const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL)); - const hooksController = new HooksController(appId, databaseController, webhookKey); + + const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter); const analyticsController = new AnalyticsController(analyticsControllerAdapter); - // TODO: create indexes on first creation of a _User object. Otherwise it's impossible to - // have a Parse app without it having a _User collection. + const liveQueryController = new LiveQueryController(liveQuery); + const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL)); + const hooksController = new HooksController(appId, databaseController, webhookKey); + const dbInitPromise = databaseController.performInitizalization(); AppCache.put(appId, { @@ -251,6 +238,17 @@ class ParseServer { if (process.env.TESTING) { __indexBuildCompletionCallbackForTests(dbInitPromise); } + + if (cloud) { + addParseCloud(); + if (typeof cloud === 'function') { + cloud(Parse) + } else if (typeof cloud === 'string') { + require(path.resolve(process.cwd(), cloud)); + } else { + throw "argument 'cloud' must either be a string or a function"; + } + } } get app() { diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index 78635c93..c61c59fb 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -65,7 +65,7 @@ export class FunctionsRouter extends PromiseRouter { master: req.auth && req.auth.isMaster, user: req.auth && req.auth.user, installationId: req.info.installationId, - log: req.config.loggerController && req.config.loggerController.adapter, + log: req.config.loggerController, headers: req.headers, functionName: req.params.functionName }; diff --git a/src/cli/cli-definitions.js b/src/cli/cli-definitions.js index 9f1d7565..b8acc3de 100644 --- a/src/cli/cli-definitions.js +++ b/src/cli/cli-definitions.js @@ -32,6 +32,13 @@ function booleanParser(opt) { return false; } +function nullParser(opt) { + if (opt == 'null') { + return null; + } + return opt; +} + export default { "appId": { env: "PARSE_SERVER_APPLICATION_ID", @@ -193,6 +200,18 @@ export default { env: "JSON_LOGS", help: "Log as structured JSON objects" }, + "logLevel": { + env: "PARSE_SERVER_LOG_LEVEL", + help: "Sets the level for logs" + }, + "logsFolder": { + env: "PARSE_SERVER_LOGS_FOLDER", + help: "Folder for the logs (defaults to './logs'); set to null to disable file based logging", + action: nullParser + }, + "silent": { + help: "Disables console output", + }, "revokeSessionOnPasswordReset": { env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET", help: "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.", diff --git a/src/defaults.js b/src/defaults.js new file mode 100644 index 00000000..838346d6 --- /dev/null +++ b/src/defaults.js @@ -0,0 +1,31 @@ +let logsFolder = (() => { + let folder = './logs/'; + if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { + folder = './test_logs/' + } + folder = process.env.PARSE_SERVER_LOGS_FOLDER || folder; + return folder; +})(); + +let { verbose, level } = (() => { + let verbose = process.env.VERBOSE ? true : false; + return { verbose, level: verbose ? 'verbose' : undefined } +})(); + +export default { + DefaultMongoURI: 'mongodb://localhost:27017/parse', + jsonLogs: process.env.JSON_LOGS || false, + logsFolder, + verbose, + level, + silent: false, + enableAnonymousUsers: true, + allowClientClassCreation: true, + maxUploadSize: '20mb', + verifyUserEmails: false, + preventLoginWithUnverifiedEmail: false, + sessionLength: 31536000, + expireInactiveSessions: true, + revokeSessionOnPasswordReset: true, + schemaCacheTTL: 5000 // in ms +} diff --git a/src/index.js b/src/index.js index 82d3c35a..8f3e0e2d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ import ParseServer from './ParseServer'; -import logger from './logger'; import S3Adapter from 'parse-server-s3-adapter' import FileSystemAdapter from 'parse-server-fs-adapter' import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter' import TestUtils from './TestUtils'; -import { useExternal } from './deprecated' +import { useExternal } from './deprecated'; +import { getLogger } from './logger'; // Factory function let _ParseServer = function(options) { @@ -16,5 +16,9 @@ _ParseServer.createLiveQueryServer = ParseServer.createLiveQueryServer; let GCSAdapter = useExternal('GCSAdapter', 'parse-server-gcs-adapter'); +Object.defineProperty(module.exports, 'logger', { + get: getLogger +}); + export default ParseServer; -export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, TestUtils, logger, _ParseServer as ParseServer }; +export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, TestUtils, _ParseServer as ParseServer }; diff --git a/src/logger.js b/src/logger.js index 15ae1f6b..7c825743 100644 --- a/src/logger.js +++ b/src/logger.js @@ -1,104 +1,20 @@ -import winston from 'winston'; -import fs from 'fs'; -import path from 'path'; -import DailyRotateFile from 'winston-daily-rotate-file'; +'use strict'; +let logger; -let LOGS_FOLDER = './logs/'; - -if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { - LOGS_FOLDER = './test_logs/' +export function setLogger(aLogger) { + logger = aLogger; } -LOGS_FOLDER = process.env.PARSE_SERVER_LOGS_FOLDER || LOGS_FOLDER; -const JSON_LOGS = process.env.JSON_LOGS || false; - -let currentLogsFolder = LOGS_FOLDER; -const additionalTransports = []; - -function generateTransports(level, options = {}) { - let transports = [ - new (DailyRotateFile)( - Object.assign({ - filename: 'parse-server.info', - dirname: currentLogsFolder, - name: 'parse-server', - level: level - }, options) - ), - new (DailyRotateFile)( - Object.assign({ - filename: 'parse-server.err', - dirname: currentLogsFolder, - name: 'parse-server-error', - level: 'error' - } - ), options) - ].concat(additionalTransports); - if (!process.env.TESTING || process.env.VERBOSE) { - transports = [ - new (winston.transports.Console)( - Object.assign({ - colorize: true, - level: level - }, options) - ) - ].concat(transports); - } - return transports; +export function getLogger() { + return logger; } -const logger = new winston.Logger(); +// for: `import logger from './logger'` +Object.defineProperty(module.exports, 'default', { + get: getLogger +}); -export function configureLogger({ logsFolder, jsonLogs, level = winston.level }) { - winston.level = level; - logsFolder = logsFolder || currentLogsFolder; - - if (!path.isAbsolute(logsFolder)) { - logsFolder = path.resolve(process.cwd(), logsFolder); - } - try { - fs.mkdirSync(logsFolder); - } catch (exception) { - // Ignore, assume the folder already exists - } - currentLogsFolder = logsFolder; - - const options = {}; - if (jsonLogs) { - options.json = true; - options.stringify = true; - } - const transports = generateTransports(level, options); - logger.configure({ - transports: transports - }) -} - -configureLogger({ logsFolder: LOGS_FOLDER, jsonLogs: JSON_LOGS }); - -export function addGroup(groupName) { - let level = winston.level; - let transports = generateTransports().concat(new (DailyRotateFile)({ - filename: groupName, - dirname: currentLogsFolder, - name: groupName, - level: level - })); - - winston.loggers.add(groupName, { - transports: transports - }); - return winston.loggers.get(groupName); -} - -export function addTransport(transport) { - const level = winston.level; - additionalTransports.push(transport); - const transports = generateTransports(level); - logger.configure({ - transports: transports - }); -} - -export { logger, addTransport }; -export default logger; +// for: `import { logger } from './logger'` +Object.defineProperty(module.exports, 'logger', { + get: getLogger +}); diff --git a/src/triggers.js b/src/triggers.js index ea1853f9..da74b4fc 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -102,7 +102,7 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb triggerName: triggerType, object: parseObject, master: false, - log: config.loggerController && config.loggerController.adapter + log: config.loggerController }; if (originalParseObject) {