From 2becedc54b06168ffa01c2ef0671e994b589608e Mon Sep 17 00:00:00 2001 From: Stuart Colville Date: Mon, 16 May 2016 12:04:26 +0100 Subject: [PATCH] Add logging for the browser --- config/default.js | 1 + karma.conf.js | 5 ++- src/core/client/base.js | 5 ++- .../client/config.js} | 0 src/core/client/logger.js | 38 +++++++++++++++++++ src/core/logger.js | 14 +++++++ src/core/server/base.js | 8 +--- .../{ => core/client}/test_client_config.js | 2 +- tests/client/core/client/test_logger.js | 32 ++++++++++++++++ webpack.dev.config.babel.js | 6 ++- webpack.prod.config.babel.js | 5 ++- 11 files changed, 105 insertions(+), 11 deletions(-) rename src/{client-config.js => core/client/config.js} (100%) create mode 100644 src/core/client/logger.js create mode 100644 src/core/logger.js rename tests/client/{ => core/client}/test_client_config.js (95%) create mode 100644 tests/client/core/client/test_logger.js diff --git a/config/default.js b/config/default.js index 2108de0793f..a02c21e4c81 100644 --- a/config/default.js +++ b/config/default.js @@ -47,6 +47,7 @@ module.exports = { // Since by definition client-side code is public these config keys // must not contain sensitive data. clientConfigKeys: [ + 'appName', 'apiHost', 'apiPath', 'cookieName', diff --git a/karma.conf.js b/karma.conf.js index 3153070f104..87cd0a5cc94 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -22,7 +22,10 @@ const newWebpackConfig = Object.assign({}, webpackConfigProd, { new webpack.DefinePlugin({ CLIENT_CONFIG: JSON.stringify(clientConfig), }), - new webpack.NormalModuleReplacementPlugin(/config$/, 'client-config.js'), + // Replaces server config module with the subset clientConfig object. + new webpack.NormalModuleReplacementPlugin(/config$/, 'core/client/config.js'), + // Substitutes client only config. + new webpack.NormalModuleReplacementPlugin(/core\/logger$/, 'core/client/logger.js'), ], devtool: 'inline-source-map', module: { diff --git a/src/core/client/base.js b/src/core/client/base.js index 4867e5038d0..217d069455d 100644 --- a/src/core/client/base.js +++ b/src/core/client/base.js @@ -5,6 +5,9 @@ import { Provider } from 'react-redux'; import { Router, browserHistory } from 'react-router'; import { ReduxAsyncConnect } from 'redux-async-connect'; +import log from 'core/logger'; + + export default function makeClient(routes, createStore) { const initialStateContainer = document.getElementById('redux-store-state'); let initialState; @@ -13,7 +16,7 @@ export default function makeClient(routes, createStore) { try { initialState = JSON.parse(initialStateContainer.textContent); } catch (error) { - console.error('Could not load initial redux data'); // eslint-disable-line no-console + log.error('Could not load initial redux data'); } } const store = createStore(initialState); diff --git a/src/client-config.js b/src/core/client/config.js similarity index 100% rename from src/client-config.js rename to src/core/client/config.js diff --git a/src/core/client/logger.js b/src/core/client/logger.js new file mode 100644 index 00000000000..a4b19e3a44d --- /dev/null +++ b/src/core/client/logger.js @@ -0,0 +1,38 @@ +/* eslint-disable no-console */ +import config from 'config'; + +/* + * This is the client version of the logger. + * This module loader is loaded when importing 'core/logger' in the client. + */ + +export function bindConsoleMethod(consoleMethodName, _consoleObj = window.console, + _function = Function) { + let consoleMethod; + let consoleFunc; + const appName = config.get('appName'); + if (typeof _consoleObj[consoleMethodName] !== 'undefined') { + consoleMethod = _consoleObj[consoleMethodName]; + } else { + throw new Error(`console method "${consoleMethodName}" does not exist`); + } + + const app = `[${appName}]`; + + if (_function.prototype.bind) { + consoleFunc = _function.prototype.bind.call(consoleMethod, _consoleObj, app); + } else { + // Fallback for IE < 10; + consoleFunc = function fallbackConsoleFunc(...args) { + return _function.prototype.apply.apply(consoleMethod, [_consoleObj, app, ...args]); + }; + } + return consoleFunc; +} + +const log = {}; +['log', 'info', 'error', 'warn'].forEach((logMethodName) => { + log[logMethodName] = bindConsoleMethod(logMethodName); +}); + +export default log; diff --git a/src/core/logger.js b/src/core/logger.js new file mode 100644 index 00000000000..9030f461391 --- /dev/null +++ b/src/core/logger.js @@ -0,0 +1,14 @@ +import config from 'config'; +import bunyan from 'bunyan'; + +/* + * NOTE: the client version of this is loaded from + * core/client/logger when running client code. + * This magical substitution is orchestrated by webpack. + */ + +export default bunyan.createLogger({ + name: 'server', + app: config.get('appName'), + serializers: bunyan.stdSerializers, +}); diff --git a/src/core/server/base.js b/src/core/server/base.js index 38d85df0445..3bb57dd4b2b 100644 --- a/src/core/server/base.js +++ b/src/core/server/base.js @@ -8,7 +8,6 @@ import cookie from 'react-cookie'; import React from 'react'; import ReactDOM from 'react-dom/server'; import ReactHelmet from 'react-helmet'; -import bunyan from 'bunyan'; import { Provider } from 'react-redux'; import { match } from 'react-router'; @@ -20,6 +19,8 @@ import ServerHtml from 'core/containers/ServerHtml'; import config from 'config'; import { setJWT } from 'core/actions'; +import log from 'core/logger'; + const env = config.util.getEnv('NODE_ENV'); const isDeployed = config.get('isDeployed'); @@ -28,11 +29,6 @@ const isDevelopment = config.get('isDevelopment'); const errorString = 'Internal Server Error'; const appName = config.get('appName'); -const log = bunyan.createLogger({ - name: 'server', - app: appName, - serializers: bunyan.stdSerializers, -}); function logRequests(req, res, next) { const start = new Date(); diff --git a/tests/client/test_client_config.js b/tests/client/core/client/test_client_config.js similarity index 95% rename from tests/client/test_client_config.js rename to tests/client/core/client/test_client_config.js index f1722b4132d..7f3bd9c7b69 100644 --- a/tests/client/test_client_config.js +++ b/tests/client/core/client/test_client_config.js @@ -1,4 +1,4 @@ -import { ClientConfig } from 'client-config'; +import { ClientConfig } from 'core/client/config'; describe('client-config module', () => { let config; diff --git a/tests/client/core/client/test_logger.js b/tests/client/core/client/test_logger.js new file mode 100644 index 00000000000..8ee9a9d77f1 --- /dev/null +++ b/tests/client/core/client/test_logger.js @@ -0,0 +1,32 @@ +import { bindConsoleMethod } from 'core/logger'; + +describe('logger.bindConsoleMethod()', () => { + const fakeConsole = { + log: sinon.stub(), + info: sinon.stub(), + }; + + it('aliases as expected', () => { + const logAlias = bindConsoleMethod('log', fakeConsole); + logAlias('whatever'); + assert.ok(fakeConsole.log.called, 'log should be called'); + }); + + it('throws if the method does not exist on the object', () => { + assert.throws(() => { + bindConsoleMethod('bazinga', fakeConsole); + }, Error, 'console method "bazinga" does not exist'); + }); + + it('should call Function.prototype.apply if bind does not exist', () => { + const fakeFunc = { + prototype: { + bind: null, + apply: sinon.stub(), + }, + }; + const infoAlias = bindConsoleMethod('info', fakeConsole, fakeFunc); + infoAlias('hello'); + assert.ok(fakeFunc.prototype.apply.called); + }); +}); diff --git a/webpack.dev.config.babel.js b/webpack.dev.config.babel.js index 69547cbfc44..53dd0a5f929 100644 --- a/webpack.dev.config.babel.js +++ b/webpack.dev.config.babel.js @@ -78,8 +78,12 @@ export default Object.assign({}, webpackConfig, { plugins: [ new webpack.DefinePlugin({ CLIENT_CONFIG: JSON.stringify(clientConfig), + 'process.env.NODE_ENV': JSON.stringify('production'), }), - new webpack.NormalModuleReplacementPlugin(/config$/, 'client-config.js'), + // Replaces server config module with the subset clientConfig object. + new webpack.NormalModuleReplacementPlugin(/config$/, 'core/client/config.js'), + // Substitutes client only config. + new webpack.NormalModuleReplacementPlugin(/core\/logger$/, 'core/client/logger.js'), new webpack.HotModuleReplacementPlugin(), new webpack.IgnorePlugin(/webpack-stats\.json$/), webpackIsomorphicToolsPlugin.development(), diff --git a/webpack.prod.config.babel.js b/webpack.prod.config.babel.js index 9771c619310..bfd52cd664b 100644 --- a/webpack.prod.config.babel.js +++ b/webpack.prod.config.babel.js @@ -55,7 +55,9 @@ export default { 'process.env.NODE_ENV': JSON.stringify('production'), }), // Replaces server config module with the subset clientConfig object. - new webpack.NormalModuleReplacementPlugin(/config$/, 'client-config.js'), + new webpack.NormalModuleReplacementPlugin(/config$/, 'core/client/config.js'), + // Substitutes client only config. + new webpack.NormalModuleReplacementPlugin(/core\/logger$/, 'core/client/logger.js'), new ExtractTextPlugin('[name]-[chunkhash].css', {allChunks: true}), new SriStatsPlugin({ algorithm: 'sha512', @@ -70,6 +72,7 @@ export default { new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, + drop_console: true, }, }), new WebpackIsomorphicToolsPlugin(webpackIsomorphicToolsConfig),