From fa736f1df7b6a4f6dbc3f08b2f9db8711de23694 Mon Sep 17 00:00:00 2001 From: Arthur Cinader Date: Sat, 6 Aug 2016 08:29:19 -0400 Subject: [PATCH] Allow logger to add transports (#2363) - Move all of the winston logic from FileLoggerAdapter to WinstonLoggerAdapter - Export WinstonLoggerAdapter so it can be sublcassed - Expost the ability to add adittional transports to logger - Import FirehoseLoggerAdapter alongside other adapters so it can be configured. --- spec/CloudCodeLogger.spec.js | 6 +-- spec/Logger.spec.js | 18 +++++++ spec/LoggerController.spec.js | 6 +-- spec/LogsRouter.spec.js | 4 +- ...r.spec.js => WinstonLoggerAdapter.spec.js} | 51 ++++++++++--------- src/Adapters/Logger/LoggerAdapter.js | 2 +- ...ggerAdapter.js => WinstonLoggerAdapter.js} | 28 ++++------ src/ParseServer.js | 6 +-- src/cli/cli-definitions.js | 2 +- src/logger.js | 14 ++++- 10 files changed, 79 insertions(+), 58 deletions(-) create mode 100644 spec/Logger.spec.js rename spec/{FileLoggerAdapter.spec.js => WinstonLoggerAdapter.spec.js} (66%) rename src/Adapters/Logger/{FileLoggerAdapter.js => WinstonLoggerAdapter.js} (82%) diff --git a/spec/CloudCodeLogger.spec.js b/spec/CloudCodeLogger.spec.js index 145a6155..ac1b15aa 100644 --- a/spec/CloudCodeLogger.spec.js +++ b/spec/CloudCodeLogger.spec.js @@ -1,10 +1,10 @@ 'use strict'; var LoggerController = require('../src/Controllers/LoggerController').LoggerController; -var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; +var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter; describe("Cloud Code Logger", () => { it("should expose log to functions", (done) => { - var logController = new LoggerController(new FileLoggerAdapter()); + var logController = new LoggerController(new WinstonLoggerAdapter()); Parse.Cloud.define("loggerTest", (req, res) => { req.log.info('logTest', 'info log', {info: 'some log' }); @@ -35,7 +35,7 @@ describe("Cloud Code Logger", () => { }); it("should expose log to trigger", (done) => { - var logController = new LoggerController(new FileLoggerAdapter()); + var logController = new LoggerController(new WinstonLoggerAdapter()); Parse.Cloud.beforeSave("MyObject", (req, res) => { req.log.info('beforeSave MyObject', 'info log', {info: 'some log' }); diff --git a/spec/Logger.spec.js b/spec/Logger.spec.js new file mode 100644 index 00000000..37f8f871 --- /dev/null +++ b/spec/Logger.spec.js @@ -0,0 +1,18 @@ +var logger = require('../src/logger'); +var winston = require('winston'); + +class TestTransport extends winston.Transport { + log(level, msg, meta, callback) { + callback(null, true); + } +} + +describe('Logger', () => { + it('should add transport', () => { + const testTransport = new (TestTransport)({}); + spyOn(testTransport, 'log'); + logger.addTransport(testTransport); + logger.logger.info('hi'); + expect(testTransport.log).toHaveBeenCalled(); + }); +}); diff --git a/spec/LoggerController.spec.js b/spec/LoggerController.spec.js index 3e992911..2d773db8 100644 --- a/spec/LoggerController.spec.js +++ b/spec/LoggerController.spec.js @@ -1,12 +1,12 @@ var LoggerController = require('../src/Controllers/LoggerController').LoggerController; -var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; +var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter; describe('LoggerController', () => { it('can check process a query without throwing', (done) => { // Make mock request var query = {}; - var loggerController = new LoggerController(new FileLoggerAdapter()); + var loggerController = new LoggerController(new WinstonLoggerAdapter()); expect(() => { loggerController.getLogs(query).then(function(res) { @@ -69,7 +69,7 @@ describe('LoggerController', () => { level: 'error' }; - var loggerController = new LoggerController(new FileLoggerAdapter()); + var loggerController = new LoggerController(new WinstonLoggerAdapter()); expect(() => { loggerController.getLogs(query).then(function(res) { diff --git a/spec/LogsRouter.spec.js b/spec/LogsRouter.spec.js index e8907a39..36fab149 100644 --- a/spec/LogsRouter.spec.js +++ b/spec/LogsRouter.spec.js @@ -3,9 +3,9 @@ const request = require('request'); var LogsRouter = require('../src/Routers/LogsRouter').LogsRouter; var LoggerController = require('../src/Controllers/LoggerController').LoggerController; -var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; +var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter; -const loggerController = new LoggerController(new FileLoggerAdapter()); +const loggerController = new LoggerController(new WinstonLoggerAdapter()); describe('LogsRouter', () => { it('can check valid master key of request', (done) => { diff --git a/spec/FileLoggerAdapter.spec.js b/spec/WinstonLoggerAdapter.spec.js similarity index 66% rename from spec/FileLoggerAdapter.spec.js rename to spec/WinstonLoggerAdapter.spec.js index 2816e95c..ee2a72d4 100644 --- a/spec/FileLoggerAdapter.spec.js +++ b/spec/WinstonLoggerAdapter.spec.js @@ -1,35 +1,36 @@ 'use strict'; -var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter; +var WinstonLoggerAdapter = require('../src/Adapters/Logger/WinstonLoggerAdapter').WinstonLoggerAdapter; var Parse = require('parse/node').Parse; var request = require('request'); describe('info logs', () => { + it("Verify INFO logs", (done) => { - var fileLoggerAdapter = new FileLoggerAdapter(); - fileLoggerAdapter.info('testing info logs', () => { - fileLoggerAdapter.query({ - from: new Date(Date.now() - 500), - size: 100, - level: 'info' - }, (results) => { - if(results.length == 0) { - fail('The adapter should return non-empty results'); - done(); - } else { - expect(results[0].message).toEqual('testing info logs'); - done(); - } + var winstonLoggerAdapter = new WinstonLoggerAdapter(); + winstonLoggerAdapter.info('testing info logs', () => { + winstonLoggerAdapter.query({ + from: new Date(Date.now() - 500), + size: 100, + level: 'info' + }, (results) => { + if (results.length == 0) { + fail('The adapter should return non-empty results'); + done(); + } else { + expect(results[0].message).toEqual('testing info logs'); + done(); + } + }); }); }); - }); }); describe('error logs', () => { it("Verify ERROR logs", (done) => { - var fileLoggerAdapter = new FileLoggerAdapter(); - fileLoggerAdapter.error('testing error logs', () => { - fileLoggerAdapter.query({ + var winstonLoggerAdapter = new WinstonLoggerAdapter(); + winstonLoggerAdapter.error('testing error logs', () => { + winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, level: 'error' @@ -52,8 +53,8 @@ describe('verbose logs', () => { reconfigureServer({ verbose: true }) .then(() => createTestUser()) .then(() => { - let fileLoggerAdapter = new FileLoggerAdapter(); - return fileLoggerAdapter.query({ + let winstonLoggerAdapter = new WinstonLoggerAdapter(); + return winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, level: 'verbose' @@ -71,8 +72,8 @@ describe('verbose logs', () => { headers: headers, url: 'http://localhost:8378/1/login?username=test&password=moon-y' }, (error, response, body) => { - let fileLoggerAdapter = new FileLoggerAdapter(); - return fileLoggerAdapter.query({ + let winstonLoggerAdapter = new WinstonLoggerAdapter(); + return winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, level: 'verbose' @@ -93,8 +94,8 @@ describe('verbose logs', () => { let obj = new Parse.Object('users'); obj.set('password', 'pw'); obj.save().then(() => { - let fileLoggerAdapter = new FileLoggerAdapter(); - return fileLoggerAdapter.query({ + let winstonLoggerAdapter = new WinstonLoggerAdapter(); + return winstonLoggerAdapter.query({ from: new Date(Date.now() - 500), size: 100, level: 'verbose' diff --git a/src/Adapters/Logger/LoggerAdapter.js b/src/Adapters/Logger/LoggerAdapter.js index b1fe31b8..9fda8eab 100644 --- a/src/Adapters/Logger/LoggerAdapter.js +++ b/src/Adapters/Logger/LoggerAdapter.js @@ -6,7 +6,7 @@ // * info(obj1 [, obj2, .., objN]) // * error(obj1 [, obj2, .., objN]) // * query(options, callback) -// Default is FileLoggerAdapter.js +// Default is WinstonLoggerAdapter.js export class LoggerAdapter { info() {} diff --git a/src/Adapters/Logger/FileLoggerAdapter.js b/src/Adapters/Logger/WinstonLoggerAdapter.js similarity index 82% rename from src/Adapters/Logger/FileLoggerAdapter.js rename to src/Adapters/Logger/WinstonLoggerAdapter.js index 4b018c20..d3728f28 100644 --- a/src/Adapters/Logger/FileLoggerAdapter.js +++ b/src/Adapters/Logger/WinstonLoggerAdapter.js @@ -1,23 +1,9 @@ -// Logger -// -// Wrapper around Winston logging library with custom query -// -// expected log entry to be in the shape of: -// {"level":"info","message":"Your Message","timestamp":"2016-02-04T05:59:27.412Z"} -// import { LoggerAdapter } from './LoggerAdapter'; -import { Parse } from 'parse/node'; -import { logger, configure } from '../../logger'; +import { logger, addTransport } from '../../logger'; const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000; const CACHE_TIME = 1000 * 60; -let LOGS_FOLDER = './logs/'; - -if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { - LOGS_FOLDER = './test_logs/' -} - let currentDate = new Date(); let simpleCache = { @@ -65,8 +51,7 @@ let _isValidLogEntry = (from, until, entry) => { : false }; -export class FileLoggerAdapter extends LoggerAdapter { - +export class WinstonLoggerAdapter extends LoggerAdapter { info() { return logger.info.apply(undefined, arguments); } @@ -75,6 +60,13 @@ export class FileLoggerAdapter extends LoggerAdapter { return logger.error.apply(undefined, arguments); } + addTransport(transport) { + // Note that this is calling addTransport + // from logger. See import - confusing. + // but this is not recursive. + addTransport(transport); + } + // custom query as winston is currently limited query(options, callback = () => {}) { if (!options) { @@ -114,4 +106,4 @@ export class FileLoggerAdapter extends LoggerAdapter { } } -export default FileLoggerAdapter; +export default WinstonLoggerAdapter; diff --git a/src/ParseServer.js b/src/ParseServer.js index d8db7d36..8c870edd 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -28,7 +28,7 @@ import { InMemoryCacheAdapter } from './Adapters/Cache/InMemoryCacheAdapter'; import { AnalyticsController } from './Controllers/AnalyticsController'; import { CacheController } from './Controllers/CacheController'; import { AnalyticsAdapter } from './Adapters/Analytics/AnalyticsAdapter'; -import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter'; +import { WinstonLoggerAdapter } from './Adapters/Logger/WinstonLoggerAdapter'; import { FilesController } from './Controllers/FilesController'; import { FilesRouter } from './Routers/FilesRouter'; import { FunctionsRouter } from './Routers/FunctionsRouter'; @@ -71,7 +71,7 @@ const requiredUserFields = { fields: { ...SchemaController.defaultColumns._Defau // "analyticsAdapter": an adapter class for analytics // "filesAdapter": a class like GridStoreAdapter providing create, get, // and delete -// "loggerAdapter": a class like FileLoggerAdapter providing info, error, +// "loggerAdapter": a class like WinstonLoggerAdapter providing info, error, // and query // "jsonLogs": log as structured JSON objects // "databaseURI": a uri like mongodb://localhost:27017/dbname to tell us @@ -186,7 +186,7 @@ class ParseServer { }); // Pass the push options too as it works with the default const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push || {}); - const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter); + const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter); const emailControllerAdapter = loadAdapter(emailAdapter); const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId}); const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter); diff --git a/src/cli/cli-definitions.js b/src/cli/cli-definitions.js index f0b7d0de..9f1d7565 100644 --- a/src/cli/cli-definitions.js +++ b/src/cli/cli-definitions.js @@ -172,7 +172,7 @@ export default { }, "customPages": { env: "PARSE_SERVER_CUSTOM_PAGES", - help: "custom pages for pasword validation and reset", + help: "custom pages for password validation and reset", action: objectParser }, "maxUploadSize": { diff --git a/src/logger.js b/src/logger.js index 0c9308ee..15ae1f6b 100644 --- a/src/logger.js +++ b/src/logger.js @@ -13,6 +13,7 @@ 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 = [ @@ -32,7 +33,7 @@ function generateTransports(level, options = {}) { level: 'error' } ), options) - ]; + ].concat(additionalTransports); if (!process.env.TESTING || process.env.VERBOSE) { transports = [ new (winston.transports.Console)( @@ -90,5 +91,14 @@ export function addGroup(groupName) { return winston.loggers.get(groupName); } -export { logger }; +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;