mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Add Exception Monitor functionality with related types and components
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import ExceptionMonitorResponse from "../../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
||||
import DataToProcess from "../DataToProcess";
|
||||
import CompareCriteria from "./CompareCriteria";
|
||||
import {
|
||||
CheckOn,
|
||||
CriteriaFilter,
|
||||
} from "../../../../Types/Monitor/CriteriaFilter";
|
||||
|
||||
export default class ExceptionMonitorCriteria {
|
||||
@CaptureSpan()
|
||||
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
||||
dataToProcess: DataToProcess;
|
||||
criteriaFilter: CriteriaFilter;
|
||||
}): Promise<string | null> {
|
||||
let threshold: number | string | undefined | null =
|
||||
input.criteriaFilter.value;
|
||||
|
||||
if (input.criteriaFilter.checkOn === CheckOn.ExceptionCount) {
|
||||
threshold = CompareCriteria.convertToNumber(threshold);
|
||||
|
||||
const currentExceptionCount: number =
|
||||
(input.dataToProcess as ExceptionMonitorResponse).exceptionCount || 0;
|
||||
|
||||
return CompareCriteria.compareCriteriaNumbers({
|
||||
value: currentExceptionCount,
|
||||
threshold: threshold as number,
|
||||
criteriaFilter: input.criteriaFilter,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
|
||||
import LogMonitorResponse from "../../../Types/Monitor/LogMonitor/LogMonitorResponse";
|
||||
import TraceMonitorResponse from "../../../Types/Monitor/TraceMonitor/TraceMonitorResponse";
|
||||
import MetricMonitorResponse from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
||||
import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
|
||||
type DataToProcess =
|
||||
| ProbeMonitorResponse
|
||||
@@ -11,6 +12,7 @@ type DataToProcess =
|
||||
| ServerMonitorResponse
|
||||
| LogMonitorResponse
|
||||
| TraceMonitorResponse
|
||||
| MetricMonitorResponse;
|
||||
| MetricMonitorResponse
|
||||
| ExceptionMonitorResponse;
|
||||
|
||||
export default DataToProcess;
|
||||
|
||||
@@ -14,6 +14,7 @@ import AggregatedResult from "../../../Types/BaseDatabase/AggregatedResult";
|
||||
import AggregateModel from "../../../Types/BaseDatabase/AggregatedModel";
|
||||
import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
|
||||
import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
|
||||
import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
|
||||
export default class MonitorCriteriaDataExtractor {
|
||||
public static getProbeMonitorResponse(
|
||||
@@ -79,6 +80,18 @@ export default class MonitorCriteriaDataExtractor {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static getExceptionMonitorResponse(
|
||||
dataToProcess: DataToProcess,
|
||||
): ExceptionMonitorResponse | null {
|
||||
if (
|
||||
(dataToProcess as ExceptionMonitorResponse).exceptionCount !== undefined
|
||||
) {
|
||||
return dataToProcess as ExceptionMonitorResponse;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static getCustomCodeMonitorResponse(
|
||||
dataToProcess: DataToProcess,
|
||||
): CustomCodeMonitorResponse | null {
|
||||
|
||||
@@ -9,6 +9,7 @@ import SyntheticMonitoringCriteria from "./Criteria/SyntheticMonitor";
|
||||
import LogMonitorCriteria from "./Criteria/LogMonitorCriteria";
|
||||
import MetricMonitorCriteria from "./Criteria/MetricMonitorCriteria";
|
||||
import TraceMonitorCriteria from "./Criteria/TraceMonitorCriteria";
|
||||
import ExceptionMonitorCriteria from "./Criteria/ExceptionMonitorCriteria";
|
||||
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
|
||||
import DataToProcess from "./DataToProcess";
|
||||
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
||||
@@ -434,6 +435,18 @@ export default class MonitorCriteriaEvaluator {
|
||||
}
|
||||
}
|
||||
|
||||
if (input.monitor.monitorType === MonitorType.Exceptions) {
|
||||
const exceptionMonitorResult: string | null =
|
||||
await ExceptionMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
|
||||
dataToProcess: input.dataToProcess,
|
||||
criteriaFilter: input.criteriaFilter,
|
||||
});
|
||||
|
||||
if (exceptionMonitorResult) {
|
||||
return exceptionMonitorResult;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import SyntheticMonitorResponse from "../../../Types/Monitor/SyntheticMonitors/S
|
||||
import CustomCodeMonitorResponse from "../../../Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse";
|
||||
import LogMonitorResponse from "../../../Types/Monitor/LogMonitor/LogMonitorResponse";
|
||||
import TraceMonitorResponse from "../../../Types/Monitor/TraceMonitor/TraceMonitorResponse";
|
||||
import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
|
||||
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
||||
import MonitorCriteriaExpectationBuilder from "./MonitorCriteriaExpectationBuilder";
|
||||
@@ -161,6 +162,10 @@ export default class MonitorCriteriaObservationBuilder {
|
||||
return MonitorCriteriaObservationBuilder.describeMetricValueObservation(
|
||||
input,
|
||||
);
|
||||
case CheckOn.ExceptionCount:
|
||||
return MonitorCriteriaObservationBuilder.describeExceptionCountObservation(
|
||||
input,
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -1083,6 +1088,21 @@ export default class MonitorCriteriaObservationBuilder {
|
||||
return `Span count was ${traceResponse.spanCount}.`;
|
||||
}
|
||||
|
||||
private static describeExceptionCountObservation(input: {
|
||||
dataToProcess: DataToProcess;
|
||||
}): string | null {
|
||||
const exceptionResponse: ExceptionMonitorResponse | null =
|
||||
MonitorCriteriaDataExtractor.getExceptionMonitorResponse(
|
||||
input.dataToProcess,
|
||||
);
|
||||
|
||||
if (!exceptionResponse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `Exception count was ${exceptionResponse.exceptionCount}.`;
|
||||
}
|
||||
|
||||
private static describeMetricValueObservation(input: {
|
||||
criteriaFilter: CriteriaFilter;
|
||||
dataToProcess: DataToProcess;
|
||||
|
||||
@@ -31,6 +31,7 @@ import LogMonitorResponse from "../../../Types/Monitor/LogMonitor/LogMonitorResp
|
||||
import MetricMonitorResponse from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
||||
import TelemetryType from "../../../Types/Telemetry/TelemetryType";
|
||||
import TraceMonitorResponse from "../../../Types/Monitor/TraceMonitor/TraceMonitorResponse";
|
||||
import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
|
||||
import MonitorIncident from "./MonitorIncident";
|
||||
import MonitorAlert from "./MonitorAlert";
|
||||
@@ -557,6 +558,22 @@ export default class MonitorResourceUtil {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
dataToProcess &&
|
||||
(dataToProcess as ExceptionMonitorResponse).exceptionQuery
|
||||
) {
|
||||
telemetryQuery = {
|
||||
telemetryQuery: (dataToProcess as ExceptionMonitorResponse)
|
||||
.exceptionQuery,
|
||||
telemetryType: TelemetryType.Exception,
|
||||
metricViewData: null,
|
||||
};
|
||||
|
||||
logger.debug(
|
||||
`${dataToProcess.monitorId.toString()} - Exception query found.`,
|
||||
);
|
||||
}
|
||||
|
||||
const matchedCriteriaInstance: MonitorCriteriaInstance =
|
||||
criteriaInstanceMap[response.criteriaMetId!]!;
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ export enum CheckOn {
|
||||
// Trace monitors.
|
||||
SpanCount = "Span Count",
|
||||
|
||||
// Exception monitors.
|
||||
ExceptionCount = "Exception Count",
|
||||
|
||||
// Metric Monitors.
|
||||
MetricValue = "Metric Value",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import Query from "../../BaseDatabase/Query";
|
||||
import ObjectID from "../../ObjectID";
|
||||
import TelemetryException from "../../../Models/DatabaseModels/TelemetryException";
|
||||
import MonitorEvaluationSummary from "../MonitorEvaluationSummary";
|
||||
|
||||
export default interface ExceptionMonitorResponse {
|
||||
projectId: ObjectID;
|
||||
exceptionCount: number;
|
||||
exceptionQuery: Query<TelemetryException>;
|
||||
monitorId: ObjectID;
|
||||
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import MonitorStepTraceMonitor, {
|
||||
import MonitorStepMetricMonitor, {
|
||||
MonitorStepMetricMonitorUtil,
|
||||
} from "./MonitorStepMetricMonitor";
|
||||
import MonitorStepExceptionMonitor, {
|
||||
MonitorStepExceptionMonitorUtil,
|
||||
} from "./MonitorStepExceptionMonitor";
|
||||
import Zod, { ZodSchema } from "../../Utils/Schema/Zod";
|
||||
|
||||
export interface MonitorStepType {
|
||||
@@ -57,6 +60,9 @@ export interface MonitorStepType {
|
||||
|
||||
// Metric Monitor
|
||||
metricMonitor: MonitorStepMetricMonitor | undefined;
|
||||
|
||||
// Exception monitor
|
||||
exceptionMonitor?: MonitorStepExceptionMonitor | undefined;
|
||||
}
|
||||
|
||||
export default class MonitorStep extends DatabaseProperty {
|
||||
@@ -80,6 +86,7 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
logMonitor: undefined,
|
||||
traceMonitor: undefined,
|
||||
metricMonitor: undefined,
|
||||
exceptionMonitor: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,6 +115,7 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
logMonitor: undefined,
|
||||
traceMonitor: undefined,
|
||||
metricMonitor: undefined,
|
||||
exceptionMonitor: undefined,
|
||||
};
|
||||
|
||||
return monitorStep;
|
||||
@@ -186,6 +194,13 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setExceptionMonitor(
|
||||
exceptionMonitor: MonitorStepExceptionMonitor,
|
||||
): MonitorStep {
|
||||
this.data!.exceptionMonitor = exceptionMonitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setCustomCode(customCode: string): MonitorStep {
|
||||
this.data!.customCode = customCode;
|
||||
return this;
|
||||
@@ -212,6 +227,7 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
screenSizeTypes: undefined,
|
||||
browserTypes: undefined,
|
||||
lgoMonitor: undefined,
|
||||
exceptionMonitor: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -310,6 +326,12 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
MonitorStepTraceMonitorUtil.getDefault(),
|
||||
)
|
||||
: undefined,
|
||||
exceptionMonitor: this.data.exceptionMonitor
|
||||
? MonitorStepExceptionMonitorUtil.toJSON(
|
||||
this.data.exceptionMonitor ||
|
||||
MonitorStepExceptionMonitorUtil.getDefault(),
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -408,6 +430,9 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
traceMonitor: json["traceMonitor"]
|
||||
? (json["traceMonitor"] as JSONObject)
|
||||
: undefined,
|
||||
exceptionMonitor: json["exceptionMonitor"]
|
||||
? (json["exceptionMonitor"] as JSONObject)
|
||||
: undefined,
|
||||
}) as any;
|
||||
|
||||
if (monitorStep.data && !monitorStep.data?.logMonitor) {
|
||||
@@ -423,6 +448,11 @@ export default class MonitorStep extends DatabaseProperty {
|
||||
MonitorStepMetricMonitorUtil.getDefault();
|
||||
}
|
||||
|
||||
if (monitorStep.data && !monitorStep.data?.exceptionMonitor) {
|
||||
monitorStep.data.exceptionMonitor =
|
||||
MonitorStepExceptionMonitorUtil.getDefault();
|
||||
}
|
||||
|
||||
return monitorStep;
|
||||
}
|
||||
|
||||
|
||||
102
Common/Types/Monitor/MonitorStepExceptionMonitor.ts
Normal file
102
Common/Types/Monitor/MonitorStepExceptionMonitor.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import TelemetryException from "../../Models/DatabaseModels/TelemetryException";
|
||||
import InBetween from "../BaseDatabase/InBetween";
|
||||
import Includes from "../BaseDatabase/Includes";
|
||||
import Query from "../BaseDatabase/Query";
|
||||
import Search from "../BaseDatabase/Search";
|
||||
import OneUptimeDate from "../Date";
|
||||
import { JSONObject } from "../JSON";
|
||||
import ObjectID from "../ObjectID";
|
||||
|
||||
export default interface MonitorStepExceptionMonitor {
|
||||
telemetryServiceIds: Array<ObjectID>;
|
||||
exceptionTypes: Array<string>;
|
||||
message: string;
|
||||
includeResolved: boolean;
|
||||
includeArchived: boolean;
|
||||
lastXSecondsOfExceptions: number;
|
||||
}
|
||||
|
||||
export class MonitorStepExceptionMonitorUtil {
|
||||
public static toQuery(
|
||||
monitorStepExceptionMonitor: MonitorStepExceptionMonitor,
|
||||
): Query<TelemetryException> {
|
||||
const query: Query<TelemetryException> = {};
|
||||
|
||||
if (
|
||||
monitorStepExceptionMonitor.telemetryServiceIds &&
|
||||
monitorStepExceptionMonitor.telemetryServiceIds.length > 0
|
||||
) {
|
||||
query.telemetryServiceId = new Includes(
|
||||
monitorStepExceptionMonitor.telemetryServiceIds,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
monitorStepExceptionMonitor.exceptionTypes &&
|
||||
monitorStepExceptionMonitor.exceptionTypes.length > 0
|
||||
) {
|
||||
query.exceptionType = new Includes(
|
||||
monitorStepExceptionMonitor.exceptionTypes,
|
||||
);
|
||||
}
|
||||
|
||||
if (monitorStepExceptionMonitor.message) {
|
||||
query.message = new Search(monitorStepExceptionMonitor.message);
|
||||
}
|
||||
|
||||
if (!monitorStepExceptionMonitor.includeResolved) {
|
||||
query.isResolved = false;
|
||||
}
|
||||
|
||||
if (!monitorStepExceptionMonitor.includeArchived) {
|
||||
query.isArchived = false;
|
||||
}
|
||||
|
||||
if (monitorStepExceptionMonitor.lastXSecondsOfExceptions) {
|
||||
const endDate: Date = OneUptimeDate.getCurrentDate();
|
||||
const startDate: Date = OneUptimeDate.addRemoveSeconds(
|
||||
endDate,
|
||||
monitorStepExceptionMonitor.lastXSecondsOfExceptions * -1,
|
||||
);
|
||||
query.lastSeenAt = new InBetween(startDate, endDate);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static getDefault(): MonitorStepExceptionMonitor {
|
||||
return {
|
||||
telemetryServiceIds: [],
|
||||
exceptionTypes: [],
|
||||
message: "",
|
||||
includeResolved: false,
|
||||
includeArchived: false,
|
||||
lastXSecondsOfExceptions: 60,
|
||||
};
|
||||
}
|
||||
|
||||
public static fromJSON(json: JSONObject): MonitorStepExceptionMonitor {
|
||||
return {
|
||||
telemetryServiceIds: ObjectID.fromJSONArray(
|
||||
(json["telemetryServiceIds"] as Array<JSONObject>) || [],
|
||||
),
|
||||
exceptionTypes: (json["exceptionTypes"] as Array<string>) || [],
|
||||
message: (json["message"] as string) || "",
|
||||
includeResolved: Boolean(json["includeResolved"]) || false,
|
||||
includeArchived: Boolean(json["includeArchived"]) || false,
|
||||
lastXSecondsOfExceptions:
|
||||
(json["lastXSecondsOfExceptions"] as number | undefined) || 60,
|
||||
};
|
||||
}
|
||||
|
||||
public static toJSON(monitor: MonitorStepExceptionMonitor): JSONObject {
|
||||
return {
|
||||
telemetryServiceIds: ObjectID.toJSONArray(monitor.telemetryServiceIds),
|
||||
exceptionTypes: monitor.exceptionTypes,
|
||||
message: monitor.message,
|
||||
includeResolved: monitor.includeResolved,
|
||||
includeArchived: monitor.includeArchived,
|
||||
lastXSecondsOfExceptions: monitor.lastXSecondsOfExceptions,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ enum MonitorType {
|
||||
Logs = "Logs",
|
||||
Metrics = "Metrics",
|
||||
Traces = "Traces",
|
||||
Exceptions = "Exceptions",
|
||||
}
|
||||
|
||||
export default MonitorType;
|
||||
@@ -35,7 +36,8 @@ export class MonitorTypeHelper {
|
||||
return (
|
||||
monitorType === MonitorType.Logs ||
|
||||
monitorType === MonitorType.Metrics ||
|
||||
monitorType === MonitorType.Traces
|
||||
monitorType === MonitorType.Traces ||
|
||||
monitorType === MonitorType.Exceptions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +125,12 @@ export class MonitorTypeHelper {
|
||||
title: "Logs",
|
||||
description: "This monitor type lets you monitor logs from any source.",
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Exceptions,
|
||||
title: "Exceptions",
|
||||
description:
|
||||
"This monitor type lets you monitor exceptions and error groups from any source.",
|
||||
},
|
||||
{
|
||||
monitorType: MonitorType.Traces,
|
||||
title: "Traces",
|
||||
@@ -198,6 +206,7 @@ export class MonitorTypeHelper {
|
||||
MonitorType.Logs,
|
||||
MonitorType.Metrics,
|
||||
MonitorType.Traces,
|
||||
MonitorType.Exceptions,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ import Span from "../../Models/AnalyticsModels/Span";
|
||||
import Query from "../BaseDatabase/Query";
|
||||
import MetricViewData from "../Metrics/MetricViewData";
|
||||
import TelemetryType from "./TelemetryType";
|
||||
import TelemetryException from "../../Models/DatabaseModels/TelemetryException";
|
||||
|
||||
export interface TelemetryQuery {
|
||||
telemetryType: TelemetryType;
|
||||
telemetryQuery: Query<Log> | Query<Span> | null;
|
||||
telemetryQuery: Query<Log> | Query<Span> | Query<TelemetryException> | null;
|
||||
metricViewData: MetricViewData | null;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ enum TelemetryType {
|
||||
Metric = "Metric",
|
||||
Trace = "Trace",
|
||||
Log = "Log",
|
||||
Exception = "Exception",
|
||||
}
|
||||
|
||||
export default TelemetryType;
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
import MonitorStepExceptionMonitor, {
|
||||
MonitorStepExceptionMonitorUtil,
|
||||
} from "Common/Types/Monitor/MonitorStepExceptionMonitor";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import BasicForm from "Common/UI/Components/Forms/BasicForm";
|
||||
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
|
||||
import FieldLabelElement from "Common/UI/Components/Forms/Fields/FieldLabel";
|
||||
import HorizontalRule from "Common/UI/Components/HorizontalRule/HorizontalRule";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
import TelemetryExceptionTable from "../../../Exceptions/ExceptionsTable";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorStepExceptionMonitor: MonitorStepExceptionMonitor;
|
||||
onMonitorStepExceptionMonitorChanged: (
|
||||
monitorStepExceptionMonitor: MonitorStepExceptionMonitor,
|
||||
) => void;
|
||||
telemetryServices: Array<TelemetryService>;
|
||||
}
|
||||
|
||||
type ExceptionMonitorFormValues = {
|
||||
message: string;
|
||||
exceptionTypesInput: string;
|
||||
telemetryServiceIds: Array<string>;
|
||||
includeResolved: boolean;
|
||||
includeArchived: boolean;
|
||||
lastXSecondsOfExceptions: number;
|
||||
};
|
||||
|
||||
const DURATION_OPTIONS: Array<{ label: string; value: number }> = [
|
||||
{ label: "Last 5 seconds", value: 5 },
|
||||
{ label: "Last 10 seconds", value: 10 },
|
||||
{ label: "Last 30 seconds", value: 30 },
|
||||
{ label: "Last 1 minute", value: 60 },
|
||||
{ label: "Last 5 minutes", value: 300 },
|
||||
{ label: "Last 15 minutes", value: 900 },
|
||||
{ label: "Last 30 minutes", value: 1800 },
|
||||
{ label: "Last 1 hour", value: 3600 },
|
||||
{ label: "Last 6 hours", value: 21600 },
|
||||
{ label: "Last 12 hours", value: 43200 },
|
||||
{ label: "Last 24 hours", value: 86400 },
|
||||
];
|
||||
|
||||
type ParseExceptionTypesFunction = (input: string) => Array<string>;
|
||||
|
||||
const parseExceptionTypes: ParseExceptionTypesFunction = (input: string) => {
|
||||
return input
|
||||
.split(",")
|
||||
.map((item: string): string => {
|
||||
return item.trim();
|
||||
})
|
||||
.filter((item: string): boolean => {
|
||||
return item.length > 0;
|
||||
});
|
||||
};
|
||||
|
||||
type ToFormValuesFunction = (
|
||||
monitor: MonitorStepExceptionMonitor,
|
||||
) => ExceptionMonitorFormValues;
|
||||
|
||||
const toFormValues: ToFormValuesFunction = (
|
||||
monitor: MonitorStepExceptionMonitor,
|
||||
) => {
|
||||
return {
|
||||
message: monitor.message || "",
|
||||
exceptionTypesInput: monitor.exceptionTypes.join(", "),
|
||||
telemetryServiceIds: monitor.telemetryServiceIds.map(
|
||||
(id: ObjectID): string => {
|
||||
return id.toString();
|
||||
},
|
||||
),
|
||||
includeResolved: monitor.includeResolved || false,
|
||||
includeArchived: monitor.includeArchived || false,
|
||||
lastXSecondsOfExceptions:
|
||||
monitor.lastXSecondsOfExceptions ||
|
||||
MonitorStepExceptionMonitorUtil.getDefault().lastXSecondsOfExceptions,
|
||||
};
|
||||
};
|
||||
|
||||
type ToMonitorConfigFunction = (
|
||||
values: ExceptionMonitorFormValues,
|
||||
) => MonitorStepExceptionMonitor;
|
||||
|
||||
const toMonitorConfig: ToMonitorConfigFunction = (
|
||||
values: ExceptionMonitorFormValues,
|
||||
) => {
|
||||
return {
|
||||
telemetryServiceIds: values.telemetryServiceIds
|
||||
.filter((id: string): boolean => {
|
||||
return Boolean(id);
|
||||
})
|
||||
.map((id: string): ObjectID => {
|
||||
return new ObjectID(id);
|
||||
}),
|
||||
exceptionTypes: parseExceptionTypes(values.exceptionTypesInput),
|
||||
message: values.message || "",
|
||||
includeResolved: values.includeResolved || false,
|
||||
includeArchived: values.includeArchived || false,
|
||||
lastXSecondsOfExceptions:
|
||||
values.lastXSecondsOfExceptions ||
|
||||
MonitorStepExceptionMonitorUtil.getDefault().lastXSecondsOfExceptions,
|
||||
};
|
||||
};
|
||||
|
||||
type HasAdvancedConfigurationFunction = (
|
||||
monitor: MonitorStepExceptionMonitor,
|
||||
) => boolean;
|
||||
|
||||
const hasAdvancedConfiguration: HasAdvancedConfigurationFunction = (
|
||||
monitor: MonitorStepExceptionMonitor,
|
||||
) => {
|
||||
return (
|
||||
monitor.includeResolved ||
|
||||
monitor.includeArchived ||
|
||||
(monitor.telemetryServiceIds && monitor.telemetryServiceIds.length > 0)
|
||||
);
|
||||
};
|
||||
|
||||
const ExceptionMonitorStepForm: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [formValues, setFormValues] = useState<ExceptionMonitorFormValues>(
|
||||
toFormValues(props.monitorStepExceptionMonitor),
|
||||
);
|
||||
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState<boolean>(
|
||||
hasAdvancedConfiguration(props.monitorStepExceptionMonitor),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFormValues(toFormValues(props.monitorStepExceptionMonitor));
|
||||
setShowAdvancedOptions(
|
||||
hasAdvancedConfiguration(props.monitorStepExceptionMonitor),
|
||||
);
|
||||
}, [props.monitorStepExceptionMonitor]);
|
||||
|
||||
type HandleFormChangeFunction = (values: ExceptionMonitorFormValues) => void;
|
||||
|
||||
const handleFormChange: HandleFormChangeFunction = (
|
||||
values: ExceptionMonitorFormValues,
|
||||
) => {
|
||||
setFormValues(values);
|
||||
props.onMonitorStepExceptionMonitorChanged(toMonitorConfig(values));
|
||||
};
|
||||
|
||||
const handleAdvancedToggle: () => void = (): void => {
|
||||
setShowAdvancedOptions((current: boolean): boolean => {
|
||||
return !current;
|
||||
});
|
||||
};
|
||||
|
||||
const previewQuery: Query<TelemetryException> = useMemo(() => {
|
||||
const monitorConfig: MonitorStepExceptionMonitor =
|
||||
toMonitorConfig(formValues);
|
||||
|
||||
return JSONFunctions.anyObjectToJSONObject(
|
||||
MonitorStepExceptionMonitorUtil.toQuery(monitorConfig),
|
||||
) as Query<TelemetryException>;
|
||||
}, [formValues]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BasicForm
|
||||
id="exception-monitor-form"
|
||||
hideSubmitButton={true}
|
||||
initialValues={formValues}
|
||||
onChange={handleFormChange}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
message: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
title: "Filter Exception Message",
|
||||
description:
|
||||
"Filter exceptions that include this text in the message.",
|
||||
hideOptionalLabel: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
exceptionTypesInput: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
title: "Exception Types",
|
||||
description:
|
||||
"Provide a comma-separated list of exception types to monitor.",
|
||||
placeholder: "TypeError, NullReferenceException",
|
||||
hideOptionalLabel: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastXSecondsOfExceptions: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions: DURATION_OPTIONS,
|
||||
title: "Monitor exceptions for (time)",
|
||||
description:
|
||||
"We will evaluate exceptions generated within this time window.",
|
||||
defaultValue:
|
||||
MonitorStepExceptionMonitorUtil.getDefault()
|
||||
.lastXSecondsOfExceptions,
|
||||
hideOptionalLabel: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
telemetryServiceIds: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.MultiSelectDropdown,
|
||||
dropdownOptions: props.telemetryServices.map(
|
||||
(service: TelemetryService): { label: string; value: string } => {
|
||||
return {
|
||||
label: service.name || "Untitled Service",
|
||||
value: service.id?.toString() || "",
|
||||
};
|
||||
},
|
||||
),
|
||||
title: "Filter by Telemetry Service",
|
||||
description: "Select telemetry services to scope this monitor.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: (): boolean => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
includeResolved: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Checkbox,
|
||||
title: "Include Resolved Exceptions",
|
||||
description: "When enabled, resolved exceptions will be counted.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: (): boolean => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
includeArchived: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Checkbox,
|
||||
title: "Include Archived Exceptions",
|
||||
description:
|
||||
"When enabled, archived exceptions will be included in results.",
|
||||
hideOptionalLabel: true,
|
||||
showIf: (): boolean => {
|
||||
return showAdvancedOptions;
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="-ml-3">
|
||||
<Button
|
||||
buttonStyle={ButtonStyleType.SECONDARY_LINK}
|
||||
title={
|
||||
showAdvancedOptions
|
||||
? "Hide Advanced Options"
|
||||
: "Show Advanced Options"
|
||||
}
|
||||
onClick={handleAdvancedToggle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<HorizontalRule />
|
||||
<FieldLabelElement
|
||||
title="Exceptions Preview"
|
||||
description={
|
||||
"Here is the preview of the exceptions that will be monitored based on the filters you have set above."
|
||||
}
|
||||
hideOptionalLabel={true}
|
||||
isHeading={true}
|
||||
/>
|
||||
<div className="mt-5 mb-5">
|
||||
<TelemetryExceptionTable
|
||||
title="Exceptions Preview"
|
||||
description="Exceptions matching the current monitor filters."
|
||||
query={previewQuery}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExceptionMonitorStepForm;
|
||||
@@ -66,6 +66,10 @@ import MonitorStepMetricMonitor, {
|
||||
MonitorStepMetricMonitorUtil,
|
||||
} from "Common/Types/Monitor/MonitorStepMetricMonitor";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import ExceptionMonitorStepForm from "./ExceptionMonitor/ExceptionMonitorStepForm";
|
||||
import MonitorStepExceptionMonitor, {
|
||||
MonitorStepExceptionMonitorUtil,
|
||||
} from "Common/Types/Monitor/MonitorStepExceptionMonitor";
|
||||
|
||||
export interface ComponentProps {
|
||||
monitorStatusDropdownOptions: Array<DropdownOption>;
|
||||
@@ -648,6 +652,24 @@ return {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.monitorType === MonitorType.Exceptions && (
|
||||
<div className="mt-5">
|
||||
<ExceptionMonitorStepForm
|
||||
monitorStepExceptionMonitor={
|
||||
monitorStep.data?.exceptionMonitor ||
|
||||
MonitorStepExceptionMonitorUtil.getDefault()
|
||||
}
|
||||
telemetryServices={telemetryServices}
|
||||
onMonitorStepExceptionMonitorChanged={(
|
||||
value: MonitorStepExceptionMonitor,
|
||||
) => {
|
||||
monitorStep.setExceptionMonitor(value);
|
||||
props.onChange?.(MonitorStep.clone(monitorStep));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCodeMonitor && (
|
||||
<div className="mt-5">
|
||||
<FieldLabelElement
|
||||
|
||||
@@ -49,6 +49,9 @@ import HeaderAlert, {
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import ColorSwatch from "Common/Types/ColorSwatch";
|
||||
import AlertFeedElement from "../../../Components/Alert/AlertFeed";
|
||||
import TelemetryExceptionTable from "../../../Components/Exceptions/ExceptionsTable";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
|
||||
const AlertView: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID();
|
||||
@@ -578,6 +581,16 @@ const AlertView: FunctionComponent<PageComponentProps> = (): ReactElement => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{telemetryQuery &&
|
||||
telemetryQuery.telemetryType === TelemetryType.Exception &&
|
||||
telemetryQuery.telemetryQuery && (
|
||||
<TelemetryExceptionTable
|
||||
title="Exceptions"
|
||||
description="Exceptions for this alert."
|
||||
query={telemetryQuery.telemetryQuery as Query<TelemetryException>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AlertFeedElement alertId={modelId} />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,9 @@ import IncidentFeedElement from "../../../Components/Incident/IncidentFeed";
|
||||
import Monitor from "Common/Models/DatabaseModels/Monitor";
|
||||
import MonitorStatus from "Common/Models/DatabaseModels/MonitorStatus";
|
||||
import StatusPageSubscriberNotificationStatus from "Common/Types/StatusPage/StatusPageSubscriberNotificationStatus";
|
||||
import TelemetryExceptionTable from "../../../Components/Exceptions/ExceptionsTable";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
|
||||
const IncidentView: FunctionComponent<
|
||||
PageComponentProps
|
||||
@@ -672,6 +675,16 @@ const IncidentView: FunctionComponent<
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{telemetryQuery &&
|
||||
telemetryQuery.telemetryType === TelemetryType.Exception &&
|
||||
telemetryQuery.telemetryQuery && (
|
||||
<TelemetryExceptionTable
|
||||
title="Exceptions"
|
||||
description="Exceptions related to this incident."
|
||||
query={telemetryQuery.telemetryQuery as Query<TelemetryException>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<IncidentFeedElement incidentId={modelId} />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -253,6 +253,12 @@ export default class CriteriaFilterUtil {
|
||||
});
|
||||
}
|
||||
|
||||
if (monitorType === MonitorType.Exceptions) {
|
||||
options = options.filter((i: DropdownOption) => {
|
||||
return i.value === CheckOn.ExceptionCount;
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ import MetricService from "Common/Server/Services/MetricService";
|
||||
import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import Metric from "Common/Models/AnalyticsModels/Metric";
|
||||
import ExceptionMonitorResponse from "Common/Types/Monitor/ExceptionMonitor/ExceptionMonitorResponse";
|
||||
import MonitorStepExceptionMonitor, {
|
||||
MonitorStepExceptionMonitorUtil,
|
||||
} from "Common/Types/Monitor/MonitorStepExceptionMonitor";
|
||||
import TelemetryExceptionService from "Common/Server/Services/TelemetryExceptionService";
|
||||
import DatabaseQuery from "Common/Types/BaseDatabase/Query";
|
||||
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
|
||||
|
||||
RunCron(
|
||||
"TelemetryMonitor:MonitorTelemetryMonitor",
|
||||
@@ -53,6 +60,7 @@ RunCron(
|
||||
MonitorType.Logs,
|
||||
MonitorType.Traces,
|
||||
MonitorType.Metrics,
|
||||
MonitorType.Exceptions,
|
||||
]),
|
||||
telemetryMonitorNextMonitorAt:
|
||||
DatabaseQueryHelper.lessThanEqualToOrNull(
|
||||
@@ -113,7 +121,12 @@ RunCron(
|
||||
logger.debug(telemetryMonitors);
|
||||
|
||||
const monitorResponses: Array<
|
||||
Promise<LogMonitorResponse | TraceMonitorResponse | MetricMonitorResponse>
|
||||
Promise<
|
||||
| LogMonitorResponse
|
||||
| TraceMonitorResponse
|
||||
| MetricMonitorResponse
|
||||
| ExceptionMonitorResponse
|
||||
>
|
||||
> = [];
|
||||
|
||||
for (const monitor of telemetryMonitors) {
|
||||
@@ -145,7 +158,10 @@ RunCron(
|
||||
}
|
||||
|
||||
const responses: Array<
|
||||
LogMonitorResponse | TraceMonitorResponse | MetricMonitorResponse
|
||||
| LogMonitorResponse
|
||||
| TraceMonitorResponse
|
||||
| MetricMonitorResponse
|
||||
| ExceptionMonitorResponse
|
||||
> = await Promise.all(monitorResponses);
|
||||
|
||||
for (const response of responses) {
|
||||
@@ -160,7 +176,10 @@ type MonitorTelemetryMonitorFunction = (data: {
|
||||
monitorId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
}) => Promise<
|
||||
LogMonitorResponse | TraceMonitorResponse | MetricMonitorResponse
|
||||
| LogMonitorResponse
|
||||
| TraceMonitorResponse
|
||||
| MetricMonitorResponse
|
||||
| ExceptionMonitorResponse
|
||||
>;
|
||||
|
||||
const monitorTelemetryMonitor: MonitorTelemetryMonitorFunction = async (data: {
|
||||
@@ -169,7 +188,10 @@ const monitorTelemetryMonitor: MonitorTelemetryMonitorFunction = async (data: {
|
||||
monitorId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
}): Promise<
|
||||
LogMonitorResponse | TraceMonitorResponse | MetricMonitorResponse
|
||||
| LogMonitorResponse
|
||||
| TraceMonitorResponse
|
||||
| MetricMonitorResponse
|
||||
| ExceptionMonitorResponse
|
||||
> => {
|
||||
const { monitorStep, monitorType, monitorId, projectId } = data;
|
||||
|
||||
@@ -197,6 +219,14 @@ const monitorTelemetryMonitor: MonitorTelemetryMonitorFunction = async (data: {
|
||||
});
|
||||
}
|
||||
|
||||
if (monitorType === MonitorType.Exceptions) {
|
||||
return monitorException({
|
||||
monitorStep,
|
||||
monitorId,
|
||||
projectId,
|
||||
});
|
||||
}
|
||||
|
||||
throw new BadDataException("Monitor type is not supported");
|
||||
};
|
||||
|
||||
@@ -320,6 +350,50 @@ const monitorMetric: MonitorMetricFunction = async (data: {
|
||||
monitorId: data.monitorId,
|
||||
};
|
||||
};
|
||||
|
||||
type MonitorExceptionFunction = (data: {
|
||||
monitorStep: MonitorStep;
|
||||
monitorId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
}) => Promise<ExceptionMonitorResponse>;
|
||||
|
||||
const monitorException: MonitorExceptionFunction = async (data: {
|
||||
monitorStep: MonitorStep;
|
||||
monitorId: ObjectID;
|
||||
projectId: ObjectID;
|
||||
}): Promise<ExceptionMonitorResponse> => {
|
||||
const exceptionMonitorConfig: MonitorStepExceptionMonitor | undefined =
|
||||
data.monitorStep.data?.exceptionMonitor;
|
||||
|
||||
if (!exceptionMonitorConfig) {
|
||||
throw new BadDataException("Exception monitor config is missing");
|
||||
}
|
||||
|
||||
const query: DatabaseQuery<TelemetryException> =
|
||||
MonitorStepExceptionMonitorUtil.toQuery(exceptionMonitorConfig);
|
||||
|
||||
query.projectId = data.projectId;
|
||||
|
||||
const exceptionCount: PositiveNumber =
|
||||
await TelemetryExceptionService.countBy({
|
||||
query,
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
projectId: data.projectId,
|
||||
exceptionCount: exceptionCount.toNumber(),
|
||||
exceptionQuery: JSONFunctions.anyObjectToJSONObject(
|
||||
query,
|
||||
) as DatabaseQuery<TelemetryException>,
|
||||
monitorId: data.monitorId,
|
||||
};
|
||||
};
|
||||
|
||||
type MonitorLogsFunction = (data: {
|
||||
monitorStep: MonitorStep;
|
||||
monitorId: ObjectID;
|
||||
|
||||
Reference in New Issue
Block a user