@zintrust/workers 0.4.50 → 0.4.61
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.
- package/dist/WorkerFactory.js +16 -2
- package/dist/build-manifest.json +19 -11
- package/dist/createQueueWorker.js +27 -0
- package/dist/queueMonitorHistory.d.ts +17 -0
- package/dist/queueMonitorHistory.js +62 -0
- package/package.json +1 -1
- package/src/WorkerFactory.ts +16 -1
- package/src/createQueueWorker.ts +30 -0
- package/src/queueMonitorHistory.ts +114 -0
- package/src/types/queue-monitor.d.ts +46 -0
package/dist/WorkerFactory.js
CHANGED
|
@@ -23,6 +23,7 @@ import { WorkerMetrics } from './WorkerMetrics.js';
|
|
|
23
23
|
import { WorkerRegistry } from './WorkerRegistry.js';
|
|
24
24
|
import { WorkerVersioning } from './WorkerVersioning.js';
|
|
25
25
|
import { keyPrefix } from './config/workerConfig.js';
|
|
26
|
+
import { recordQueueMonitorJob } from './queueMonitorHistory.js';
|
|
26
27
|
import { DbWorkerStore, InMemoryWorkerStore, RedisWorkerStore, } from './storage/WorkerStore.js';
|
|
27
28
|
const path = NodeSingletons.path;
|
|
28
29
|
const isNodeRuntime = () => typeof process !== 'undefined' && Boolean(process.versions?.node);
|
|
@@ -1905,10 +1906,15 @@ const initializeDatacenter = (config) => {
|
|
|
1905
1906
|
},
|
|
1906
1907
|
});
|
|
1907
1908
|
};
|
|
1908
|
-
const setupWorkerEventListeners = (worker, workerName, workerVersion, features) => {
|
|
1909
|
+
const setupWorkerEventListeners = (worker, workerName, queueName, workerVersion, features) => {
|
|
1909
1910
|
worker.on('completed', (job) => {
|
|
1910
1911
|
try {
|
|
1911
1912
|
Logger.debug(`Job completed: ${workerName}`, { jobId: job.id });
|
|
1913
|
+
void recordQueueMonitorJob({
|
|
1914
|
+
queueName,
|
|
1915
|
+
status: 'completed',
|
|
1916
|
+
job,
|
|
1917
|
+
});
|
|
1912
1918
|
if (features?.observability === true) {
|
|
1913
1919
|
Observability.incrementCounter('worker.jobs.completed', 1, {
|
|
1914
1920
|
worker: workerName,
|
|
@@ -1924,6 +1930,14 @@ const setupWorkerEventListeners = (worker, workerName, workerVersion, features)
|
|
|
1924
1930
|
worker.on('failed', (job, error) => {
|
|
1925
1931
|
try {
|
|
1926
1932
|
Logger.error(`Job failed: ${workerName}`, { error, jobId: job?.id }, 'workers');
|
|
1933
|
+
if (job) {
|
|
1934
|
+
void recordQueueMonitorJob({
|
|
1935
|
+
queueName,
|
|
1936
|
+
status: 'failed',
|
|
1937
|
+
job,
|
|
1938
|
+
error,
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1927
1941
|
if (features?.observability === true) {
|
|
1928
1942
|
Observability.incrementCounter('worker.jobs.failed', 1, {
|
|
1929
1943
|
worker: workerName,
|
|
@@ -2087,7 +2101,7 @@ export const WorkerFactory = Object.freeze({
|
|
|
2087
2101
|
// Create BullMQ worker
|
|
2088
2102
|
const resolvedOptions = resolveWorkerOptions(config, autoStart);
|
|
2089
2103
|
const worker = new Worker(queueName, enhancedProcessor, resolvedOptions);
|
|
2090
|
-
setupWorkerEventListeners(worker, name, workerVersion, features);
|
|
2104
|
+
setupWorkerEventListeners(worker, name, queueName, workerVersion, features);
|
|
2091
2105
|
// Update status to "starting"
|
|
2092
2106
|
await store.update(name, {
|
|
2093
2107
|
status: WorkerCreationStatus.STARTING,
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/workers",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"buildDate": "2026-04-
|
|
3
|
+
"version": "0.4.60",
|
|
4
|
+
"buildDate": "2026-04-05T07:03:43.939Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v22.22.1",
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "be058c1d",
|
|
12
12
|
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -178,8 +178,8 @@
|
|
|
178
178
|
"sha256": "3869f960c87260588e40941ff91bffcfa0757be7a04815fd28b57dd4840c51df"
|
|
179
179
|
},
|
|
180
180
|
"WorkerFactory.js": {
|
|
181
|
-
"size":
|
|
182
|
-
"sha256": "
|
|
181
|
+
"size": 105839,
|
|
182
|
+
"sha256": "b26135593d6e3849f5540d74d097082d8186b50c72a456722f83d624141c42c8"
|
|
183
183
|
},
|
|
184
184
|
"WorkerInit.d.ts": {
|
|
185
185
|
"size": 3284,
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
},
|
|
232
232
|
"build-manifest.json": {
|
|
233
233
|
"size": 19594,
|
|
234
|
-
"sha256": "
|
|
234
|
+
"sha256": "7402721e5f5a98fb988fa78f5ee49b6e82f2f3fab1c3cfb8ca2bf8116012169f"
|
|
235
235
|
},
|
|
236
236
|
"config/workerConfig.d.ts": {
|
|
237
237
|
"size": 132,
|
|
@@ -246,8 +246,8 @@
|
|
|
246
246
|
"sha256": "dacd49f6c112eba439bdd9bb457eea90daedbf32efc381cd3189ce562fa5b0a8"
|
|
247
247
|
},
|
|
248
248
|
"createQueueWorker.js": {
|
|
249
|
-
"size":
|
|
250
|
-
"sha256": "
|
|
249
|
+
"size": 15594,
|
|
250
|
+
"sha256": "0710f2fb6936a326cdbe6fa6a2b8acfb60f7c6b27acb2aed0b98bf2402fc67f4"
|
|
251
251
|
},
|
|
252
252
|
"dashboard/index.d.ts": {
|
|
253
253
|
"size": 109,
|
|
@@ -270,8 +270,8 @@
|
|
|
270
270
|
"sha256": "8e0e04329e1119d8ae835dd4458efead084293bcc2c263c09dd5a19d467e5ca4"
|
|
271
271
|
},
|
|
272
272
|
"dashboard/workers-api.js": {
|
|
273
|
-
"size":
|
|
274
|
-
"sha256": "
|
|
273
|
+
"size": 28568,
|
|
274
|
+
"sha256": "c28f0d4b8134337a2cc71f1ce2f1d6195f44ccd9bb0b8fa26a4edaed88ee4fc0"
|
|
275
275
|
},
|
|
276
276
|
"helper/index.d.ts": {
|
|
277
277
|
"size": 159,
|
|
@@ -415,7 +415,15 @@
|
|
|
415
415
|
},
|
|
416
416
|
"index.js": {
|
|
417
417
|
"size": 2337,
|
|
418
|
-
"sha256": "
|
|
418
|
+
"sha256": "85a789d0f2ed71dde649f59658827ff16eaf2c1bc77df99b2297c7eb7e340108"
|
|
419
|
+
},
|
|
420
|
+
"queueMonitorHistory.d.ts": {
|
|
421
|
+
"size": 433,
|
|
422
|
+
"sha256": "1ba25cf47b0cad83e92c7b6481d12de974b921d0e01fc4cf17fc833acdf8b8c8"
|
|
423
|
+
},
|
|
424
|
+
"queueMonitorHistory.js": {
|
|
425
|
+
"size": 2194,
|
|
426
|
+
"sha256": "03d406adc046e18c3526837a654fae93b112fb6010ddc1163b129d10e934c4d4"
|
|
419
427
|
},
|
|
420
428
|
"register.d.ts": {
|
|
421
429
|
"size": 256,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Core from '@zintrust/core';
|
|
2
2
|
import { Env, Logger, Queue } from '@zintrust/core';
|
|
3
|
+
import { recordQueueMonitorJob } from './queueMonitorHistory.js';
|
|
3
4
|
const TypedQueue = Queue;
|
|
4
5
|
const RETRY_BASE_DELAY_MS = 1000;
|
|
5
6
|
const RETRY_MAX_DELAY_MS = 30000;
|
|
@@ -168,6 +169,18 @@ const checkAndRequeueIfNotDue = async (options, queueName, driverName, message,
|
|
|
168
169
|
};
|
|
169
170
|
const onProcessSuccess = async (input) => {
|
|
170
171
|
await TypedQueue.ack(input.queueName, input.message.id, input.driverName);
|
|
172
|
+
await recordQueueMonitorJob({
|
|
173
|
+
queueName: input.queueName,
|
|
174
|
+
status: 'completed',
|
|
175
|
+
job: {
|
|
176
|
+
id: input.message.id,
|
|
177
|
+
name: `${input.queueName}-job`,
|
|
178
|
+
data: input.message.payload,
|
|
179
|
+
attemptsMade: getAttemptsFromMessage(input.message),
|
|
180
|
+
processedOn: input.startedAtMs,
|
|
181
|
+
finishedOn: Date.now(),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
171
184
|
if (typeof input.trackerApi.completed === 'function') {
|
|
172
185
|
await input.trackerApi.completed({
|
|
173
186
|
queueName: input.queueName,
|
|
@@ -217,6 +230,20 @@ const onProcessFailure = async (input) => {
|
|
|
217
230
|
});
|
|
218
231
|
}
|
|
219
232
|
await TypedQueue.ack(input.queueName, input.message.id, input.driverName);
|
|
233
|
+
await recordQueueMonitorJob({
|
|
234
|
+
queueName: input.queueName,
|
|
235
|
+
status: 'failed',
|
|
236
|
+
job: {
|
|
237
|
+
id: input.message.id,
|
|
238
|
+
name: `${input.queueName}-job`,
|
|
239
|
+
data: input.message.payload,
|
|
240
|
+
attemptsMade: nextAttempts,
|
|
241
|
+
failedReason: failure.message,
|
|
242
|
+
processedOn: Date.now(),
|
|
243
|
+
finishedOn: Date.now(),
|
|
244
|
+
},
|
|
245
|
+
error: failure,
|
|
246
|
+
});
|
|
220
247
|
await removeHeartbeatIfSupported(input.queueName, input.message.id);
|
|
221
248
|
if (typeof input.trackerApi.failed === 'function') {
|
|
222
249
|
await input.trackerApi.failed({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type QueueMonitorStatus = 'completed' | 'failed';
|
|
2
|
+
type QueueMonitorJobLike = {
|
|
3
|
+
id?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
attemptsMade?: number;
|
|
7
|
+
failedReason?: string;
|
|
8
|
+
processedOn?: number;
|
|
9
|
+
finishedOn?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const recordQueueMonitorJob: (input: {
|
|
12
|
+
queueName: string;
|
|
13
|
+
status: QueueMonitorStatus;
|
|
14
|
+
job: QueueMonitorJobLike;
|
|
15
|
+
error?: Error;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isNonEmptyString, isObject, Logger, queueConfig } from '@zintrust/core';
|
|
2
|
+
let queueMonitorMetricsPromise = null;
|
|
3
|
+
const toFiniteInteger = (value, fallback) => {
|
|
4
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
5
|
+
return Math.floor(value);
|
|
6
|
+
}
|
|
7
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
if (Number.isFinite(parsed)) {
|
|
10
|
+
return Math.floor(parsed);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return fallback;
|
|
14
|
+
};
|
|
15
|
+
const resolveQueueMonitorRedisConfig = () => {
|
|
16
|
+
const redisConfig = queueConfig?.drivers?.redis;
|
|
17
|
+
if (!isObject(redisConfig) || redisConfig['driver'] !== 'redis') {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const host = isNonEmptyString(redisConfig['host']) ? redisConfig['host'].trim() : '127.0.0.1';
|
|
21
|
+
const port = toFiniteInteger(redisConfig['port'], 6379);
|
|
22
|
+
const db = toFiniteInteger(redisConfig['database'], 0);
|
|
23
|
+
const password = isNonEmptyString(redisConfig['password']) ? redisConfig['password'] : undefined;
|
|
24
|
+
return { host, port, password, db };
|
|
25
|
+
};
|
|
26
|
+
const loadQueueMonitorMetrics = async () => {
|
|
27
|
+
const redisConfig = resolveQueueMonitorRedisConfig();
|
|
28
|
+
if (redisConfig === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const module = (await import('@zintrust/queue-monitor'));
|
|
33
|
+
if (typeof module.createMetrics !== 'function') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return module.createMetrics(redisConfig);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
Logger.debug('Queue monitor metrics are unavailable for worker history recording', error);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const getQueueMonitorMetrics = async () => {
|
|
44
|
+
queueMonitorMetricsPromise ??= loadQueueMonitorMetrics();
|
|
45
|
+
return queueMonitorMetricsPromise;
|
|
46
|
+
};
|
|
47
|
+
export const recordQueueMonitorJob = async (input) => {
|
|
48
|
+
const metrics = await getQueueMonitorMetrics();
|
|
49
|
+
if (metrics === null) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await metrics.recordJob(input.queueName, input.status, input.job, input.error);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
Logger.debug('Queue monitor history write failed', {
|
|
57
|
+
queueName: input.queueName,
|
|
58
|
+
status: input.status,
|
|
59
|
+
error,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
package/package.json
CHANGED
package/src/WorkerFactory.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { WorkerMetrics } from './WorkerMetrics';
|
|
|
47
47
|
import { WorkerRegistry, type WorkerInstance as RegistryWorkerInstance } from './WorkerRegistry';
|
|
48
48
|
import { WorkerVersioning } from './WorkerVersioning';
|
|
49
49
|
import { keyPrefix } from './config/workerConfig';
|
|
50
|
+
import { recordQueueMonitorJob } from './queueMonitorHistory';
|
|
50
51
|
import {
|
|
51
52
|
DbWorkerStore,
|
|
52
53
|
InMemoryWorkerStore,
|
|
@@ -2685,12 +2686,18 @@ const initializeDatacenter = (config: WorkerFactoryConfig): void => {
|
|
|
2685
2686
|
const setupWorkerEventListeners = (
|
|
2686
2687
|
worker: Worker,
|
|
2687
2688
|
workerName: string,
|
|
2689
|
+
queueName: string,
|
|
2688
2690
|
workerVersion: string,
|
|
2689
2691
|
features?: WorkerFactoryConfig['features']
|
|
2690
2692
|
): void => {
|
|
2691
2693
|
worker.on('completed', (job: Job) => {
|
|
2692
2694
|
try {
|
|
2693
2695
|
Logger.debug(`Job completed: ${workerName}`, { jobId: job.id });
|
|
2696
|
+
void recordQueueMonitorJob({
|
|
2697
|
+
queueName,
|
|
2698
|
+
status: 'completed',
|
|
2699
|
+
job,
|
|
2700
|
+
});
|
|
2694
2701
|
|
|
2695
2702
|
if (features?.observability === true) {
|
|
2696
2703
|
Observability.incrementCounter('worker.jobs.completed', 1, {
|
|
@@ -2707,6 +2714,14 @@ const setupWorkerEventListeners = (
|
|
|
2707
2714
|
worker.on('failed', (job: Job | undefined, error: Error) => {
|
|
2708
2715
|
try {
|
|
2709
2716
|
Logger.error(`Job failed: ${workerName}`, { error, jobId: job?.id }, 'workers');
|
|
2717
|
+
if (job) {
|
|
2718
|
+
void recordQueueMonitorJob({
|
|
2719
|
+
queueName,
|
|
2720
|
+
status: 'failed',
|
|
2721
|
+
job,
|
|
2722
|
+
error,
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2710
2725
|
|
|
2711
2726
|
if (features?.observability === true) {
|
|
2712
2727
|
Observability.incrementCounter('worker.jobs.failed', 1, {
|
|
@@ -2900,7 +2915,7 @@ export const WorkerFactory = Object.freeze({
|
|
|
2900
2915
|
const resolvedOptions = resolveWorkerOptions(config, autoStart);
|
|
2901
2916
|
const worker = new Worker(queueName, enhancedProcessor, resolvedOptions);
|
|
2902
2917
|
|
|
2903
|
-
setupWorkerEventListeners(worker, name, workerVersion, features);
|
|
2918
|
+
setupWorkerEventListeners(worker, name, queueName, workerVersion, features);
|
|
2904
2919
|
|
|
2905
2920
|
// Update status to "starting"
|
|
2906
2921
|
await store.update(name, {
|
package/src/createQueueWorker.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BullMQPayload, QueueMessage } from '@zintrust/core';
|
|
2
2
|
import * as Core from '@zintrust/core';
|
|
3
3
|
import { Env, Logger, Queue } from '@zintrust/core';
|
|
4
|
+
import { recordQueueMonitorJob } from './queueMonitorHistory';
|
|
4
5
|
|
|
5
6
|
type QueueApi = Readonly<{
|
|
6
7
|
enqueue: (queue: string, payload: BullMQPayload, driverName?: string) => Promise<string>;
|
|
@@ -320,6 +321,19 @@ const onProcessSuccess = async <TPayload>(input: {
|
|
|
320
321
|
}): Promise<boolean> => {
|
|
321
322
|
await TypedQueue.ack(input.queueName, input.message.id, input.driverName);
|
|
322
323
|
|
|
324
|
+
await recordQueueMonitorJob({
|
|
325
|
+
queueName: input.queueName,
|
|
326
|
+
status: 'completed',
|
|
327
|
+
job: {
|
|
328
|
+
id: input.message.id,
|
|
329
|
+
name: `${input.queueName}-job`,
|
|
330
|
+
data: input.message.payload,
|
|
331
|
+
attemptsMade: getAttemptsFromMessage(input.message),
|
|
332
|
+
processedOn: input.startedAtMs,
|
|
333
|
+
finishedOn: Date.now(),
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
|
|
323
337
|
if (typeof input.trackerApi.completed === 'function') {
|
|
324
338
|
await input.trackerApi.completed({
|
|
325
339
|
queueName: input.queueName,
|
|
@@ -386,6 +400,22 @@ const onProcessFailure = async <TPayload>(input: {
|
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
await TypedQueue.ack(input.queueName, input.message.id, input.driverName);
|
|
403
|
+
|
|
404
|
+
await recordQueueMonitorJob({
|
|
405
|
+
queueName: input.queueName,
|
|
406
|
+
status: 'failed',
|
|
407
|
+
job: {
|
|
408
|
+
id: input.message.id,
|
|
409
|
+
name: `${input.queueName}-job`,
|
|
410
|
+
data: input.message.payload,
|
|
411
|
+
attemptsMade: nextAttempts,
|
|
412
|
+
failedReason: failure.message,
|
|
413
|
+
processedOn: Date.now(),
|
|
414
|
+
finishedOn: Date.now(),
|
|
415
|
+
},
|
|
416
|
+
error: failure,
|
|
417
|
+
});
|
|
418
|
+
|
|
389
419
|
await removeHeartbeatIfSupported(input.queueName, input.message.id);
|
|
390
420
|
|
|
391
421
|
if (typeof input.trackerApi.failed === 'function') {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { isNonEmptyString, isObject, Logger, queueConfig } from '@zintrust/core';
|
|
2
|
+
|
|
3
|
+
type QueueMonitorStatus = 'completed' | 'failed';
|
|
4
|
+
|
|
5
|
+
type QueueMonitorJobLike = {
|
|
6
|
+
id?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
attemptsMade?: number;
|
|
10
|
+
failedReason?: string;
|
|
11
|
+
processedOn?: number;
|
|
12
|
+
finishedOn?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type QueueMonitorMetrics = {
|
|
16
|
+
recordJob: (
|
|
17
|
+
queueName: string,
|
|
18
|
+
status: QueueMonitorStatus,
|
|
19
|
+
job: QueueMonitorJobLike,
|
|
20
|
+
error?: Error
|
|
21
|
+
) => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type QueueMonitorModule = {
|
|
25
|
+
createMetrics?: (config: {
|
|
26
|
+
host: string;
|
|
27
|
+
port: number;
|
|
28
|
+
password?: string;
|
|
29
|
+
db: number;
|
|
30
|
+
}) => QueueMonitorMetrics;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let queueMonitorMetricsPromise: Promise<QueueMonitorMetrics | null> | null = null;
|
|
34
|
+
|
|
35
|
+
const toFiniteInteger = (value: unknown, fallback: number): number => {
|
|
36
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
37
|
+
return Math.floor(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
41
|
+
const parsed = Number(value);
|
|
42
|
+
if (Number.isFinite(parsed)) {
|
|
43
|
+
return Math.floor(parsed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return fallback;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const resolveQueueMonitorRedisConfig = (): {
|
|
51
|
+
host: string;
|
|
52
|
+
port: number;
|
|
53
|
+
password?: string;
|
|
54
|
+
db: number;
|
|
55
|
+
} | null => {
|
|
56
|
+
const redisConfig = queueConfig?.drivers?.redis;
|
|
57
|
+
|
|
58
|
+
if (!isObject(redisConfig) || redisConfig['driver'] !== 'redis') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const host = isNonEmptyString(redisConfig['host']) ? redisConfig['host'].trim() : '127.0.0.1';
|
|
63
|
+
const port = toFiniteInteger(redisConfig['port'], 6379);
|
|
64
|
+
const db = toFiniteInteger(redisConfig['database'], 0);
|
|
65
|
+
const password = isNonEmptyString(redisConfig['password']) ? redisConfig['password'] : undefined;
|
|
66
|
+
|
|
67
|
+
return { host, port, password, db };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const loadQueueMonitorMetrics = async (): Promise<QueueMonitorMetrics | null> => {
|
|
71
|
+
const redisConfig = resolveQueueMonitorRedisConfig();
|
|
72
|
+
if (redisConfig === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const module = (await import('@zintrust/queue-monitor')) as QueueMonitorModule;
|
|
78
|
+
if (typeof module.createMetrics !== 'function') {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return module.createMetrics(redisConfig);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
Logger.debug('Queue monitor metrics are unavailable for worker history recording', error);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getQueueMonitorMetrics = async (): Promise<QueueMonitorMetrics | null> => {
|
|
90
|
+
queueMonitorMetricsPromise ??= loadQueueMonitorMetrics();
|
|
91
|
+
return queueMonitorMetricsPromise;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const recordQueueMonitorJob = async (input: {
|
|
95
|
+
queueName: string;
|
|
96
|
+
status: QueueMonitorStatus;
|
|
97
|
+
job: QueueMonitorJobLike;
|
|
98
|
+
error?: Error;
|
|
99
|
+
}): Promise<void> => {
|
|
100
|
+
const metrics = await getQueueMonitorMetrics();
|
|
101
|
+
if (metrics === null) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await metrics.recordJob(input.queueName, input.status, input.job, input.error);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
Logger.debug('Queue monitor history write failed', {
|
|
109
|
+
queueName: input.queueName,
|
|
110
|
+
status: input.status,
|
|
111
|
+
error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
@@ -1,4 +1,43 @@
|
|
|
1
1
|
declare module '@zintrust/queue-monitor' {
|
|
2
|
+
export type JobStatus = 'completed' | 'failed';
|
|
3
|
+
|
|
4
|
+
export type JobSummary = {
|
|
5
|
+
id: string | undefined;
|
|
6
|
+
name: string;
|
|
7
|
+
queue?: string;
|
|
8
|
+
data: unknown;
|
|
9
|
+
attempts: number;
|
|
10
|
+
status?: string;
|
|
11
|
+
failedReason?: string;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
processedOn?: number;
|
|
14
|
+
finishedOn?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type Metrics = {
|
|
18
|
+
recordJob: (
|
|
19
|
+
queue: string,
|
|
20
|
+
status: JobStatus,
|
|
21
|
+
job: {
|
|
22
|
+
id?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
data?: unknown;
|
|
25
|
+
attemptsMade?: number;
|
|
26
|
+
failedReason?: string;
|
|
27
|
+
processedOn?: number;
|
|
28
|
+
finishedOn?: number;
|
|
29
|
+
},
|
|
30
|
+
error?: Error
|
|
31
|
+
) => Promise<void>;
|
|
32
|
+
getStats: (
|
|
33
|
+
queue: string,
|
|
34
|
+
minutes?: number
|
|
35
|
+
) => Promise<Array<{ time: string; completed: number; failed: number }>>;
|
|
36
|
+
getRecentJobs: (queue: string) => Promise<JobSummary[]>;
|
|
37
|
+
getFailedJobs: (queue: string) => Promise<JobSummary[]>;
|
|
38
|
+
close: () => Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
|
|
2
41
|
export type QueueCounts = {
|
|
3
42
|
waiting: number;
|
|
4
43
|
active: number;
|
|
@@ -34,6 +73,13 @@ declare module '@zintrust/queue-monitor' {
|
|
|
34
73
|
getSnapshot: () => Promise<QueueMonitorSnapshot>;
|
|
35
74
|
};
|
|
36
75
|
|
|
76
|
+
export const createMetrics: (config: {
|
|
77
|
+
host: string;
|
|
78
|
+
port: number;
|
|
79
|
+
password?: string;
|
|
80
|
+
db: number;
|
|
81
|
+
}) => Metrics;
|
|
82
|
+
|
|
37
83
|
export const QueueMonitor: Readonly<{
|
|
38
84
|
create: (config: QueueMonitorConfig) => QueueMonitorApi;
|
|
39
85
|
}>;
|