@infotorg/winston-log-formats
Version:
Infotorg log formats for Winston logger
66 lines • 6.65 kB
JavaScript
;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