Accessing express-http-context in dependent module

Viewed 53

Short form question:

We are trying to access the express-http-context in a dependent logger module of our main NodeJS service. However, we are getting it as undefined.

Context:

We are trying to tag all our logs with a requestId for each unique request in NodeJS. We have a logger module that wraps winston logger. This logger module is a separate package and is used across all our different NodeJs (Express) services.

Our winston logger wrapper file in our cf-logger-module is as follows:

import { omit } from 'lodash';
import { Logger, createLogger, format, transports, LeveledLogMethod } from 'winston';
import httpcontext from 'express-http-context';
import Sentry from 'winston-sentry-log';

import { ILoggerConfig } from '../interfaces/ILoggerConfig';
import { LevelCodes } from '../constants/LoggingLevels';
import ILoggerAdapter from '../interfaces/ILoggerAdapter';
import LoggerConfiguration from '../LoggerConfiguration';

export default class WinstonLogger implements ILoggerAdapter {
  private readonly _logger: Logger;
  private _config: ILoggerConfig;
  private _moduleName: string;
  private _loggingMethods: { [prop: number]: LeveledLogMethod };

  public static createAdapter(moduleName: string, config: LoggerConfiguration): ILoggerAdapter {
    return new WinstonLogger(moduleName, config);
  }

  public constructor(moduleName: string, loggerConfiguration: LoggerConfiguration) {
    this._config = loggerConfiguration.config;
    this._moduleName = moduleName;

    const sentryOptions = {
      config: {
        dsn: 'https://b71e24c577604eab977dcf18720f1d5a@o1185085.ingest.sentry.io/6468703',
      },
      level: 'info',
    };

    this._logger = createLogger({
      levels: LevelCodes,
      transports: [new transports.Console({ level: this._config.logging.level }), new Sentry(sentryOptions)],
      format: format.combine(
        format.timestamp({ format: this._config.logging.timestamp.format }),
        format.errors({ stack: this._config.logging.errors.stack }),
        format.simple(),
        format.colorize(),
        format.printf(({ level, message, timestamp, ...metadata }) => {
          const module = metadata.module;
          const method = `${metadata.methodName}()`;
          metadata = omit(metadata, 'module');
          metadata = omit(metadata, 'methodName');
          // console.log('Logger http context', httpcontext.get('reqId'));
          return `${timestamp} [${module}:${method}] [${level}]: ${message} ${JSON.stringify(metadata)}`;
        }),
      ),
    });

    /* istanbul ignore next */
    this._loggingMethods = {
      [LevelCodes.debug]: this._logger?.debug.bind(this._logger),
      [LevelCodes.error]: this._logger?.error.bind(this._logger),
      [LevelCodes.http]: this._logger?.http.bind(this._logger),
      [LevelCodes.info]: this._logger?.info.bind(this._logger),
      [LevelCodes.silly]: this._logger?.silly.bind(this._logger),
      [LevelCodes.verbose]: this._logger?.verbose.bind(this._logger),
      [LevelCodes.warn]: this._logger?.warn.bind(this._logger),
    };
  }

  public logMessage(level: number, message: string, metadata?: object, error?: Error): void {
    this._loggingMethods[level](message, this.process(metadata, error));
  }

  private process(metadata?: object, error?: Error): object {
    /** Extract Service Name */
    console.log('Logger http context', httpcontext.get('reqId'));
    Object.assign(metadata, {
      module: `${this._config.service.name}.${this._moduleName}`,
    });

    /** Extract Error */
    if (error !== undefined) {
      Object.assign(error, metadata);
    }

    /** TODO: Sanitize and metadata */

    return error || metadata;
  }
}

As you can see we are trying to access the reqID set using express-http-context.

Our nodeJs apps package.json dependencies look like:

"dependencies": {
    "@coverforce-platform/cf-logger-module": "^1.0.26",
    ....
    "express-http-context": "^1.2.4",
    "express-ruid": "^1.1.4",
  },

The app.ts looks like:

private initializeMiddlewares() {
    this.app.use(cors({ origin: AppConfig.getValues().SERVICE.ORIGIN }));
    this.app.use(hpp());
    this.app.use(helmet());
    this.app.use(compression());
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
    this.app.use(cookieParser());
    this.app.use(httpcontext.middleware);
    this.app.use(ruid({ setInContext: true, attribute: 'reqId' }));
    this.app.use(Sentry.Handlers.requestHandler() as express.RequestHandler);
    this.app.use(Sentry.Handlers.errorHandler());
    this.app.use(Sentry.Handlers.tracingHandler());
  }

and the server.ts is:

import 'reflect-metadata';
import { Container } from 'typedi';
import { LoggerFactory, ILogger } from '@coverforce-platform/cf-logger-module';
import { AppConfig } from '@coverforce-platform/cf-config-module';
import { ErrorResponseMiddleware } from '@coverforce-platform/cf-error-module';
import { SchemaValidationMiddleware } from '@coverforce-platform/cf-schema-validation-module';
import { AccountsServiceSchema } from '@coverforce-platform/cf-common-api-model';
import { accountsDatabaseLoader } from './dependencyInjection/accountsDatabase.dependency';
import { App } from './app';
import { ERROR_RESPONSE_MIDDLEWARE, SCHEMA_VALIDATION_MIDDLEWARE } from './constants/dependencyInjection.constants';

main();

async function main() {
  const logger: ILogger = initLogger();
  await initAppConfig(logger);
  initializeDependencyInjection();
  const app: App = Container.get(App);
  app.listen();
}

However, in the logger the console.log('Http Context', httpcontext.get('reqId') is always returning undefined for the reqId field in the httpcontext. If we do the same within any class in the main service we are able to retrieve the httpcontext's reqId. However, we are not able to access it in the dependent package/module.

0 Answers
Related