Skip to content

Commit

Permalink
Merge eecc7d6 into 7bab10f
Browse files Browse the repository at this point in the history
  • Loading branch information
itsdaiego committed May 22, 2020
2 parents 7bab10f + eecc7d6 commit fc78681
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 40 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ const { httpLogger } = escriba({
propMaxLength: {
body: 2048,
url: 1024
},
propsToParse: {
request: {
'id': String,
'body.document_number': Number,
},
response: {
'body.customer.id': Number
}
}
}
})
Expand All @@ -135,6 +144,8 @@ Also it's possible to skip logs or only the body property through skipRules, in

The `propMaxLength` attribute is responsible to limit the number of characters for certain properties if they exist within `propsTolog` definition.

The `propsToParse` attribute is responsible to parse any atribute based on a path, the parsing works by providing a valid javascript native Function (e.g String, Number etc).

## Masks

Just like the `loggerEngine` option, `escriba` accepts two types of mask engines, they are [iron-mask](https://www.npmjs.com/package/iron-mask) and [mask-json](https://www.npmjs.com/package/mask-json). If you don't pass any `maskEngine`, `iron-mask` will be used as default.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "escriba",
"version": "2.7.0",
"version": "2.8.0",
"description": "Logging with steroids",
"main": "src/index.js",
"scripts": {
Expand Down
15 changes: 10 additions & 5 deletions src/http-logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const prepareConfigProps = ({
propsToLog,
skipRules,
logIdPath,
propMaxLength
propMaxLength,
propsToParse
}) => {
const defaultPropsToLog = R.defaultTo({}, propsToLog)
const defaultSkipRules = R.defaultTo([], skipRules)
Expand All @@ -31,7 +32,8 @@ const prepareConfigProps = ({
propsToLog: propsToLogConfig,
skipRules: defaultSkipRules,
logIdPath: defaultLogIdPath,
propMaxLength
propMaxLength,
propsToParse
}
}

Expand All @@ -48,22 +50,25 @@ const httpLogger = (logger, messageBuilder, config) => {
propsToLog,
skipRules,
logIdPath,
propMaxLength
propMaxLength,
propsToParse
} = prepareConfigProps(config)
const { request, response } = propsToLog
const skipper = createSkipper(skipRules)
const reqLogger = createRequestLogger({
logger,
messageBuilder,
request,
propMaxLength
propMaxLength,
propsToParse
})
const resLogger = createResponseLogger({
logger,
messageBuilder,
response,
skipper,
propMaxLength
propMaxLength,
propsToParse
})
return middleware(reqLogger, resLogger, logIdPath, skipper)
}
Expand Down
20 changes: 15 additions & 5 deletions src/request-logger.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
const R = require('ramda')
const { pickProperties, stringify } = require('./utils')
const { filterLargeProp, filterLargeUrl } = require('./utils')
const {
filterLargeProp,
filterLargeUrl,
parsePropsType
} = require('./utils')

const buildRequestLog = (propsToLog, propMaxLength = {}) => req => {
const buildRequestLog = (
propsToLog,
propMaxLength = {},
propsToParse = {}
) => req => {
const reqProps = pickProperties(req, propsToLog)
reqProps.body = filterLargeProp(reqProps.body, propMaxLength.body)
reqProps.url = filterLargeUrl(reqProps.url, propMaxLength.url)
const env = pickProperties(process.env, propsToLog)
const headerProps = pickProperties(req.headers, propsToLog)
const reqParsedProps = parsePropsType(reqProps, propsToParse.request)
return R.mergeAll([
reqProps,
reqParsedProps,
headerProps,
{
level: 'info',
Expand All @@ -23,9 +32,10 @@ const requestLogger = ({
logger,
messageBuilder,
request: propsToLog,
propMaxLength
propMaxLength,
propsToParse
}) => (req) => {
const log = R.pipe(buildRequestLog(propsToLog, propMaxLength), messageBuilder)(req)
const log = R.pipe(buildRequestLog(propsToLog, propMaxLength, propsToParse), messageBuilder)(req)
req.startTime = log.startTime
logger.info(stringify(log))
return log
Expand Down
26 changes: 18 additions & 8 deletions src/response-logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ const {
generateLogLevel,
stringify,
filterLargeProp,
filterLargeUrl
filterLargeUrl,
parsePropsType
} = require('./utils')

const buildResLog = (propsToLog, propMaxLength = {}) => ({ req, res }) => {
const buildResLog = (
propsToLog,
propMaxLength = {},
propsToParse = {}
) => ({ req, res }) => {
const level = generateLogLevel(res.statusCode)

const reqProps = R.merge(
Expand All @@ -22,6 +27,8 @@ const buildResLog = (propsToLog, propMaxLength = {}) => ({ req, res }) => {
resProps.body = filterLargeProp(resProps.body, propMaxLength.body)
reqProps.url = filterLargeUrl(reqProps.url, propMaxLength.url)

const reqParsedProps = parsePropsType(reqProps, propsToParse.response)
const resParsedProps = parsePropsType(resProps, propsToParse.response)
const reqResProps = pickProperties(
{
req,
Expand All @@ -31,8 +38,8 @@ const buildResLog = (propsToLog, propMaxLength = {}) => ({ req, res }) => {
)

return R.mergeAll([
reqProps,
resProps,
reqParsedProps,
resParsedProps,
reqResProps,
{
level,
Expand Down Expand Up @@ -66,7 +73,8 @@ const captureLog = ({
skipper,
logger,
messageBuilder,
propMaxLength
propMaxLength,
propsToParse
}) => {
const { req, res } = http
const { write, end } = res
Expand All @@ -81,7 +89,7 @@ const captureLog = ({
res.end = chunk => {
if (chunk && !shouldSkipChunk) chunks.push(Buffer.from(chunk))
prepareResLog(req, res, Buffer.concat(chunks))
.then(buildResLog(propsToLog, propMaxLength))
.then(buildResLog(propsToLog, propMaxLength, propsToParse))
.then(messageBuilder)
.then(addLatency(req, propsToLog))
.then(loggerByStatusCode(logger))
Expand All @@ -97,15 +105,17 @@ const responseLogger = ({
messageBuilder,
response: propsToLog,
skipper,
propMaxLength
propMaxLength,
propsToParse
}) => (req, res) => (
captureLog({
http: { req, res },
propsToLog,
skipper,
logger,
messageBuilder,
propMaxLength
propMaxLength,
propsToParse
})
)

Expand Down
23 changes: 22 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ const filterLargeUrl = (url, urlLengthLimit) => {
return url.length > urlLengthLimit ? truncateUrl(url) : url
}

const parsePropsType = (reqProps, propsType) => {
const propsTypeKeys = R.keys(propsType)

if (propsTypeKeys.length === 0) {
return reqProps
}

return propsTypeKeys.reduce((acc, key) => {
const propPath = key.split('.')
const reqProp = R.path(propPath, acc)

if (reqProp !== undefined) {
const propTypeFunction = propsType[key]
return R.assocPath(propPath, propTypeFunction(reqProp), acc)
}

return acc
}, reqProps)
}

module.exports = {
parseStringToJSON,
pickProperties,
Expand All @@ -80,5 +100,6 @@ module.exports = {
isVendorMaskValid,
stringify,
filterLargeProp,
filterLargeUrl
filterLargeUrl,
parsePropsType
}
18 changes: 0 additions & 18 deletions test/unit/request-logger/prop-max-length-attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,24 +89,6 @@ test('should return empty object', t => {
t.deepEqual(body, {})
})

test('should return empty object', t => {
const req = {
id: 123,
body: [{
foo: 'bar',
bar: 'foo'
}],
method: 'POST',
url: 'https://foobar.com',
user_agent: 'pagarme-ruby',
env: {}
}

const { body } = reqLogger(req)

t.deepEqual(body, [])
})

test('should return url without query string parameters', t => {
const req = {
id: 123,
Expand Down
75 changes: 75 additions & 0 deletions test/unit/request-logger/prop-type-attribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const test = require('ava')
const log4js = require('log4js')
const ironMask = require('iron-mask')

const { createRequestLogger } = require('../../../src/request-logger')
const { createMessageBuilder } = require('../../../src/message-builder.js')

const loggerEngine = log4js.configure({
appenders: {
api: {
type: 'console',
layout: {
type: 'pattern',
pattern: '%m'
}
}
},
categories: { default: { appenders: ['api'], level: 'info' } }
})

const sensitive = {
password: {
paths: ['message.password'],
pattern: /\w.*/g,
replacer: '*'
}
}

const messageBuilder = createMessageBuilder(ironMask.create(sensitive), 'test')
const logger = loggerEngine.getLogger()

const request = [
'id',
'method',
'url',
'body',
'user-agent',
'env'
]

test('should return parsed body properties', t => {
const propMaxLength = {}
const propsToParse = {
request: {
'body.id': Number,
'body.keys': String
}
}

const reqLogger = createRequestLogger({
logger,
messageBuilder,
request,
propMaxLength,
propsToParse
})

const expectedBody = {
id: 123,
keys: '1,2,3',
foo: 'bar',
bar: 'foo'
}

const req = {
body: {
id: '123',
keys: [1, 2, 3],
foo: 'bar',
bar: 'foo'
}
}
const { body } = reqLogger(req)
t.deepEqual(body, expectedBody)
})
Loading

0 comments on commit fc78681

Please sign in to comment.