UNPKG

@earnaha/auth0-action-helper

Version:
246 lines (213 loc) 5.2 kB
const createEcsPinoOptions = require('@elastic/ecs-pino-format'); const Sentry = require('@sentry/node'); const pino = require('pino'); const pinoOpenSearch = require('pino-opensearch'); const pretty = require('pino-pretty'); const DEFAULT_CONFIG = Object.freeze({ SENTRY_TRACES_SAMPLE_RATE: 0.05, LOWEST_LOG_LEVEL: 'trace', LOG_LEVEL: 'info', }); class LogHelper { constructor( NODE_ENV, APP, options = { CONSOLE: false, }, ) { if (!NODE_ENV || !APP) { throw new Error('NODE_ENV and APP are required'); } this._DEBUG_LEVEL = DEFAULT_CONFIG.LOG_LEVEL; this._NODE_ENV = NODE_ENV; this._APP = APP; this._OPTIONS = options; this.logger = null; } setOpenSearchNode(node) { this._OPEN_SEARCH_NODE = node; if (this.logger) { this.initialize(); } return this; } setSentryDsn(dsn) { this._SENTRY_DSN = dsn; if (this.logger) { this.initialize(); } return this; } setSentryTracesSampleRate(rate) { this._SENTRY_TRACES_SAMPLE_RATE = rate; if (this.logger) { this.initialize(); } return this; } setMeta(meta = {}) { if (typeof meta === 'object') { this._META = { ...this._META, ...meta }; if (this.logger) { this.initialize(); } } return this; } setLogLevel(level) { if (level) { if (Object.keys(pino.levels.values).includes(level)) { this._DEBUG_LEVEL = level; if (this.logger) { this.logger.level = level; } return this; } throw new Error('Invalid log level'); } return this; } initialize() { const pinoStreams = []; const { _APP: APP, _NODE_ENV: NODE_ENV, _OPEN_SEARCH_NODE: OPEN_SEARCH_NODE, } = this; const isTest = this._getIsTestEnv(); let pinoConfig = { formatters: { log: obj => ({ data: obj, meta: this._formatMeta(), msg: obj.message || obj.msg, }), }, }; if (isTest || (this._OPTIONS && this._OPTIONS.CONSOLE)) { // In order for multistream to work, the log level must be set to the lowest level used in the streams array. pinoStreams.push({ level: 'trace', stream: this._getStreamToConsole(), }); } if (OPEN_SEARCH_NODE) { if (typeof OPEN_SEARCH_NODE !== 'string') throw new Error('OPEN_SEARCH_NODE must be a string'); if (!OPEN_SEARCH_NODE.startsWith('https')) throw new Error('OPEN_SEARCH_NODE must start with https'); } if ( !isTest && OPEN_SEARCH_NODE && typeof OPEN_SEARCH_NODE === 'string' && OPEN_SEARCH_NODE.startsWith('https') ) { const that = this; const escFormatters = createEcsPinoOptions(); pinoConfig = { timestamp: () => `,"@timestamp":"${new Date().toISOString()}"`, formatters: { ...escFormatters.formatters, log(obj) { const pinoPayload = escFormatters.formatters.log(obj); return { data: pinoPayload, meta: that._formatMeta(), msg: obj.message || obj.msg, }; }, }, }; pinoStreams.push({ level: 'trace', stream: this._getStreamToOpenSearch(), }); } this.logger = pino( { ...pinoConfig, level: isTest ? DEFAULT_CONFIG.LOWEST_LOG_LEVEL : this._DEBUG_LEVEL, }, pino.multistream(pinoStreams), ); this.logger.streams = pinoStreams; this.logger.NODE_ENV = NODE_ENV; this.logger.APP = APP; return this; } _getIsTestEnv() { return this._NODE_ENV === 'test'; } _getStreamToConsole() { return pretty({ sync: true, singleLine: true, levelFirst: true, minimumLevel: DEFAULT_CONFIG.LOWEST_LOG_LEVEL, }); } _getStreamToOpenSearch(isTest) { const { _OPEN_SEARCH_NODE: OPEN_SEARCH_NODE, _SENTRY_DSN: SENTRY_DSN, _SENTRY_TRACES_SAMPLE_RATE: SENTRY_TRACES_SAMPLE_RATE, } = this; const streamToOpenSearch = pinoOpenSearch({ node: OPEN_SEARCH_NODE, consistency: 'one', 'es-version': 7, index(logTime) { return `logs-${logTime.substring(0, 10).replace(/-/g, '.')}`; }, }); if (SENTRY_DSN && !isTest) { Sentry.init({ dsn: SENTRY_DSN, tracesSampleRate: (typeof SENTRY_TRACES_SAMPLE_RATE === 'string' ? parseFloat(SENTRY_TRACES_SAMPLE_RATE) : SENTRY_TRACES_SAMPLE_RATE) || DEFAULT_CONFIG.SENTRY_TRACES_SAMPLE_RATE, }); } streamToOpenSearch.on('unknown', (line, error) => { // eslint-disable-next-line no-console console.error( 'streamToOpenSearch: Expect to see a lot of these with Pino Pretty turned on.', line, error, ); Sentry.captureException(error); }); streamToOpenSearch.on('insertError', error => { // eslint-disable-next-line no-console console.error( `streamToOpenSearch: An error occurred insert document:`, error, ); Sentry.captureException(error); }); streamToOpenSearch.on('error', error => { // eslint-disable-next-line no-console console.error(`streamToOpenSearch: An error occurred :`, error); Sentry.captureException(error); }); return streamToOpenSearch; } _formatMeta() { const { _APP: APP, _META: META, _NODE_ENV: NODE_ENV } = this; return { ...META, app: APP, env: NODE_ENV, service: `aha${ (NODE_ENV || '').startsWith('prod') ? '' : `-${NODE_ENV}` }`, }; } } module.exports = LogHelper;