koa2-winston
Version:
koa2 version winston logger like express-winston
210 lines (196 loc) • 5.62 kB
JavaScript
const set = require('lodash.set');
const get = require('lodash.get');
const mapvalues = require('lodash.mapvalues');
const clonedeep = require('lodash.clonedeep');
const fastJson = require('fast-json-stringify');
const PREFIXS = ['req', 'res'];
const defaultSchemas = {
res: {
title: 'koa2-winston-info-res',
type: 'object',
properties: {
header: {
type: 'object',
additionalProperties: { type: 'string' },
},
status: { type: 'string' },
body: {
type: 'object',
additionalProperties: true,
},
},
},
req: {
title: 'koa2-winston-info-req',
type: 'object',
properties: {
header: {
type: 'object',
additionalProperties: { type: 'string' },
},
url: { type: 'string' },
method: { type: 'string' },
href: { type: 'string' },
body: {
type: 'object',
additionalProperties: true,
},
query: {
type: 'object',
additionalProperties: { type: 'string' },
},
origin: { type: 'string' },
originalUrl: { type: 'string' },
path: { type: 'string' },
querystring: { type: 'string' },
search: { type: 'string' },
hostname: { type: 'string' },
URL: { type: 'string' },
type: { type: 'string' },
charset: { type: 'string' },
protocol: { type: 'string' },
secure: { type: 'string' },
ip: { type: 'string' },
httpVersion: { type: 'string' },
length: { type: 'integer' },
},
},
info: {
title: 'koa2-winston-info',
definitions: {
req: {},
res: {},
},
type: 'object',
properties: {
started_at: { type: 'integer' },
duration: { type: 'integer' },
level: { type: 'string' },
message: { type: 'string' },
req: { $ref: '#/definitions/req' },
res: { $ref: '#/definitions/res' },
},
},
};
const DOT_RE = /\./g;
/**
* @param {string} path
*/
const asJsonSchemaPath = (path) => path.replace(DOT_RE, '.properties.');
/**
* @param {object} schema - generated json schema
*/
const ensureTypeObject = (schema) => mapvalues(schema, (value) => {
if (typeof value !== 'object') {
return value;
}
if (value.properties) {
// eslint-disable-next-line no-param-reassign
value.type = 'object';
}
return ensureTypeObject(value);
});
/**
* @callback schemaKeysHandlerFn
* @param {string} path
*/
/**
* @param {string[]} keys - schema keys
* @param {schemaKeysHandlerFn} handler - assign path
*/
const schemaKeysHandler = (keys, handler) => keys
.map(asJsonSchemaPath)
.map((path) => `properties.${path}`)
.map(handler);
const schemaKeysHandlers = ({
keys, select, unselect, schema,
}) => {
const outputSchema = { type: 'object', properties: {} };
schemaKeysHandler(keys.concat(select), (path) => {
set(outputSchema, path, get(schema, path, {}));
});
schemaKeysHandler(unselect, (path) => {
const parentPath = path.match(DOT_RE).length > 2
? path
.split('.')
.slice(0, -2)
.join('.')
: path;
if (!get(outputSchema, parentPath)) {
return;
}
set(outputSchema, path, { type: 'null' });
});
return outputSchema;
};
/**
* logger middleware for koa2 use winston
*
* @param {object} [payload={}] - input arguments
* @param {string[]} [payload.reqKeys=['header','url','method','httpVersion', 'href', 'query', 'length']] - default request fields to be logged
* @param {string[]} [payload.reqSelect=[]] - additional request fields to be logged
* @param {string[]} [payload.reqUnselect=['header.cookie']] - request field will be removed from the log
* @param {string[]} [payload.resKeys=['header', 'status']] - default response fields to be logged
* @param {string[]} [payload.resSelect=[]] - additional response fields to be logged
* @param {string[]} [payload.resUnselect=[]] - response field will be removed from the log
*/
const generateSchema = (payload) => {
const options = {
reqUnselect: ['header.cookie'],
reqSelect: [],
reqKeys: [
'header',
'url',
'method',
'httpVersion',
'href',
'query',
'length',
],
resUnselect: [],
resSelect: [],
resKeys: ['header', 'status'],
...payload,
};
const { info: infoSchema } = clonedeep(defaultSchemas);
PREFIXS.forEach((prefix) => {
infoSchema.definitions[prefix] = schemaKeysHandlers({
keys: options[`${prefix}Keys`],
select: options[`${prefix}Select`],
unselect: options[`${prefix}Unselect`],
schema: defaultSchemas[prefix],
});
});
ensureTypeObject(infoSchema.definitions);
return infoSchema;
};
const generateFormat = (payload) => {
const schema = generateSchema(payload);
const stringify = fastJson(schema);
const keys = {
req: Object.keys(schema.definitions.req.properties),
res: Object.keys(schema.definitions.res.properties),
};
return (info) => {
// enforce get properties from koa ctx.request/responseo
// in koa, Request.toJSON only return ['method', 'url', 'header']
// https://github.com/koajs/koa/blob/master/lib/request.js#L708
PREFIXS.forEach((prefix) => {
const prefixCopy = {};
keys[prefix].forEach((key) => {
prefixCopy[key] = info[prefix][key];
});
info[prefix] = prefixCopy;
});
const infoMsg = stringify(info);
// rewrite info object as omit function
Object.assign(info, JSON.parse(infoMsg));
return infoMsg;
};
};
module.exports = {
defaultSchemas,
generateSchema,
generateFormat,
asJsonSchemaPath,
};