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
This commit is contained in:
Florent Vilmart
2016-08-12 13:25:24 -04:00
committed by Drew
parent 6e0a25dea0
commit a5a172918e
28 changed files with 396 additions and 293 deletions

View File

@@ -45,6 +45,19 @@ describe("AdapterLoader", ()=>{
done(); 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) => { it("should instantiate an adapter from function/Class", (done) => {
var adapter = loadAdapter({ var adapter = loadAdapter({
adapter: FilesAdapter adapter: FilesAdapter

View File

@@ -5,7 +5,7 @@ var InstallationsRouter = require('../src/Routers/InstallationsRouter').Installa
var config = new Config('test'); var config = new Config('test');
describe('InstallationsRouter', () => { describe_only_db(['mongo'])('InstallationsRouter', () => {
it('uses find condition from request.body', (done) => { it('uses find condition from request.body', (done) => {
var androidDeviceRequest = { var androidDeviceRequest = {
'installationId': '12345678-abcd-abcd-abcd-123456789abc', 'installationId': '12345678-abcd-abcd-abcd-123456789abc',
@@ -71,6 +71,9 @@ describe('InstallationsRouter', () => {
var results = res.response.results; var results = res.response.results;
expect(results.length).toEqual(1); expect(results.length).toEqual(1);
done(); done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
}); });
}); });
@@ -172,6 +175,9 @@ describe('InstallationsRouter', () => {
expect(response.results.length).toEqual(0); expect(response.results.length).toEqual(0);
expect(response.count).toEqual(2); expect(response.count).toEqual(2);
done(); done();
}).catch((err) => {
fail(JSON.stringify(err));
done();
}); });
}); });
}); });

View File

@@ -1,4 +1,4 @@
var logger = require('../src/logger'); var logging = require('../src/Adapters/Logger/WinstonLogger');
var winston = require('winston'); var winston = require('winston');
class TestTransport extends winston.Transport { class TestTransport extends winston.Transport {
@@ -9,10 +9,55 @@ class TestTransport extends winston.Transport {
describe('Logger', () => { describe('Logger', () => {
it('should add transport', () => { it('should add transport', () => {
const testTransport = new (TestTransport)({}); const testTransport = new (TestTransport)({
name: 'test'
});
spyOn(testTransport, 'log'); spyOn(testTransport, 'log');
logger.addTransport(testTransport); logging.addTransport(testTransport);
logger.logger.info('hi'); expect(Object.keys(logging.logger.transports).length).toBe(4);
logging.logger.info('hi');
expect(testTransport.log).toHaveBeenCalled(); 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();
});
}); });
}); });

View File

@@ -1,10 +1,10 @@
var Subscription = require('../src/LiveQuery/Subscription').Subscription; var Subscription = require('../src/LiveQuery/Subscription').Subscription;
let logger;
describe('Subscription', function() { describe('Subscription', function() {
beforeEach(function() { beforeEach(function() {
var mockError = jasmine.createSpy('error'); logger = require('../src/logger').logger;
jasmine.mockLibrary('../src/LiveQuery/PLog', 'error', mockError); spyOn(logger, 'error').and.callThrough();
}); });
it('can be initialized', function() { it('can be initialized', function() {
@@ -62,8 +62,7 @@ describe('Subscription', function() {
var subscription = new Subscription('className', { key : 'value' }, 'hash'); var subscription = new Subscription('className', { key : 'value' }, 'hash');
subscription.deleteClientSubscription(1, 1); subscription.deleteClientSubscription(1, 1);
var PLog =require('../src/LiveQuery/PLog'); expect(logger.error).toHaveBeenCalled();
expect(PLog.error).toHaveBeenCalled();
}); });
it('can delete nonexistent request for one client', function() { it('can delete nonexistent request for one client', function() {
@@ -71,8 +70,7 @@ describe('Subscription', function() {
subscription.addClientSubscription(1, 1); subscription.addClientSubscription(1, 1);
subscription.deleteClientSubscription(1, 2); subscription.deleteClientSubscription(1, 2);
var PLog =require('../src/LiveQuery/PLog'); expect(logger.error).toHaveBeenCalled();
expect(PLog.error).toHaveBeenCalled();
expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.size).toBe(1);
expect(subscription.clientRequestIds.get(1)).toEqual([1]); expect(subscription.clientRequestIds.get(1)).toEqual([1]);
}); });
@@ -83,8 +81,7 @@ describe('Subscription', function() {
subscription.addClientSubscription(1, 2); subscription.addClientSubscription(1, 2);
subscription.deleteClientSubscription(1, 2); subscription.deleteClientSubscription(1, 2);
var PLog =require('../src/LiveQuery/PLog'); expect(logger.error).not.toHaveBeenCalled();
expect(PLog.error).not.toHaveBeenCalled();
expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.size).toBe(1);
expect(subscription.clientRequestIds.get(1)).toEqual([1]); expect(subscription.clientRequestIds.get(1)).toEqual([1]);
}); });
@@ -96,8 +93,7 @@ describe('Subscription', function() {
subscription.deleteClientSubscription(1, 1); subscription.deleteClientSubscription(1, 1);
subscription.deleteClientSubscription(1, 2); subscription.deleteClientSubscription(1, 2);
var PLog =require('../src/LiveQuery/PLog'); expect(logger.error).not.toHaveBeenCalled();
expect(PLog.error).not.toHaveBeenCalled();
expect(subscription.clientRequestIds.size).toBe(0); expect(subscription.clientRequestIds.size).toBe(0);
}); });
@@ -111,13 +107,8 @@ describe('Subscription', function() {
subscription.deleteClientSubscription(2, 1); subscription.deleteClientSubscription(2, 1);
subscription.deleteClientSubscription(2, 2); subscription.deleteClientSubscription(2, 2);
var PLog =require('../src/LiveQuery/PLog'); expect(logger.error).not.toHaveBeenCalled();
expect(PLog.error).not.toHaveBeenCalled();
expect(subscription.clientRequestIds.size).toBe(1); expect(subscription.clientRequestIds.size).toBe(1);
expect(subscription.clientRequestIds.get(1)).toEqual([1]); expect(subscription.clientRequestIds.get(1)).toEqual([1]);
}); });
afterEach(function(){
jasmine.restoreLibrary('../src/LiveQuery/PLog', 'error');
});
}); });

View File

@@ -8,7 +8,7 @@ describe('info logs', () => {
it("Verify INFO logs", (done) => { it("Verify INFO logs", (done) => {
var winstonLoggerAdapter = new WinstonLoggerAdapter(); var winstonLoggerAdapter = new WinstonLoggerAdapter();
winstonLoggerAdapter.info('testing info logs', () => { winstonLoggerAdapter.log('info', 'testing info logs', () => {
winstonLoggerAdapter.query({ winstonLoggerAdapter.query({
from: new Date(Date.now() - 500), from: new Date(Date.now() - 500),
size: 100, size: 100,
@@ -29,7 +29,7 @@ describe('info logs', () => {
describe('error logs', () => { describe('error logs', () => {
it("Verify ERROR logs", (done) => { it("Verify ERROR logs", (done) => {
var winstonLoggerAdapter = new WinstonLoggerAdapter(); var winstonLoggerAdapter = new WinstonLoggerAdapter();
winstonLoggerAdapter.error('testing error logs', () => { winstonLoggerAdapter.log('error', 'testing error logs', () => {
winstonLoggerAdapter.query({ winstonLoggerAdapter.query({
from: new Date(Date.now() - 500), from: new Date(Date.now() - 500),
size: 100, size: 100,

View File

@@ -45,6 +45,7 @@ var defaultConfiguration = {
webhookKey: 'hook', webhookKey: 'hook',
masterKey: 'test', masterKey: 'test',
fileKey: 'test', fileKey: 'test',
silent: !process.env.VERBOSE,
push: { push: {
'ios': { 'ios': {
cert: 'prodCert.pem', cert: 'prodCert.pem',
@@ -352,8 +353,6 @@ global.describe_only_db = db => {
} }
} }
// LiveQuery test setting
require('../src/LiveQuery/PLog').logLevel = 'NONE';
var libraryCache = {}; var libraryCache = {};
jasmine.mockLibrary = function(library, name, mock) { jasmine.mockLibrary = function(library, name, mock) {
var original = require(library)[name]; var original = require(library)[name];

View File

@@ -8,14 +8,13 @@
import { MongoClient, GridStore, Db} from 'mongodb'; import { MongoClient, GridStore, Db} from 'mongodb';
import { FilesAdapter } from './FilesAdapter'; import { FilesAdapter } from './FilesAdapter';
import defaults from '../../defaults';
const DefaultMongoURI = 'mongodb://localhost:27017/parse';
export class GridStoreAdapter extends FilesAdapter { export class GridStoreAdapter extends FilesAdapter {
_databaseURI: string; _databaseURI: string;
_connectionPromise: Promise<Db>; _connectionPromise: Promise<Db>;
constructor(mongoDatabaseURI = DefaultMongoURI) { constructor(mongoDatabaseURI = defaults.DefaultMongoURI) {
super(); super();
this._databaseURI = mongoDatabaseURI; this._databaseURI = mongoDatabaseURI;
this._connect(); this._connect();

View File

@@ -3,15 +3,13 @@
// Allows you to change the logger mechanism // Allows you to change the logger mechanism
// //
// Adapter classes must implement the following functions: // Adapter classes must implement the following functions:
// * info(obj1 [, obj2, .., objN]) // * log() {}
// * error(obj1 [, obj2, .., objN]) // * query(options, callback) /* optional */
// * query(options, callback)
// Default is WinstonLoggerAdapter.js // Default is WinstonLoggerAdapter.js
export class LoggerAdapter { export class LoggerAdapter {
info() {} constructor(options) {}
error() {} log(level, message, /* meta */) {}
query(options, callback) {}
} }
export default LoggerAdapter; export default LoggerAdapter;

View File

@@ -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;

View File

@@ -1,63 +1,23 @@
import { LoggerAdapter } from './LoggerAdapter'; 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 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 // returns Date object rounded to nearest day
let _getNearestDay = (date) => { let _getNearestDay = (date) => {
return new Date(date.getFullYear(), date.getMonth(), date.getDate()); 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 { export class WinstonLoggerAdapter extends LoggerAdapter {
info() { constructor(options) {
return logger.info.apply(undefined, arguments); super();
if (options) {
configureLogger(options);
}
} }
error() { log() {
return logger.error.apply(undefined, arguments); return logger.log.apply(logger, arguments);
} }
addTransport(transport) { addTransport(transport) {

View File

@@ -17,7 +17,6 @@ let mongodb = require('mongodb');
let MongoClient = mongodb.MongoClient; let MongoClient = mongodb.MongoClient;
const MongoSchemaCollectionName = '_SCHEMA'; const MongoSchemaCollectionName = '_SCHEMA';
const DefaultMongoURI = 'mongodb://localhost:27017/parse';
const storageAdapterAllCollections = mongoAdapter => { const storageAdapterAllCollections = mongoAdapter => {
return mongoAdapter.connect() return mongoAdapter.connect()
@@ -86,7 +85,7 @@ export class MongoStorageAdapter {
database; database;
constructor({ constructor({
uri = DefaultMongoURI, uri = defaults.DefaultMongoURI,
collectionPrefix = '', collectionPrefix = '',
mongoOptions = {}, mongoOptions = {},
}) { }) {

View File

@@ -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() { DatabaseController.prototype.performInitizalization = function() {
const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._User } }; const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Default, ...SchemaController.defaultColumns._User } };

View File

@@ -16,7 +16,35 @@ export const LogOrder = {
} }
export class LoggerController extends AdaptableController { 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 // check that date input is valid
static validDateTime(date) { static validDateTime(date) {
if (!date) { if (!date) {
@@ -60,6 +88,10 @@ export class LoggerController extends AdaptableController {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
'Logger adapter is not availabe'); '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); options = LoggerController.parseOptions(options);
return this.adapter.query(options); return this.adapter.query(options);
} }

View File

@@ -3,11 +3,12 @@ const SCHEMA_CACHE_PREFIX = "__SCHEMA";
const ALL_KEYS = "__ALL_KEYS"; const ALL_KEYS = "__ALL_KEYS";
import { randomString } from '../cryptoUtils'; import { randomString } from '../cryptoUtils';
import defaults from '../defaults';
export default class SchemaCache { export default class SchemaCache {
cache: Object; cache: Object;
constructor(cacheController, ttl = 30) { constructor(cacheController, ttl = defaults.schemaCacheTTL) {
this.ttl = ttl; this.ttl = ttl;
if (typeof ttl == 'string') { if (typeof ttl == 'string') {
this.ttl = parseInt(ttl); this.ttl = parseInt(ttl);

View File

@@ -1,5 +1,5 @@
import PLog from './PLog';
import Parse from 'parse/node'; import Parse from 'parse/node';
import logger from '../logger';
import type { FlattenedObjectData } from './Subscription'; import type { FlattenedObjectData } from './Subscription';
export type Message = { [attr: string]: any }; export type Message = { [attr: string]: any };
@@ -37,7 +37,7 @@ class Client {
} }
static pushResponse(parseWebSocket: any, message: Message): void { static pushResponse(parseWebSocket: any, message: Message): void {
PLog.verbose('Push Response : %j', message); logger.verbose('Push Response : %j', message);
parseWebSocket.send(message); parseWebSocket.send(message);
} }

View File

@@ -1,5 +0,0 @@
import { addGroup } from '../logger';
let PLog = addGroup('parse-live-query-server');
module.exports = PLog;

View File

@@ -1,5 +1,5 @@
import { ParsePubSub } from './ParsePubSub'; import { ParsePubSub } from './ParsePubSub';
import PLog from './PLog'; import logger from '../logger';
class ParseCloudCodePublisher { class ParseCloudCodePublisher {
parsePublisher: Object; parsePublisher: Object;
@@ -20,7 +20,7 @@ class ParseCloudCodePublisher {
// Request is the request object from cloud code functions. request.object is a ParseObject. // Request is the request object from cloud code functions. request.object is a ParseObject.
_onCloudCodeMessage(type: string, request: any): void { _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 // We need the full JSON which includes className
let message = { let message = {
currentParseObject: request.object._toFullJSON() currentParseObject: request.object._toFullJSON()

View File

@@ -3,7 +3,7 @@ import Parse from 'parse/node';
import { Subscription } from './Subscription'; import { Subscription } from './Subscription';
import { Client } from './Client'; import { Client } from './Client';
import { ParseWebSocketServer } from './ParseWebSocketServer'; import { ParseWebSocketServer } from './ParseWebSocketServer';
import PLog from './PLog'; import logger from '../logger';
import RequestSchema from './RequestSchema'; import RequestSchema from './RequestSchema';
import { matchesQuery, queryHash } from './QueryTools'; import { matchesQuery, queryHash } from './QueryTools';
import { ParsePubSub } from './ParsePubSub'; import { ParsePubSub } from './ParsePubSub';
@@ -25,15 +25,14 @@ class ParseLiveQueryServer {
this.subscriptions = new Map(); this.subscriptions = new Map();
config = config || {}; config = config || {};
// Set LogLevel
PLog.level = config.logLevel || 'INFO';
// Store keys, convert obj to map // Store keys, convert obj to map
let keyPairs = config.keyPairs || {}; let keyPairs = config.keyPairs || {};
this.keyPairs = new Map(); this.keyPairs = new Map();
for (let key of Object.keys(keyPairs)) { for (let key of Object.keys(keyPairs)) {
this.keyPairs.set(key, keyPairs[key]); this.keyPairs.set(key, keyPairs[key]);
} }
PLog.verbose('Support key pairs', this.keyPairs); logger.verbose('Support key pairs', this.keyPairs);
// Initialize Parse // Initialize Parse
Parse.Object.disableSingleInstance(); Parse.Object.disableSingleInstance();
@@ -62,7 +61,7 @@ class ParseLiveQueryServer {
// Register message handler for subscriber. When publisher get messages, it will publish message // Register message handler for subscriber. When publisher get messages, it will publish message
// to the subscribers and the handler will be called. // to the subscribers and the handler will be called.
this.subscriber.on('message', (channel, messageStr) => { this.subscriber.on('message', (channel, messageStr) => {
PLog.verbose('Subscribe messsage %j', messageStr); logger.verbose('Subscribe messsage %j', messageStr);
let message = JSON.parse(messageStr); let message = JSON.parse(messageStr);
this._inflateParseObject(message); this._inflateParseObject(message);
if (channel === 'afterSave') { if (channel === 'afterSave') {
@@ -70,7 +69,7 @@ class ParseLiveQueryServer {
} else if (channel === 'afterDelete') { } else if (channel === 'afterDelete') {
this._onAfterDelete(message); this._onAfterDelete(message);
} else { } 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 is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes.
// Message.originalParseObject is the original ParseObject. // Message.originalParseObject is the original ParseObject.
_onAfterDelete(message: any): void { _onAfterDelete(message: any): void {
PLog.verbose('afterDelete is triggered'); logger.verbose('afterDelete is triggered');
let deletedParseObject = message.currentParseObject.toJSON(); let deletedParseObject = message.currentParseObject.toJSON();
let className = deletedParseObject.className; let className = deletedParseObject.className;
PLog.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id); logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id);
PLog.verbose('Current client number : %d', this.clients.size); logger.verbose('Current client number : %d', this.clients.size);
let classSubscriptions = this.subscriptions.get(className); let classSubscriptions = this.subscriptions.get(className);
if (typeof classSubscriptions === 'undefined') { 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; return;
} }
for (let subscription of classSubscriptions.values()) { for (let subscription of classSubscriptions.values()) {
@@ -131,7 +130,7 @@ class ParseLiveQueryServer {
} }
client.pushDelete(requestId, deletedParseObject); client.pushDelete(requestId, deletedParseObject);
}, (error) => { }, (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 is the JSON object from publisher after inflated. Message.currentParseObject is the ParseObject after changes.
// Message.originalParseObject is the original ParseObject. // Message.originalParseObject is the original ParseObject.
_onAfterSave(message: any): void { _onAfterSave(message: any): void {
PLog.verbose('afterSave is triggered'); logger.verbose('afterSave is triggered');
let originalParseObject = null; let originalParseObject = null;
if (message.originalParseObject) { if (message.originalParseObject) {
@@ -149,12 +148,12 @@ class ParseLiveQueryServer {
} }
let currentParseObject = message.currentParseObject.toJSON(); let currentParseObject = message.currentParseObject.toJSON();
let className = currentParseObject.className; let className = currentParseObject.className;
PLog.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id); logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id);
PLog.verbose('Current client number : %d', this.clients.size); logger.verbose('Current client number : %d', this.clients.size);
let classSubscriptions = this.subscriptions.get(className); let classSubscriptions = this.subscriptions.get(className);
if (typeof classSubscriptions === 'undefined') { 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; return;
} }
for (let subscription of classSubscriptions.values()) { for (let subscription of classSubscriptions.values()) {
@@ -192,7 +191,7 @@ class ParseLiveQueryServer {
originalACLCheckingPromise, originalACLCheckingPromise,
currentACLCheckingPromise currentACLCheckingPromise
).then((isOriginalMatched, isCurrentMatched) => { ).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, originalParseObject,
currentParseObject, currentParseObject,
isOriginalSubscriptionMatched, isOriginalSubscriptionMatched,
@@ -220,7 +219,7 @@ class ParseLiveQueryServer {
let functionName = 'push' + type; let functionName = 'push' + type;
client[functionName](requestId, currentParseObject); client[functionName](requestId, currentParseObject);
}, (error) => { }, (error) => {
PLog.error('Matching ACL error : ', error); logger.error('Matching ACL error : ', error);
}); });
} }
} }
@@ -232,12 +231,12 @@ class ParseLiveQueryServer {
if (typeof request === 'string') { if (typeof request === 'string') {
request = JSON.parse(request); 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 // 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])) { if (!tv4.validate(request, RequestSchema['general']) || !tv4.validate(request, RequestSchema[request.op])) {
Client.pushError(parseWebsocket, 1, tv4.error.message); 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; return;
} }
@@ -253,15 +252,15 @@ class ParseLiveQueryServer {
break; break;
default: default:
Client.pushError(parseWebsocket, 3, 'Get unknown operation'); Client.pushError(parseWebsocket, 3, 'Get unknown operation');
PLog.error('Get unknown operation', request.op); logger.error('Get unknown operation', request.op);
} }
}); });
parseWebsocket.on('disconnect', () => { parseWebsocket.on('disconnect', () => {
PLog.log('Client disconnect: %d', parseWebsocket.clientId); logger.info('Client disconnect: %d', parseWebsocket.clientId);
let clientId = parseWebsocket.clientId; let clientId = parseWebsocket.clientId;
if (!this.clients.has(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; return;
} }
@@ -285,8 +284,8 @@ class ParseLiveQueryServer {
} }
} }
PLog.verbose('Current clients %d', this.clients.size); logger.verbose('Current clients %d', this.clients.size);
PLog.verbose('Current subscriptions %d', this.subscriptions.size); logger.verbose('Current subscriptions %d', this.subscriptions.size);
}); });
} }
@@ -331,14 +330,14 @@ class ParseLiveQueryServer {
_handleConnect(parseWebsocket: any, request: any): any { _handleConnect(parseWebsocket: any, request: any): any {
if (!this._validateKeys(request, this.keyPairs)) { if (!this._validateKeys(request, this.keyPairs)) {
Client.pushError(parseWebsocket, 4, 'Key in request is not valid'); 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; return;
} }
let client = new Client(this.clientId, parseWebsocket); let client = new Client(this.clientId, parseWebsocket);
parseWebsocket.clientId = this.clientId; parseWebsocket.clientId = this.clientId;
this.clientId += 1; this.clientId += 1;
this.clients.set(parseWebsocket.clientId, client); this.clients.set(parseWebsocket.clientId, client);
PLog.log('Create new client: %d', parseWebsocket.clientId); logger.info('Create new client: %d', parseWebsocket.clientId);
client.pushConnect(); client.pushConnect();
} }
@@ -361,7 +360,7 @@ class ParseLiveQueryServer {
// If we can not find this client, return error to client // If we can not find this client, return error to client
if (!parseWebsocket.hasOwnProperty('clientId')) { if (!parseWebsocket.hasOwnProperty('clientId')) {
Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before subscribing'); 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; return;
} }
let client = this.clients.get(parseWebsocket.clientId); let client = this.clients.get(parseWebsocket.clientId);
@@ -400,15 +399,15 @@ class ParseLiveQueryServer {
client.pushSubscribe(request.requestId); client.pushSubscribe(request.requestId);
PLog.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId); logger.verbose('Create client %d new subscription: %d', parseWebsocket.clientId, request.requestId);
PLog.verbose('Current client number: %d', this.clients.size); logger.verbose('Current client number: %d', this.clients.size);
} }
_handleUnsubscribe(parseWebsocket: any, request: any): any { _handleUnsubscribe(parseWebsocket: any, request: any): any {
// If we can not find this client, return error to client // If we can not find this client, return error to client
if (!parseWebsocket.hasOwnProperty('clientId')) { if (!parseWebsocket.hasOwnProperty('clientId')) {
Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before unsubscribing'); 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; return;
} }
let requestId = request.requestId; let requestId = request.requestId;
@@ -416,7 +415,7 @@ class ParseLiveQueryServer {
if (typeof client === 'undefined') { if (typeof client === 'undefined') {
Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId + Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId +
'. Make sure you connect to live query server before unsubscribing.'); '. 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; return;
} }
@@ -424,7 +423,7 @@ class ParseLiveQueryServer {
if (typeof subscriptionInfo === 'undefined') { if (typeof subscriptionInfo === 'undefined') {
Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId + Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId +
' subscriptionId ' + requestId + '. Make sure you subscribe to live query server before unsubscribing.'); ' 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; return;
} }
@@ -446,14 +445,10 @@ class ParseLiveQueryServer {
client.pushUnsubscribe(request.requestId); 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 { export {
ParseLiveQueryServer ParseLiveQueryServer
} }

View File

@@ -1,4 +1,4 @@
import PLog from './PLog'; import logger from '../logger';
let typeMap = new Map([['disconnect', 'close']]); let typeMap = new Map([['disconnect', 'close']]);
@@ -9,7 +9,7 @@ export class ParseWebSocketServer {
let WebSocketServer = require('ws').Server; let WebSocketServer = require('ws').Server;
let wss = new WebSocketServer({ server: server }); let wss = new WebSocketServer({ server: server });
wss.on('listening', () => { wss.on('listening', () => {
PLog.log('Parse LiveQuery Server starts running'); logger.info('Parse LiveQuery Server starts running');
}); });
wss.on('connection', (ws) => { wss.on('connection', (ws) => {
onConnect(new ParseWebSocket(ws)); onConnect(new ParseWebSocket(ws));

View File

@@ -1,6 +1,6 @@
import Parse from 'parse/node'; import Parse from 'parse/node';
import LRU from 'lru-cache'; import LRU from 'lru-cache';
import PLog from './PLog'; import logger from '../logger';
class SessionTokenCache { class SessionTokenCache {
cache: Object; cache: Object;
@@ -18,16 +18,16 @@ class SessionTokenCache {
} }
let userId = this.cache.get(sessionToken); let userId = this.cache.get(sessionToken);
if (userId) { 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.Promise.as(userId);
} }
return Parse.User.become(sessionToken).then((user) => { 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; let userId = user.id;
this.cache.set(sessionToken, userId); this.cache.set(sessionToken, userId);
return Parse.Promise.as(userId); return Parse.Promise.as(userId);
}, (error) => { }, (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); return Parse.Promise.error(error);
}); });
} }

View File

@@ -1,5 +1,5 @@
import {matchesQuery, queryHash} from './QueryTools'; import {matchesQuery, queryHash} from './QueryTools';
import PLog from './PLog'; import logger from '../logger';
export type FlattenedObjectData = { [attr: string]: any }; export type FlattenedObjectData = { [attr: string]: any };
export type QueryData = { [attr: string]: any }; export type QueryData = { [attr: string]: any };
@@ -29,13 +29,13 @@ class Subscription {
deleteClientSubscription(clientId: number, requestId: number): void { deleteClientSubscription(clientId: number, requestId: number): void {
let requestIds = this.clientRequestIds.get(clientId); let requestIds = this.clientRequestIds.get(clientId);
if (typeof requestIds === 'undefined') { 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; return;
} }
let index = requestIds.indexOf(requestId); let index = requestIds.indexOf(requestId);
if (index < 0) { 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; return;
} }
requestIds.splice(index, 1); requestIds.splice(index, 1);

View File

@@ -13,8 +13,8 @@ if (!global._babelPolyfill) {
require('babel-polyfill'); require('babel-polyfill');
} }
import { logger, import defaults from './defaults';
configureLogger } from './logger'; import * as logging from './logger';
import AppCache from './cache'; import AppCache from './cache';
import Config from './Config'; import Config from './Config';
import parseServerPackage from '../package.json'; import parseServerPackage from '../package.json';
@@ -94,13 +94,16 @@ class ParseServer {
appId = requiredParameter('You must provide an appId!'), appId = requiredParameter('You must provide an appId!'),
masterKey = requiredParameter('You must provide a masterKey!'), masterKey = requiredParameter('You must provide a masterKey!'),
appName, appName,
analyticsAdapter = undefined, analyticsAdapter,
filesAdapter, filesAdapter,
push, push,
loggerAdapter, loggerAdapter,
jsonLogs, jsonLogs = defaults.jsonLogs,
logsFolder, logsFolder = defaults.logsFolder,
databaseURI, verbose = defaults.verbose,
logLevel = defaults.level,
silent = defaults.silent,
databaseURI = defaults.DefaultMongoURI,
databaseOptions, databaseOptions,
databaseAdapter, databaseAdapter,
cloud, cloud,
@@ -110,15 +113,15 @@ class ParseServer {
dotNetKey, dotNetKey,
restAPIKey, restAPIKey,
webhookKey, webhookKey,
fileKey = undefined, fileKey,
facebookAppIds = [], facebookAppIds = [],
enableAnonymousUsers = true, enableAnonymousUsers = defaults.enableAnonymousUsers,
allowClientClassCreation = true, allowClientClassCreation = defaults.allowClientClassCreation,
oauth = {}, oauth = {},
serverURL = requiredParameter('You must provide a serverURL!'), serverURL = requiredParameter('You must provide a serverURL!'),
maxUploadSize = '20mb', maxUploadSize = defaults.maxUploadSize,
verifyUserEmails = false, verifyUserEmails = defaults.verifyUserEmails,
preventLoginWithUnverifiedEmail = false, preventLoginWithUnverifiedEmail = defaults.preventLoginWithUnverifiedEmail,
emailVerifyTokenValidityDuration, emailVerifyTokenValidityDuration,
cacheAdapter, cacheAdapter,
emailAdapter, emailAdapter,
@@ -130,17 +133,16 @@ class ParseServer {
passwordResetSuccess: undefined passwordResetSuccess: undefined
}, },
liveQuery = {}, liveQuery = {},
sessionLength = 31536000, // 1 Year in seconds sessionLength = defaults.sessionLength, // 1 Year in seconds
expireInactiveSessions = true, expireInactiveSessions = defaults.expireInactiveSessions,
verbose = false, revokeSessionOnPasswordReset = defaults.revokeSessionOnPasswordReset,
revokeSessionOnPasswordReset = true, schemaCacheTTL = defaults.schemaCacheTTL, // cache for 5s
schemaCacheTTL = 5, // cache for 5s
__indexBuildCompletionCallbackForTests = () => {}, __indexBuildCompletionCallbackForTests = () => {},
}) { }) {
// Initialize the node client SDK automatically // Initialize the node client SDK automatically
Parse.initialize(appId, javascriptKey || 'unused', masterKey); Parse.initialize(appId, javascriptKey || 'unused', masterKey);
Parse.serverURL = serverURL; 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.'; throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/connectionPrefix.';
} else if (!databaseAdapter) { } else if (!databaseAdapter) {
databaseAdapter = new MongoStorageAdapter({ databaseAdapter = new MongoStorageAdapter({
@@ -156,49 +158,34 @@ class ParseServer {
throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.'; throw 'When using an explicit database adapter, you must also use and explicit filesAdapter.';
} }
if (logsFolder) { const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
configureLogger({logsFolder, jsonLogs}); const loggerController = new LoggerController(loggerControllerAdapter, appId);
} logging.setLogger(loggerController);
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 filesControllerAdapter = loadAdapter(filesAdapter, () => { const filesControllerAdapter = loadAdapter(filesAdapter, () => {
return new GridStoreAdapter(databaseURI); return new GridStoreAdapter(databaseURI);
}); });
const filesController = new FilesController(filesControllerAdapter, appId);
// Pass the push options too as it works with the default // Pass the push options too as it works with the default
const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push || {}); 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, // We pass the options and the base class for the adatper,
// Note that passing an instance would work too // Note that passing an instance would work too
const filesController = new FilesController(filesControllerAdapter, appId);
const pushController = new PushController(pushControllerAdapter, appId, push); 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 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 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); const analyticsController = new AnalyticsController(analyticsControllerAdapter);
// TODO: create indexes on first creation of a _User object. Otherwise it's impossible to const liveQueryController = new LiveQueryController(liveQuery);
// have a Parse app without it having a _User collection. const databaseController = new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL));
const hooksController = new HooksController(appId, databaseController, webhookKey);
const dbInitPromise = databaseController.performInitizalization(); const dbInitPromise = databaseController.performInitizalization();
AppCache.put(appId, { AppCache.put(appId, {
@@ -251,6 +238,17 @@ class ParseServer {
if (process.env.TESTING) { if (process.env.TESTING) {
__indexBuildCompletionCallbackForTests(dbInitPromise); __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() { get app() {

View File

@@ -65,7 +65,7 @@ export class FunctionsRouter extends PromiseRouter {
master: req.auth && req.auth.isMaster, master: req.auth && req.auth.isMaster,
user: req.auth && req.auth.user, user: req.auth && req.auth.user,
installationId: req.info.installationId, installationId: req.info.installationId,
log: req.config.loggerController && req.config.loggerController.adapter, log: req.config.loggerController,
headers: req.headers, headers: req.headers,
functionName: req.params.functionName functionName: req.params.functionName
}; };

View File

@@ -32,6 +32,13 @@ function booleanParser(opt) {
return false; return false;
} }
function nullParser(opt) {
if (opt == 'null') {
return null;
}
return opt;
}
export default { export default {
"appId": { "appId": {
env: "PARSE_SERVER_APPLICATION_ID", env: "PARSE_SERVER_APPLICATION_ID",
@@ -193,6 +200,18 @@ export default {
env: "JSON_LOGS", env: "JSON_LOGS",
help: "Log as structured JSON objects" 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": { "revokeSessionOnPasswordReset": {
env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET", 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.", 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.",

31
src/defaults.js Normal file
View File

@@ -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
}

View File

@@ -1,10 +1,10 @@
import ParseServer from './ParseServer'; import ParseServer from './ParseServer';
import logger from './logger';
import S3Adapter from 'parse-server-s3-adapter' import S3Adapter from 'parse-server-s3-adapter'
import FileSystemAdapter from 'parse-server-fs-adapter' import FileSystemAdapter from 'parse-server-fs-adapter'
import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter' import InMemoryCacheAdapter from './Adapters/Cache/InMemoryCacheAdapter'
import TestUtils from './TestUtils'; import TestUtils from './TestUtils';
import { useExternal } from './deprecated' import { useExternal } from './deprecated';
import { getLogger } from './logger';
// Factory function // Factory function
let _ParseServer = function(options) { let _ParseServer = function(options) {
@@ -16,5 +16,9 @@ _ParseServer.createLiveQueryServer = ParseServer.createLiveQueryServer;
let GCSAdapter = useExternal('GCSAdapter', 'parse-server-gcs-adapter'); let GCSAdapter = useExternal('GCSAdapter', 'parse-server-gcs-adapter');
Object.defineProperty(module.exports, 'logger', {
get: getLogger
});
export default ParseServer; export default ParseServer;
export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, TestUtils, logger, _ParseServer as ParseServer }; export { S3Adapter, GCSAdapter, FileSystemAdapter, InMemoryCacheAdapter, TestUtils, _ParseServer as ParseServer };

View File

@@ -1,104 +1,20 @@
import winston from 'winston'; 'use strict';
import fs from 'fs'; let logger;
import path from 'path';
import DailyRotateFile from 'winston-daily-rotate-file';
let LOGS_FOLDER = './logs/'; export function setLogger(aLogger) {
logger = aLogger;
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
LOGS_FOLDER = './test_logs/'
} }
LOGS_FOLDER = process.env.PARSE_SERVER_LOGS_FOLDER || LOGS_FOLDER; export function getLogger() {
const JSON_LOGS = process.env.JSON_LOGS || false; return logger;
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;
} }
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 }) { // for: `import { logger } from './logger'`
winston.level = level; Object.defineProperty(module.exports, 'logger', {
logsFolder = logsFolder || currentLogsFolder; get: getLogger
});
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;

View File

@@ -102,7 +102,7 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
triggerName: triggerType, triggerName: triggerType,
object: parseObject, object: parseObject,
master: false, master: false,
log: config.loggerController && config.loggerController.adapter log: config.loggerController
}; };
if (originalParseObject) { if (originalParseObject) {