UNPKG

@infotorg/winston-log-formats

Version:

Infotorg log formats for Winston logger

66 lines 6.65 kB
"use strict";const{MaskData,MaskDataOptions}=require("@coder.ua/mask-data");const get=require("lodash.get");const{format}=require("logform");const{MASK_DATA_SEVERITY_OPEN,MASK_DATA_SEVERITY_PARTIAL}=require("@infotorg/mask-data-severity-levels");const{parseFields}=require("./utils/parse-fields");const{isObject}=require("./utils/is-object");/** * FormatOptions * * @typedef {{severity: (string|string), fullyMaskedFields: string[], whiteList: string[], maskOptions: MaskDataOptions, target: string}} FormatOptions */ /** * MaskOptionsPojo * * @typedef {{maskNull: boolean, maskString: boolean, maxMaskedChars: number, maskWith: string, unmaskedEndChars: number, maskNumber: boolean, unmaskedStartChars: number, maskUndefined: boolean, maskBoolean: boolean}} MaskOptionsPojo */ /** * Generates options for the MaskData library depending on the severity level. * * @param {string} severityLevel. Available values: 'open', 'partial', 'strict'. See @infotorg/mask-data-severity-levels package: https://github.com/infotorg/mask-data-severity-levels. * @param {{}|MaskOptionsPojo} [options={}] Options for mask data compatible with @coder.ua/mask-data. Documentation: https://github.com/coderua/mask-data#default-options * @returns {MaskDataOptions} */const logMaskOptions=(severityLevel,options={})=>{const rewriteOptions=typeof options!=="object"&&options!==null?{}:options;const opts=severityLevel===MASK_DATA_SEVERITY_PARTIAL?{// To limit the output String length to 30. maxMaskedChars:30,// First 2 symbols that won't be masked unmaskedStartChars:2,// Last 2 symbols that won't be masked unmaskedEndChars:2,// Do not mask data with type 'boolean' maskBoolean:false,// Do not mask 'undefined' data maskUndefined:false,// Do not mask 'null' data maskNull:false,// Do not mask 'number' data maskNumber:false}:{};// For open and strict severity const maskOptions=new MaskDataOptions(opts);if(rewriteOptions&&(rewriteOptions instanceof MaskDataOptions||Object.keys(rewriteOptions).length)){// Merge options with the default ones maskOptions.options=rewriteOptions instanceof MaskDataOptions?rewriteOptions.options:rewriteOptions}return maskOptions};/** * Default mask options. * * @type {Readonly<{enabled: boolean, severity: (string|string), fullyMaskedFields: string[], whiteList: string[], maskOptions: MaskDataOptions, target: string}>} */const defaultOptions=Object.freeze({// Enable/disable the format enabled:true,// Severity of the data masking. Values are taken from the "@infotorg/mask-data-severity-levels" package. See https://github.com/infotorg/mask-data-severity-levels severity:MASK_DATA_SEVERITY_PARTIAL,// Target property for masking in the info object target:"meta",// Fields that won't be masked whiteList:[],// Fields that will be masked completely even if they are in the whiteList fullyMaskedFields:[],// Masking options for the MaskData library. // Documentation here: https://github.com/coderua/mask-data#default-options maskOptions:logMaskOptions(MASK_DATA_SEVERITY_PARTIAL)});/** * Merges provided configuration with a default one. * * @param {{enabled: boolean, severity: string, fullyMaskedFields: [], whiteList: [], maskOptions: {}|MaskDataOptions, target: string}} options * @returns {{enabled: boolean, severity: string, fullyMaskedFields: Set, whiteList: Set, maskOptions: MaskDataOptions, target: string}} */const mergeOptions=(options={})=>{const mergedOptions={...defaultOptions,...(isObject(options)?options:{})};Object.keys(defaultOptions).forEach(name=>{if(mergedOptions[name]===undefined||mergedOptions[name]===null||mergedOptions[name]===""){mergedOptions[name]=defaultOptions[name]}if(name==="whiteList"||name==="fullyMaskedFields"){mergedOptions[name]=parseFields(mergedOptions[name])}if(name==="maskOptions"){mergedOptions[name]=logMaskOptions(mergedOptions.severity,options.maskOptions)}});return mergedOptions};/** * function mask (info) * Returns a new instance of the mask Format which masks fields depends on opts configuration. * * @param {Object} [info={}] The info parameter provided to a given format represents a single log message. The object itself is mutable. Every info must have at least the level and message properties. * @param {{severity: string, target: string, whiteList: [], maskOptions: {}}|undefined} [opts={severity: 'open', target: 'meta', whiteList: [], maskOptions: {}] Setting specific to the current instance of the format. * * @param {string} [opts.enabled=true] Enable/disable the format * @param {string} [opts.severity='partial'] Severity of the data masking. Values are taken from the "@infotorg/mask-data-severity-levels" package. See https://github.com/infotorg/mask-data-severity-levels. For 'open' severity level no masking will be applied. * @param {string} [opts.target='meta'] Target property for masking in the info object * @param {[]} [opts.whiteList=[]] Fields that won't be masked * @param {[]} [opts.fullyMaskedFields=[]] // Fields that will be masked completely even if they are in the whiteList * @param {{}|MaskOptionsPojo} [opts.maskOptions={}] Masking options for the MaskData library. Documentation here: https://github.com/coderua/mask-data#default-options * * @type {Function} */module.exports=format((info={},opts={...defaultOptions})=>{const{enabled=true,severity,target,fullyMaskedFields,whiteList,maskOptions}=mergeOptions(opts);if(!enabled){// Format is disabled, so we return the info object return info}const data={...get(info,target,{})};if(Object.keys(data).length===0){// There is no data in the info object, so we return the info object return info}if(severity===MASK_DATA_SEVERITY_OPEN){// No need to mask data for the "open" severity return{...info,[target]:data}}// Prepare mask instances const maskData=new MaskData(maskOptions);const maskDataCompletely=new MaskData({...maskOptions.options,...{// First N symbols that won't be masked unmaskedStartChars:0,// Last N symbols that won't be masked unmaskedEndChars:0,// Mask data with type 'string' maskString:true,// Mask data with type 'number' maskNumber:true}});const mask=(input,path)=>{if(!fullyMaskedFields.has(path)&&whiteList.has(path)){// No masking for the white listed fields that are not in the fullyMaskedFields list return input}if(input!==null&&typeof input==="object"){const result={};Object.keys(input).forEach(key=>{result[key]=mask(input[key],`${path}.${key}`)});return result}return fullyMaskedFields.has(path)?maskDataCompletely.mask(input):maskData.mask(input)};// Masking data starting from root elements Object.keys(data).forEach(key=>{data[key]=mask(data[key],key)});return{...info,[target]:data}}); //# sourceMappingURL=mask.js.map