chore: Sentry Wrapper with Performance and Error Tracing (#12642)
* add wrapper for sentry and update functions in 'getUserAvailability'. Update tracesSampleRate to 1.0 * Make Sentry Wrapper utilize parent transaction, if it exists. * Update wrapper for functions to inherit parameters from the child function * add comment of when to use the wrapper * check for sentry before wrapping, if not call unwrapped function * refactored wrapper to have async and sync separate functions that utilize helpers for common behaviour * update type of args to unknown * fixed types of returns from wrapped functions --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
parent
4062ae8486
commit
9a6d4e63e8
|
@ -2,4 +2,5 @@ import * as Sentry from "@sentry/nextjs";
|
|||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@ import type {
|
|||
} from "@calcom/types/Calendar";
|
||||
|
||||
import { getBusyTimes, getBusyTimesForLimitChecks } from "./getBusyTimes";
|
||||
import monitorCallbackAsync, { monitorCallbackSync } from "./sentryWrapper";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["getUserAvailability"] });
|
||||
const availabilitySchema = z
|
||||
|
@ -41,7 +42,13 @@ const availabilitySchema = z
|
|||
})
|
||||
.refine((data) => !!data.username || !!data.userId, "Either username or userId should be filled in.");
|
||||
|
||||
const getEventType = async (id: number) => {
|
||||
const getEventType = async (
|
||||
...args: Parameters<typeof _getEventType>
|
||||
): Promise<ReturnType<typeof _getEventType>> => {
|
||||
return monitorCallbackAsync(_getEventType, ...args);
|
||||
};
|
||||
|
||||
const _getEventType = async (id: number) => {
|
||||
const eventType = await prisma.eventType.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
|
@ -86,7 +93,11 @@ const getEventType = async (id: number) => {
|
|||
|
||||
type EventType = Awaited<ReturnType<typeof getEventType>>;
|
||||
|
||||
const getUser = (where: Prisma.UserWhereInput) =>
|
||||
const getUser = (...args: Parameters<typeof _getUser>): ReturnType<typeof _getUser> => {
|
||||
return monitorCallbackSync(_getUser, ...args);
|
||||
};
|
||||
|
||||
const _getUser = (where: Prisma.UserWhereInput) =>
|
||||
prisma.user.findFirst({
|
||||
where,
|
||||
select: {
|
||||
|
@ -99,7 +110,13 @@ const getUser = (where: Prisma.UserWhereInput) =>
|
|||
|
||||
type User = Awaited<ReturnType<typeof getUser>>;
|
||||
|
||||
export const getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) =>
|
||||
export const getCurrentSeats = (
|
||||
...args: Parameters<typeof _getCurrentSeats>
|
||||
): ReturnType<typeof _getCurrentSeats> => {
|
||||
return monitorCallbackSync(_getCurrentSeats, ...args);
|
||||
};
|
||||
|
||||
const _getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Dayjs) =>
|
||||
prisma.booking.findMany({
|
||||
where: {
|
||||
eventTypeId,
|
||||
|
@ -122,8 +139,14 @@ export const getCurrentSeats = (eventTypeId: number, dateFrom: Dayjs, dateTo: Da
|
|||
|
||||
export type CurrentSeats = Awaited<ReturnType<typeof getCurrentSeats>>;
|
||||
|
||||
export const getUserAvailability = async (
|
||||
...args: Parameters<typeof _getUserAvailability>
|
||||
): Promise<ReturnType<typeof _getUserAvailability>> => {
|
||||
return monitorCallbackAsync(_getUserAvailability, ...args);
|
||||
};
|
||||
|
||||
/** This should be called getUsersWorkingHoursAndBusySlots (...and remaining seats, and final timezone) */
|
||||
export const getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseAndEverythingElse(
|
||||
const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseAndEverythingElse(
|
||||
query: {
|
||||
withSource?: boolean;
|
||||
username?: string;
|
||||
|
@ -305,7 +328,13 @@ export const getUserAvailability = async function getUsersWorkingHoursLifeTheUni
|
|||
};
|
||||
};
|
||||
|
||||
const getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit) => {
|
||||
const getPeriodStartDatesBetween = (
|
||||
...args: Parameters<typeof _getPeriodStartDatesBetween>
|
||||
): ReturnType<typeof _getPeriodStartDatesBetween> => {
|
||||
return monitorCallbackSync(_getPeriodStartDatesBetween, ...args);
|
||||
};
|
||||
|
||||
const _getPeriodStartDatesBetween = (dateFrom: Dayjs, dateTo: Dayjs, period: IntervalLimitUnit) => {
|
||||
const dates = [];
|
||||
let startDate = dayjs(dateFrom).startOf(period);
|
||||
const endDate = dayjs(dateTo).endOf(period);
|
||||
|
@ -378,6 +407,12 @@ class LimitManager {
|
|||
}
|
||||
|
||||
const getBusyTimesFromLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromLimits = async (
|
||||
bookingLimits: IntervalLimit | null,
|
||||
durationLimits: IntervalLimit | null,
|
||||
dateFrom: Dayjs,
|
||||
|
@ -450,6 +485,12 @@ const getBusyTimesFromLimits = async (
|
|||
};
|
||||
|
||||
const getBusyTimesFromBookingLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromBookingLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromBookingLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromBookingLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromBookingLimits = async (
|
||||
bookings: EventBusyDetails[],
|
||||
bookingLimits: IntervalLimit,
|
||||
dateFrom: Dayjs,
|
||||
|
@ -504,6 +545,12 @@ const getBusyTimesFromBookingLimits = async (
|
|||
};
|
||||
|
||||
const getBusyTimesFromDurationLimits = async (
|
||||
...args: Parameters<typeof _getBusyTimesFromDurationLimits>
|
||||
): Promise<ReturnType<typeof _getBusyTimesFromDurationLimits>> => {
|
||||
return monitorCallbackAsync(_getBusyTimesFromDurationLimits, ...args);
|
||||
};
|
||||
|
||||
const _getBusyTimesFromDurationLimits = async (
|
||||
bookings: EventBusyDetails[],
|
||||
durationLimits: IntervalLimit,
|
||||
dateFrom: Dayjs,
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import * as Sentry from "@sentry/nextjs";
|
||||
import type { Span, Transaction } from "@sentry/types";
|
||||
|
||||
/*
|
||||
WHEN TO USE
|
||||
We ran a script that performs a simple mathematical calculation within a loop of 1000000 iterations.
|
||||
Our results were: Plain execution time: 441, Monitored execution time: 8094.
|
||||
This suggests that using these wrappers within large loops can incur significant overhead and is thus not recommended.
|
||||
|
||||
For smaller loops, the cost incurred may not be very significant on an absolute scale
|
||||
considering that a million monitored iterations only took roughly 8 seconds when monitored.
|
||||
*/
|
||||
|
||||
const setUpMonitoring = (name: string) => {
|
||||
// Attempt to retrieve the current transaction from Sentry's scope
|
||||
let transaction = Sentry.getCurrentHub().getScope()?.getTransaction();
|
||||
|
||||
// Check if there's an existing transaction, if not, start a new one
|
||||
if (!transaction) {
|
||||
transaction = Sentry.startTransaction({
|
||||
op: name,
|
||||
name: name,
|
||||
});
|
||||
}
|
||||
|
||||
// Start a new span in the current transaction
|
||||
const span = transaction.startChild({
|
||||
op: name,
|
||||
description: `Executing ${name}`,
|
||||
});
|
||||
return [transaction, span];
|
||||
};
|
||||
|
||||
// transaction will always be Transaction, since returned in a list with Span type must be listed as either or here
|
||||
const finishMonitoring = (transaction: Transaction | Span, span: Span) => {
|
||||
// Attempt to retrieve the current transaction from Sentry's scope
|
||||
span.finish();
|
||||
|
||||
// If this was a new transaction, finish it
|
||||
if (!Sentry.getCurrentHub().getScope()?.getTransaction()) {
|
||||
transaction.finish();
|
||||
}
|
||||
};
|
||||
|
||||
const monitorCallbackAsync = async <T extends (...args: any[]) => any>(
|
||||
cb: T,
|
||||
...args: Parameters<T>
|
||||
): Promise<ReturnType<T>> => {
|
||||
// Check if Sentry set
|
||||
if (!process.env.NEXT_PUBLIC_SENTRY_DSN) return (await cb(...args)) as ReturnType<T>;
|
||||
|
||||
const [transaction, span] = setUpMonitoring(cb.name);
|
||||
|
||||
try {
|
||||
const result = await cb(...args);
|
||||
return result as ReturnType<T>;
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
throw error;
|
||||
} finally {
|
||||
finishMonitoring(transaction, span);
|
||||
}
|
||||
};
|
||||
|
||||
const monitorCallbackSync = <T extends (...args: any[]) => any>(
|
||||
cb: T,
|
||||
...args: Parameters<T>
|
||||
): ReturnType<T> => {
|
||||
// Check if Sentry set
|
||||
if (!process.env.NEXT_PUBLIC_SENTRY_DSN) return cb(...args) as ReturnType<T>;
|
||||
|
||||
const [transaction, span] = setUpMonitoring(cb.name);
|
||||
|
||||
try {
|
||||
const result = cb(...args);
|
||||
return result as ReturnType<T>;
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
throw error;
|
||||
} finally {
|
||||
finishMonitoring(transaction, span);
|
||||
}
|
||||
};
|
||||
|
||||
export default monitorCallbackAsync;
|
||||
export { monitorCallbackSync };
|
Loading…
Reference in New Issue
Block a user