feat: distributed tracing - 2 (#24861)

* feat: distributed tracing 2

* feat: distributed tracing 2

* refactor: feedback

* refactor: feedback

* fix: type error

* fix: trpc error

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
Udit Takkar
2025-11-13 11:51:54 +00:00
committed by GitHub
co-authored by Hariom Balhara
parent 19641ffc05
commit 1ff5d57ded
7 changed files with 158 additions and 67 deletions
+5 -2
View File
@@ -10,10 +10,11 @@ import getIP from "@calcom/lib/getIP";
import { piiHasher } from "@calcom/lib/server/PiiHasher";
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
import type { TraceContext } from "@calcom/lib/tracing";
import { prisma } from "@calcom/prisma";
import { CreationSource } from "@calcom/prisma/enums";
async function handler(req: NextApiRequest & { userId?: number }) {
async function handler(req: NextApiRequest & { userId?: number; traceContext: TraceContext }) {
const userIp = getIP(req);
if (process.env.NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1") {
@@ -52,8 +53,10 @@ async function handler(req: NextApiRequest & { userId?: number }) {
userId: session?.user?.id || -1,
hostname: req.headers.host || "",
forcedSlug: req.headers["x-cal-force-slug"] as string | undefined,
traceContext: req.traceContext,
},
});
// const booking = await createBookingThroughFactory();
return booking;
+2
View File
@@ -4,6 +4,7 @@
*/
import type getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema";
import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
import type { TraceContext } from "@calcom/lib/tracing";
import type { SchedulingType } from "@calcom/prisma/enums";
import type { ExtendedBookingCreateBody } from "../bookingCreateBodySchema";
@@ -37,6 +38,7 @@ export type CreateBookingMeta = {
hostname?: string;
forcedSlug?: string;
noEmail?: boolean;
traceContext?: TraceContext;
} & PlatformParams;
export type BookingHandlerInput = {
@@ -66,12 +66,12 @@ import { extractBaseEmail } from "@calcom/lib/extract-base-email";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { criticalLogger } from "@calcom/lib/logger.server";
import { getPiiFreeCalendarEvent, getPiiFreeEventType } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { distributedTracing } from "@calcom/lib/tracing/factory";
import type { PrismaClient } from "@calcom/prisma";
import type { DestinationCalendar, Prisma, User, AssignmentReasonEnum } from "@calcom/prisma/client";
import {
@@ -115,7 +115,6 @@ import { getSeatedBooking } from "../handleNewBooking/getSeatedBooking";
import { getVideoCallDetails } from "../handleNewBooking/getVideoCallDetails";
import { handleAppsStatus } from "../handleNewBooking/handleAppsStatus";
import { loadAndValidateUsers } from "../handleNewBooking/loadAndValidateUsers";
import { createLoggerWithEventDetails } from "../handleNewBooking/logger";
import { getOriginalRescheduledBooking } from "../handleNewBooking/originalRescheduledBookingUtils";
import type { BookingType } from "../handleNewBooking/originalRescheduledBookingUtils";
import { scheduleNoShowTriggers } from "../handleNewBooking/scheduleNoShowTriggers";
@@ -126,7 +125,6 @@ import handleSeats from "../handleSeats/handleSeats";
import type { IBookingService } from "../interfaces/IBookingService";
const translator = short();
const log = logger.getSubLogger({ prefix: ["[api] book:user"] });
type IsFixedAwareUserWithCredentials = Omit<IsFixedAwareUser, "credentials"> & {
credentials: CredentialForCalendarService[];
@@ -479,8 +477,19 @@ async function handler(
skipAvailabilityCheck = false,
skipEventLimitsCheck = false,
skipCalendarSyncTaskCreation = false,
traceContext: passedTraceContext,
} = input;
const traceContext = passedTraceContext
? passedTraceContext
: distributedTracing.createTrace("booking_creation");
const tracingLogger = distributedTracing.getTracingLogger(traceContext, {
eventTypeId: rawBookingData.eventTypeId,
userId: userId,
eventTypeSlug: rawBookingData.eventTypeSlug,
});
const isPlatformBooking = !!platformClientId;
const eventType = await getEventType({
@@ -527,8 +536,7 @@ async function handler(
eventType,
});
const loggerWithEventDetails = createLoggerWithEventDetails(eventTypeId, reqBody.user, eventTypeSlug);
const emailsAndSmsHandler = new BookingEmailSmsHandler({ logger: loggerWithEventDetails });
const emailsAndSmsHandler = new BookingEmailSmsHandler({ logger: tracingLogger });
try {
await checkIfBookerEmailIsBlocked({
@@ -565,6 +573,7 @@ async function handler(
throw new HttpError({
statusCode: 400,
message: "email_verification_required",
data: { traceId: traceContext.traceId },
});
}
@@ -574,12 +583,13 @@ async function handler(
throw new HttpError({
statusCode: 400,
message: "invalid_verification_code",
data: { traceId: traceContext.traceId },
});
}
}
if (isEventTypeLoggingEnabled({ eventTypeId, usernameOrTeamName: reqBody.user })) {
logger.settings.minLevel = 0;
tracingLogger.settings.minLevel = 0;
}
const fullName = getFullName(bookerName);
@@ -587,12 +597,18 @@ async function handler(
const tGuests = await getTranslation("en", "common");
const dynamicUserList = Array.isArray(reqBody.user) ? reqBody.user : getUsernameList(reqBody.user);
if (!eventType) throw new HttpError({ statusCode: 404, message: "event_type_not_found" });
if (!eventType)
throw new HttpError({
statusCode: 404,
message: "event_type_not_found",
data: { traceId: traceContext.traceId },
});
if (eventType.seatsPerTimeSlot && eventType.recurringEvent) {
throw new HttpError({
statusCode: 400,
message: "recurring_event_seats_error",
data: { traceId: traceContext.traceId },
});
}
@@ -669,7 +685,7 @@ async function handler(
const shouldServeCache = false;
loggerWithEventDetails.info(
tracingLogger.info(
`Booking eventType ${eventTypeId} started`,
safeStringify({
reqBody: {
@@ -704,7 +720,7 @@ async function handler(
reqBody.timeZone,
eventType,
eventTimeZone,
loggerWithEventDetails
tracingLogger
);
validateEventLength({
@@ -712,7 +728,7 @@ async function handler(
reqBodyEnd: reqBody.end,
eventTypeMultipleDuration: eventType.metadata?.multipleDuration,
eventTypeLength: eventType.length,
logger: loggerWithEventDetails,
logger: tracingLogger,
});
const contactOwnerFromReq = reqBody.teamMemberEmail ?? null;
@@ -731,7 +747,11 @@ async function handler(
if (routedTeamMemberIds) {
//routingFormResponseId could be 0 for dry run. So, we just avoid undefined value
if (routingFormResponseId === undefined) {
throw new HttpError({ statusCode: 400, message: "Missing routingFormResponseId" });
throw new HttpError({
statusCode: 400,
message: "Missing routingFormResponseId",
data: { traceId: traceContext.traceId },
});
}
routingFormResponse = await deps.prismaClient.app_RoutingForms_FormResponse.findUnique({
where: {
@@ -757,7 +777,7 @@ async function handler(
eventType,
eventTypeId,
dynamicUserList,
logger: loggerWithEventDetails,
logger: tracingLogger,
routedTeamMemberIds: routedTeamMemberIds ?? null,
contactOwnerEmail,
rescheduleUid: reqBody.rescheduleUid || null,
@@ -868,7 +888,7 @@ async function handler(
timeZone: reqBody.timeZone,
originalRescheduledBooking: originalRescheduledBooking ?? null,
},
loggerWithEventDetails,
tracingLogger,
shouldServeCache
);
}
@@ -883,7 +903,7 @@ async function handler(
timeZone: reqBody.timeZone,
originalRescheduledBooking,
},
loggerWithEventDetails,
tracingLogger,
shouldServeCache
);
}
@@ -902,7 +922,7 @@ async function handler(
timeZone: reqBody.timeZone,
originalRescheduledBooking,
},
loggerWithEventDetails,
tracingLogger,
shouldServeCache
);
} else {
@@ -910,7 +930,7 @@ async function handler(
}
} catch {
if (additionalFallbackRRUsers.length) {
loggerWithEventDetails.debug(
tracingLogger.debug(
"Qualified users not available, check for fallback users",
safeStringify({
qualifiedRRUsers: qualifiedRRUsers.map((user) => user.id),
@@ -931,14 +951,14 @@ async function handler(
timeZone: reqBody.timeZone,
originalRescheduledBooking,
},
loggerWithEventDetails,
tracingLogger,
shouldServeCache
);
} else {
availableUsers = [...additionalFallbackRRUsers, ...fixedUsers] as IsFixedAwareUser[];
}
} else {
loggerWithEventDetails.debug(
tracingLogger.debug(
"Qualified users not available, no fallback users",
safeStringify({
qualifiedRRUsers: qualifiedRRUsers.map((user) => user.id),
@@ -967,7 +987,7 @@ async function handler(
const notAvailableLuckyUsers: typeof users = [];
loggerWithEventDetails.debug(
tracingLogger.debug(
"Computed available users",
safeStringify({
availableUsers: availableUsers.map((user) => user.id),
@@ -1044,7 +1064,7 @@ async function handler(
timeZone: reqBody.timeZone,
originalRescheduledBooking,
},
loggerWithEventDetails,
tracingLogger,
shouldServeCache
);
}
@@ -1054,7 +1074,7 @@ async function handler(
luckUserFound = true;
} catch {
notAvailableLuckyUsers.push(newLuckyUser);
loggerWithEventDetails.info(
tracingLogger.info(
`Round robin host ${newLuckyUser.name} not available for first two slots. Trying to find another host.`
);
}
@@ -1119,7 +1139,7 @@ async function handler(
}
if (users.length === 0 && eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
loggerWithEventDetails.error(`No available users found for round robin event.`);
tracingLogger.error(`No available users found for round robin event.`);
throw new Error(ErrorCode.RoundRobinHostsUnavailableForBooking);
}
@@ -1233,7 +1253,7 @@ async function handler(
}, [] as Invitee);
if (guestsRemoved.length > 0) {
log.info("Removed guests from the booking", guestsRemoved);
tracingLogger.info("Removed guests from the booking", guestsRemoved);
}
const seed = `${organizerUser.username}:${dayjs(reqBody.start).utc().format()}:${new Date().getTime()}`;
@@ -1248,8 +1268,8 @@ async function handler(
}
: getLocationValueForDB(locationBodyString, eventType.locations);
log.info("locationBodyString", locationBodyString);
log.info("event type locations", eventType.locations);
tracingLogger.info("locationBodyString", locationBodyString);
tracingLogger.info("event type locations", eventType.locations);
const customInputs = getCustomInputsResponses(reqBody, eventType.customInputs);
const attendeesList = [...invitee, ...guests];
@@ -1663,7 +1683,7 @@ async function handler(
let booking: CreatedBooking | null = null;
loggerWithEventDetails.debug(
tracingLogger.debug(
"Going to create booking in DB now",
safeStringify({
organizerUser: organizerUser.id,
@@ -1824,13 +1844,13 @@ async function handler(
}
} catch (_err) {
const err = getErrorFromUnknown(_err);
loggerWithEventDetails.error(
`Booking ${eventTypeId} failed`,
"Error when saving booking to db",
err.message
);
tracingLogger.error(`Booking ${eventTypeId} failed`, "Error when saving booking to db", err.message);
if (err.code === "P2002") {
throw new HttpError({ statusCode: 409, message: ErrorCode.BookingConflict });
throw new HttpError({
statusCode: 409,
message: ErrorCode.BookingConflict,
data: { traceId: traceContext.traceId },
});
}
throw err;
}
@@ -1847,7 +1867,7 @@ async function handler(
// this is the actual rescheduling logic
if (!eventType.seatsPerTimeSlot && originalRescheduledBooking?.uid) {
log.silly("Rescheduling booking", originalRescheduledBooking.uid);
tracingLogger.silly("Rescheduling booking", originalRescheduledBooking.uid);
// cancel workflow reminders from previous rescheduled booking
await WorkflowRepository.deleteAllWorkflowReminders(originalRescheduledBooking.workflowReminders);
@@ -1878,7 +1898,7 @@ async function handler(
{ ...originalRescheduledBooking.user, credentials: refreshedOriginalHostCredentials },
apps
);
log.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
tracingLogger.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
// Create deletion event with original host's organizer info and original booking properties
const deletionEvent = {
...evt,
@@ -1949,7 +1969,7 @@ async function handler(
message: "Booking Rescheduling failed",
};
loggerWithEventDetails.error(
tracingLogger.error(
`EventManager.reschedule failure in some of the integrations ${organizerUser.username}`,
safeStringify({ error, results })
);
@@ -1972,7 +1992,7 @@ async function handler(
const googleCalResult = results[googleCalIndex];
if (!googleCalResult) {
loggerWithEventDetails.warn("Google Calendar not installed but using Google Meet as location");
tracingLogger.warn("Google Calendar not installed but using Google Meet as location");
results.push({
...googleMeetResult,
success: false,
@@ -2074,9 +2094,10 @@ async function handler(
const error = {
errorCode: "BookingCreatingMeetingFailed",
message: "Booking failed",
data: { traceId: traceContext.traceId },
};
loggerWithEventDetails.error(
tracingLogger.error(
`EventManager.create failure in some of the integrations ${organizerUser.username}`,
safeStringify({ error, results })
);
@@ -2101,7 +2122,7 @@ async function handler(
const googleCalResult = results[googleCalIndex];
if (!googleCalResult) {
loggerWithEventDetails.warn("Google Calendar not installed but using Google Meet as location");
tracingLogger.warn("Google Calendar not installed but using Google Meet as location");
results.push({
...googleMeetResult,
success: false,
@@ -2179,7 +2200,7 @@ async function handler(
}
} else {
// If isConfirmedByDefault is false, then booking can't be considered ACCEPTED and thus EventManager has no role to play. Booking is created as PENDING
loggerWithEventDetails.debug(
tracingLogger.debug(
`EventManager doesn't need to create or reschedule event for booking ${organizerUser.username}`,
safeStringify({
calEvent: getPiiFreeCalendarEvent(evt),
@@ -2196,7 +2217,7 @@ async function handler(
!!booking;
if (!isConfirmedByDefault && noEmail !== true && !bookingRequiresPayment) {
loggerWithEventDetails.debug(
tracingLogger.debug(
`Emails: Booking ${organizerUser.username} requires confirmation, sending request emails`,
safeStringify({
calEvent: getPiiFreeCalendarEvent(evt),
@@ -2236,7 +2257,7 @@ async function handler(
const bookingRescheduledPayload = bookingCreatedPayload;
const bookingEventHandler = new BookingEventHandlerService({
log: loggerWithEventDetails,
log: tracingLogger,
hashedLinkService: deps.hashedLinkService,
});
@@ -2268,7 +2289,7 @@ async function handler(
};
if (bookingRequiresPayment) {
loggerWithEventDetails.debug(`Booking ${organizerUser.username} requires payment`);
tracingLogger.debug(`Booking ${organizerUser.username} requires payment`);
// Load credentials.app.categories
const credentialPaymentAppCategories = await deps.prismaClient.credential.findMany({
where: {
@@ -2295,7 +2316,11 @@ async function handler(
});
if (!eventTypePaymentAppCredential) {
throw new HttpError({ statusCode: 400, message: "Missing payment credentials" });
throw new HttpError({
statusCode: 400,
message: "Missing payment credentials",
data: { traceId: traceContext.traceId },
});
}
// Convert type of eventTypePaymentAppCredential to appId: EventTypeAppList
@@ -2363,7 +2388,7 @@ async function handler(
});
}
} catch (error) {
loggerWithEventDetails.error(
tracingLogger.error(
"Error while scheduling workflow reminders for booking payment initiated",
JSON.stringify({ error })
);
@@ -2394,7 +2419,7 @@ async function handler(
};
}
loggerWithEventDetails.debug(`Booking ${organizerUser.username} completed`);
tracingLogger.debug(`Booking ${organizerUser.username} completed`);
// We are here so, booking doesn't require payment and booking is also created in DB already, through createBooking call
if (isConfirmedByDefault) {
@@ -2456,7 +2481,7 @@ async function handler(
const failures = scheduledTriggerResults.filter((result) => result.status === "rejected");
if (failures.length > 0) {
loggerWithEventDetails.error(
tracingLogger.error(
"Error while scheduling or canceling webhook triggers",
safeStringify({
errors: failures.map((f) => f.reason),
@@ -2504,7 +2529,7 @@ async function handler(
});
}
} catch (error) {
loggerWithEventDetails.error("Error while creating booking references", JSON.stringify({ error }));
tracingLogger.error("Error while creating booking references", JSON.stringify({ error }));
}
const evtWithMetadata = {
@@ -2540,7 +2565,7 @@ async function handler(
isRescheduleEvent: !!rescheduleUid,
});
} catch (error) {
loggerWithEventDetails.error("Error while scheduling workflow reminders", JSON.stringify({ error }));
tracingLogger.error("Error while scheduling workflow reminders", JSON.stringify({ error }));
}
try {
@@ -2561,7 +2586,7 @@ async function handler(
});
}
} catch (error) {
loggerWithEventDetails.error("Error while scheduling no show triggers", JSON.stringify({ error }));
tracingLogger.error("Error while scheduling no show triggers", JSON.stringify({ error }));
}
if (!isDryRun) {
+20 -4
View File
@@ -11,7 +11,11 @@ describe("defaultResponder", () => {
it("should call res.json when response is still writable and result is not null", async () => {
const f = vi.fn().mockResolvedValue({});
const req = {} as NextApiRequest;
const res = { json: vi.fn(), writableEnded: false } as unknown as NextApiResponse;
const res = {
json: vi.fn(),
writableEnded: false,
setHeader: vi.fn(),
} as unknown as NextApiResponse;
await defaultResponder(f)(req, res);
expect(res.json).toHaveBeenCalled();
});
@@ -19,21 +23,33 @@ describe("defaultResponder", () => {
it("should not call res.json when response is not writable", async () => {
const f = vi.fn().mockResolvedValue({});
const req = {} as NextApiRequest;
const res = { json: vi.fn(), writableEnded: true } as unknown as NextApiResponse;
const res = {
json: vi.fn(),
writableEnded: true,
setHeader: vi.fn(),
} as unknown as NextApiResponse;
await defaultResponder(f)(req, res);
expect(res.json).not.toHaveBeenCalled();
});
it("should respond with status code 409 for NoAvailableUsersFound", async () => {
const f = vi.fn().mockRejectedValue(new Error(ErrorCode.NoAvailableUsersFound));
const req = {} as NextApiRequest;
const res = { status: vi.fn().mockReturnThis(), json: vi.fn() } as unknown as NextApiResponse;
const res = {
status: vi.fn().mockReturnThis(),
json: vi.fn(),
setHeader: vi.fn(),
} as unknown as NextApiResponse;
await defaultResponder(f)(req, res);
expect(res.status).toHaveBeenCalledWith(409);
});
it("Rate limit should respond with a 429 status code", async () => {
const f = vi.fn().mockRejectedValue(new TRPCError({ code: "TOO_MANY_REQUESTS" }));
const req = {} as NextApiRequest;
const res = { status: vi.fn().mockReturnThis(), json: vi.fn() } as unknown as NextApiResponse;
const res = {
status: vi.fn().mockReturnThis(),
json: vi.fn(),
setHeader: vi.fn(),
} as unknown as NextApiResponse;
await defaultResponder(f)(req, res);
expect(res.status).toHaveBeenCalledWith(429);
});
+28 -4
View File
@@ -3,11 +3,19 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
import { type TraceContext } from "@calcom/lib/tracing";
import { TracedError } from "@calcom/lib/tracing/error";
import { distributedTracing } from "@calcom/lib/tracing/factory";
import { HttpError } from "../http-error";
import { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
import { performance } from "./perfObserver";
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
export interface TracedRequest extends NextApiRequest {
traceContext: TraceContext;
}
type Handle<T> = (req: TracedRequest, res: NextApiResponse) => Promise<T>;
/** Allows us to get type inference from API handler responses */
export function defaultResponder<T>(
@@ -17,22 +25,37 @@ export function defaultResponder<T>(
) {
return async (req: NextApiRequest, res: NextApiResponse) => {
let ok = false;
const operation = endpointRoute ? endpointRoute.replace(/^\//, "").replace(/\//g, "_") : "api_request";
const traceContext = distributedTracing.createTrace(operation, {
meta: {
method: req.method || "",
url: req.url || "",
},
});
const tracingLogger = distributedTracing.getTracingLogger(traceContext);
const tracedReq = req as TracedRequest;
tracedReq.traceContext = traceContext;
try {
performance.mark("Start");
let result: T | undefined;
if (process.env.NODE_ENV === "development" || !endpointRoute) {
result = await f(req, res);
result = await f(tracedReq, res);
} else {
const { wrapApiHandlerWithSentry } = await import("@sentry/nextjs");
result = await wrapApiHandlerWithSentry(f, endpointRoute)(req, res);
result = await wrapApiHandlerWithSentry(f, endpointRoute)(tracedReq, res);
}
ok = true;
if (result && !res.writableEnded) {
res.setHeader("X-Trace-Id", traceContext.traceId);
return res.json(result);
}
} catch (err) {
tracingLogger.error(`${operation} request failed`, { error: err });
const tracedError = TracedError.createFromError(err, traceContext);
let error: HttpError;
if (err instanceof TRPCError) {
const statusCode = getHTTPStatusCodeFromError(err);
@@ -44,8 +67,9 @@ export function defaultResponder<T>(
if (!(error.statusCode >= 400 && error.statusCode < 500)) {
console.error(error);
const { captureException } = await import("@sentry/nextjs");
captureException(error);
captureException(tracedError);
}
res.setHeader("X-Trace-Id", tracedError.traceId);
return res
.status(error.statusCode)
.json({ message: error.message, url: error.url, method: error.method, data: error?.data || null });
+17 -6
View File
@@ -6,7 +6,7 @@ export interface TraceContext {
parentSpanId?: string;
operation: string;
// So that we don't violate open closed principle, we allow meta to be added to the trace context
meta?: Record<string, string>;
meta?: Record<string, string | number | boolean | null | undefined>;
}
export interface IdGenerator {
@@ -31,7 +31,7 @@ export class DistributedTracing {
createSpan(
parentContext: TraceContext,
operation: string,
additionalMeta?: Record<string, string>
additionalMeta?: Record<string, string | number | boolean | null | undefined>
): TraceContext {
const mergedMeta = {
...parentContext.meta,
@@ -47,7 +47,15 @@ export class DistributedTracing {
};
}
getTracingLogger(context: TraceContext) {
getTracingLogger(
context: TraceContext,
additionalMeta?: Record<string, string | number | boolean | null | undefined>
) {
const mergedMeta = {
...context.meta,
...additionalMeta,
};
const prefixes = [
"distributed-trace",
`trace:${context.traceId}`,
@@ -55,8 +63,8 @@ export class DistributedTracing {
`op:${context.operation}`,
];
if (context.meta) {
Object.entries(context.meta).forEach(([key, value]) => {
if (mergedMeta) {
Object.entries(mergedMeta).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
prefixes.push(`${key}:${value}`);
}
@@ -85,7 +93,10 @@ export class DistributedTracing {
};
}
updateTrace(traceContext: TraceContext, additionalMeta?: Record<string, string>): TraceContext {
updateTrace(
traceContext: TraceContext,
additionalMeta?: Record<string, string | number | boolean | null | undefined>
): TraceContext {
return {
...traceContext,
meta: {
+12 -2
View File
@@ -5,7 +5,9 @@ import type { serverSideTranslations } from "next-i18next/serverSideTranslations
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import getIP from "@calcom/lib/getIP";
import prisma, { readonlyPrisma } from "@calcom/prisma";
import type { TraceContext } from "@calcom/lib/tracing";
import { distributedTracing } from "@calcom/lib/tracing/factory";
import { prisma, readonlyPrisma } from "@calcom/prisma";
import type { SelectedCalendar, User as PrismaUser } from "@calcom/prisma/client";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
@@ -50,6 +52,7 @@ export type GetSessionFn =
export type InnerContext = CreateInnerContextOptions & {
prisma: typeof prisma;
insightsDb: typeof readonlyPrisma;
traceContext: TraceContext;
};
/**
@@ -62,10 +65,17 @@ export type InnerContext = CreateInnerContextOptions & {
* @see https://trpc.io/docs/context#inner-and-outer-context
*/
export async function createContextInner(opts: CreateInnerContextOptions): Promise<InnerContext> {
const traceContext = distributedTracing.createTrace("trpc_request", {
meta: {
userId: opts.session?.user?.id?.toString() || "anonymous",
},
});
return {
prisma,
insightsDb: readonlyPrisma,
...opts,
traceContext,
};
}
@@ -87,7 +97,7 @@ export const createContext = async (
// This type may not be accurate if this request is coming from SSG init but they both should satisfy the requirements of getIP.
// TODO: @sean - figure out a way to make getIP be happy with trpc req. params
const sourceIp = getIP(req as NextApiRequest);
const session = !!sessionGetter ? await sessionGetter({ req, res }) : null;
const session = sessionGetter ? await sessionGetter({ req, res }) : null;
const contextInner = await createContextInner({ locale, session, sourceIp });
return {
...contextInner,