1 |
|
2 |
|
3 |
|
4 |
|
5 | import autoBind from 'auto-bind'
|
6 |
|
7 | import System from './System'
|
8 | import { isError } from './errorUtils'
|
9 | import { errorToObj } from './stringUtils'
|
10 |
|
11 | import type { MetricDimensions, MinimalMetricDimensions } from './types'
|
12 | import type { Metric } from './InfluxDBClient'
|
13 |
|
14 | type ModuleConfig = {
|
15 | isEntryPoint?: ?boolean,
|
16 | }
|
17 |
|
18 | export default class Module {
|
19 |
|
20 |
|
21 |
|
22 | MODULE_NAME: string
|
23 |
|
24 | _moduleConfig: ModuleConfig
|
25 |
|
26 |
|
27 | constructor(moduleName: string, moduleConfig: ?ModuleConfig) {
|
28 | this.MODULE_NAME = (moduleName.split('/').pop() || '').split('.')[0]
|
29 | this._moduleConfig = moduleConfig || {}
|
30 |
|
31 |
|
32 |
|
33 | setTimeout(() => {
|
34 | const lifesignIntervalMS = System.getConfig().lifesignIntervalMS
|
35 | if (lifesignIntervalMS && this._moduleConfig.isEntryPoint) {
|
36 | setInterval(() => {
|
37 | this.noteCount('lifesign')
|
38 | }, lifesignIntervalMS)
|
39 | }
|
40 | }, 1000)
|
41 |
|
42 | autoBind(this)
|
43 | }
|
44 |
|
45 |
|
46 | debug = (message: string, data: ?any, logOnly: ?boolean) => {
|
47 | this._internalLog('debug', this.MODULE_NAME, message, data, logOnly)
|
48 | }
|
49 |
|
50 | log = (message: string, data: ?any, logOnly: ?boolean) => {
|
51 | this._internalLog('info', this.MODULE_NAME, message, data, logOnly)
|
52 | }
|
53 |
|
54 | warn = (message: string, data: ?any, logOnly: ?boolean) => {
|
55 | this._internalLog('warn', this.MODULE_NAME, message, data, logOnly)
|
56 | }
|
57 |
|
58 | error = (message: string, data: ?any, logOnly: ?boolean) => {
|
59 | this._internalLog('error', this.MODULE_NAME, message, data, logOnly)
|
60 | }
|
61 |
|
62 | trackCountsDated = async (countMetrics: Array<Metric>) => {
|
63 | try {
|
64 | await _trackMetricsToInfluxDB(countMetrics.map(metric => ({
|
65 | ...metric,
|
66 | metricType: 'counters',
|
67 | module: this.MODULE_NAME,
|
68 | })))
|
69 | } catch (err) {
|
70 | this.error('Failed to track dated counts', { err })
|
71 | }
|
72 | }
|
73 |
|
74 | trackCountDated = async (statName: string, value: number, timestampMs: number, dims: MetricDimensions) => {
|
75 |
|
76 | await this.trackCountsDated([ { statName, value, timestampMs, dims } ])
|
77 | }
|
78 |
|
79 | noteGauge = async (statName: string, value: number, dims: ?MetricDimensions) => {
|
80 | try {
|
81 |
|
82 | await _trackMetricToInfluxDB(statName, value, {
|
83 | ...dims,
|
84 | metricType: 'gauges',
|
85 | module: this.MODULE_NAME,
|
86 | })
|
87 | } catch (err) {
|
88 | this.error('Failed to track gauge', { err, statName })
|
89 | }
|
90 | }
|
91 |
|
92 | noteCount = async (statName: string, value: number = 1, dims: ?MetricDimensions) => {
|
93 | try {
|
94 |
|
95 | await _trackMetricToInfluxDB(statName, value, {
|
96 | ...dims,
|
97 | metricType: 'counters',
|
98 | module: this.MODULE_NAME,
|
99 | })
|
100 | } catch (err) {
|
101 | this.error('Failed to track count', { err, statName }, true)
|
102 | }
|
103 | }
|
104 |
|
105 | noteTimer = async (statName: string, durationMs: number, dims: ?MetricDimensions) => {
|
106 | if (typeof durationMs !== 'number')
|
107 | throw new Error(`durationMs must be a number (${statName})`)
|
108 | try {
|
109 |
|
110 | await _trackMetricToInfluxDB(statName, durationMs, {
|
111 | ...dims,
|
112 | metricType: 'timers',
|
113 | module: this.MODULE_NAME,
|
114 | })
|
115 | } catch (err) {
|
116 | this.error('Failed to track timer', { err, statName })
|
117 | }
|
118 | }
|
119 |
|
120 |
|
121 | async trackOp<T>(op: () => Promise<T>, name: ?string, dims: ?MetricDimensions, options: ?{ log?: ?boolean }): Promise<T> {
|
122 | if (name == null) {
|
123 | return await op()
|
124 | }
|
125 |
|
126 | options = options || {}
|
127 | dims = { ...dims, module: this.MODULE_NAME }
|
128 | const startMS = Date.now()
|
129 | let endMS = startMS
|
130 | try {
|
131 | options.log && this.log(`Operation '${name}' started...`)
|
132 | this.noteCount(`${name}.start`, 1, dims)
|
133 | const ret = await op()
|
134 | this.noteCount(`${name}.success`, 1, dims)
|
135 | endMS = Date.now()
|
136 | options.log && this.log(`Operation '${name}' completed`, { execMS: endMS - startMS })
|
137 | return ret
|
138 | } catch (err) {
|
139 | endMS = Date.now()
|
140 | options.log && this.log(`Operation '${name}' threw an error`, { execMS: endMS - startMS, err: err })
|
141 | this.noteCount(`${name}.fail`, 1, dims)
|
142 | throw err
|
143 | } finally {
|
144 | this.noteCount(`${name}.end`, 1, dims)
|
145 | this.noteTimer(name, endMS - startMS, dims)
|
146 | }
|
147 | }
|
148 |
|
149 |
|
150 | _internalLog(level: string, module: string, message: string, data: ?any, logOnly: ?boolean) {
|
151 | try {
|
152 |
|
153 | if (System.activeLogLevels && !System.activeLogLevels.includes(level))
|
154 | return
|
155 |
|
156 | const logRecord = {
|
157 | level: level,
|
158 | module: module,
|
159 | message: message,
|
160 | data: _correctData(data),
|
161 | }
|
162 |
|
163 |
|
164 | System.queueLogRecord(logRecord)
|
165 |
|
166 | if (!logOnly)
|
167 | this.noteCount(`logging.${level}`, 1)
|
168 | } catch (err) {
|
169 | console.error(`Error thrown during logging operation: ${err.toString()}`)
|
170 | }
|
171 | }
|
172 | }
|
173 |
|
174 | async function _trackMetricsToInfluxDB(metrics: Array<Metric>) {
|
175 | await Promise.all(System.influxDBClients.map(idbClient => idbClient.trackMetrics(metrics.map(metric => ({
|
176 | ...metric,
|
177 | timestampMs: Date.now(),
|
178 | })))))
|
179 | }
|
180 |
|
181 | async function _trackMetricToInfluxDB(statName: string, value: number, dims: MinimalMetricDimensions, timestampMs?: ?number) {
|
182 | await _trackMetricsToInfluxDB([ { statName, value, dims, timestampMs } ])
|
183 | }
|
184 |
|
185 | function _correctData(data: ?any): ?any {
|
186 | if (data != null) {
|
187 | if (isError(data)) {
|
188 | data = {
|
189 | err: data,
|
190 | }
|
191 | }
|
192 |
|
193 | let iteratingData = data
|
194 | let maxDepth = 4
|
195 | while (--maxDepth && iteratingData && isError(iteratingData.err)) {
|
196 |
|
197 | iteratingData.err = errorToObj(iteratingData.err)
|
198 |
|
199 | iteratingData = iteratingData.err.data
|
200 | }
|
201 | }
|
202 |
|
203 | return data
|
204 | }
|