1 |
|
2 |
|
3 |
|
4 |
|
5 | import stringify from 'json-stringify-safe'
|
6 | import _ from 'lodash'
|
7 |
|
8 | import { httpReq } from './httpUtils'
|
9 | import { padRight } from './stringUtils'
|
10 |
|
11 | const LOG_FLUSH_INTERVAL_MS = 5 * 1000
|
12 |
|
13 | type LoggerOptions = {
|
14 | noHTTPS?: boolean,
|
15 | host?: ?string,
|
16 | dataFieldsToPromote?: ?{ [id: string]: string },
|
17 | dataFieldsToRemove?: ?Array<string>,
|
18 | systemMeta?: ?{},
|
19 | }
|
20 |
|
21 | export default class S3Logger {
|
22 |
|
23 | _logObjects: Array<Object> = []
|
24 | _logRecordNum: number = 0
|
25 | _options: LoggerOptions
|
26 |
|
27 |
|
28 | constructor(options: LoggerOptions) {
|
29 | this._options = options
|
30 |
|
31 | if (this._options.host) {
|
32 | setInterval(this.flushLogs, LOG_FLUSH_INTERVAL_MS)
|
33 | }
|
34 | }
|
35 |
|
36 |
|
37 | queueLogRecord = (logObj: Object): void => {
|
38 | try {
|
39 | const nowMS = Date.now()
|
40 |
|
41 | let { data, message, ...restLogObj } = logObj
|
42 |
|
43 | const objToPush = {
|
44 | event: message,
|
45 |
|
46 | ...restLogObj,
|
47 |
|
48 | record_num: ++this._logRecordNum,
|
49 | time_ms: nowMS,
|
50 | time_ts: new Date(nowMS).toISOString(),
|
51 | }
|
52 |
|
53 | if (data != null) {
|
54 |
|
55 |
|
56 | if (typeof data !== 'object') {
|
57 | data = {
|
58 | value: data.toString()
|
59 | }
|
60 | }
|
61 |
|
62 |
|
63 | if (this._options.dataFieldsToPromote) {
|
64 | Object.entries(this._options.dataFieldsToPromote).forEach(([logProp, dataPath]) => {
|
65 |
|
66 | const dataVal = _.get(data, dataPath)
|
67 | if (dataVal !== undefined) {
|
68 | objToPush[logProp] = dataVal
|
69 | }
|
70 | })
|
71 | }
|
72 |
|
73 |
|
74 | if (this._options.dataFieldsToRemove) {
|
75 | this._options.dataFieldsToRemove.forEach(dataProp => {
|
76 | delete data[dataProp]
|
77 | })
|
78 | }
|
79 |
|
80 |
|
81 | if (Object.keys(data).length > 0)
|
82 | objToPush.data = stringify(data)
|
83 |
|
84 | }
|
85 |
|
86 |
|
87 | console.log(`${objToPush.time_ts.substr(11, 12)}: ${padRight(objToPush.level, 5, ' ')} [${objToPush.module}] ${objToPush.event} ${objToPush.data || ''}`)
|
88 |
|
89 | this._logObjects.push(objToPush)
|
90 | } catch (err) {
|
91 | console.error('Failed to queue a log record', logObj, err)
|
92 | }
|
93 | }
|
94 |
|
95 |
|
96 | flushLogs = async (): Promise<void> => {
|
97 | try {
|
98 | if (!this._logObjects.length || !this._options.host)
|
99 | return
|
100 |
|
101 | const logsToFlush = this._logObjects
|
102 | this._logObjects = []
|
103 |
|
104 | await httpReq(`${this._options.noHTTPS ? 'http' : 'https'}://${this._options.host}/p1`, {
|
105 | method: 'POST',
|
106 | body: {
|
107 | beacons: logsToFlush,
|
108 | common: this._options.systemMeta,
|
109 | client_sending_time_ms: Date.now(),
|
110 | },
|
111 | timeout: 10000,
|
112 | })
|
113 |
|
114 | } catch (err) {
|
115 | console.error('Failed to flush logs', (new Date()).toUTCString(), err)
|
116 | }
|
117 | }
|
118 | }
|