From 111a1708d1faa81ef2c7fdfde6564b795c8d83e4 Mon Sep 17 00:00:00 2001 From: Anuj Date: Tue, 3 Oct 2017 10:31:28 -0700 Subject: [PATCH] Server logging enhancements (#508) * Improving logging experience + consolidating to single log function * Adding pretty-error for more readable node error stack * Fix eslint error * Logging requests received * Re-commit logging requests received --- config/utils/envVars.js | 9 ++++-- internal/utils.js | 16 +++++++--- internal/webpack/configFactory.js | 28 +++++++++-------- package.json | 1 + server/index.js | 35 +++++++++++++++++++-- server/middleware/errorHandlers.js | 13 ++++++-- server/middleware/reactApplication/index.js | 13 +++++--- 7 files changed, 88 insertions(+), 27 deletions(-) diff --git a/config/utils/envVars.js b/config/utils/envVars.js index 12c069ed..83db61b1 100644 --- a/config/utils/envVars.js +++ b/config/utils/envVars.js @@ -7,7 +7,6 @@ */ import appRootDir from 'app-root-dir'; -import colors from 'colors/safe'; import dotenv from 'dotenv'; import fs from 'fs'; import path from 'path'; @@ -15,6 +14,8 @@ import path from 'path'; import ifElse from '../../shared/utils/logic/ifElse'; import removeNil from '../../shared/utils/arrays/removeNil'; +import { log } from '../../internal/utils'; + // PRIVATES function registerEnvFile() { @@ -40,7 +41,11 @@ function registerEnvFile() { // If we found an env file match the register it. if (envFilePath) { // eslint-disable-next-line no-console - console.log(colors.bgBlue.white(`==> Registering environment variables from: ${envFilePath}`)); + log({ + title: 'server', + level: 'special', + message: `Registering environment variables from: ${envFilePath}`, + }); dotenv.config({ path: envFilePath }); } } diff --git a/internal/utils.js b/internal/utils.js index 8a876dd4..d9172bda 100644 --- a/internal/utils.js +++ b/internal/utils.js @@ -26,13 +26,21 @@ export function log(options) { } const level = options.level || 'info'; - const msg = `==> ${title} -> ${options.message}`; + const msg = `${title}: ${options.message}`; switch (level) { - case 'warn': console.log(colors.yellow(msg)); break; - case 'error': console.log(colors.bgRed.white(msg)); break; + case 'warn': + console.log(colors.yellow(msg)); + break; + case 'error': + console.log(colors.bgRed.white(msg)); + break; + case 'special': + console.log(colors.italic.cyan(msg)); + break; case 'info': - default: console.log(colors.green(msg)); + default: + console.log(colors.green.dim(msg)); } } diff --git a/internal/webpack/configFactory.js b/internal/webpack/configFactory.js index da5ecaa0..a7060c5c 100644 --- a/internal/webpack/configFactory.js +++ b/internal/webpack/configFactory.js @@ -6,7 +6,7 @@ import path from 'path'; import webpack from 'webpack'; import WebpackMd5Hash from 'webpack-md5-hash'; -import { happyPackPlugin } from '../utils'; +import { happyPackPlugin, log } from '../utils'; import { ifElse } from '../../shared/utils/logic'; import { mergeDeep } from '../../shared/utils/objects'; import { removeNil } from '../../shared/utils/arrays'; @@ -44,18 +44,20 @@ export default function webpackConfigFactory(buildOptions) { const ifDevClient = ifElse(isDev && isClient); const ifProdClient = ifElse(isProd && isClient); - console.log( - `==> Creating ${isProd + log({ + level: 'info', + title: 'Webpack', + message: `Creating ${isProd ? 'an optimised' : 'a development'} bundle configuration for the "${target}"`, - ); + }); const bundleConfig = isServer || isClient ? // This is either our "server" or "client" bundle. - config(['bundles', target]) + config(['bundles', target]) : // Otherwise it must be an additional node bundle. - config(['additionalNodeBundles', target]); + config(['additionalNodeBundles', target]); if (!bundleConfig) { throw new Error('No bundle configuration exists for target:', target); @@ -124,9 +126,9 @@ export default function webpackConfigFactory(buildOptions) { target: isClient ? // Only our client bundle will target the web as a runtime. - 'web' + 'web' : // Any other bundle must be targetting node as a runtime. - 'node', + 'node', // Ensure that webpack polyfills the following node features for use // within any bundles that are targetting node as a runtime. This will be @@ -517,12 +519,12 @@ export default function webpackConfigFactory(buildOptions) { // paths used on the client. publicPath: isDev ? // When running in dev mode the client bundle runs on a - // seperate port so we need to put an absolute path here. - `http://${config('host')}:${config('clientDevServerPort')}${config( - 'bundles.client.webPath', - )}` + // seperate port so we need to put an absolute path here. + `http://${config('host')}:${config('clientDevServerPort')}${config( + 'bundles.client.webPath', + )}` : // Otherwise we just use the configured web path for the client. - config('bundles.client.webPath'), + config('bundles.client.webPath'), // We only emit files when building a web bundle, for the server // bundle we only care about the file loader being able to create // the correct asset URLs. diff --git a/package.json b/package.json index 56c4e242..65873d52 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "modernizr": "3.5.0", "normalize.css": "7.0.0", "offline-plugin": "4.8.3", + "pretty-error": "2.1.1", "prop-types": "15.5.10", "react": "15.6.1", "react-async-bootstrapper": "1.1.1", diff --git a/server/index.js b/server/index.js index d02377d3..d4a08edc 100644 --- a/server/index.js +++ b/server/index.js @@ -11,6 +11,7 @@ import serviceWorker from './middleware/serviceWorker'; import offlinePage from './middleware/offlinePage'; import errorHandlers from './middleware/errorHandlers'; import config from '../config'; +import { log } from '../internal/utils'; // Create our express based server. const app = express(); @@ -43,14 +44,44 @@ app.use(config('bundles.client.webPath'), clientBundle); app.use(express.static(pathResolve(appRootDir.get(), config('publicAssetsPath')))); // The React application middleware. -app.get('*', reactApplication); +app.get('*', (request, response) => { + log({ + title: 'Request', + level: 'special', + message: `Received for "${request.url}"`, + }); + + return reactApplication(request, response); +}); // Error Handler middlewares. app.use(...errorHandlers); // Create an http listener for our express app. const listener = app.listen(config('port'), () => - console.log(`Server listening on port ${config('port')}`)); + log({ + title: 'server', + level: 'special', + message: `✓ + + ${config('welcomeMessage')} + + ${config('htmlPage.defaultTitle')} is ready! + + with + + Service Workers: ${config('serviceWorker.enabled')} + Polyfills: ${config('polyfillIO.enabled')} (${config('polyfillIO.features').join(', ')}) + + Server is now listening on Port ${config('port')} + You can access it in the browser at http://${config('host')}/${config('port')} + Press Ctrl-C to stop. + + + + `, + }), +); // We export the listener as it will be handy for our development hot reloader, // or for exposing a general extension layer for application customisations. diff --git a/server/middleware/errorHandlers.js b/server/middleware/errorHandlers.js index c727cf43..b9b4b36d 100644 --- a/server/middleware/errorHandlers.js +++ b/server/middleware/errorHandlers.js @@ -1,6 +1,16 @@ /* eslint-disable no-console */ /* eslint-disable no-unused-vars */ +const prettyError = require('pretty-error').start(); + +// Configure prettyError to simplify the stack trace: + +// skip events.js and http.js and similar core node files +prettyError.skipNodeFiles(); + +// skip all the trace lines about express` core and sub-modules +prettyError.skipPackage('express'); + const errorHandlersMiddleware = [ /** * 404 errors middleware. @@ -21,8 +31,7 @@ const errorHandlersMiddleware = [ */ function unexpectedErrorMiddleware(err, req, res, next) { if (err) { - console.log(err); - console.log(err.stack); + console.log(prettyError.render(err)); } res.status(500).send('Sorry, an unexpected error occurred.'); }, diff --git a/server/middleware/reactApplication/index.js b/server/middleware/reactApplication/index.js index 6b31357b..6560bafb 100644 --- a/server/middleware/reactApplication/index.js +++ b/server/middleware/reactApplication/index.js @@ -9,6 +9,7 @@ import config from '../../../config'; import ServerHTML from './ServerHTML'; import DemoApp from '../../../shared/components/DemoApp'; +import { log } from '../../../internal/utils'; /** * React application middleware, supports server side rendering. @@ -26,7 +27,11 @@ export default function reactApplicationMiddleware(request, response) { if (config('disableSSR')) { if (process.env.BUILD_FLAG_IS_DEV === 'true') { // eslint-disable-next-line no-console - console.log('==> Handling react route without SSR'); + log({ + title: 'Server', + level: 'info', + message: `Handling react route without SSR: ${request.url}`, + }); } // SSR is disabled so we will return an "empty" html page and // rely on the client to initialize and render the react application. @@ -78,10 +83,10 @@ export default function reactApplicationMiddleware(request, response) { .status( reactRouterContext.missed ? // If the renderResult contains a "missed" match then we set a 404 code. - // Our App component will handle the rendering of an Error404 view. - 404 + // Our App component will handle the rendering of an Error404 view. + 404 : // Otherwise everything is all good and we send a 200 OK status. - 200, + 200, ) .send(`${html}`); });