Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ coverage

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
dist/

# Dependency directories
node_modules
Expand Down
1 change: 0 additions & 1 deletion index.js

This file was deleted.

52 changes: 27 additions & 25 deletions lib/express-logger.js → lib/express-logger.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
'use strict';
import { RequestHandler } from 'express';
import { AuditOptions, AugmentedRequest, AugmentedResponse } from './types';
import * as _ from 'lodash';

var _ = require('lodash'),
logger = require('bunyan').createLogger({ name: 'ExpressLogger' }),
let logger = require('bunyan').createLogger({ name: 'ExpressLogger' }),
loggerHelper = require('./logger-helper'),
setupOptions,
setupOptions: AuditOptions,
flatten = require('flat');

var audit = function (req, res, next) {
var oldWrite = res.write;
var oldEnd = res.end;
var oldJson = res.json;
var chunks = [];
var audit: RequestHandler = function (req: AugmentedRequest, res: AugmentedResponse, next) {
const oldWrite = res.write;
const oldEnd = res.end;
const oldJson = res.json;
let chunks: any[] = [];

// Log start time of request processing
req.timestamp = new Date();
Expand All @@ -19,35 +20,38 @@ var audit = function (req, res, next) {
loggerHelper.auditRequest(req, setupOptions);
}

res.write = function (chunk) {
chunks.push(new Buffer(chunk));
oldWrite.apply(res, arguments);
res.write = function (chunk: any, ...rest: any[]) {
chunks.push(Buffer.from(chunk));
return (oldWrite as any).apply(res, [chunk, ...rest]);
};

// decorate response#json method from express
res.json = function (bodyJson) {
res.json = function (bodyJson: any, ...rest: any[]) {
res._bodyJson = bodyJson;
oldJson.apply(res, arguments);
return (oldJson as any).apply(res, [bodyJson, ...rest]);
};

// decorate response#end method from express
res.end = function (chunk) {
// TODO Can't seem to get the types to match.
// @ts-ignore
res.end = function (chunk: any, ...rest): AugmentedResponse {
res.timestamp = new Date();
if (chunk) {
chunks.push(new Buffer(chunk));
chunks.push(Buffer.from(chunk));
}

res._bodyStr = Buffer.concat(chunks).toString('utf8');

// call to original express#res.end()
oldEnd.apply(res, arguments);
const ret = (oldEnd as any).apply(res, [chunk, ...rest]);
loggerHelper.auditResponse(req, res, setupOptions);
return ret;
};

next();
};

module.exports = function (options) {
export default function (options: Partial<AuditOptions>) {
options = options || {};
var defaults = {
logger: logger,
Expand Down Expand Up @@ -76,14 +80,14 @@ module.exports = function (options) {
}
};

_.defaultsDeep(options, defaults);
setupOptions = validateArrayFields(options, defaults);
setupOptions = _.defaultsDeep(options, defaults);
validateArrayFields(options, defaults);
setBodyLengthFields(setupOptions);
return audit;
};

// Convert all options fields that need to be array by default
function validateArrayFields(options, defaults) {
function validateArrayFields(options: Partial<AuditOptions>, defaults: Record<string, any>) {
let defaultsCopy = Object.assign({}, defaults);
delete defaultsCopy.logger;

Expand All @@ -96,12 +100,10 @@ function validateArrayFields(options, defaults) {
throw new Error(errMsg);
}
});

return options;
}

function setBodyLengthFields(options) {
const isValid = field => field && !isNaN(field) && field > 0;
function setBodyLengthFields(options: AuditOptions) {
const isValid = (field: any) => field && !isNaN(field) && field > 0;
options.request.maxBodyLength = !isValid(options.request.maxBodyLength) ? undefined : options.request.maxBodyLength;
options.response.maxBodyLength = !isValid(options.response.maxBodyLength) ? undefined : options.response.maxBodyLength;
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './express-logger';
49 changes: 23 additions & 26 deletions lib/logger-helper.js → lib/logger-helper.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
'use strict';
import { Request, Response } from 'express';
import { AuditOptions, AugmentedRequest, AugmentedResponse } from './types';
import * as utils from './utils';
import * as _ from 'lodash';

var utils = require('./utils');
var _ = require('lodash');
var ALL_FIELDS = '*';
const ALL_FIELDS = '*';
const NA = 'N/A';
const DEFAULT_LOG_LEVEL = 'info';
const START = 'start';
const END = 'end';

var auditRequest = function (req, options) {
var shouldAudit = utils.shouldAuditURL(options.excludeURLs, req);
export const auditRequest = function (req: AugmentedRequest, options: AuditOptions) {
const shouldAudit = utils.shouldAuditURL(options.excludeURLs, req);

if (shouldAudit) {
var request;
let request;

if (options.setupFunc) {
options.setupFunc(req, res);
options.setupFunc(req);
}

if (options.request.audit) {
request = getRequestAudit(req, options);
}

var auditObject = {
let auditObject = {
request: request,
'millis-timestamp': Date.now(),
'utc-timestamp': new Date().toISOString(),
Expand All @@ -38,7 +39,7 @@ var auditRequest = function (req, options) {
}
};

var auditResponse = function (req, res, options) {
export const auditResponse = function (req: AugmentedRequest, res: AugmentedResponse, options: AuditOptions) {
var request;
var response;

Expand Down Expand Up @@ -79,10 +80,11 @@ var auditResponse = function (req, res, options) {
}
};

function getRequestAudit(req, options) {
function getRequestAudit(req: AugmentedRequest, options: AuditOptions) {
var headers = _.get(req, 'headers');
var requestFullURL = utils.getUrl(req);
var requestRoute = utils.getRoute(req);
// @ts-ignore
var queryParams = req && req.query !== {} ? req.query : NA;
var method = req && req.method ? req.method : NA;
var URLParams = req && req.params ? req.params : NA;
Expand Down Expand Up @@ -117,7 +119,7 @@ function getRequestAudit(req, options) {
return auditObject;
}

function handleResponseJson(objJson, objStr, logger, excludeFields, maskFields) {
function handleResponseJson(objJson: any, objStr: any, logger: any, excludeFields: string[], maskFields: string[]) {
let result;
if (shouldBeParsed(maskFields, excludeFields)) {
result = objJson || objStr
Expand All @@ -127,7 +129,7 @@ function handleResponseJson(objJson, objStr, logger, excludeFields, maskFields)
return handleJson(result, logger, excludeFields, maskFields);
}

function handleJson(obj, logger, excludeFields, maskFields) {
function handleJson(obj: any, logger: any, excludeFields: string[], maskFields: string[]) {

let result = obj;
if (_.includes(excludeFields, ALL_FIELDS)) {
Expand All @@ -148,11 +150,11 @@ function handleJson(obj, logger, excludeFields, maskFields) {
//order is important because body is clone first
let maskedClonedObj = utils.maskJson(jsonObj, maskFields);
result = utils.cleanOmitKeys(maskedClonedObj, excludeFields);
} catch (err) {
} catch (err: unknown) {
logger.warn({
error: {
message: err.message,
stack: err.stack
message: (err as Error).message,
stack: (err as Error).stack
}
}, 'Error parsing json');
result = undefined;
Expand All @@ -162,13 +164,13 @@ function handleJson(obj, logger, excludeFields, maskFields) {
return result;
}

function shouldBeParsed(maskFields, excludeFields) {
function shouldBeParsed(maskFields: string[], excludeFields: string[]) {
return !_.includes(excludeFields, ALL_FIELDS) && (!_.isEmpty(maskFields) || !_.isEmpty(excludeFields));
}

function getResponseAudit(req, res, options) {
function getResponseAudit(req: AugmentedRequest, res: AugmentedResponse, options: AuditOptions) {
var headers = res && 'function' === typeof res.getHeaders ? res.getHeaders() : _.get(res, '_headers');
var elapsed = req && res ? res.timestamp - req.timestamp : 0;
var elapsed = req && res && req.timestamp && res.timestamp ? res.timestamp.valueOf() - req.timestamp.valueOf() : 0;
var timestamp = res && res.timestamp ? res.timestamp.toISOString() : NA;
var timestamp_ms = res && res.timestamp ? res.timestamp.valueOf() : NA;
var statusCode = res && res.statusCode ? res.statusCode : NA;
Expand Down Expand Up @@ -198,19 +200,14 @@ function getResponseAudit(req, res, options) {
return auditObject;
}

function isJsonBody(headers) {
function isJsonBody(headers: any) {
return headers && headers['content-type'] && headers['content-type'].includes('application/json')
}

function getMaskedQuery(query, fieldsToMask) {
function getMaskedQuery(query: any, fieldsToMask: string[]) {
if (query) {
return !_.isEmpty(fieldsToMask) ? utils.maskJson(query, fieldsToMask) : query
} else {
return NA;
}
}

module.exports = {
auditRequest: auditRequest,
auditResponse: auditResponse
};
48 changes: 48 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Request, Response } from 'express';

export type LevelsMap = {
[key: string]: string;
'2xx': string;
'3xx': string;
'4xx': string;
'5xx': string;
};

export type AuditOptions = {
logger: any;
request: {
audit: boolean;
maskBody: string[];
maskQuery: string[];
maskHeaders: string[];
excludeBody: string[];
excludeHeaders: string[];
customMaskBodyFunc: (req: AugmentedRequest) => any;
maxBodyLength: number | undefined;
};
response: {
audit: boolean;
maskBody: string[];
maskHeaders: string[];
excludeBody: string[];
excludeHeaders: string[];
maxBodyLength: number | undefined;
};
doubleAudit: boolean;
excludeURLs: string[];
levels: LevelsMap;
setupFunc: (req: Request, res?: Response) => void;
};

export type AugmentedRequest = Request & {
additionalAudit?: any;
timestamp?: Date;
}

export type AugmentedResponse = Response & {
additionalAudit?: any;
_bodyStr?: string;
_bodyJson?: any;
timestamp?: Date;
}

Loading