@adalo/metrics 0.1.164 → 0.1.165
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const http = require('http')
|
|
2
|
+
const https = require('https')
|
|
3
|
+
const { RedisMetricsClient } = require('../src/metrics/metricsRedisClient')
|
|
4
|
+
const { IOREDIS, REDIS_V3, REDIS_V4 } = require('../src/redisUtils')
|
|
5
|
+
|
|
6
|
+
jest.mock('../src/redisUtils', () => ({
|
|
7
|
+
...jest.requireActual('../src/redisUtils'),
|
|
8
|
+
getRedisClientType: jest.fn(),
|
|
9
|
+
IOREDIS: 'IOREDIS',
|
|
10
|
+
REDIS_V3: 'REDIS_V3',
|
|
11
|
+
REDIS_V4: 'REDIS_V4',
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const { getRedisClientType } = require('../src/redisUtils')
|
|
15
|
+
|
|
16
|
+
describe('RedisMetricsClient', () => {
|
|
17
|
+
const originalEnv = process.env
|
|
18
|
+
let mockRedisClient
|
|
19
|
+
let mockHttpRequest
|
|
20
|
+
let mockHttpsRequest
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks()
|
|
24
|
+
process.env = { ...originalEnv }
|
|
25
|
+
process.exit = jest.fn()
|
|
26
|
+
|
|
27
|
+
mockRedisClient = {
|
|
28
|
+
duplicate: jest.fn(() => ({
|
|
29
|
+
on: jest.fn(),
|
|
30
|
+
subscribe: jest.fn(),
|
|
31
|
+
disconnect: jest.fn(),
|
|
32
|
+
call: jest.fn().mockResolvedValue(null),
|
|
33
|
+
sendCommand: jest.fn().mockResolvedValue(null),
|
|
34
|
+
send_command: jest.fn((cmd, args, cb) => (cb ? cb(null, null) : undefined)),
|
|
35
|
+
})),
|
|
36
|
+
publish: jest.fn().mockResolvedValue(1),
|
|
37
|
+
sendCommand: jest.fn().mockResolvedValue(1),
|
|
38
|
+
send_command: jest.fn((cmd, args, cb) => (cb ? cb(null, 1) : undefined)),
|
|
39
|
+
disconnect: jest.fn(),
|
|
40
|
+
quit: jest.fn().mockResolvedValue(undefined),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const createMockRequest = (cb) => {
|
|
44
|
+
const req = {
|
|
45
|
+
setHeader: jest.fn(),
|
|
46
|
+
end: jest.fn(),
|
|
47
|
+
on: jest.fn(),
|
|
48
|
+
}
|
|
49
|
+
setImmediate(() => {
|
|
50
|
+
const res = { statusCode: 200, on: jest.fn() }
|
|
51
|
+
if (cb) cb(res)
|
|
52
|
+
})
|
|
53
|
+
return req
|
|
54
|
+
}
|
|
55
|
+
mockHttpRequest = jest.spyOn(http, 'request').mockImplementation((opts, cb) => {
|
|
56
|
+
const req = createMockRequest((res) => {
|
|
57
|
+
if (typeof cb === 'function') cb(res)
|
|
58
|
+
})
|
|
59
|
+
return req
|
|
60
|
+
})
|
|
61
|
+
mockHttpsRequest = jest.spyOn(https, 'request').mockImplementation((opts, cb) => {
|
|
62
|
+
const req = createMockRequest((res) => {
|
|
63
|
+
if (typeof cb === 'function') cb(res)
|
|
64
|
+
})
|
|
65
|
+
return req
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
process.env = originalEnv
|
|
71
|
+
mockHttpRequest.mockRestore()
|
|
72
|
+
mockHttpsRequest.mockRestore()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('constructor', () => {
|
|
76
|
+
it('throws when redisClient is missing', () => {
|
|
77
|
+
expect(() => new RedisMetricsClient({})).toThrow('RedisMetricsClient requires redisClient')
|
|
78
|
+
expect(() => new RedisMetricsClient()).toThrow('RedisMetricsClient requires redisClient')
|
|
79
|
+
expect(() => new RedisMetricsClient({ redisClient: null })).toThrow('RedisMetricsClient requires redisClient')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('accepts redisClient and passes other options via metricsConfig', () => {
|
|
83
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
84
|
+
const client = new RedisMetricsClient({
|
|
85
|
+
redisClient: mockRedisClient,
|
|
86
|
+
appName: 'my-app',
|
|
87
|
+
processType: 'web',
|
|
88
|
+
pushgatewayUrl: 'http://vm:8428/api/v1/import/prometheus',
|
|
89
|
+
})
|
|
90
|
+
expect(client.redisClient).toBe(mockRedisClient)
|
|
91
|
+
expect(client.appName).toBe('my-app')
|
|
92
|
+
expect(client.processType).toBe('web')
|
|
93
|
+
expect(client.pushgatewayUrl).toBe('http://vm:8428/api/v1/import/prometheus')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('reads gracefulShutdownRedis from metricsConfig', () => {
|
|
97
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
98
|
+
const client = new RedisMetricsClient({
|
|
99
|
+
redisClient: mockRedisClient,
|
|
100
|
+
gracefulShutdownRedis: false,
|
|
101
|
+
})
|
|
102
|
+
expect(client._gracefulShutdownRedis).toBe(false)
|
|
103
|
+
expect(client._gracefulShutdownStream).toBeNull()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('enables graceful shutdown by default', () => {
|
|
107
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
108
|
+
const client = new RedisMetricsClient({
|
|
109
|
+
redisClient: mockRedisClient,
|
|
110
|
+
appName: 'test-app',
|
|
111
|
+
processType: 'worker',
|
|
112
|
+
})
|
|
113
|
+
expect(client._gracefulShutdownRedis).toBe(true)
|
|
114
|
+
expect(client._gracefulShutdownStream).toBe('metrics:graceful-shutdown:test-app:worker')
|
|
115
|
+
expect(client._gracefulShutdownAckChannel).toBe('metrics:graceful-shutdown-ack:test-app:worker')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('_initGracefulShutdown', () => {
|
|
120
|
+
it('sets channels and log prefix when enabled', () => {
|
|
121
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
122
|
+
const client = new RedisMetricsClient({
|
|
123
|
+
redisClient: mockRedisClient,
|
|
124
|
+
appName: 'myapp',
|
|
125
|
+
processType: 'queue-metrics',
|
|
126
|
+
})
|
|
127
|
+
expect(client._gracefulShutdownLogPrefix).toContain('myapp')
|
|
128
|
+
expect(client._gracefulShutdownLogPrefix).toContain('queue-metrics')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('respects METRICS_GRACEFUL_SHUTDOWN_REDIS=false', () => {
|
|
132
|
+
process.env.METRICS_GRACEFUL_SHUTDOWN_REDIS = 'false'
|
|
133
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
134
|
+
const client = new RedisMetricsClient({
|
|
135
|
+
redisClient: mockRedisClient,
|
|
136
|
+
})
|
|
137
|
+
expect(client._gracefulShutdownRedis).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('_createSubscriberClient', () => {
|
|
142
|
+
it('uses duplicate() for IOREDIS client', () => {
|
|
143
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
144
|
+
const dup = {
|
|
145
|
+
on: jest.fn(),
|
|
146
|
+
subscribe: jest.fn(),
|
|
147
|
+
call: jest.fn().mockResolvedValue(null),
|
|
148
|
+
}
|
|
149
|
+
mockRedisClient.duplicate.mockReturnValue(dup)
|
|
150
|
+
const client = new RedisMetricsClient({
|
|
151
|
+
redisClient: mockRedisClient,
|
|
152
|
+
gracefulShutdownRedis: true,
|
|
153
|
+
})
|
|
154
|
+
expect(client._subClient).toBe(dup)
|
|
155
|
+
expect(mockRedisClient.duplicate).toHaveBeenCalled()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('uses duplicate() for node-redis v4 when available', () => {
|
|
159
|
+
getRedisClientType.mockReturnValue(REDIS_V4)
|
|
160
|
+
const dup = {
|
|
161
|
+
on: jest.fn(),
|
|
162
|
+
subscribe: jest.fn(),
|
|
163
|
+
quit: jest.fn().mockResolvedValue(),
|
|
164
|
+
sendCommand: jest.fn().mockResolvedValue(null),
|
|
165
|
+
}
|
|
166
|
+
mockRedisClient.duplicate = jest.fn(() => dup)
|
|
167
|
+
const client = new RedisMetricsClient({
|
|
168
|
+
redisClient: mockRedisClient,
|
|
169
|
+
gracefulShutdownRedis: true,
|
|
170
|
+
})
|
|
171
|
+
expect(client._subClient).toBe(dup)
|
|
172
|
+
expect(mockRedisClient.duplicate).toHaveBeenCalled()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('does not override skipFirstPush from config', () => {
|
|
176
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
177
|
+
const client = new RedisMetricsClient({
|
|
178
|
+
redisClient: mockRedisClient,
|
|
179
|
+
skipFirstPush: false,
|
|
180
|
+
})
|
|
181
|
+
expect(client.skipFirstPush).toBe(false)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('gateway push (mocked)', () => {
|
|
186
|
+
it('calls http.request when pushgatewayUrl is set', async () => {
|
|
187
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
188
|
+
const client = new RedisMetricsClient({
|
|
189
|
+
redisClient: mockRedisClient,
|
|
190
|
+
pushgatewayUrl: 'http://localhost:8428/api/v1/import/prometheus',
|
|
191
|
+
enabled: true,
|
|
192
|
+
cleanupExitsProcess: false,
|
|
193
|
+
})
|
|
194
|
+
await client.gatewayPush()
|
|
195
|
+
expect(mockHttpRequest).toHaveBeenCalled()
|
|
196
|
+
expect(mockHttpRequest.mock.calls[0][0].method).toBe('POST')
|
|
197
|
+
expect(mockHttpRequest.mock.calls[0][0].path).toContain('import/prometheus')
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('gateway delete (mocked)', () => {
|
|
202
|
+
it('calls http.request for delete_series when removeOldMetrics and pushgatewayUrl set', async () => {
|
|
203
|
+
getRedisClientType.mockReturnValue(IOREDIS)
|
|
204
|
+
const client = new RedisMetricsClient({
|
|
205
|
+
redisClient: mockRedisClient,
|
|
206
|
+
pushgatewayUrl: 'http://localhost:8428/api/v1/import/prometheus',
|
|
207
|
+
removeOldMetrics: true,
|
|
208
|
+
enabled: true,
|
|
209
|
+
appName: 'test-app',
|
|
210
|
+
dynoId: 'dyno-1',
|
|
211
|
+
processType: 'web',
|
|
212
|
+
cleanupExitsProcess: false,
|
|
213
|
+
})
|
|
214
|
+
await client.gatewayDelete()
|
|
215
|
+
expect(mockHttpRequest).toHaveBeenCalled()
|
|
216
|
+
const call = mockHttpRequest.mock.calls[0][0]
|
|
217
|
+
expect(call.path).toContain('delete_series')
|
|
218
|
+
expect(call.path).toContain('match')
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adalo/metrics",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.165",
|
|
4
4
|
"description": "Reusable metrics utilities for Node.js and Laravel apps",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"@types/pg": "^8.15.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@babel/cli": "7.
|
|
35
|
-
"@babel/core": "7.
|
|
36
|
-
"@babel/preset-env": "7.
|
|
37
|
-
"@babel/preset-typescript": "7.
|
|
34
|
+
"@babel/cli": "^7.24.0",
|
|
35
|
+
"@babel/core": "^7.24.0",
|
|
36
|
+
"@babel/preset-env": "^7.24.0",
|
|
37
|
+
"@babel/preset-typescript": "^7.24.0",
|
|
38
38
|
"@types/ioredis": "^5.0.0",
|
|
39
39
|
"@types/jest": "28.1.6",
|
|
40
40
|
"@types/node": "18.0.4",
|