// @flow // NOTE: don't use Module here, since Module uses this class import stringify from 'json-stringify-safe' import _ from 'lodash' import { httpReq } from './httpUtils' import { padRight } from './stringUtils' const LOG_FLUSH_INTERVAL_MS = 5 * 1000 type LoggerOptions = { noHTTPS?: boolean, host?: ?string, dataFieldsToPromote?: ?{ [id: string]: string }, dataFieldsToRemove?: ?Array, systemMeta?: ?{}, } export default class S3Logger { _logObjects: Array = [] _logRecordNum: number = 0 _options: LoggerOptions constructor(options: LoggerOptions) { this._options = options if (this._options.host) { setInterval(this.flushLogs, LOG_FLUSH_INTERVAL_MS) } } queueLogRecord = (logObj: Object): void => { try { const nowMS = Date.now() let { data, message, ...restLogObj } = logObj const objToPush = { event: message, ...restLogObj, record_num: ++this._logRecordNum, time_ms: nowMS, time_ts: new Date(nowMS).toISOString(), } if (data != null) { // make sure data is a object if (typeof data !== 'object') { data = { value: data.toString() } } // promote data fields if (this._options.dataFieldsToPromote) { Object.entries(this._options.dataFieldsToPromote).forEach(([logProp, dataPath]) => { // $FlowIgnore const dataVal = _.get(data, dataPath) if (dataVal !== undefined) { objToPush[logProp] = dataVal } }) } // remove data fields if (this._options.dataFieldsToRemove) { this._options.dataFieldsToRemove.forEach(dataProp => { delete data[dataProp] }) } // serialize data if (Object.keys(data).length > 0) objToPush.data = stringify(data) } // log to console console.log(`${objToPush.time_ts.substr(11, 12)}: ${padRight(objToPush.level, 5, ' ')} [${objToPush.module}] ${objToPush.event} ${objToPush.data || ''}`) // eslint-disable-line no-console this._logObjects.push(objToPush) } catch (err) { console.error('Failed to queue a log record', logObj, err) // eslint-disable-line no-console } } flushLogs = async (): Promise => { try { if (!this._logObjects.length || !this._options.host) return const logsToFlush = this._logObjects this._logObjects = [] await httpReq(`${this._options.noHTTPS ? 'http' : 'https'}://${this._options.host}/p1`, { method: 'POST', body: { beacons: logsToFlush, common: this._options.systemMeta, client_sending_time_ms: Date.now(), }, timeout: 10000, }) } catch (err) { console.error('Failed to flush logs', (new Date()).toUTCString(), err) // eslint-disable-line no-console } } }