refactor: Add TelemetryExceptionStatusService to handle telemetry exception status in the database

This commit is contained in:
Simon Larsen 2024-08-25 19:29:25 +01:00
parent a6a5f189c3
commit 2f3dbebe9a
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
13 changed files with 409 additions and 152 deletions

View File

@ -6,12 +6,13 @@ import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
import ObjectID from "../../Types/ObjectID"; import ObjectID from "../../Types/ObjectID";
import Permission from "../../Types/Permission"; import Permission from "../../Types/Permission";
export default class Exception extends AnalyticsBaseModel { export default class ExceptionInstance extends AnalyticsBaseModel {
public constructor() { public constructor() {
super({ super({
tableName: "ExceptionTelemetry", tableName: "ExceptionInstanceTelemetry",
tableEngine: AnalyticsTableEngine.MergeTree, tableEngine: AnalyticsTableEngine.MergeTree,
singularName: "Exception", singularName: "Exception",
pluralName: "Exceptions",
enableRealtimeEventsOn: { enableRealtimeEventsOn: {
create: true, create: true,
}, },

View File

@ -4,7 +4,7 @@ import Metric from "./Metric";
import MonitorMetricsByMinute from "./MonitorMetricsByMinute"; import MonitorMetricsByMinute from "./MonitorMetricsByMinute";
import Span from "./Span"; import Span from "./Span";
import TelemetryAttribute from "./TelemetryAttribute"; import TelemetryAttribute from "./TelemetryAttribute";
import Exception from "./Exception"; import ExceptionInstance from "./ExceptionInstance";
const AnalyticsModels: Array<typeof AnalyticsBaseModel> = [ const AnalyticsModels: Array<typeof AnalyticsBaseModel> = [
Log, Log,
@ -12,7 +12,7 @@ const AnalyticsModels: Array<typeof AnalyticsBaseModel> = [
Metric, Metric,
MonitorMetricsByMinute, MonitorMetricsByMinute,
TelemetryAttribute, TelemetryAttribute,
Exception, ExceptionInstance,
]; ];
export default AnalyticsModels; export default AnalyticsModels;

View File

@ -135,7 +135,7 @@ import UserTwoFactorAuth from "./UserTwoFactorAuth";
import TelemetryIngestionKey from "./TelemetryIngestionKey"; import TelemetryIngestionKey from "./TelemetryIngestionKey";
import TelemetryExceptionStatus from "./TelemetryExceptionStatus"; import TelemetryException from "./TelemetryException";
export default [ export default [
User, User,
@ -292,5 +292,5 @@ export default [
TelemetryIngestionKey, TelemetryIngestionKey,
TelemetryExceptionStatus TelemetryException,
]; ];

View File

@ -53,17 +53,17 @@ import TelemetryService from "./TelemetryService";
}) })
@CrudApiEndpoint(new Route("/telemetry-exception-status")) @CrudApiEndpoint(new Route("/telemetry-exception-status"))
@TableMetadata({ @TableMetadata({
tableName: "TelemetryExceptionStatus", tableName: "TelemetryException",
singularName: "TelemetryExceptionStatus", singularName: "TelemetryException",
pluralName: "TelemetryExceptionsStatus", pluralName: "TelemetryExceptionsStatus",
icon: IconProp.Error, icon: IconProp.Error,
tableDescription: tableDescription:
"List of all Telemetry Exceptions created for the telemetry service for this OneUptime project and it's status.", "List of all Telemetry Exceptions created for the telemetry service for this OneUptime project and it's status.",
}) })
@Entity({ @Entity({
name: "TelemetryExceptionStatus", name: "TelemetryException",
}) })
export default class TelemetryExceptionStatus extends DatabaseBaseModel { export default class TelemetryException extends DatabaseBaseModel {
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
@ -128,7 +128,6 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public projectId?: ObjectID = undefined; public projectId?: ObjectID = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
@ -233,7 +232,6 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public message?: string = undefined; public message?: string = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
@ -257,7 +255,8 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
type: TableColumnType.LongText, type: TableColumnType.LongText,
canReadOnRelationQuery: false, canReadOnRelationQuery: false,
title: "Stack Trace", title: "Stack Trace",
description: "Stack trace of the exception that was thrown by the telemetry service", description:
"Stack trace of the exception that was thrown by the telemetry service",
}) })
@Column({ @Column({
nullable: true, nullable: true,
@ -265,7 +264,6 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public stackTrace?: string = undefined; public stackTrace?: string = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
@ -289,7 +287,8 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
type: TableColumnType.LongText, type: TableColumnType.LongText,
canReadOnRelationQuery: false, canReadOnRelationQuery: false,
title: "Exception Type", title: "Exception Type",
description: "Type of the exception that was thrown by the telemetry service", description:
"Type of the exception that was thrown by the telemetry service",
}) })
@Column({ @Column({
nullable: true, nullable: true,
@ -297,6 +296,7 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public exceptionType?: string = undefined; public exceptionType?: string = undefined;
@Index()
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
@ -320,14 +320,15 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
type: TableColumnType.ShortText, type: TableColumnType.ShortText,
canReadOnRelationQuery: false, canReadOnRelationQuery: false,
title: "Finger Print", title: "Finger Print",
description: "Finger print of the exception that was thrown by the telemetry service", description:
"Finger print of the exception that was thrown by the telemetry service",
}) })
@Column({ @Column({
nullable: true, nullable: true,
type: ColumnType.ShortText, type: ColumnType.ShortText,
length: ColumnLength.ShortText, length: ColumnLength.ShortText,
}) })
public fingerPrint?: string = undefined; public fingerprint?: string = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
@ -393,14 +394,22 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public createdByUserId?: ObjectID = undefined; public createdByUserId?: ObjectID = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
manyToOneRelationColumn: "deletedByUserId", manyToOneRelationColumn: "deletedByUserId",
@ -425,14 +434,22 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public deletedByUser?: User = undefined; public deletedByUser?: User = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
type: TableColumnType.ObjectID, type: TableColumnType.ObjectID,
@ -447,19 +464,23 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public deletedByUserId?: ObjectID = undefined; public deletedByUserId?: ObjectID = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.CreateProjectTeam, Permission.CreateTelemetryException,
Permission.InviteProjectTeamMembers,
], ],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ReadProjectTeam, Permission.ProjectMember,
Permission.ReadTelemetryException,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
], ],
update: [Permission.CurrentUser],
}) })
@TableColumn({ @TableColumn({
required: false, required: false,
@ -474,21 +495,23 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public markedAsResolvedAt?: Date = undefined; public markedAsResolvedAt?: Date = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.CreateProjectTeam, Permission.CreateTelemetryException,
Permission.InviteProjectTeamMembers,
], ],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ReadProjectTeam, Permission.ProjectMember,
Permission.ReadTelemetryException,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
], ],
update: [Permission.CurrentUser],
}) })
@TableColumn({ @TableColumn({
required: false, required: false,
@ -503,20 +526,23 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public markedAsMutedAt?: Date = undefined; public markedAsMutedAt?: Date = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.CreateProjectTeam, Permission.CreateTelemetryException,
Permission.InviteProjectTeamMembers,
], ],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ReadProjectTeam, Permission.ProjectMember,
Permission.ReadTelemetryException,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
], ],
update: [Permission.CurrentUser],
}) })
@TableColumn({ @TableColumn({
required: false, required: false,
@ -535,15 +561,19 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
create: [ create: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.CreateProjectTeam, Permission.CreateTelemetryException,
Permission.InviteProjectTeamMembers,
], ],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ReadProjectTeam, Permission.ProjectMember,
Permission.ReadTelemetryException,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
], ],
update: [Permission.CurrentUser],
}) })
@TableColumn({ @TableColumn({
required: false, required: false,
@ -558,24 +588,30 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public lastSeenAt?: Date = undefined; public lastSeenAt?: Date = undefined;
// assign to. // assign to.
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
manyToOneRelationColumn: "assignToUserId", manyToOneRelationColumn: "assignToUserId",
type: TableColumnType.Entity, type: TableColumnType.Entity,
title: "Assign to User", title: "Assign to User",
description: description: "Relation to User who this exception is assigned to.",
"Relation to User who this exception is assigned to.",
}) })
@ManyToOne( @ManyToOne(
() => { () => {
@ -593,20 +629,27 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public assignToUser?: User = undefined; public assignToUser?: User = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
type: TableColumnType.ObjectID, type: TableColumnType.ObjectID,
title: "Assign to User ID", title: "Assign to User ID",
description: description: "User ID who this exception is assigned to.",
"User ID who this exception is assigned to.",
}) })
@Column({ @Column({
type: ColumnType.ObjectID, type: ColumnType.ObjectID,
@ -615,27 +658,32 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public assignToUserId?: ObjectID = undefined; public assignToUserId?: ObjectID = undefined;
// assign to team. // assign to team.
// assign to. // assign to.
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
manyToOneRelationColumn: "assignToTeamId", manyToOneRelationColumn: "assignToTeamId",
type: TableColumnType.Entity, type: TableColumnType.Entity,
title: "Assign to Team", title: "Assign to Team",
description: description: "Relation to Team who this exception is assigned to.",
"Relation to Team who this exception is assigned to.",
}) })
@ManyToOne( @ManyToOne(
() => { () => {
@ -653,20 +701,27 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public assignToTeam?: Team = undefined; public assignToTeam?: Team = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
type: TableColumnType.ObjectID, type: TableColumnType.ObjectID,
title: "Assign to Team ID", title: "Assign to Team ID",
description: description: "Team ID who this exception is assigned to.",
"Team ID who this exception is assigned to.",
}) })
@Column({ @Column({
type: ColumnType.ObjectID, type: ColumnType.ObjectID,
@ -675,18 +730,25 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public assignToTeamId?: ObjectID = undefined; public assignToTeamId?: ObjectID = undefined;
// mark as resolved by. // mark as resolved by.
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
manyToOneRelationColumn: "markedAsResolvedByUserId", manyToOneRelationColumn: "markedAsResolvedByUserId",
@ -711,20 +773,27 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public markedAsResolvedByUser?: User = undefined; public markedAsResolvedByUser?: User = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
type: TableColumnType.ObjectID, type: TableColumnType.ObjectID,
title: "Marked as Resolved By User ID", title: "Marked as Resolved By User ID",
description: description: "User ID who marked this exception as resolved.",
"User ID who marked this exception as resolved.",
}) })
@Column({ @Column({
type: ColumnType.ObjectID, type: ColumnType.ObjectID,
@ -733,26 +802,31 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
}) })
public markedAsResolvedByUserId?: ObjectID = undefined; public markedAsResolvedByUserId?: ObjectID = undefined;
// Mark as muted by. // Mark as muted by.
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
manyToOneRelationColumn: "markedAsMutedByUserId", manyToOneRelationColumn: "markedAsMutedByUserId",
type: TableColumnType.Entity, type: TableColumnType.Entity,
title: "Mark as Muted By User", title: "Mark as Muted By User",
description: description: "Mark as muted by User",
"Mark as muted by User",
}) })
@ManyToOne( @ManyToOne(
() => { () => {
@ -770,20 +844,27 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
public markedAsMutedByUser?: User = undefined; public markedAsMutedByUser?: User = undefined;
@ColumnAccessControl({ @ColumnAccessControl({
create: [], create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateTelemetryException,
],
read: [ read: [
Permission.ProjectOwner, Permission.ProjectOwner,
Permission.ProjectAdmin, Permission.ProjectAdmin,
Permission.ProjectMember, Permission.ProjectMember,
Permission.ReadTelemetryException, Permission.ReadTelemetryException,
], ],
update: [], update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditTelemetryException,
],
}) })
@TableColumn({ @TableColumn({
type: TableColumnType.ObjectID, type: TableColumnType.ObjectID,
title: "Mark as Muted By User ID", title: "Mark as Muted By User ID",
description: description: "User ID who marked this exception as muted.",
"User ID who marked this exception as muted.",
}) })
@Column({ @Column({
type: ColumnType.ObjectID, type: ColumnType.ObjectID,
@ -791,8 +872,4 @@ export default class TelemetryExceptionStatus extends DatabaseBaseModel {
transformer: ObjectID.getDatabaseTransformer(), transformer: ObjectID.getDatabaseTransformer(),
}) })
public markedAsMutedByUserId?: ObjectID = undefined; public markedAsMutedByUserId?: ObjectID = undefined;
} }

View File

@ -1,38 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1724607603707 implements MigrationInterface {
public name = 'MigrationName1724607603707'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "TelemetryExceptionStatus" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "telemetryServiceId" uuid NOT NULL, "message" character varying, "stackTrace" character varying, "exceptionType" character varying, "fingerPrint" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "markedAsResolvedAt" TIMESTAMP WITH TIME ZONE, "markedAsMutedAt" TIMESTAMP WITH TIME ZONE, "firstSeenAt" TIMESTAMP WITH TIME ZONE, "lastSeenAt" TIMESTAMP WITH TIME ZONE, "assignToUserId" uuid, "assignToTeamId" uuid, "markedAsResolvedByUserId" uuid, "markedAsMutedByUserId" uuid, CONSTRAINT "PK_8db287c0fc7516e22d53876137c" PRIMARY KEY ("_id"))`);
await queryRunner.query(`CREATE INDEX "IDX_7c02d07bf73bfdac7301a6c86d" ON "TelemetryExceptionStatus" ("projectId") `);
await queryRunner.query(`CREATE INDEX "IDX_0f66442452b5a89efa085ede0f" ON "TelemetryExceptionStatus" ("telemetryServiceId") `);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_7c02d07bf73bfdac7301a6c86d5" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_0f66442452b5a89efa085ede0fd" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_fe9e43b2cf2278894f9fe67c92f" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_3b402ffb6fe47992c38f4cd5e7a" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_88a1d97d74c54cd80b384f2a911" FOREIGN KEY ("assignToUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_1c8cc9368c92f60cb093af277f8" FOREIGN KEY ("assignToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_fe156e1dce6bdae3f349d33e293" FOREIGN KEY ("markedAsResolvedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" ADD CONSTRAINT "FK_9d7647bf6d537f5afbd00ef4a8b" FOREIGN KEY ("markedAsMutedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_9d7647bf6d537f5afbd00ef4a8b"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_fe156e1dce6bdae3f349d33e293"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_1c8cc9368c92f60cb093af277f8"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_88a1d97d74c54cd80b384f2a911"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_3b402ffb6fe47992c38f4cd5e7a"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_fe9e43b2cf2278894f9fe67c92f"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_0f66442452b5a89efa085ede0fd"`);
await queryRunner.query(`ALTER TABLE "TelemetryExceptionStatus" DROP CONSTRAINT "FK_7c02d07bf73bfdac7301a6c86d5"`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`);
await queryRunner.query(`DROP INDEX "public"."IDX_0f66442452b5a89efa085ede0f"`);
await queryRunner.query(`DROP INDEX "public"."IDX_7c02d07bf73bfdac7301a6c86d"`);
await queryRunner.query(`DROP TABLE "TelemetryExceptionStatus"`);
}
}

View File

@ -0,0 +1,93 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1724610006927 implements MigrationInterface {
public name = "MigrationName1724610006927";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "TelemetryException" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "version" integer NOT NULL, "projectId" uuid NOT NULL, "telemetryServiceId" uuid NOT NULL, "message" character varying, "stackTrace" character varying, "exceptionType" character varying, "fingerprint" character varying(100), "createdByUserId" uuid, "deletedByUserId" uuid, "markedAsResolvedAt" TIMESTAMP WITH TIME ZONE, "markedAsMutedAt" TIMESTAMP WITH TIME ZONE, "firstSeenAt" TIMESTAMP WITH TIME ZONE, "lastSeenAt" TIMESTAMP WITH TIME ZONE, "assignToUserId" uuid, "assignToTeamId" uuid, "markedAsResolvedByUserId" uuid, "markedAsMutedByUserId" uuid, CONSTRAINT "PK_53717afe73c3e72c11713e5e25f" PRIMARY KEY ("_id"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_3310c3a807a7e8bca9d1bd8e0e" ON "TelemetryException" ("projectId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_6470c69cb5f53c5899c0483df5" ON "TelemetryException" ("telemetryServiceId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_e270eb229cd583c653c2176db9" ON "TelemetryException" ("fingerprint") `,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_3310c3a807a7e8bca9d1bd8e0eb" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_6470c69cb5f53c5899c0483df5f" FOREIGN KEY ("telemetryServiceId") REFERENCES "TelemetryService"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_d2e1b4f5dcaebbf14ed6cbd303d" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_757f473e68b584bc42fcfbd9373" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_f7ec3f51dae2b4963cfb8fe5c46" FOREIGN KEY ("assignToUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_63221e8bd973ab71a49598d6c88" FOREIGN KEY ("assignToTeamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_10c7733499d5afa9b857f4a00c5" FOREIGN KEY ("markedAsResolvedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" ADD CONSTRAINT "FK_199e3572d19b75e59f2082251f8" FOREIGN KEY ("markedAsMutedByUserId") REFERENCES "User"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_199e3572d19b75e59f2082251f8"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_10c7733499d5afa9b857f4a00c5"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_63221e8bd973ab71a49598d6c88"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_f7ec3f51dae2b4963cfb8fe5c46"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_757f473e68b584bc42fcfbd9373"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_d2e1b4f5dcaebbf14ed6cbd303d"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_6470c69cb5f53c5899c0483df5f"`,
);
await queryRunner.query(
`ALTER TABLE "TelemetryException" DROP CONSTRAINT "FK_3310c3a807a7e8bca9d1bd8e0eb"`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_e270eb229cd583c653c2176db9"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_6470c69cb5f53c5899c0483df5"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_3310c3a807a7e8bca9d1bd8e0e"`,
);
await queryRunner.query(`DROP TABLE "TelemetryException"`);
}
}

View File

@ -41,6 +41,7 @@ import { MigrationName1722892318363 } from "./1722892318363-MigrationName";
import { MigrationName1723825511054 } from "./1723825511054-MigrationName"; import { MigrationName1723825511054 } from "./1723825511054-MigrationName";
import { MigrationName1723828588502 } from "./1723828588502-MigrationName"; import { MigrationName1723828588502 } from "./1723828588502-MigrationName";
import { MigrationName1724078044172 } from "./1724078044172-MigrationName"; import { MigrationName1724078044172 } from "./1724078044172-MigrationName";
import { MigrationName1724610006927 } from "./1724610006927-MigrationName";
export default [ export default [
InitialMigration, InitialMigration,
@ -86,5 +87,6 @@ export default [
MigrationName1723825511054, MigrationName1723825511054,
MigrationName1723828588502, MigrationName1723828588502,
MigrationName1724078044172, MigrationName1724078044172,
MigrationName1720812937067 MigrationName1720812937067,
MigrationName1724610006927,
]; ];

View File

@ -130,6 +130,7 @@ import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel
import CopilotPullRequestService from "./CopilotPullRequestService"; import CopilotPullRequestService from "./CopilotPullRequestService";
import ServiceCatalogDependencyService from "./ServiceCatalogDependencyService"; import ServiceCatalogDependencyService from "./ServiceCatalogDependencyService";
import TelemetryAttributeService from "./TelemetryAttributeService"; import TelemetryAttributeService from "./TelemetryAttributeService";
import TelemetryExceptionStatusService from "./TelemetryExceptionStatusService";
const services: Array<BaseService> = [ const services: Array<BaseService> = [
AcmeCertificateService, AcmeCertificateService,
@ -271,6 +272,8 @@ const services: Array<BaseService> = [
CopilotActionService, CopilotActionService,
ServiceCopilotCodeRepositoryService, ServiceCopilotCodeRepositoryService,
CopilotPullRequestService, CopilotPullRequestService,
TelemetryExceptionStatusService,
]; ];
export const AnalyticsServices: Array< export const AnalyticsServices: Array<

View File

@ -0,0 +1,10 @@
import DatabaseService from "./DatabaseService";
import Model from "Common/Models/DatabaseModels/TelemetryException";
export class Service extends DatabaseService<Model> {
public constructor() {
super(Model);
}
}
export default new Service();

View File

@ -13,6 +13,10 @@ export default class OneUptimeDate {
return seconds * 1000 * 1000 * 1000; return seconds * 1000 * 1000 * 1000;
} }
public static now(): Date {
return this.getCurrentDate();
}
public static getDateFromYYYYMMDD( public static getDateFromYYYYMMDD(
year: string, year: string,
month: string, month: string,

View File

@ -1,12 +1,11 @@
import CryptoJS from "crypto-js"; import CryptoJS from "crypto-js";
export default class Crypto { export default class Crypto {
public static getMd5Hash(text: string): string { public static getMd5Hash(text: string): string {
return CryptoJS.MD5(text).toString(); return CryptoJS.MD5(text).toString();
} }
public static getSha256Hash(text: string): string {
return CryptoJS.SHA256(text).toString();
}
public static getSha256Hash(text: string): string {
return CryptoJS.SHA256(text).toString();
}
} }

View File

@ -7,7 +7,7 @@ import OTelIngestService, {
TelemetryServiceDataIngested, TelemetryServiceDataIngested,
} from "../Service/OTelIngest"; } from "../Service/OTelIngest";
import OneUptimeDate from "Common/Types/Date"; import OneUptimeDate from "Common/Types/Date";
import BadRequestException from "Common/Types/Exception/BadRequestException"; import BadRequestException from "Common/Types/ExceptionInstance/BadRequestException";
import { JSONArray, JSONObject } from "Common/Types/JSON"; import { JSONArray, JSONObject } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions"; import JSONFunctions from "Common/Types/JSONFunctions";
import ProductType from "Common/Types/MeteredPlan/ProductType"; import ProductType from "Common/Types/MeteredPlan/ProductType";
@ -36,7 +36,7 @@ import protobuf from "protobufjs";
import Dictionary from "Common/Types/Dictionary"; import Dictionary from "Common/Types/Dictionary";
import ObjectID from "Common/Types/ObjectID"; import ObjectID from "Common/Types/ObjectID";
import LogSeverity from "Common/Types/Log/LogSeverity"; import LogSeverity from "Common/Types/Log/LogSeverity";
import Exception from "Common/Models/AnalyticsModels/Exception"; import ExceptionInstance from "Common/Models/AnalyticsModels/ExceptionInstance";
import ExceptionUtil from "../Utils/Exception"; import ExceptionUtil from "../Utils/Exception";
// Load proto file for OTel // Load proto file for OTel
@ -127,7 +127,7 @@ router.post(
const resourceSpans: JSONArray = traceData["resourceSpans"] as JSONArray; const resourceSpans: JSONArray = traceData["resourceSpans"] as JSONArray;
const dbSpans: Array<Span> = []; const dbSpans: Array<Span> = [];
const dbExceptions: Array<Exception> = []; const dbExceptions: Array<ExceptionInstance> = [];
let attributes: string[] = []; let attributes: string[] = [];
@ -301,7 +301,7 @@ router.post(
if (event["name"] === SpanEventType.Exception) { if (event["name"] === SpanEventType.Exception) {
// add exception object. // add exception object.
const exception: Exception = new Exception(); const exception: ExceptionInstance = new ExceptionInstance();
exception.projectId = dbSpan.projectId; exception.projectId = dbSpan.projectId;
exception.serviceId = dbSpan.serviceId; exception.serviceId = dbSpan.serviceId;
exception.spanId = dbSpan.spanId; exception.spanId = dbSpan.spanId;
@ -314,10 +314,15 @@ router.post(
] as string; ] as string;
exception.exceptionType = eventAttributes["type"] as string; exception.exceptionType = eventAttributes["type"] as string;
exception.escaped = eventAttributes["escaped"] as boolean; exception.escaped = eventAttributes["escaped"] as boolean;
exception.fingerprint = ExceptionUtil.getFingerprint(exception); exception.fingerprint =
ExceptionUtil.getFingerprint(exception);
// add exception to dbExceptions // add exception to dbExceptions
dbExceptions.push(exception); dbExceptions.push(exception);
// save exception status
// maybe this can be improved instead of doing a lot of db calls.
await ExceptionUtil.saveOrUpdateTelemetryException(exception);
} }
} }
} }
@ -360,7 +365,12 @@ router.post(
}, },
}); });
await ExceptionService; await ExceptionService.createMany({
items: dbExceptions,
props: {
isRoot: true,
},
});
OTelIngestService.indexAttributes({ OTelIngestService.indexAttributes({
attributes: ArrayUtil.removeDuplicates(attributes), attributes: ArrayUtil.removeDuplicates(attributes),

View File

@ -1,16 +1,112 @@
import Exception from 'Common/Models/AnalyticsModels/Exception'; import ExceptionInstance from "Common/Models/AnalyticsModels/ExceptionInstance";
import Crypto from 'Common/Utils/Crypto'; import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
import TelemetryExceptionStatusService from "Common/Server/Services/TelemetryExceptionStatusService";
import OneUptimeDate from "Common/Types/Date";
import BadDataException from "Common/Types/Exception/BadDataException";
import Crypto from "Common/Utils/Crypto";
export default class ExceptionUtil { export default class ExceptionUtil {
public static getFingerprint(exception: Exception): string { public static getFingerprint(exception: ExceptionInstance): string {
const message: string = exception.message || ""; const message: string = exception.message || "";
const stackTrace: string = exception.stackTrace || ""; const stackTrace: string = exception.stackTrace || "";
const type: string = exception.exceptionType || ""; const type: string = exception.exceptionType || "";
const projectId: string = exception.projectId?.toString() || ""; const projectId: string = exception.projectId?.toString() || "";
const serviceId: string = exception.serviceId?.toString() || ""; const serviceId: string = exception.serviceId?.toString() || "";
const hash: string = Crypto.getSha256Hash(projectId + serviceId + message + stackTrace + type); const hash: string = Crypto.getSha256Hash(
projectId + serviceId + message + stackTrace + type,
);
return hash; return hash;
}
public static async saveOrUpdateTelemetryException(
exception: ExceptionInstance,
): Promise<void> {
// Exception is saved to main database as well (not just analytics db), so users can assgin it, resolve it, etc.
if (!exception.fingerprint) {
throw new BadDataException(
"Fingerprint is required to save exception status",
);
} }
if (!exception.projectId) {
throw new BadDataException(
"Project ID is required to save exception status",
);
}
if (!exception.serviceId) {
throw new BadDataException(
"Service ID is required to save exception status",
);
}
const fingerprint: string = exception.fingerprint;
// check if the exception with the same fingerprint already exists in the database
const existingExceptionStatus: TelemetryException | null =
await TelemetryExceptionStatusService.findOneBy({
query: {
fingerprint: fingerprint,
projectId: exception.projectId,
telemetryServiceId: exception.serviceId,
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (existingExceptionStatus) {
// then update last seen as and unmark as resolved/muted
await TelemetryExceptionStatusService.updateOneBy({
query: {
_id: existingExceptionStatus._id,
},
data: {
lastSeenAt: OneUptimeDate.now(),
markedAsResolvedByUserId: null,
markedAsResolvedAt: null, // unmark as resolved if it was marked as resolved
},
props: {
isRoot: true,
},
});
}
if (!existingExceptionStatus) {
// Create a new exception status if it doesn't exist
const newExceptionStatus: TelemetryException = new TelemetryException();
newExceptionStatus.fingerprint = exception.fingerprint;
newExceptionStatus.projectId = exception.projectId;
newExceptionStatus.telemetryServiceId = exception.serviceId;
newExceptionStatus.lastSeenAt = OneUptimeDate.now();
newExceptionStatus.firstSeenAt = OneUptimeDate.now();
if (exception.exceptionType) {
newExceptionStatus.exceptionType = exception.exceptionType;
}
if (exception.message) {
newExceptionStatus.message = exception.message;
}
if (exception.stackTrace) {
newExceptionStatus.stackTrace = exception.stackTrace;
}
// Save the new exception status to the database
await TelemetryExceptionStatusService.create({
data: newExceptionStatus,
props: {
isRoot: true,
},
});
}
}
} }