@oneuptime/common 10.5.23 → 10.5.25

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.
@@ -58,17 +58,6 @@ export default class MonitorResourceUtil {
58
58
  public static async monitorResource(
59
59
  dataToProcess: DataToProcess,
60
60
  ): Promise<ProbeApiIngestResponse> {
61
- let mutex: SemaphoreMutex | null = null;
62
-
63
- try {
64
- mutex = await Semaphore.lock({
65
- key: dataToProcess.monitorId.toString(),
66
- namespace: "MonitorResourceUtil.monitorResource",
67
- });
68
- } catch (err) {
69
- logger.error(err);
70
- }
71
-
72
61
  let response: ProbeApiIngestResponse = {
73
62
  monitorId: dataToProcess.monitorId,
74
63
  criteriaMetId: undefined,
@@ -188,114 +177,181 @@ export default class MonitorResourceUtil {
188
177
  );
189
178
  }
190
179
 
191
- let probeName: string | undefined = undefined;
192
- const monitorName: string | undefined = monitor.name || undefined;
180
+ /*
181
+ * Acquire a per-monitor lock so concurrent results for the same monitor are
182
+ * processed serially. This MUST come after the validation above: acquiring
183
+ * before the not-found/disabled checks meant those throw paths held — and
184
+ * leaked — the lock. redis-semaphore keeps a refresh timer alive until
185
+ * release() is called, so a leaked lock pinned the Redis key until pod
186
+ * restart and forced every other worker to spin on acquire. The try/finally
187
+ * below guarantees the lock is released on every exit path (return or
188
+ * throw); acquireTimeout/retryInterval cap the acquire spin so a contended
189
+ * lock fails fast instead of polling Redis for 10s.
190
+ */
191
+ let mutex: SemaphoreMutex | null = null;
193
192
 
194
- // save the last log to MonitorProbe.
193
+ try {
194
+ mutex = await Semaphore.lock({
195
+ key: dataToProcess.monitorId.toString(),
196
+ namespace: "MonitorResourceUtil.monitorResource",
197
+ acquireTimeout: 2000,
198
+ retryInterval: 100,
199
+ acquireAttemptsLimit: 20,
200
+ });
201
+ } catch (err) {
202
+ logger.error(err);
203
+ }
195
204
 
196
- // get last log. We do this because there are many monitoring steps and we need to store those.
197
- logger.debug(
198
- `${dataToProcess.monitorId.toString()} - monitor type ${
199
- monitor.monitorType
200
- }`,
201
- );
205
+ const releaseMutex: () => Promise<void> = async (): Promise<void> => {
206
+ if (mutex) {
207
+ try {
208
+ await Semaphore.release(mutex);
209
+ } catch (err) {
210
+ logger.error(err);
211
+ }
212
+ mutex = null;
213
+ }
214
+ };
215
+
216
+ try {
217
+ let probeName: string | undefined = undefined;
218
+ const monitorName: string | undefined = monitor.name || undefined;
219
+
220
+ // save the last log to MonitorProbe.
202
221
 
203
- if (
204
- monitor.monitorType &&
205
- MonitorTypeHelper.isProbableMonitor(monitor.monitorType)
206
- ) {
207
- dataToProcess = dataToProcess as ProbeMonitorResponse;
208
- if ((dataToProcess as ProbeMonitorResponse).probeId) {
209
- const monitorProbe: MonitorProbe | null =
210
- await MonitorProbeService.findOneBy({
222
+ // get last log. We do this because there are many monitoring steps and we need to store those.
223
+ logger.debug(
224
+ `${dataToProcess.monitorId.toString()} - monitor type ${
225
+ monitor.monitorType
226
+ }`,
227
+ );
228
+
229
+ if (
230
+ monitor.monitorType &&
231
+ MonitorTypeHelper.isProbableMonitor(monitor.monitorType)
232
+ ) {
233
+ dataToProcess = dataToProcess as ProbeMonitorResponse;
234
+ if ((dataToProcess as ProbeMonitorResponse).probeId) {
235
+ const monitorProbe: MonitorProbe | null =
236
+ await MonitorProbeService.findOneBy({
237
+ query: {
238
+ monitorId: monitor.id!,
239
+ probeId: (dataToProcess as ProbeMonitorResponse).probeId!,
240
+ },
241
+ select: {
242
+ lastMonitoringLog: true,
243
+ probe: {
244
+ name: true,
245
+ },
246
+ },
247
+ props: {
248
+ isRoot: true,
249
+ },
250
+ });
251
+
252
+ if (!monitorProbe) {
253
+ throw new BadDataException("Probe is not assigned to this monitor");
254
+ }
255
+
256
+ probeName = monitorProbe.probe?.name || undefined;
257
+
258
+ await MonitorProbeService.updateOneBy({
211
259
  query: {
212
260
  monitorId: monitor.id!,
213
261
  probeId: (dataToProcess as ProbeMonitorResponse).probeId!,
214
262
  },
215
- select: {
216
- lastMonitoringLog: true,
217
- probe: {
218
- name: true,
219
- },
263
+ data: {
264
+ lastMonitoringLog: {
265
+ ...(monitorProbe.lastMonitoringLog || {}),
266
+ [(
267
+ dataToProcess as ProbeMonitorResponse
268
+ ).monitorStepId.toString()]: {
269
+ ...JSON.parse(JSON.stringify(dataToProcess)),
270
+ monitoredAt: OneUptimeDate.getCurrentDate(),
271
+ },
272
+ } as any,
220
273
  },
221
274
  props: {
222
275
  isRoot: true,
223
276
  },
224
277
  });
278
+ }
279
+ }
225
280
 
226
- if (!monitorProbe) {
227
- throw new BadDataException("Probe is not assigned to this monitor");
281
+ const serverMonitorResponse: ServerMonitorResponse | undefined =
282
+ monitor.monitorType === MonitorType.Server &&
283
+ (dataToProcess as ServerMonitorResponse).requestReceivedAt
284
+ ? (dataToProcess as ServerMonitorResponse)
285
+ : undefined;
286
+
287
+ const incomingMonitorRequest: IncomingMonitorRequest | undefined =
288
+ monitor.monitorType === MonitorType.IncomingRequest &&
289
+ (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt &&
290
+ !(dataToProcess as IncomingMonitorRequest)
291
+ .onlyCheckForIncomingRequestReceivedAt
292
+ ? (dataToProcess as IncomingMonitorRequest)
293
+ : undefined;
294
+
295
+ let hasPersistedMonitorData: boolean = false;
296
+
297
+ const persistLatestMonitorPayload: () => Promise<void> = async () => {
298
+ if (hasPersistedMonitorData) {
299
+ return;
228
300
  }
229
301
 
230
- probeName = monitorProbe.probe?.name || undefined;
302
+ if (serverMonitorResponse) {
303
+ logger.debug(
304
+ `${dataToProcess.monitorId.toString()} - Server request received at ${serverMonitorResponse.requestReceivedAt}`,
305
+ );
231
306
 
232
- await MonitorProbeService.updateOneBy({
233
- query: {
234
- monitorId: monitor.id!,
235
- probeId: (dataToProcess as ProbeMonitorResponse).probeId!,
236
- },
237
- data: {
238
- lastMonitoringLog: {
239
- ...(monitorProbe.lastMonitoringLog || {}),
240
- [(
241
- dataToProcess as ProbeMonitorResponse
242
- ).monitorStepId.toString()]: {
243
- ...JSON.parse(JSON.stringify(dataToProcess)),
244
- monitoredAt: OneUptimeDate.getCurrentDate(),
307
+ logger.debug(dataToProcess);
308
+
309
+ /*
310
+ * Skip persistence when this evaluation originated from the
311
+ * CheckOnlineStatus cron (onlyCheckRequestReceivedAt=true). The cron
312
+ * re-evaluates using the already-stale value read from the DB and has
313
+ * no new heartbeat data to persist — writing it back would race with
314
+ * (and overwrite) the ingest path's fresh heartbeat update, causing
315
+ * the monitor to flap between Online and Offline every minute.
316
+ */
317
+ if (!serverMonitorResponse.onlyCheckRequestReceivedAt) {
318
+ await MonitorService.updateOneById({
319
+ id: monitor.id!,
320
+ data: {
321
+ serverMonitorRequestReceivedAt:
322
+ serverMonitorResponse.requestReceivedAt!,
323
+ serverMonitorResponse,
245
324
  },
246
- } as any,
247
- },
248
- props: {
249
- isRoot: true,
250
- },
251
- });
252
- }
253
- }
254
-
255
- const serverMonitorResponse: ServerMonitorResponse | undefined =
256
- monitor.monitorType === MonitorType.Server &&
257
- (dataToProcess as ServerMonitorResponse).requestReceivedAt
258
- ? (dataToProcess as ServerMonitorResponse)
259
- : undefined;
260
-
261
- const incomingMonitorRequest: IncomingMonitorRequest | undefined =
262
- monitor.monitorType === MonitorType.IncomingRequest &&
263
- (dataToProcess as IncomingMonitorRequest).incomingRequestReceivedAt &&
264
- !(dataToProcess as IncomingMonitorRequest)
265
- .onlyCheckForIncomingRequestReceivedAt
266
- ? (dataToProcess as IncomingMonitorRequest)
267
- : undefined;
268
-
269
- let hasPersistedMonitorData: boolean = false;
270
-
271
- const persistLatestMonitorPayload: () => Promise<void> = async () => {
272
- if (hasPersistedMonitorData) {
273
- return;
274
- }
325
+ props: {
326
+ isRoot: true,
327
+ ignoreHooks: true,
328
+ },
329
+ });
330
+
331
+ logger.debug(
332
+ `${dataToProcess.monitorId.toString()} - Monitor Server Response Updated`,
333
+ );
334
+ } else {
335
+ logger.debug(
336
+ `${dataToProcess.monitorId.toString()} - Skipping Monitor Server Response persist (cron re-evaluation).`,
337
+ );
338
+ }
339
+ }
275
340
 
276
- if (serverMonitorResponse) {
277
- logger.debug(
278
- `${dataToProcess.monitorId.toString()} - Server request received at ${serverMonitorResponse.requestReceivedAt}`,
279
- );
341
+ if (incomingMonitorRequest) {
342
+ logger.debug(
343
+ `${dataToProcess.monitorId.toString()} - Incoming request received at ${incomingMonitorRequest.incomingRequestReceivedAt}`,
344
+ );
280
345
 
281
- logger.debug(dataToProcess);
282
-
283
- /*
284
- * Skip persistence when this evaluation originated from the
285
- * CheckOnlineStatus cron (onlyCheckRequestReceivedAt=true). The cron
286
- * re-evaluates using the already-stale value read from the DB and has
287
- * no new heartbeat data to persist — writing it back would race with
288
- * (and overwrite) the ingest path's fresh heartbeat update, causing
289
- * the monitor to flap between Online and Offline every minute.
290
- */
291
- if (!serverMonitorResponse.onlyCheckRequestReceivedAt) {
292
346
  await MonitorService.updateOneById({
293
347
  id: monitor.id!,
294
348
  data: {
295
- serverMonitorRequestReceivedAt:
296
- serverMonitorResponse.requestReceivedAt!,
297
- serverMonitorResponse,
298
- },
349
+ incomingRequestMonitorHeartbeatCheckedAt:
350
+ OneUptimeDate.getCurrentDate(),
351
+ incomingMonitorRequest: JSON.parse(
352
+ JSON.stringify(incomingMonitorRequest),
353
+ ) as IncomingMonitorRequest,
354
+ } as any,
299
355
  props: {
300
356
  isRoot: true,
301
357
  ignoreHooks: true,
@@ -303,267 +359,162 @@ export default class MonitorResourceUtil {
303
359
  });
304
360
 
305
361
  logger.debug(
306
- `${dataToProcess.monitorId.toString()} - Monitor Server Response Updated`,
307
- );
308
- } else {
309
- logger.debug(
310
- `${dataToProcess.monitorId.toString()} - Skipping Monitor Server Response persist (cron re-evaluation).`,
362
+ `${dataToProcess.monitorId.toString()} - Monitor Incoming Request Updated`,
311
363
  );
312
364
  }
313
- }
314
365
 
315
- if (incomingMonitorRequest) {
316
- logger.debug(
317
- `${dataToProcess.monitorId.toString()} - Incoming request received at ${incomingMonitorRequest.incomingRequestReceivedAt}`,
318
- );
366
+ hasPersistedMonitorData = true;
367
+ };
319
368
 
320
- await MonitorService.updateOneById({
321
- id: monitor.id!,
322
- data: {
323
- incomingRequestMonitorHeartbeatCheckedAt:
324
- OneUptimeDate.getCurrentDate(),
325
- incomingMonitorRequest: JSON.parse(
326
- JSON.stringify(incomingMonitorRequest),
327
- ) as IncomingMonitorRequest,
328
- } as any,
329
- props: {
330
- isRoot: true,
331
- ignoreHooks: true,
332
- },
333
- });
369
+ logger.debug(
370
+ `${dataToProcess.monitorId.toString()} - Saving monitor metrics`,
371
+ );
334
372
 
335
- logger.debug(
336
- `${dataToProcess.monitorId.toString()} - Monitor Incoming Request Updated`,
337
- );
373
+ try {
374
+ await MonitorMetricUtil.saveMonitorMetrics({
375
+ monitorId: monitor.id!,
376
+ projectId: monitor.projectId!,
377
+ dataToProcess: dataToProcess,
378
+ probeName: probeName || undefined,
379
+ monitorName: monitorName || undefined,
380
+ });
381
+ } catch (err) {
382
+ logger.error("Unable to save metrics");
383
+ logger.error(err);
338
384
  }
339
385
 
340
- hasPersistedMonitorData = true;
341
- };
342
-
343
- logger.debug(
344
- `${dataToProcess.monitorId.toString()} - Saving monitor metrics`,
345
- );
346
-
347
- try {
348
- await MonitorMetricUtil.saveMonitorMetrics({
349
- monitorId: monitor.id!,
350
- projectId: monitor.projectId!,
351
- dataToProcess: dataToProcess,
352
- probeName: probeName || undefined,
353
- monitorName: monitorName || undefined,
354
- });
355
- } catch (err) {
356
- logger.error("Unable to save metrics");
357
- logger.error(err);
358
- }
359
-
360
- logger.debug(
361
- `${dataToProcess.monitorId.toString()} - Monitor metrics saved`,
362
- );
363
-
364
- const monitorSteps: MonitorSteps = monitor.monitorSteps!;
365
-
366
- if (
367
- !monitorSteps.data?.monitorStepsInstanceArray ||
368
- monitorSteps.data?.monitorStepsInstanceArray.length === 0
369
- ) {
370
386
  logger.debug(
371
- `${dataToProcess.monitorId.toString()} - No monitoring steps.`,
387
+ `${dataToProcess.monitorId.toString()} - Monitor metrics saved`,
372
388
  );
373
- await persistLatestMonitorPayload();
374
389
 
375
- MonitorLogUtil.saveMonitorLog({
376
- monitorId: monitor.id!,
377
- projectId: monitor.projectId!,
378
- dataToProcess: dataToProcess,
379
- });
380
- return response;
381
- }
382
-
383
- logger.debug(
384
- `${dataToProcess.monitorId.toString()} - Auto resolving criteria instances.`,
385
- );
390
+ const monitorSteps: MonitorSteps = monitor.monitorSteps!;
386
391
 
387
- const criteriaInstances: Array<MonitorCriteriaInstance> =
388
- monitorSteps.data.monitorStepsInstanceArray
389
- .map((step: MonitorStep) => {
390
- return step.data?.monitorCriteria;
391
- })
392
- .filter((criteria: MonitorCriteria | undefined) => {
393
- return Boolean(criteria);
394
- })
395
- .map((criteria: MonitorCriteria | undefined) => {
396
- return [...(criteria?.data?.monitorCriteriaInstanceArray || [])];
397
- })
398
- .flat();
392
+ if (
393
+ !monitorSteps.data?.monitorStepsInstanceArray ||
394
+ monitorSteps.data?.monitorStepsInstanceArray.length === 0
395
+ ) {
396
+ logger.debug(
397
+ `${dataToProcess.monitorId.toString()} - No monitoring steps.`,
398
+ );
399
+ await persistLatestMonitorPayload();
399
400
 
400
- const autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary<
401
- Array<string>
402
- > = {};
401
+ MonitorLogUtil.saveMonitorLog({
402
+ monitorId: monitor.id!,
403
+ projectId: monitor.projectId!,
404
+ dataToProcess: dataToProcess,
405
+ });
406
+ return response;
407
+ }
403
408
 
404
- const criteriaInstanceMap: Dictionary<MonitorCriteriaInstance> = {};
409
+ logger.debug(
410
+ `${dataToProcess.monitorId.toString()} - Auto resolving criteria instances.`,
411
+ );
405
412
 
406
- for (const criteriaInstance of criteriaInstances) {
407
- criteriaInstanceMap[criteriaInstance.data?.id || ""] = criteriaInstance;
413
+ const criteriaInstances: Array<MonitorCriteriaInstance> =
414
+ monitorSteps.data.monitorStepsInstanceArray
415
+ .map((step: MonitorStep) => {
416
+ return step.data?.monitorCriteria;
417
+ })
418
+ .filter((criteria: MonitorCriteria | undefined) => {
419
+ return Boolean(criteria);
420
+ })
421
+ .map((criteria: MonitorCriteria | undefined) => {
422
+ return [...(criteria?.data?.monitorCriteriaInstanceArray || [])];
423
+ })
424
+ .flat();
425
+
426
+ const autoResolveCriteriaInstanceIdIncidentIdsDictionary: Dictionary<
427
+ Array<string>
428
+ > = {};
429
+
430
+ const criteriaInstanceMap: Dictionary<MonitorCriteriaInstance> = {};
431
+
432
+ for (const criteriaInstance of criteriaInstances) {
433
+ criteriaInstanceMap[criteriaInstance.data?.id || ""] = criteriaInstance;
434
+
435
+ if (
436
+ criteriaInstance.data?.incidents &&
437
+ criteriaInstance.data?.incidents.length > 0
438
+ ) {
439
+ for (const incidentTemplate of criteriaInstance.data!.incidents) {
440
+ if (incidentTemplate.autoResolveIncident) {
441
+ if (
442
+ !autoResolveCriteriaInstanceIdIncidentIdsDictionary[
443
+ criteriaInstance.data.id.toString()
444
+ ]
445
+ ) {
446
+ autoResolveCriteriaInstanceIdIncidentIdsDictionary[
447
+ criteriaInstance.data.id.toString()
448
+ ] = [];
449
+ }
408
450
 
409
- if (
410
- criteriaInstance.data?.incidents &&
411
- criteriaInstance.data?.incidents.length > 0
412
- ) {
413
- for (const incidentTemplate of criteriaInstance.data!.incidents) {
414
- if (incidentTemplate.autoResolveIncident) {
415
- if (
416
- !autoResolveCriteriaInstanceIdIncidentIdsDictionary[
417
- criteriaInstance.data.id.toString()
418
- ]
419
- ) {
420
451
  autoResolveCriteriaInstanceIdIncidentIdsDictionary[
421
452
  criteriaInstance.data.id.toString()
422
- ] = [];
453
+ ]?.push(incidentTemplate.id);
423
454
  }
424
-
425
- autoResolveCriteriaInstanceIdIncidentIdsDictionary[
426
- criteriaInstance.data.id.toString()
427
- ]?.push(incidentTemplate.id);
428
455
  }
429
456
  }
430
457
  }
431
- }
432
-
433
- // alerts.
434
458
 
435
- const autoResolveCriteriaInstanceIdAlertIdsDictionary: Dictionary<
436
- Array<string>
437
- > = {};
459
+ // alerts.
460
+
461
+ const autoResolveCriteriaInstanceIdAlertIdsDictionary: Dictionary<
462
+ Array<string>
463
+ > = {};
464
+
465
+ const criteriaInstanceAlertMap: Dictionary<MonitorCriteriaInstance> = {};
466
+
467
+ for (const criteriaInstance of criteriaInstances) {
468
+ criteriaInstanceAlertMap[criteriaInstance.data?.id || ""] =
469
+ criteriaInstance;
470
+
471
+ if (
472
+ criteriaInstance.data?.alerts &&
473
+ criteriaInstance.data?.alerts.length > 0
474
+ ) {
475
+ for (const alertTemplate of criteriaInstance.data!.alerts) {
476
+ if (alertTemplate.autoResolveAlert) {
477
+ if (
478
+ !autoResolveCriteriaInstanceIdAlertIdsDictionary[
479
+ criteriaInstance.data.id.toString()
480
+ ]
481
+ ) {
482
+ autoResolveCriteriaInstanceIdAlertIdsDictionary[
483
+ criteriaInstance.data.id.toString()
484
+ ] = [];
485
+ }
438
486
 
439
- const criteriaInstanceAlertMap: Dictionary<MonitorCriteriaInstance> = {};
440
-
441
- for (const criteriaInstance of criteriaInstances) {
442
- criteriaInstanceAlertMap[criteriaInstance.data?.id || ""] =
443
- criteriaInstance;
444
-
445
- if (
446
- criteriaInstance.data?.alerts &&
447
- criteriaInstance.data?.alerts.length > 0
448
- ) {
449
- for (const alertTemplate of criteriaInstance.data!.alerts) {
450
- if (alertTemplate.autoResolveAlert) {
451
- if (
452
- !autoResolveCriteriaInstanceIdAlertIdsDictionary[
453
- criteriaInstance.data.id.toString()
454
- ]
455
- ) {
456
487
  autoResolveCriteriaInstanceIdAlertIdsDictionary[
457
488
  criteriaInstance.data.id.toString()
458
- ] = [];
489
+ ]?.push(alertTemplate.id);
459
490
  }
460
-
461
- autoResolveCriteriaInstanceIdAlertIdsDictionary[
462
- criteriaInstance.data.id.toString()
463
- ]?.push(alertTemplate.id);
464
491
  }
465
492
  }
466
493
  }
467
- }
468
-
469
- const monitorStep: MonitorStep | undefined =
470
- monitorSteps.data.monitorStepsInstanceArray[0];
471
494
 
472
- logger.debug(`Monitor Step: ${monitorStep ? monitorStep.id : "undefined"}`);
495
+ const monitorStep: MonitorStep | undefined =
496
+ monitorSteps.data.monitorStepsInstanceArray[0];
473
497
 
474
- if ((dataToProcess as ProbeMonitorResponse).monitorStepId) {
475
- monitorSteps.data.monitorStepsInstanceArray.find(
476
- (monitorStep: MonitorStep) => {
477
- return (
478
- monitorStep.id.toString() ===
479
- (dataToProcess as ProbeMonitorResponse).monitorStepId.toString()
480
- );
481
- },
482
- );
483
498
  logger.debug(
484
- `Found Monitor Step ID: ${(dataToProcess as ProbeMonitorResponse).monitorStepId}`,
485
- );
486
- }
487
-
488
- if (!monitorStep) {
489
- logger.debug("No steps found, ignoring everything.");
490
- await persistLatestMonitorPayload();
491
-
492
- MonitorLogUtil.saveMonitorLog({
493
- monitorId: monitor.id!,
494
- projectId: monitor.projectId!,
495
- dataToProcess: dataToProcess,
496
- });
497
- return response;
498
- }
499
-
500
- // now process the monitor step
501
- response.ingestedMonitorStepId = monitorStep.id;
502
- logger.debug(`Ingested Monitor Step ID: ${monitorStep.id}`);
503
-
504
- //find next monitor step after this one.
505
- const nextMonitorStepIndex: number =
506
- monitorSteps.data.monitorStepsInstanceArray.findIndex(
507
- (step: MonitorStep) => {
508
- return step.id.toString() === monitorStep.id.toString();
509
- },
499
+ `Monitor Step: ${monitorStep ? monitorStep.id : "undefined"}`,
510
500
  );
511
501
 
512
- response.nextMonitorStepId =
513
- monitorSteps.data.monitorStepsInstanceArray[nextMonitorStepIndex + 1]?.id;
514
-
515
- logger.debug(`Next Monitor Step ID: ${response.nextMonitorStepId}`);
516
-
517
- // now process probe response monitors
518
- logger.debug(
519
- `${dataToProcess.monitorId.toString()} - Processing monitor step...`,
520
- );
521
-
522
- response = await MonitorCriteriaEvaluator.processMonitorStep({
523
- dataToProcess: dataToProcess,
524
- monitorStep: monitorStep,
525
- monitor: monitor,
526
- probeApiIngestResponse: response,
527
- evaluationSummary: evaluationSummary,
528
- });
529
-
530
- // Check probe agreement for probe-based monitors
531
- if (
532
- monitor.monitorType &&
533
- MonitorTypeHelper.isProbableMonitor(monitor.monitorType)
534
- ) {
535
- const probeAgreementResult: ProbeAgreementResult =
536
- await MonitorResourceUtil.checkProbeAgreement({
537
- monitor: monitor,
538
- monitorStep: monitorStep,
539
- currentCriteriaMetId: response.criteriaMetId || null,
540
- currentRootCause: response.rootCause || null,
541
- });
542
-
543
- // Add probe agreement event to evaluation summary
544
- evaluationSummary.events.push({
545
- type: "probe-agreement",
546
- title: "Probe Agreement Check",
547
- message: probeAgreementResult.hasAgreement
548
- ? `Probe agreement reached: ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree (${probeAgreementResult.totalActiveProbes} active probes total).`
549
- : `Probe agreement not reached: ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree (${probeAgreementResult.totalActiveProbes} active probes total). Skipping status change.`,
550
- at: OneUptimeDate.getCurrentDate(),
551
- });
552
-
553
- if (!probeAgreementResult.hasAgreement) {
502
+ if ((dataToProcess as ProbeMonitorResponse).monitorStepId) {
503
+ monitorSteps.data.monitorStepsInstanceArray.find(
504
+ (monitorStep: MonitorStep) => {
505
+ return (
506
+ monitorStep.id.toString() ===
507
+ (dataToProcess as ProbeMonitorResponse).monitorStepId.toString()
508
+ );
509
+ },
510
+ );
554
511
  logger.debug(
555
- `${dataToProcess.monitorId.toString()} - Probe agreement not met. ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree. Skipping status change.`,
512
+ `Found Monitor Step ID: ${(dataToProcess as ProbeMonitorResponse).monitorStepId}`,
556
513
  );
514
+ }
557
515
 
558
- // Release lock and return early - no status change
559
- if (mutex) {
560
- try {
561
- await Semaphore.release(mutex);
562
- } catch (err) {
563
- logger.error(err);
564
- }
565
- }
566
-
516
+ if (!monitorStep) {
517
+ logger.debug("No steps found, ignoring everything.");
567
518
  await persistLatestMonitorPayload();
568
519
 
569
520
  MonitorLogUtil.saveMonitorLog({
@@ -571,273 +522,347 @@ export default class MonitorResourceUtil {
571
522
  projectId: monitor.projectId!,
572
523
  dataToProcess: dataToProcess,
573
524
  });
574
-
575
- response.evaluationSummary = evaluationSummary;
576
525
  return response;
577
526
  }
578
527
 
579
- // Use the agreed criteria result
580
- response.criteriaMetId = probeAgreementResult.agreedCriteriaId
581
- ? probeAgreementResult.agreedCriteriaId
582
- : undefined;
583
- response.rootCause = probeAgreementResult.agreedRootCause;
528
+ // now process the monitor step
529
+ response.ingestedMonitorStepId = monitorStep.id;
530
+ logger.debug(`Ingested Monitor Step ID: ${monitorStep.id}`);
584
531
 
585
- // Add probe names in agreement to the root cause
586
- if (
587
- response.rootCause &&
588
- probeAgreementResult.agreedProbeNames.length > 0
589
- ) {
590
- response.rootCause += `
591
- **Probes in Agreement**: ${probeAgreementResult.agreedProbeNames.join(", ")}
592
- `;
593
- }
594
- }
532
+ //find next monitor step after this one.
533
+ const nextMonitorStepIndex: number =
534
+ monitorSteps.data.monitorStepsInstanceArray.findIndex(
535
+ (step: MonitorStep) => {
536
+ return step.id.toString() === monitorStep.id.toString();
537
+ },
538
+ );
595
539
 
596
- if (response.criteriaMetId && response.rootCause) {
597
- logger.debug(
598
- `${dataToProcess.monitorId.toString()} - Criteria met: ${
599
- response.criteriaMetId
600
- }`,
601
- );
540
+ response.nextMonitorStepId =
541
+ monitorSteps.data.monitorStepsInstanceArray[
542
+ nextMonitorStepIndex + 1
543
+ ]?.id;
544
+
545
+ logger.debug(`Next Monitor Step ID: ${response.nextMonitorStepId}`);
546
+
547
+ // now process probe response monitors
602
548
  logger.debug(
603
- `${dataToProcess.monitorId.toString()} - Root cause: ${
604
- response.rootCause
605
- }`,
549
+ `${dataToProcess.monitorId.toString()} - Processing monitor step...`,
606
550
  );
607
551
 
608
- let telemetryQuery: TelemetryQuery | undefined = undefined;
552
+ response = await MonitorCriteriaEvaluator.processMonitorStep({
553
+ dataToProcess: dataToProcess,
554
+ monitorStep: monitorStep,
555
+ monitor: monitor,
556
+ probeApiIngestResponse: response,
557
+ evaluationSummary: evaluationSummary,
558
+ });
609
559
 
610
- if (dataToProcess && (dataToProcess as LogMonitorResponse).logQuery) {
611
- telemetryQuery = {
612
- telemetryQuery: (dataToProcess as LogMonitorResponse).logQuery,
613
- telemetryType: TelemetryType.Log,
614
- metricViewData: null,
615
- };
616
- logger.debug(
617
- `${dataToProcess.monitorId.toString()} - Log query found.`,
618
- );
560
+ // Check probe agreement for probe-based monitors
561
+ if (
562
+ monitor.monitorType &&
563
+ MonitorTypeHelper.isProbableMonitor(monitor.monitorType)
564
+ ) {
565
+ const probeAgreementResult: ProbeAgreementResult =
566
+ await MonitorResourceUtil.checkProbeAgreement({
567
+ monitor: monitor,
568
+ monitorStep: monitorStep,
569
+ currentCriteriaMetId: response.criteriaMetId || null,
570
+ currentRootCause: response.rootCause || null,
571
+ });
572
+
573
+ // Add probe agreement event to evaluation summary
574
+ evaluationSummary.events.push({
575
+ type: "probe-agreement",
576
+ title: "Probe Agreement Check",
577
+ message: probeAgreementResult.hasAgreement
578
+ ? `Probe agreement reached: ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree (${probeAgreementResult.totalActiveProbes} active probes total).`
579
+ : `Probe agreement not reached: ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree (${probeAgreementResult.totalActiveProbes} active probes total). Skipping status change.`,
580
+ at: OneUptimeDate.getCurrentDate(),
581
+ });
582
+
583
+ if (!probeAgreementResult.hasAgreement) {
584
+ logger.debug(
585
+ `${dataToProcess.monitorId.toString()} - Probe agreement not met. ${probeAgreementResult.agreementCount}/${probeAgreementResult.requiredCount} probes agree. Skipping status change.`,
586
+ );
587
+
588
+ // Release lock and return early - no status change
589
+ await releaseMutex();
590
+
591
+ await persistLatestMonitorPayload();
592
+
593
+ MonitorLogUtil.saveMonitorLog({
594
+ monitorId: monitor.id!,
595
+ projectId: monitor.projectId!,
596
+ dataToProcess: dataToProcess,
597
+ });
598
+
599
+ response.evaluationSummary = evaluationSummary;
600
+ return response;
601
+ }
602
+
603
+ // Use the agreed criteria result
604
+ response.criteriaMetId = probeAgreementResult.agreedCriteriaId
605
+ ? probeAgreementResult.agreedCriteriaId
606
+ : undefined;
607
+ response.rootCause = probeAgreementResult.agreedRootCause;
608
+
609
+ // Add probe names in agreement to the root cause
610
+ if (
611
+ response.rootCause &&
612
+ probeAgreementResult.agreedProbeNames.length > 0
613
+ ) {
614
+ response.rootCause += `
615
+ **Probes in Agreement**: ${probeAgreementResult.agreedProbeNames.join(", ")}
616
+ `;
617
+ }
619
618
  }
620
619
 
621
- if (dataToProcess && (dataToProcess as TraceMonitorResponse).spanQuery) {
622
- telemetryQuery = {
623
- telemetryQuery: (dataToProcess as TraceMonitorResponse).spanQuery,
624
- telemetryType: TelemetryType.Trace,
625
- metricViewData: null,
626
- };
620
+ if (response.criteriaMetId && response.rootCause) {
627
621
  logger.debug(
628
- `${dataToProcess.monitorId.toString()} - Span query found.`,
622
+ `${dataToProcess.monitorId.toString()} - Criteria met: ${
623
+ response.criteriaMetId
624
+ }`,
629
625
  );
630
- }
631
-
632
- if (
633
- dataToProcess &&
634
- (dataToProcess as MetricMonitorResponse).metricViewConfig &&
635
- (dataToProcess as MetricMonitorResponse).startAndEndDate
636
- ) {
637
- telemetryQuery = {
638
- telemetryQuery: null,
639
- telemetryType: TelemetryType.Metric,
640
- metricViewData: {
641
- startAndEndDate:
642
- (dataToProcess as MetricMonitorResponse).startAndEndDate || null,
643
- queryConfigs: (dataToProcess as MetricMonitorResponse)
644
- .metricViewConfig.queryConfigs,
645
- formulaConfigs: (dataToProcess as MetricMonitorResponse)
646
- .metricViewConfig.formulaConfigs,
647
- },
648
- };
649
626
  logger.debug(
650
- `${dataToProcess.monitorId.toString()} - Span query found.`,
627
+ `${dataToProcess.monitorId.toString()} - Root cause: ${
628
+ response.rootCause
629
+ }`,
651
630
  );
652
- }
653
631
 
654
- if (
655
- dataToProcess &&
656
- (dataToProcess as ExceptionMonitorResponse).exceptionQuery
657
- ) {
658
- const exceptionResponse: ExceptionMonitorResponse =
659
- dataToProcess as ExceptionMonitorResponse;
660
- telemetryQuery = {
661
- telemetryQuery: exceptionResponse.exceptionQuery,
662
- telemetryType: TelemetryType.Exception,
663
- metricViewData: null,
664
- };
632
+ let telemetryQuery: TelemetryQuery | undefined = undefined;
665
633
 
666
- logger.debug(
667
- `${dataToProcess.monitorId.toString()} - Exception query found.`,
668
- );
669
- }
634
+ if (dataToProcess && (dataToProcess as LogMonitorResponse).logQuery) {
635
+ telemetryQuery = {
636
+ telemetryQuery: (dataToProcess as LogMonitorResponse).logQuery,
637
+ telemetryType: TelemetryType.Log,
638
+ metricViewData: null,
639
+ };
640
+ logger.debug(
641
+ `${dataToProcess.monitorId.toString()} - Log query found.`,
642
+ );
643
+ }
670
644
 
671
- const matchedCriteriaInstance: MonitorCriteriaInstance =
672
- criteriaInstanceMap[response.criteriaMetId!]!;
645
+ if (
646
+ dataToProcess &&
647
+ (dataToProcess as TraceMonitorResponse).spanQuery
648
+ ) {
649
+ telemetryQuery = {
650
+ telemetryQuery: (dataToProcess as TraceMonitorResponse).spanQuery,
651
+ telemetryType: TelemetryType.Trace,
652
+ metricViewData: null,
653
+ };
654
+ logger.debug(
655
+ `${dataToProcess.monitorId.toString()} - Span query found.`,
656
+ );
657
+ }
673
658
 
674
- const monitorStatusTimelineChange: MonitorStatusTimeline | null =
675
- await MonitorStatusTimelineUtil.updateMonitorStatusTimeline({
676
- monitor: monitor,
677
- rootCause: response.rootCause,
678
- dataToProcess: dataToProcess,
679
- criteriaInstance: matchedCriteriaInstance,
680
- props: {
681
- telemetryQuery: telemetryQuery,
682
- },
683
- });
659
+ if (
660
+ dataToProcess &&
661
+ (dataToProcess as MetricMonitorResponse).metricViewConfig &&
662
+ (dataToProcess as MetricMonitorResponse).startAndEndDate
663
+ ) {
664
+ telemetryQuery = {
665
+ telemetryQuery: null,
666
+ telemetryType: TelemetryType.Metric,
667
+ metricViewData: {
668
+ startAndEndDate:
669
+ (dataToProcess as MetricMonitorResponse).startAndEndDate ||
670
+ null,
671
+ queryConfigs: (dataToProcess as MetricMonitorResponse)
672
+ .metricViewConfig.queryConfigs,
673
+ formulaConfigs: (dataToProcess as MetricMonitorResponse)
674
+ .metricViewConfig.formulaConfigs,
675
+ },
676
+ };
677
+ logger.debug(
678
+ `${dataToProcess.monitorId.toString()} - Span query found.`,
679
+ );
680
+ }
684
681
 
685
- if (monitorStatusTimelineChange) {
686
- const changedStatusName: string | null = await getMonitorStatusName(
687
- matchedCriteriaInstance.data?.monitorStatusId ||
688
- monitorStatusTimelineChange.monitorStatusId,
689
- );
682
+ if (
683
+ dataToProcess &&
684
+ (dataToProcess as ExceptionMonitorResponse).exceptionQuery
685
+ ) {
686
+ const exceptionResponse: ExceptionMonitorResponse =
687
+ dataToProcess as ExceptionMonitorResponse;
688
+ telemetryQuery = {
689
+ telemetryQuery: exceptionResponse.exceptionQuery,
690
+ telemetryType: TelemetryType.Exception,
691
+ metricViewData: null,
692
+ };
690
693
 
691
- evaluationSummary.events.push({
692
- type: "monitor-status-changed",
693
- title: "Monitor status updated",
694
- message: changedStatusName
695
- ? `Monitor status changed to "${changedStatusName}" because criteria "${matchedCriteriaInstance.data?.name || "Unnamed criteria"}" was met.`
696
- : `Monitor status changed because criteria "${matchedCriteriaInstance.data?.name || "Unnamed criteria"}" was met.`,
697
- relatedCriteriaId: matchedCriteriaInstance.data?.id,
698
- at: OneUptimeDate.getCurrentDate(),
699
- });
700
- }
694
+ logger.debug(
695
+ `${dataToProcess.monitorId.toString()} - Exception query found.`,
696
+ );
697
+ }
701
698
 
702
- await MonitorIncident.criteriaMetCreateIncidentsAndUpdateMonitorStatus({
703
- monitor: monitor,
704
- rootCause: response.rootCause,
705
- dataToProcess: dataToProcess,
706
- autoResolveCriteriaInstanceIdIncidentIdsDictionary,
707
- criteriaInstance: matchedCriteriaInstance,
708
- evaluationSummary: evaluationSummary,
709
- props: {
710
- telemetryQuery: telemetryQuery,
711
- },
712
- matchesPerSeries: response.perSeriesMatches,
713
- });
699
+ const matchedCriteriaInstance: MonitorCriteriaInstance =
700
+ criteriaInstanceMap[response.criteriaMetId!]!;
714
701
 
715
- await MonitorAlert.criteriaMetCreateAlertsAndUpdateMonitorStatus({
716
- monitor: monitor,
717
- rootCause: response.rootCause,
718
- dataToProcess: dataToProcess,
719
- autoResolveCriteriaInstanceIdAlertIdsDictionary,
720
- criteriaInstance: criteriaInstanceAlertMap[response.criteriaMetId!]!,
721
- evaluationSummary: evaluationSummary,
722
- props: {
723
- telemetryQuery: telemetryQuery,
724
- },
725
- matchesPerSeries: response.perSeriesMatches,
726
- });
727
- } else if (
728
- !response.criteriaMetId &&
729
- monitorSteps.data.defaultMonitorStatusId &&
730
- monitor.currentMonitorStatusId?.toString() !==
731
- monitorSteps.data.defaultMonitorStatusId.toString()
732
- ) {
733
- logger.debug(
734
- `${dataToProcess.monitorId.toString()} - No criteria met. Change to default status.`,
735
- );
702
+ const monitorStatusTimelineChange: MonitorStatusTimeline | null =
703
+ await MonitorStatusTimelineUtil.updateMonitorStatusTimeline({
704
+ monitor: monitor,
705
+ rootCause: response.rootCause,
706
+ dataToProcess: dataToProcess,
707
+ criteriaInstance: matchedCriteriaInstance,
708
+ props: {
709
+ telemetryQuery: telemetryQuery,
710
+ },
711
+ });
736
712
 
737
- await MonitorIncident.checkOpenIncidentsAndCloseIfResolved({
738
- monitorId: monitor.id!,
739
- autoResolveCriteriaInstanceIdIncidentIdsDictionary,
740
- rootCause: "No monitoring criteria met. Change to default status.",
741
- criteriaInstance: null, // no criteria met!
742
- dataToProcess: dataToProcess,
743
- evaluationSummary: evaluationSummary,
744
- });
713
+ if (monitorStatusTimelineChange) {
714
+ const changedStatusName: string | null = await getMonitorStatusName(
715
+ matchedCriteriaInstance.data?.monitorStatusId ||
716
+ monitorStatusTimelineChange.monitorStatusId,
717
+ );
745
718
 
746
- await MonitorAlert.checkOpenAlertsAndCloseIfResolved({
747
- monitorId: monitor.id!,
748
- autoResolveCriteriaInstanceIdAlertIdsDictionary,
749
- rootCause: "No monitoring criteria met. Change to default status.",
750
- criteriaInstance: null, // no criteria met!
751
- dataToProcess: dataToProcess,
752
- evaluationSummary: evaluationSummary,
753
- });
719
+ evaluationSummary.events.push({
720
+ type: "monitor-status-changed",
721
+ title: "Monitor status updated",
722
+ message: changedStatusName
723
+ ? `Monitor status changed to "${changedStatusName}" because criteria "${matchedCriteriaInstance.data?.name || "Unnamed criteria"}" was met.`
724
+ : `Monitor status changed because criteria "${matchedCriteriaInstance.data?.name || "Unnamed criteria"}" was met.`,
725
+ relatedCriteriaId: matchedCriteriaInstance.data?.id,
726
+ at: OneUptimeDate.getCurrentDate(),
727
+ });
728
+ }
754
729
 
755
- // get last monitor status timeline.
756
- const lastMonitorStatusTimeline: MonitorStatusTimeline | null =
757
- await MonitorStatusTimelineService.findOneBy({
758
- query: {
759
- monitorId: monitor.id!,
760
- projectId: monitor.projectId!,
761
- },
762
- select: {
763
- _id: true,
764
- monitorStatusId: true,
765
- },
766
- sort: {
767
- startsAt: SortOrder.Descending,
768
- },
730
+ await MonitorIncident.criteriaMetCreateIncidentsAndUpdateMonitorStatus({
731
+ monitor: monitor,
732
+ rootCause: response.rootCause,
733
+ dataToProcess: dataToProcess,
734
+ autoResolveCriteriaInstanceIdIncidentIdsDictionary,
735
+ criteriaInstance: matchedCriteriaInstance,
736
+ evaluationSummary: evaluationSummary,
769
737
  props: {
770
- isRoot: true,
738
+ telemetryQuery: telemetryQuery,
771
739
  },
740
+ matchesPerSeries: response.perSeriesMatches,
772
741
  });
773
742
 
774
- if (
775
- lastMonitorStatusTimeline &&
776
- lastMonitorStatusTimeline.monitorStatusId &&
777
- lastMonitorStatusTimeline.monitorStatusId.toString() ===
778
- monitorSteps.data.defaultMonitorStatusId.toString()
779
- ) {
780
- /*
781
- * status is same as last status. do not create new status timeline.
782
- * do nothing! status is same as last status.
783
- */
784
- } else {
785
- // if no criteria is met then update monitor to default state.
786
- const monitorStatusTimeline: MonitorStatusTimeline =
787
- new MonitorStatusTimeline();
788
- monitorStatusTimeline.monitorId = monitor.id!;
789
- monitorStatusTimeline.monitorStatusId =
790
- monitorSteps.data.defaultMonitorStatusId!;
791
- monitorStatusTimeline.projectId = monitor.projectId!;
792
- monitorStatusTimeline.isOwnerNotified = true; // no need to notify owner as this is default status.
793
- monitorStatusTimeline.statusChangeLog = JSON.parse(
794
- JSON.stringify(dataToProcess),
795
- );
796
- monitorStatusTimeline.rootCause =
797
- "No monitoring criteria met. Change to default status. ";
798
-
799
- await MonitorStatusTimelineService.create({
800
- data: monitorStatusTimeline,
743
+ await MonitorAlert.criteriaMetCreateAlertsAndUpdateMonitorStatus({
744
+ monitor: monitor,
745
+ rootCause: response.rootCause,
746
+ dataToProcess: dataToProcess,
747
+ autoResolveCriteriaInstanceIdAlertIdsDictionary,
748
+ criteriaInstance: criteriaInstanceAlertMap[response.criteriaMetId!]!,
749
+ evaluationSummary: evaluationSummary,
801
750
  props: {
802
- isRoot: true,
751
+ telemetryQuery: telemetryQuery,
803
752
  },
753
+ matchesPerSeries: response.perSeriesMatches,
804
754
  });
755
+ } else if (
756
+ !response.criteriaMetId &&
757
+ monitorSteps.data.defaultMonitorStatusId &&
758
+ monitor.currentMonitorStatusId?.toString() !==
759
+ monitorSteps.data.defaultMonitorStatusId.toString()
760
+ ) {
805
761
  logger.debug(
806
- `${dataToProcess.monitorId.toString()} - Monitor status updated to default.`,
762
+ `${dataToProcess.monitorId.toString()} - No criteria met. Change to default status.`,
807
763
  );
808
764
 
809
- const defaultStatusName: string | null = await getMonitorStatusName(
810
- monitorSteps.data.defaultMonitorStatusId,
811
- );
765
+ await MonitorIncident.checkOpenIncidentsAndCloseIfResolved({
766
+ monitorId: monitor.id!,
767
+ autoResolveCriteriaInstanceIdIncidentIdsDictionary,
768
+ rootCause: "No monitoring criteria met. Change to default status.",
769
+ criteriaInstance: null, // no criteria met!
770
+ dataToProcess: dataToProcess,
771
+ evaluationSummary: evaluationSummary,
772
+ });
812
773
 
813
- evaluationSummary.events.push({
814
- type: "monitor-status-changed",
815
- title: "Monitor status reverted",
816
- message: defaultStatusName
817
- ? `Monitor status reverted to "${defaultStatusName}" because no monitoring criteria were met.`
818
- : "Monitor status reverted to its default state because no monitoring criteria were met.",
819
- at: OneUptimeDate.getCurrentDate(),
774
+ await MonitorAlert.checkOpenAlertsAndCloseIfResolved({
775
+ monitorId: monitor.id!,
776
+ autoResolveCriteriaInstanceIdAlertIdsDictionary,
777
+ rootCause: "No monitoring criteria met. Change to default status.",
778
+ criteriaInstance: null, // no criteria met!
779
+ dataToProcess: dataToProcess,
780
+ evaluationSummary: evaluationSummary,
820
781
  });
821
- }
822
- }
823
782
 
824
- if (mutex) {
825
- try {
826
- await Semaphore.release(mutex);
827
- } catch (err) {
828
- logger.error(err);
783
+ // get last monitor status timeline.
784
+ const lastMonitorStatusTimeline: MonitorStatusTimeline | null =
785
+ await MonitorStatusTimelineService.findOneBy({
786
+ query: {
787
+ monitorId: monitor.id!,
788
+ projectId: monitor.projectId!,
789
+ },
790
+ select: {
791
+ _id: true,
792
+ monitorStatusId: true,
793
+ },
794
+ sort: {
795
+ startsAt: SortOrder.Descending,
796
+ },
797
+ props: {
798
+ isRoot: true,
799
+ },
800
+ });
801
+
802
+ if (
803
+ lastMonitorStatusTimeline &&
804
+ lastMonitorStatusTimeline.monitorStatusId &&
805
+ lastMonitorStatusTimeline.monitorStatusId.toString() ===
806
+ monitorSteps.data.defaultMonitorStatusId.toString()
807
+ ) {
808
+ /*
809
+ * status is same as last status. do not create new status timeline.
810
+ * do nothing! status is same as last status.
811
+ */
812
+ } else {
813
+ // if no criteria is met then update monitor to default state.
814
+ const monitorStatusTimeline: MonitorStatusTimeline =
815
+ new MonitorStatusTimeline();
816
+ monitorStatusTimeline.monitorId = monitor.id!;
817
+ monitorStatusTimeline.monitorStatusId =
818
+ monitorSteps.data.defaultMonitorStatusId!;
819
+ monitorStatusTimeline.projectId = monitor.projectId!;
820
+ monitorStatusTimeline.isOwnerNotified = true; // no need to notify owner as this is default status.
821
+ monitorStatusTimeline.statusChangeLog = JSON.parse(
822
+ JSON.stringify(dataToProcess),
823
+ );
824
+ monitorStatusTimeline.rootCause =
825
+ "No monitoring criteria met. Change to default status. ";
826
+
827
+ await MonitorStatusTimelineService.create({
828
+ data: monitorStatusTimeline,
829
+ props: {
830
+ isRoot: true,
831
+ },
832
+ });
833
+ logger.debug(
834
+ `${dataToProcess.monitorId.toString()} - Monitor status updated to default.`,
835
+ );
836
+
837
+ const defaultStatusName: string | null = await getMonitorStatusName(
838
+ monitorSteps.data.defaultMonitorStatusId,
839
+ );
840
+
841
+ evaluationSummary.events.push({
842
+ type: "monitor-status-changed",
843
+ title: "Monitor status reverted",
844
+ message: defaultStatusName
845
+ ? `Monitor status reverted to "${defaultStatusName}" because no monitoring criteria were met.`
846
+ : "Monitor status reverted to its default state because no monitoring criteria were met.",
847
+ at: OneUptimeDate.getCurrentDate(),
848
+ });
849
+ }
829
850
  }
830
- }
831
851
 
832
- await persistLatestMonitorPayload();
852
+ await releaseMutex();
833
853
 
834
- MonitorLogUtil.saveMonitorLog({
835
- monitorId: monitor.id!,
836
- projectId: monitor.projectId!,
837
- dataToProcess: dataToProcess,
838
- });
854
+ await persistLatestMonitorPayload();
839
855
 
840
- return response;
856
+ MonitorLogUtil.saveMonitorLog({
857
+ monitorId: monitor.id!,
858
+ projectId: monitor.projectId!,
859
+ dataToProcess: dataToProcess,
860
+ });
861
+
862
+ return response;
863
+ } finally {
864
+ await releaseMutex();
865
+ }
841
866
  }
842
867
 
843
868
  @CaptureSpan()