Add Exception Monitor functionality with related types and components

This commit is contained in:
Nawaz Dhandala
2025-11-13 12:02:17 +00:00
parent b76811d152
commit ca352826ca
19 changed files with 688 additions and 7 deletions

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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!]!;

View File

@@ -42,6 +42,9 @@ export enum CheckOn {
// Trace monitors.
SpanCount = "Span Count",
// Exception monitors.
ExceptionCount = "Exception Count",
// Metric Monitors.
MetricValue = "Metric Value",
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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,
};
}
}

View File

@@ -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,
];
}

View File

@@ -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;
}

View File

@@ -2,6 +2,7 @@ enum TelemetryType {
Metric = "Metric",
Trace = "Trace",
Log = "Log",
Exception = "Exception",
}
export default TelemetryType;

View File

@@ -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;

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -253,6 +253,12 @@ export default class CriteriaFilterUtil {
});
}
if (monitorType === MonitorType.Exceptions) {
options = options.filter((i: DropdownOption) => {
return i.value === CheckOn.ExceptionCount;
});
}
return options;
}

View File

@@ -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;