UNPKG

wkr-util

Version:
207 lines (173 loc) 9.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogRepoPassthru = exports.LogRepoConsole = exports.LogRepoNoop = exports.LogRepo = exports.getReqTraceSessionId = exports.getReqTraceAppId = exports.getReqTraceId = exports.reqToIp = exports.reqToObj = exports.errorToObj = exports.logLevels = void 0; var _nodeFetch = _interopRequireDefault(require("node-fetch")); var _urlJoin = _interopRequireDefault(require("url-join")); var _f = require("@cullylarson/f"); var _index = require("./index"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const logLevels = { TRACE: 'TRACE', DEBUG: 'DEBUG', INFO: 'INFO', NOTICE: 'NOTICE', WARN: 'WARN', ERROR: 'ERROR', ALERT: 'ALERT', FATAL: 'FATAL' }; exports.logLevels = logLevels; const errorToObj = err => { if (!(0, _f.isObject)(err)) return err; const elasticSearchError = (0, _f.get)(['meta', 'body', 'error', 'root_cause'], [], err).map((0, _f.get)('reason', null)).filter(Boolean).join(' | '); const errMessage = (0, _f.get)('message', undefined, err); const finalMessage = [errMessage || null, elasticSearchError || null].filter(Boolean).join(': '); return { name: (0, _f.get)('name', undefined, err), errno: (0, _f.get)('errno', undefined, err), code: (0, _f.get)('code', undefined, err), message: finalMessage || undefined, stack: (0, _f.get)('stack', undefined, err) }; }; exports.errorToObj = errorToObj; const reqToObj = (0, _f.pick)(['baseUrl', 'body', 'fresh', 'hostname', 'ip', 'ips', 'method', 'originalUrl', 'params', 'path', 'protocol', 'query', 'secure', 'xhr']); exports.reqToObj = reqToObj; const reqToIp = (0, _f.compose)(_index.formatIpv4, (0, _f.get)('ip', undefined)); exports.reqToIp = reqToIp; const getHeader = (0, _f.curry)((headerName, expressRequestInfo, defaultValue) => { if (!expressRequestInfo) return defaultValue; expressRequestInfo = (0, _f.liftA)(expressRequestInfo); const [req] = expressRequestInfo; return req && req.header(headerName) ? req.header(headerName) : defaultValue; }); const getReqTraceId = getHeader('x-wkr-trace-id'); exports.getReqTraceId = getReqTraceId; const getReqTraceAppId = getHeader('x-wkr-trace-app-id'); exports.getReqTraceAppId = getReqTraceAppId; const getReqTraceSessionId = getHeader('x-wkr-trace-sess-id'); exports.getReqTraceSessionId = getReqTraceSessionId; const buildLogData = (source, label, level, message, traceId, traceAppId, traceSessionId, extraFields, expressRequestInfo = undefined, exception = undefined) => { const getObjFromRequest = expressRequestInfo => { if (!expressRequestInfo) return {}; expressRequestInfo = (0, _f.liftA)(expressRequestInfo); const [request, blacklist] = expressRequestInfo; return (0, _f.compose)((0, _f.ifElse)((0, _f.always)(blacklist), (0, _f.filter)((v, k) => { // make sure the key isn't in the blacklist return Array.isArray(blacklist) ? !blacklist.includes(k) : blacklist !== k; }), _f.identity), reqToObj)(request); }; return _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, expressRequestInfo ? { request: getObjFromRequest(expressRequestInfo), ip: reqToIp(Array.isArray(expressRequestInfo) ? (0, _f.get)(0, undefined, expressRequestInfo) : expressRequestInfo) } : {}), exception ? { exception: errorToObj(exception) } : {}), extraFields), {}, { source, label, level, traceId, traceAppId, traceSessionId, message }); }; // build the 'add' function. this is just here so that all add functions will have the same signature. they all use addFull anyway, so no need to redefine the signature every time. const buildAdd = addFull => (label, level, message, extraFields, expressRequestInfo = undefined, exception = undefined) => addFull(label, level, message, { traceId: getReqTraceId(expressRequestInfo, undefined), traceAppId: getReqTraceAppId(expressRequestInfo, undefined), traceSessionId: getReqTraceSessionId(expressRequestInfo, undefined) }, extraFields, expressRequestInfo, exception); const LogRepo = (appAuth, source, apiUrl) => { // traceId is a random string that is hopefully unique to each request. It should be passed from one service to another, if there are multiple services involved in a single request (e.g. A originates a request to B, B calls service C and passed traceId to it, C calls D and passes traceId again). // traceAppId is an identifier for the app originating the request (e.g. A in the example above). // traceSessionId is an identifier for the user (a user id or a random string for visitors). It should preferrably remain the same for the users whole existence. It allows tracing logs over the course of a users entire use session. // // expressRequestInfo can be a request or an array of [request, blacklist] where blacklist is an array of keys that shouldn't // be included from the request const addFull = (label, level, message, { traceId, traceAppId, traceSessionId }, extraFields, expressRequestInfo = undefined, exception = undefined) => { const addUrl = (0, _urlJoin.default)(apiUrl, '/api/v1/log/add'); const data = buildLogData(source, label, level, message, traceId, traceAppId, traceSessionId, extraFields, expressRequestInfo, exception); return appAuth.getToken().then(token => (0, _nodeFetch.default)(addUrl, { method: 'post', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } })).then(_index.responseData).then(({ response, data }) => { if (!response.ok) { const now = new Date(); if (data.error) { const errors = (0, _f.liftA)(data.error).join('\n'); console.error(`Could not log event (Stamp: ${now.getTime()} -- ${now.toString()}) (Label: ${label}, Level: ${level}, Message: ${message}). Got response status: ${response.status}. Response error message: ${errors}`); } else { console.error(`Could not log event (Stamp: ${now.getTime()} -- ${now.toString()}) (Label: ${label}, Level: ${level}, Message: ${message}). Got response status: ${response.status}`); } } }).catch(err => { const now = new Date(); console.error(`Exception while logging event (Stamp: ${now.getTime()} -- ${now.toString()}) (Label: ${label}, Level: ${level}, Message: ${message}). Got exception: [${err.name || ''} -- ${err.message || ''}]. Stack: ${err.stack || ''}`); }); }; // A shorter version of addFull. See addFull for param details. Fields are pulled from the request, if possible. This is such a common use-case, it's easier to have a shorter function call to pull request fields. const add = buildAdd(addFull); return { addFull, add }; }; // doesn't do anything. used if you want to disable logs. exports.LogRepo = LogRepo; const LogRepoNoop = () => ({ add: () => Promise.resolve(undefined), addFull: () => Promise.resolve(undefined) }); // Logs errors to console exports.LogRepoNoop = LogRepoNoop; const LogRepoConsole = () => { const addFull = (label, level, message, { traceId, traceAppId, traceSessionId }, extraFields, expressRequestInfo = undefined, exception = undefined) => { const now = new Date(); const data = _objectSpread({ stamp: now.getTime(), stampHuman: now.toString() }, buildLogData('', label, level, message, traceId, traceAppId, traceSessionId, extraFields, expressRequestInfo, exception)); console.log(data); if (data.exception && data.exception.stack) console.log('PRETTY STACK: ', data.exception.stack); return Promise.resolve(undefined); }; const add = buildAdd(addFull); return { addFull, add }; }; // instead of sending logs to an API, will just pass them to a function. callback should return a promise. exports.LogRepoConsole = LogRepoConsole; const LogRepoPassthru = (source, callback) => { const addFull = (label, level, message, { traceId, traceAppId, traceSessionId }, extraFields, expressRequestInfo = undefined, exception = undefined) => { const data = buildLogData(source, label, level, message, traceId, traceAppId, traceSessionId, extraFields, expressRequestInfo, exception); return callback(data); }; const add = buildAdd(addFull); return { addFull, add }; }; exports.LogRepoPassthru = LogRepoPassthru;