App Engine Node.js: how to link app logs and requests logs - node.js

I am using Node.js on App Engine Standard and Flexible.
In the logs viewer, is it possible to display application logs nested inside request logs?

Yes it is possible to correlate application logs and request logs. This is the end result in the Logs Viewer:
To achieve this you can either:
Use both the #google-cloud/trace-agent and #google-cloud/logging-bunyan modules in your application. When you do so, your logs are automatically annotated with the correct Trace ID (see docs for Bunyan).
Extract the trace ID from the request header
If you do not want to use the Trace module, you can extract the trace ID from the request header, use the following code to extract the traceId:
const traceHeader = req && req.headers ? req.headers['x-cloud-trace-context'] || '' : '';
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
Then, you need to populate the trace attribute of your log entries. When using the Bunyan logger, use the following code:
logger.info({
'logging.googleapis.com/trace': `projects/${project}/traces/${traceId}`
}, 'your message');

I also faced the same issue sometime back and did some workaround to make it. But in the above-mentioned solution might not help in some use cases where you have don't req, res object reference.
So here the solution. it will group all the logs under the request log.
Also created -> NPM Module
File Name: correlate-logs.js
import bunyan from 'bunyan';
import { LOGGING_TRACE_KEY } from '#google-cloud/logging-bunyan';
import cls from 'cls-hooked';
import uuid from 'uuid/v1';
/**
* CreateLogger will return loggerContextMiddleware and log.
* Bind the loggerContextMiddleware on top to corelate other middleware logs. `app.use(loggerContextMiddleware);`
* then you can log like this anywhere `log.info('This is helpful to see corelated logs in nodejs.)` and it will show with-in reqeust log.
* #param {*} options
*/
export default function createLogger(projectId, bunyanLoggerOptions) {
if (!projectId || !bunyanLoggerOptions) throw new Error('Please pass the required fields projectId and bunyanLoggerOption');
const ns = cls.createNamespace(`logger/${uuid()}`); // To create unique namespace.
const logger = bunyan.createLogger(bunyanLoggerOptions);
/**
* Express Middleware to add request context to logger for corelating the logs in GCP.
* #param {*} req
* #param {*} res
* #param {*} next
*/
const loggerContextMiddleware = (req, res, next) => {
const traceHeader = (req && req.headers && req.headers['x-cloud-trace-context']) || '';
if (traceHeader) {
ns.bindEmitter(req);
ns.bindEmitter(res);
const traceId = traceHeader ? traceHeader.split('/')[0] : '';
const trace = `projects/${projectId}/traces/${traceId}`;
ns.run(() => {
ns.set('trace', trace);
next();
});
} else {
next();
}
};
/**
* Helper method to get the trace id from CLS hook.
*/
function getTrace() {
if (ns && ns.active) return ns.get('trace');
return '';
}
/**
* Simple wrapper to avoid pushing dev logs to cloud.
* #param {*} level
* #param {*} msg
*/
function printLog(level, ...msg) {
const trace = getTrace();
if (trace) { logger[level]({ [LOGGING_TRACE_KEY]: trace }, ...msg); } else { logger[level](...msg); }
}
/**
* Little wrapper to abstract the log level.
*/
const log = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].reduce((prev, curr) => ({ [curr]: (...msg) => printLog(curr, ...msg), ...prev }), {});
return { loggerContextMiddleware, log };
}
File Name: logger.js
import { LoggingBunyan } from '#google-cloud/logging-bunyan';
import createLogger from '../lib/corelate-logs';
import { getProjectId, ifDev } from './config';
// Creates a Bunyan Stackdriver Logging client
const loggingBunyan = new LoggingBunyan();
let loggerOption;
if (ifDev()) {
const bunyanDebugStream = require('bunyan-debug-stream'); // eslint-disable-line
loggerOption = {
name: 'my-service',
streams: [{
level: 'info',
type: 'raw',
stream: bunyanDebugStream({
forceColor: true,
}),
}],
serializers: bunyanDebugStream.serializers,
};
} else {
loggerOption = {
name: 'my-service',
level: 'info',
streams: [loggingBunyan.stream('info')],
};
}
const { loggerContextMiddleware, log } = createLogger(getProjectId() || 'dev', loggerOption);
export { loggerContextMiddleware, log };
Hope this helps somebody.

Related

TypeError [ERR_UNKNOWN_FILE_EXTENSION]:

This is the command i'm trying to run ./bitgo-express --port 3080 --env test --bind localhost and I'm receiving following error:
(node:367854) ExperimentalWarning: The ESM module loader is experimental.
internal/process/esm_loader.js:90
internalBinding('errors').triggerUncaughtException(
^
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /home/root/example.com/BitGoJS/modules/express/bin/bitgo-express
at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:65:15)
at Loader.getFormat (internal/modules/esm/loader.js:116:42)
at Loader.getModuleJob (internal/modules/esm/loader.js:247:31)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async Loader.import (internal/modules/esm/loader.js:181:17)
at async Object.loadESM (internal/process/esm_loader.js:84:5) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
bitgo-express:
#!/usr/bin/env node
const { init } = require('../src/expressApp.ts');
if (require.main === module) {
init()
.catch(err => {
console.log(`Fatal error: ${err.message}`);
console.log(err.stack);
});
}
expressApp.ts:
/**
* #prettier
*/
import * as express from 'express';
import * as httpProxy from 'http-proxy';
import * as url from 'url';
import * as Bluebird from 'bluebird';
import * as path from 'path';
import * as _ from 'lodash';
import * as debugLib from 'debug';
import * as https from 'https';
import * as http from 'http';
import * as morgan from 'morgan';
import * as fs from 'fs';
import { Request as StaticRequest } from 'express-serve-static-core';
import { Config, config } from './config';
const debug = debugLib('bitgo:express');
// eslint-disable-next-line #typescript-eslint/camelcase
import { SSL_OP_NO_TLSv1 } from 'constants';
import { IpcError, NodeEnvironmentError, TlsConfigurationError } from './errors';
import { Environments } from 'bitgo';
import { setupRoutes } from './clientRoutes';
const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
/**
* Set up the logging middleware provided by morgan
*
* #param app
* #param config
*/
function setupLogging(app: express.Application, config: Config): void {
// Set up morgan for logging, with optional logging into a file
let middleware;
if (config.logFile) {
// create a write stream (in append mode)
const accessLogPath = path.resolve(config.logFile);
const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' });
console.log('Log location: ' + accessLogPath);
// setup the logger
middleware = morgan('combined', { stream: accessLogStream });
} else {
middleware = morgan('combined');
}
app.use(middleware);
morgan.token('remote-user', function(req) {
return req.isProxy ? 'proxy' : 'local_express';
});
}
/**
* If we're running in a custom env, set the appropriate environment URI and network properties
*
* #param config
*/
function configureEnvironment(config: Config): void {
const { customRootUri, customBitcoinNetwork } = config;
if (customRootUri) {
Environments['custom'].uri = customRootUri;
}
if (customBitcoinNetwork) {
Environments['custom'].network = customBitcoinNetwork;
}
}
/**
* Create and configure the proxy middleware and add it to the app middleware stack
*
* #param app bitgo-express Express app
* #param config
*/
function configureProxy(app: express.Application, config: Config): void {
const { env, timeout } = config;
// Mount the proxy middleware
const options = {
timeout: timeout,
proxyTimeout: timeout,
secure: true,
};
if (Environments[env].network === 'testnet') {
// Need to do this to make supertest agent pass (set rejectUnauthorized to false)
options.secure = false;
}
const proxy = httpProxy.createProxyServer(options);
const sendError = (res: http.ServerResponse, status: number, json: object) => {
res.writeHead(status, {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(json));
};
proxy.on('proxyReq', function(proxyReq, req) {
// Need to rewrite the host, otherwise cross-site protection kicks in
const parsedUri = url.parse(Environments[env].uri).hostname;
if (parsedUri) {
proxyReq.setHeader('host', parsedUri);
}
const userAgent = req.headers['user-agent']
? BITGOEXPRESS_USER_AGENT + ' ' + req.headers['user-agent']
: BITGOEXPRESS_USER_AGENT;
proxyReq.setHeader('User-Agent', userAgent);
});
proxy.on('error', (err, _, res) => {
debug('Proxy server error: ', err);
sendError(res, 500, {
error: 'BitGo Express encountered an error while attempting to proxy your request to BitGo. Please try again.',
});
});
proxy.on('econnreset', (err, _, res) => {
debug('Proxy server connection reset error: ', err);
sendError(res, 500, {
error:
'BitGo Express encountered a connection reset error while attempting to proxy your request to BitGo. Please try again.',
});
});
app.use(function(req: StaticRequest, res: express.Response) {
if (req.url && (/^\/api\/v[12]\/.*$/.test(req.url) || /^\/oauth\/token.*$/.test(req.url))) {
req.isProxy = true;
proxy.web(req, res, { target: Environments[env].uri, changeOrigin: true });
return;
}
// user tried to access a url which is not an api route, do not proxy
res.status(404).send('bitgo-express can only proxy BitGo API requests');
});
}
/**
* Create an HTTP server configured for accepting HTTPS connections
*
* #param config application configuration
* #param app
* #return {Server}
*/
async function createHttpsServer(
app: express.Application,
config: Config & { keyPath: string; crtPath: string }
): Promise<https.Server> {
const { keyPath, crtPath } = config;
const privateKeyPromise = fs.promises.readFile(keyPath, 'utf8');
const certificatePromise = fs.promises.readFile(crtPath, 'utf8');
const [key, cert] = await Promise.all([privateKeyPromise, certificatePromise]);
// eslint-disable-next-line #typescript-eslint/camelcase
return https.createServer({ secureOptions: SSL_OP_NO_TLSv1, key, cert }, app);
}
/**
* Create an HTTP server configured for accepting plain old HTTP connections
*
* #param app
* #return {Server}
*/
function createHttpServer(app: express.Application): http.Server {
return http.createServer(app);
}
/**
* Create a startup function which will be run upon server initialization
*
* #param config
* #param baseUri
* #return {Function}
*/
export function startup(config: Config, baseUri: string): () => void {
return function() {
const { env, ipc, customRootUri, customBitcoinNetwork } = config;
console.log('BitGo-Express running');
console.log(`Environment: ${env}`);
if (ipc) {
console.log(`IPC path: ${ipc}`);
} else {
console.log(`Base URI: ${baseUri}`);
}
if (customRootUri) {
console.log(`Custom root URI: ${customRootUri}`);
}
if (customBitcoinNetwork) {
console.log(`Custom bitcoin network: ${customBitcoinNetwork}`);
}
};
}
/**
* helper function to determine whether we should run the server over TLS or not
*/
function isTLS(config: Config): config is Config & { keyPath: string; crtPath: string } {
const { keyPath, crtPath } = config;
return Boolean(keyPath && crtPath);
}
/**
* Create either a HTTP or HTTPS server
* #param config
* #param app
* #return {Server}
*/
export async function createServer(config: Config, app: express.Application) {
return isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
}
/**
* Create the base URI where the BitGoExpress server will be available once started
* #return {string}
*/
export function createBaseUri(config: Config): string {
const { bind, port } = config;
const tls = isTLS(config);
const isStandardPort = (port === 80 && !tls) || (port === 443 && tls);
return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`;
}
/**
* Check environment and other preconditions to ensure bitgo-express can start safely
* #param config
*/
function checkPreconditions(config: Config) {
const { env, disableEnvCheck, bind, ipc, disableSSL, keyPath, crtPath, customRootUri, customBitcoinNetwork } = config;
// warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
if (env === 'prod' && process.env.NODE_ENV !== 'production') {
if (!disableEnvCheck) {
throw new NodeEnvironmentError(
'NODE_ENV should be set to production when running against prod environment. Use --disableenvcheck if you really want to run in a non-production node configuration.'
);
} else {
console.warn(
`warning: unsafe NODE_ENV '${process.env.NODE_ENV}'. NODE_ENV must be set to 'production' when running against BitGo production environment.`
);
}
}
const needsTLS = !ipc && env === 'prod' && bind !== 'localhost' && !disableSSL;
// make sure keyPath and crtPath are set when running over TLS
if (needsTLS && !(keyPath && crtPath)) {
throw new TlsConfigurationError('Must enable TLS when running against prod and listening on external interfaces!');
}
if (Boolean(keyPath) !== Boolean(crtPath)) {
throw new TlsConfigurationError('Must provide both keypath and crtpath when running in TLS mode!');
}
if ((customRootUri || customBitcoinNetwork) && env !== 'custom') {
console.warn(`customRootUri or customBitcoinNetwork is set, but env is '${env}'. Setting env to 'custom'.`);
config.env = 'custom';
}
}
export function app(cfg: Config): express.Application {
debug('app is initializing');
const app = express();
setupLogging(app, cfg);
const { debugNamespace, disableProxy } = cfg;
// enable specified debug namespaces
if (_.isArray(debugNamespace)) {
_.forEach(debugNamespace, ns => debugLib.enable(ns));
}
checkPreconditions(cfg);
// Be more robust about accepting URLs with double slashes
app.use(function(req, res, next) {
req.url = req.url.replace(/\/\//g, '/');
next();
});
// Decorate the client routes
setupRoutes(app, cfg);
configureEnvironment(cfg);
if (!disableProxy) {
configureProxy(app, cfg);
}
return app;
}
/**
* Prepare to listen on an IPC (unix domain) socket instead of a normal TCP port.
* #param ipcSocketFilePath path to file where IPC socket should be created
*/
export async function prepareIpc(ipcSocketFilePath: string) {
if (process.platform === 'win32') {
throw new IpcError(`IPC option is not supported on platform ${process.platform}`);
}
try {
const stat = fs.statSync(ipcSocketFilePath);
if (!stat.isSocket()) {
throw new IpcError('IPC socket is not actually a socket');
}
// ipc socket does exist and is indeed a socket. However, the socket cannot already exist prior
// to being bound since it will be created by express internally when binding. If there's a stale
// socket from the last run, clean it up before attempting to bind to it again. Arguably, it would
// be better to do this before exiting, but that gets a bit more complicated when all exit paths
// need to clean up the socket file correctly.
fs.unlinkSync(ipcSocketFilePath);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
export async function init(): Bluebird<void> {
const cfg = config();
const expressApp = app(cfg);
const server = await createServer(cfg, expressApp);
const { port, bind, ipc } = cfg;
const baseUri = createBaseUri(cfg);
if (ipc) {
await prepareIpc(ipc);
server.listen(ipc, startup(cfg, baseUri));
} else {
server.listen(port, bind, startup(cfg, baseUri));
}
server.timeout = 300 * 1000; // 5 minutes
}
Node.js version: 12.19.1
NPM version: 6.14.8
How can I fix this issue? I saw this SyntaxError: Cannot use import statment without a module, TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Catch 22 which was similar to mine. But that answer didn't help me as then I'll have to edit the whole file and even I did I'm stuck in 38 line. If someone can translate the whole code into node friendly require, Please translate it. Much appreciated!
You can run your script like this:
npx ts-node ./path/to/your-script.ts
You may want to build your scripts with typescript compiler and run the build result as ordinary JS script.
But for that you need to setup some bundler like for example Webpack and you need to configure plugins for handling Typescript compilation for example you can use Bable.

Translate expressApp.ts to a node friendly require

I want to change translate "import" to a node friendly require in following code.
Example: import * as express from 'express'; to const express = require('express');
I'm stucked at import { Request as StaticRequest } from 'express-serve-static-core'; line. Can someone help me, please
I want to translate the whole code to node friendly code
/**
* #prettier
*/
import * as express from 'express';
import * as httpProxy from 'http-proxy';
import * as url from 'url';
import * as Bluebird from 'bluebird';
import * as path from 'path';
import * as _ from 'lodash';
import * as debugLib from 'debug';
import * as https from 'https';
import * as http from 'http';
import * as morgan from 'morgan';
import * as fs from 'fs';
import { Request as StaticRequest } from 'express-serve-static-core';
import { Config, config } from './config';
const debug = debugLib('bitgo:express');
// eslint-disable-next-line #typescript-eslint/camelcase
import { SSL_OP_NO_TLSv1 } from 'constants';
import { IpcError, NodeEnvironmentError, TlsConfigurationError } from './errors';
import { Environments } from 'bitgo';
import { setupRoutes } from './clientRoutes';
const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
/**
* Set up the logging middleware provided by morgan
*
* #param app
* #param config
*/
function setupLogging(app: express.Application, config: Config): void {
// Set up morgan for logging, with optional logging into a file
let middleware;
if (config.logFile) {
// create a write stream (in append mode)
const accessLogPath = path.resolve(config.logFile);
const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' });
console.log('Log location: ' + accessLogPath);
// setup the logger
middleware = morgan('combined', { stream: accessLogStream });
} else {
middleware = morgan('combined');
}
app.use(middleware);
morgan.token('remote-user', function(req) {
return req.isProxy ? 'proxy' : 'local_express';
});
}
/**
* If we're running in a custom env, set the appropriate environment URI and network properties
*
* #param config
*/
function configureEnvironment(config: Config): void {
const { customRootUri, customBitcoinNetwork } = config;
if (customRootUri) {
Environments['custom'].uri = customRootUri;
}
if (customBitcoinNetwork) {
Environments['custom'].network = customBitcoinNetwork;
}
}
/**
* Create and configure the proxy middleware and add it to the app middleware stack
*
* #param app bitgo-express Express app
* #param config
*/
function configureProxy(app: express.Application, config: Config): void {
const { env, timeout } = config;
// Mount the proxy middleware
const options = {
timeout: timeout,
proxyTimeout: timeout,
secure: true,
};
if (Environments[env].network === 'testnet') {
// Need to do this to make supertest agent pass (set rejectUnauthorized to false)
options.secure = false;
}
const proxy = httpProxy.createProxyServer(options);
const sendError = (res: http.ServerResponse, status: number, json: object) => {
res.writeHead(status, {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(json));
};
proxy.on('proxyReq', function(proxyReq, req) {
// Need to rewrite the host, otherwise cross-site protection kicks in
const parsedUri = url.parse(Environments[env].uri).hostname;
if (parsedUri) {
proxyReq.setHeader('host', parsedUri);
}
const userAgent = req.headers['user-agent']
? BITGOEXPRESS_USER_AGENT + ' ' + req.headers['user-agent']
: BITGOEXPRESS_USER_AGENT;
proxyReq.setHeader('User-Agent', userAgent);
});
proxy.on('error', (err, _, res) => {
debug('Proxy server error: ', err);
sendError(res, 500, {
error: 'BitGo Express encountered an error while attempting to proxy your request to BitGo. Please try again.',
});
});
proxy.on('econnreset', (err, _, res) => {
debug('Proxy server connection reset error: ', err);
sendError(res, 500, {
error:
'BitGo Express encountered a connection reset error while attempting to proxy your request to BitGo. Please try again.',
});
});
app.use(function(req: StaticRequest, res: express.Response) {
if (req.url && (/^\/api\/v[12]\/.*$/.test(req.url) || /^\/oauth\/token.*$/.test(req.url))) {
req.isProxy = true;
proxy.web(req, res, { target: Environments[env].uri, changeOrigin: true });
return;
}
// user tried to access a url which is not an api route, do not proxy
res.status(404).send('bitgo-express can only proxy BitGo API requests');
});
}
/**
* Create an HTTP server configured for accepting HTTPS connections
*
* #param config application configuration
* #param app
* #return {Server}
*/
async function createHttpsServer(
app: express.Application,
config: Config & { keyPath: string; crtPath: string }
): Promise<https.Server> {
const { keyPath, crtPath } = config;
const privateKeyPromise = fs.promises.readFile(keyPath, 'utf8');
const certificatePromise = fs.promises.readFile(crtPath, 'utf8');
const [key, cert] = await Promise.all([privateKeyPromise, certificatePromise]);
// eslint-disable-next-line #typescript-eslint/camelcase
return https.createServer({ secureOptions: SSL_OP_NO_TLSv1, key, cert }, app);
}
/**
* Create an HTTP server configured for accepting plain old HTTP connections
*
* #param app
* #return {Server}
*/
function createHttpServer(app: express.Application): http.Server {
return http.createServer(app);
}
/**
* Create a startup function which will be run upon server initialization
*
* #param config
* #param baseUri
* #return {Function}
*/
export function startup(config: Config, baseUri: string): () => void {
return function() {
const { env, ipc, customRootUri, customBitcoinNetwork } = config;
console.log('BitGo-Express running');
console.log(`Environment: ${env}`);
if (ipc) {
console.log(`IPC path: ${ipc}`);
} else {
console.log(`Base URI: ${baseUri}`);
}
if (customRootUri) {
console.log(`Custom root URI: ${customRootUri}`);
}
if (customBitcoinNetwork) {
console.log(`Custom bitcoin network: ${customBitcoinNetwork}`);
}
};
}
/**
* helper function to determine whether we should run the server over TLS or not
*/
function isTLS(config: Config): config is Config & { keyPath: string; crtPath: string } {
const { keyPath, crtPath } = config;
return Boolean(keyPath && crtPath);
}
/**
* Create either a HTTP or HTTPS server
* #param config
* #param app
* #return {Server}
*/
export async function createServer(config: Config, app: express.Application) {
return isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
}
/**
* Create the base URI where the BitGoExpress server will be available once started
* #return {string}
*/
export function createBaseUri(config: Config): string {
const { bind, port } = config;
const tls = isTLS(config);
const isStandardPort = (port === 80 && !tls) || (port === 443 && tls);
return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`;
}
/**
* Check environment and other preconditions to ensure bitgo-express can start safely
* #param config
*/
function checkPreconditions(config: Config) {
const { env, disableEnvCheck, bind, ipc, disableSSL, keyPath, crtPath, customRootUri, customBitcoinNetwork } = config;
// warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
if (env === 'prod' && process.env.NODE_ENV !== 'production') {
if (!disableEnvCheck) {
throw new NodeEnvironmentError(
'NODE_ENV should be set to production when running against prod environment. Use --disableenvcheck if you really want to run in a non-production node configuration.'
);
} else {
console.warn(
`warning: unsafe NODE_ENV '${process.env.NODE_ENV}'. NODE_ENV must be set to 'production' when running against BitGo production environment.`
);
}
}
const needsTLS = !ipc && env === 'prod' && bind !== 'localhost' && !disableSSL;
// make sure keyPath and crtPath are set when running over TLS
if (needsTLS && !(keyPath && crtPath)) {
throw new TlsConfigurationError('Must enable TLS when running against prod and listening on external interfaces!');
}
if (Boolean(keyPath) !== Boolean(crtPath)) {
throw new TlsConfigurationError('Must provide both keypath and crtpath when running in TLS mode!');
}
if ((customRootUri || customBitcoinNetwork) && env !== 'custom') {
console.warn(`customRootUri or customBitcoinNetwork is set, but env is '${env}'. Setting env to 'custom'.`);
config.env = 'custom';
}
}
export function app(cfg: Config): express.Application {
debug('app is initializing');
const app = express();
setupLogging(app, cfg);
const { debugNamespace, disableProxy } = cfg;
// enable specified debug namespaces
if (_.isArray(debugNamespace)) {
_.forEach(debugNamespace, ns => debugLib.enable(ns));
}
checkPreconditions(cfg);
// Be more robust about accepting URLs with double slashes
app.use(function(req, res, next) {
req.url = req.url.replace(/\/\//g, '/');
next();
});
// Decorate the client routes
setupRoutes(app, cfg);
configureEnvironment(cfg);
if (!disableProxy) {
configureProxy(app, cfg);
}
return app;
}
/**
* Prepare to listen on an IPC (unix domain) socket instead of a normal TCP port.
* #param ipcSocketFilePath path to file where IPC socket should be created
*/
export async function prepareIpc(ipcSocketFilePath: string) {
if (process.platform === 'win32') {
throw new IpcError(`IPC option is not supported on platform ${process.platform}`);
}
try {
const stat = fs.statSync(ipcSocketFilePath);
if (!stat.isSocket()) {
throw new IpcError('IPC socket is not actually a socket');
}
// ipc socket does exist and is indeed a socket. However, the socket cannot already exist prior
// to being bound since it will be created by express internally when binding. If there's a stale
// socket from the last run, clean it up before attempting to bind to it again. Arguably, it would
// be better to do this before exiting, but that gets a bit more complicated when all exit paths
// need to clean up the socket file correctly.
fs.unlinkSync(ipcSocketFilePath);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
export async function init(): Bluebird<void> {
const cfg = config();
const expressApp = app(cfg);
const server = await createServer(cfg, expressApp);
const { port, bind, ipc } = cfg;
const baseUri = createBaseUri(cfg);
if (ipc) {
await prepareIpc(ipc);
server.listen(ipc, startup(cfg, baseUri));
} else {
server.listen(port, bind, startup(cfg, baseUri));
}
server.timeout = 300 * 1000; // 5 minutes
}
You can use Destructuring and Rename variable syntax of ES6.
require('express-serve-static-core') returns a object, just do it as a normal object.
const { Request: StaticRequest } = require('express-serve-static-core');

How to write a log file with winston on NodeJS

I'm building on my NodeJS API a custom logger using Winston.
I'm trying to log into a file but is it not working and cannot understand the issue.
I used some online resources to find out the solution but anything worked.
I used the transports.File() and added to my push but nothing happens.
My goal is to add the same logs I'm getting using the logger with the console into the file.
Same format and way of it.
My code
/* eslint-disable object-curly-newline */
/* eslint-disable arrow-parens */
/* eslint-disable comma-dangle */
// Logger
// This logger is used to show error/info messages about the status of the API
import winston from 'winston';
import moment from 'moment';
// import logSymbols from 'log-symbols';
import fs from 'fs-extra';
import { logger, NODE_ENV } from '../config';
const transports = [];
const dir = './logs';
// Creating the logs dir if does not exist
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// For development in prod need to check for dev env
// in dev we want more info error tracking
// in prod essential info error message
if (NODE_ENV.env !== 'development') {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat()
),
}),
new winston.transports.File({
level: 'error',
filename: `${dir}/logs.log`,
})
);
} else {
transports.push(new winston.transports.Console());
}
// Parse meta keys
const parser = string => {
if (!string) {
return '';
}
if (typeof string === 'string') {
return string;
}
return Object.keys(string).length ? JSON.stringify(string, undefined, 2) : '';
};
// Logger instance
const LoggerInstance = winston.createLogger({
level: logger.level,
levels: winston.config.npm.levels,
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.printf(info => {
const { timestamp, level, message, meta } = info;
const ts = moment(timestamp)
.local()
.format('YYYY-MM-DD HH:MM:ss');
const metaMsg = meta ? `: ${parser(meta)}` : '';
// const symbol = level === 'error' ? logSymbols.error : logSymbols.success;
return `${ts} [${level}] ${parser(message)} ${metaMsg}`;
})
),
transports,
});
export default LoggerInstance;
While you set log level explicitly to 'error' in your winston transporter, you can not see any messages in debug level in the file. Please change your configuration so that your file. transporter accepts debug level messages too.
new winston.transports.File({
level: 'debug',
filename: `${dir}/logs.log`,
})

TypeError: intlProvider.getChildContext is not a function

I'm using injectIntl in my react functional component to achieve the localization.
I'm using enzyme/jest to do the unit test. I've copied the intl-test-helper file, but got error:
" TypeError: intlProvider.getChildContext is not a function "
I've tried other suggestions from stackflow:
1. remove mock file --- which I don't have mock file
2. use const { IntlProvider } = jest.requireActual("react-intl"); to force it use the actual one , not mock --- not working.
the react component is: WarningModal.jsx:
import { FormattedMessage, injectIntl } from "react-intl";
.......
const WarningModal = ({
.........
...props
}) => {
........
export default injectIntl(WarningModal);
the intlTestHelper.js file is :
* Components using the react-intl module require access to the intl context.
* This is not available when mounting single components in Enzyme.
* These helper functions aim to address that and wrap a valid,
* English-locale intl context around them.
*/
import React from "react";
import { IntlProvider, intlShape } from "react-intl";
import { mount, shallow } from "enzyme"; // eslint-disable-line import/no-extraneous-dependencies
/** Create the IntlProvider to retrieve context for wrapping around. */
function createIntlContext(messages, locale) {
const { IntlProvider } = jest.requireActual("react-intl");
const intlProvider = new IntlProvider({ messages, locale }, {});
const { intl } = intlProvider.getChildContext();
return intl;
}
/** When using React-Intl `injectIntl` on components, props.intl is required. */
function nodeWithIntlProp(node, messages = {}, locale = "en") {
return React.cloneElement(node, {
intl: createIntlContext(messages, locale)
});
}
/**
* Create a shadow renderer that wraps a node with Intl provider context.
* #param {ReactComponent} node - Any React Component
* #param {Object} context
* #param {Object} messages - A map with keys (id) and messages (value)
* #param {string} locale - Locale string
*/
export function shallowWithIntl(
node,
{ context } = {},
messages = {},
locale = "en"
) {
return shallow(nodeWithIntlProp(node), {
context: Object.assign({}, context, {
intl: createIntlContext(messages, locale)
})
});
}
/**
* Mount the node with Intl provider context.
* #param {Component} node - Any React Component
* #param {Object} context
* #param {Object} messages - A map with keys (id) and messages (value)
* #param {string} locale - Locale string
*/
export function mountWithIntl(
node,
{ context, childContextTypes } = {},
messages = {},
locale = "en"
) {
return mount(nodeWithIntlProp(node), {
context: Object.assign({}, context, {
intl: createIntlContext(messages, locale)
}),
childContextTypes: Object.assign({}, { intl: intlShape }, childContextTypes)
});
}
here how I use it to test:
import React from "react";
import { _WM as WarningModal } from "../components/WarningModal";
// import { shallow } from "enzyme";
import { mountWithIntl } from "../utils/intlTestHelper.js";
describe("<WarningModal />", () => {
const props = {
discardChanges: jest.fn(),
saveChanges: jest.fn(),
closeWarningModal: jest.fn(),
intl: { formatMessage: jest.fn() }
};
it("should have heading", () => {
const wrapper = mountWithIntl(<WarningModal {...props} />);
expect(wrapper.find(".confirm-title")).toBeTruthy();
});
});
error:
● <WarningModal /> › should have heading
TypeError: intlProvider.getChildContext is not a function
14 | const { IntlProvider } = jest.requireActual("react-intl");
15 | const intlProvider = new IntlProvider({ messages, locale }, {});
> 16 | const { intl } = intlProvider.getChildContext();
| ^
17 | return intl;
18 | }
19 |
at getChildContext (src/utils/intlTestHelper.js:16:33)
at createIntlContext (src/utils/intlTestHelper.js:23:11)
at nodeWithIntlProp (src/utils/intlTestHelper.js:60:16)
at Object.<anonymous> (src/tests/WarningModal.spec.js:29:21)
please shine some lights on this. Thank you.
In later versions of react-intl getChildContext has been deprecated and may generate this error. You can use the following instead:
import { createIntl } from 'react-intl';
const intl = createIntl({ locale: "en",
messages: {
message1: "Hello world"
}
});
React-Intl has replaced IntlProvider.getChildContext, with the createIntl for testing purpose, while migrating V2 to V3.
We've removed IntlProvider.getChildContext for testing and now you can use createIntl to create a standalone intl object outside of React and use that for testing purposes. See Testing with React Intl for more details
Here is the Link
So the working code for this is
For resolving this error, you have to create custom shallow component. Like as
import { createIntl } from 'react-intl';
const LocalLanguage = {
french:{},
arabic:{},
english:{}
}
const lang = getCurrentLanguage('en', LocalLanguage);
const intl = createIntl({ locale: 'en', lang }, {});
export const shallowWithIntl = (node) => {
return shallow(nodeWithIntlProp(node), { context: { intl } });
}
If this not helps, then you can define the following function, in your helper file.
const messages = require('./Lang/en.json') // en.json
const defaultLocale = 'en'
const locale = defaultLocale
export const intl = (component) => {
return (
<IntlProvider
locale={locale}
messages={messages}
>
{React.cloneElement(component)}
</IntlProvider>
);
}
And use it in your test files as below
const wrapper = mount(intl(<MobileRechargeComponent />));

How to unit test an AWS SDK method called in a separate method?

I am currently writing a piece of code which simply uploads a file to an S3 bucket. This is a serverless NodeJS project written in Typescript. For testing, I am using Mocha, Chai, and attempting to use SinonJS.
I have a class with a method which uploads a buffer as a file to S3:
import { S3 } from "aws-sdk"
import { S3UploadError } from "../errors/S3UploadError"
/**
* This class provides a means of interacting with S3
*
* #export
* #class AWSS3Manager
*/
export class AWSS3Manager {
/**
* Instance of S3
*
* #private
* #type {S3}
* #memberof AWSS3Manager
*/
private s3: S3
/**
* Creates an instance of AWSS3Manager.
*
* #memberof AWSS3Manager
*/
public constructor() {
this.s3 = new S3()
}
/**
* Upload a file to bucket on S3 by using a buffer
*
* #param {Buffer} body
* #param {string} bucketName
* #param {string} fileName
* #returns {Promise<PromiseResult<S3.PutObjectOutput, S3UploadError>>}
* #memberof AWSS3Manager
*/
public async upload(body: Buffer, bucketName: string, fileName: string) {
const params = {
Body: body,
Bucket: bucketName,
Key: fileName,
}
try {
return await this.s3.putObject(params).promise()
} catch (err) {
console.log(err)
throw new S3UploadError()
}
}
}
I'm not even sure if this is the right thing to be doing, but I want to test the upload method of the above class. And in my mind, to do that I need to mock the response from S3.putObject in order to ensure that the external library doesn't cause interference. However, no matter what I have tried I can't get it to work.
I have tried stubbing the S3 put object method, but to no avail:
chai.should()
const assert = chai.assert
const expect = chai.expect
const s3manager = new AWSS3Manager()
let sandbox: sinon.SinonSandbox
let spy
describe("AWSS3Manager behaves as expected", () => {
beforeEach(() => {
sandbox = sinon.createSandbox()
spy = sinon.spy()
})
afterEach(() => {
// Restore the default sandbox here
sandbox.restore()
})
it("Uploads a file correctly to S3", async () => {
const putObjectStub = sinon.stub(S3.prototype, "putObject")
putObjectStub.yields("ERROR", 'data')
//const uploadStub = sandbox.stub(s3manager, "upload").resolves("Yup")
//sandbox.stub(AWS, "S3").resolves('HEYY')
const test = await s3manager.upload(new Buffer("ddds"), "TestBucket", "Test")
assert(putObjectStub.called)
})
})
I have tried stubbing the S3 method, and also stubbing the upload method its self and seeing how many calls there was to putObject but I can't get nothing to work.
Can anyone help?
I ran into a similar situation attempting to stub out the putObject call on an S3 instance that I was then returning chained with .promise(). See below:
export class UploadService {
s3: S3
constructor() {
this.s3 = new S3({ region: 'us-west-2' })
}
upload({ data, path }: { data: string, path: string }) {
const uploadParams = {
Body: data,
Bucket: bucketName, // defined elsewhere
Key: path
}
return this.s3.putObject(uploadParams).promise()
}
}
I dug into the aws-sdk definitions and came up with this test setup--I don't love it because it's kinda exposing the guts of the sdk that you shouldn't have to think about but it passes the typescript compiler and gets the job done 🤷‍♂️
import { expect } from 'chai'
import * as sinon from 'sinon'
import { UploadService } from '../../src/services/upload_service'
import { Request, Service } from 'aws-sdk'
describe('UploadService', () => {
const uploadService = new UploadService()
let uploadStub: sinon.SinonStub
describe('#upload', () => {
beforeEach(() => {
uploadStub = sinon
.stub(uploadService.s3, 'putObject')
.returns(new Request(new Service(), 'put'))
})
it('uploads the data to the specified path in S3', async () => {
const data = '{"cool": "data"}'
const path = 'my/cool/file/path'
await uploadService.upload({ data, path })
expect(uploadStub).to.have.been.calledOnceWith({ data, path })
})
})
})
Hope that helps!

Resources