@luxuryescapes/lib-logger

Logger for Luxury Escapes

Usage no npm install needed!

<script type="module">
  import luxuryescapesLibLogger from 'https://cdn.skypack.dev/@luxuryescapes/lib-logger';
</script>

README

lib-logger

Logger

Install

yarn add @luxuryescapes/lib-logger

Usage

import { createLogger } from '@luxuryescapes/lib-logger'

// Alternatively, for a service hosted in Heroku, we recommend using the contextual logger. See below for details.
const logger = createLogger({ env: "development", service: "svc-good-times", logLevel: "info" })

export default logger
import logger from '/path/to/lib/logger'

logger.error(error)

Options

Option Required Description
env Yes The service environment, e.g. development, production
service Yes The service name e.g. svc-public-offer
logLevel Yes The lowest log level to output
additionalFormats No Additional Winston formats that should be used

Additional Formatters

You can pass additional formatters to customize your log output. For example:

const contextFormatter = winston.format((info) => {
  info.hello = 'world'
  return info;
});

createLogger({
  logLevel: config.logLevel,
  service: config.herokuAppName,
  env: config.appEnv,
  additionalFormats: [contextFormatter()],
});

See Creating Custom Formats for more info.

Contextual Logger

The contextual logger uses Node's async_hooks functionality to add Request IDs to the log output.

import { createContextualLogger, loggingMiddleware } from '@luxuryescapes/lib-logger';
import requestAwareLogger from "@luxuryescapes/lib-logger/lib/requestAwareLogger"

const loggerConfig = {
  logLevel: config.logLevel,
  service: config.herokuAppName,
  env: config.environment
};

const logger = createContextualLogger(loggerConfig);

app.use(express.json()) // Cannot go after requestAwareLogger.getMiddleware.
app.use(loggingMiddleware())

NOTE: If you are using bodyParser.json() or express.json() middleware in your project you need to set them before using logging Middleware. Otherwise logging context will be lost

File Logger

If you specify a file when configuring the logger, the logger will log at log level debug to that file if the env is development or spec.

This is handy if you want to tail the logs when generating verbose SQL statements for example:

const loggerConfig = {
  logLevel: 'warn',
  service: 'my-service',
  env: 'development',
  filename: 'combined.log'
};

Migration existing project to lib-logger

Migrating existing project based on console.log to lib-logger can take a fair amount of effort even though it's not a hard work - just a lot of typing depending on the size of the project.

Below are the steps you need to take to successfully convert your project.

  • Update following dependencies to the latest versions:
    • @luxuryescapes/lib-logger (required) This library keeps track of request context and formatting.
    • @luxuryescapes/lib-authenticated-http-client We use this library to make http calls and log related details including errors. It is crucial to make sure that this lib logs everything correctly in JSON format
    • @luxuryescapes/lib-auth-middleware All authentication is done by this library, which makes it really important to get correct logs from this lib.
  • Define logger, if not done already. You'll need to create a logger.js/ts file with content described above in "Contextual Logger" section.
  • Add requestAware logger middleware. Add following to server.js/ts.
    // Imports  
    import { loggingMiddleware } from "@luxuryescapes/lib-logger";
    
    // Middleware definitions
    app.use(loggingMiddleware())
    
    • If your service uses express.json() or bodyParser.json() middleware you must set them before requestAwareLogger.
    • Make sure that you don't have express.json() or bodyParser.json() specified in your routes
      router.post(route, 
        bodyParser.json(), // WILL NOT WORK. Remove it and replace with global express.json() middleware in server.js
        auth.cachedVerifyUser(), 
        actions.actions.clone);
      
  • Pass logger to lib-auth-middleware.
    const logger = require("../../logging/logger");
    const cachedVerifyUser = () => auth.verifyUser({ cache: redis, logger }); // Notice, logger
    
  • Pass logger to lib-authenticated-http-client.
    import logger from "../logging/logger";
    const authenticatedHttpClient = new AuthenticatedHttpClient({
        serviceName: config.herokuAppName,
        apiHost: config.hosts.api,
        serviceUsername: config.serviceUserEmail,
        servicePassword: config.serviceUserPassword,
        logRequests: true,
        timeRequests: true,
        logger: logger  // Notice, logger
    });
    
  • Update all console.log with logger.info/warn/error. Winston logger works differently to console.log. So attention needs to be paid how to convert all console logs to the new format. Have a look at the examples below. They cover most of the scenarios.
    console.log("my message")
    logger.info("my message")
    
    const myVar = "var1"
    console.log("my var:", myVar)
    logger.info(`my var: ${myVar}`)
    
    const myError = new Error("error happened")
    console.log("Something went wrong", myError)
    logger.error("Something went wrong", myError) // will print "Something went wrong error happened" and append stacktrace as another field
    
    console.log("Request Body:", req.body)
    logger.info("Request Body", {requestBody: req.body})
    
    const varA = {a: 1, b: 2}
    const varB = "aaabbbccc"
    console.log("Params:", varA, varB)
    logger.info("Params", {params: {varA, varB}})
    
  • Update .eslintrc.json to start throwing errors for console.log
      "rules": {
        "prettier/prettier": "error",
        "no-undef": "error",
        "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
        "no-unused-expressions": "error",
        "no-console": "error" // <===================
      },
    
  • You can still leave some console logs in scripts and files that are not part of production code.

Sending logs to NewRelic

lib-logger by itself is not sufficient for your logs to appear in NewRelic. This page has more information how to do that: https://aussiecommerce.atlassian.net/wiki/spaces/TEC/pages/2192080997/Deploying+a+new+service+in+Heroku