feat: enhance API v2 exception filters with user context for improved Axiom observability (#22157)
* feat: enhance API v2 exception filters with user context for improved Axiom observability - Add extractUserContext utility to safely extract user/org/team context from request - Enhance all 5 exception filters (HTTP, TRPC, Prisma, Zod, Calendar) with user context - Include userId, userEmail, organizationId, teamId in Axiom logs when available - Improve observability for debugging and monitoring authenticated requests Co-Authored-By: morgan@cal.com <morgan@cal.com> * chore: update yarn.lock from lint-staged hooks Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: use request.organization instead of request.organizationId - Address GitHub comment feedback from ThyMinimalDev - Extract organization ID from organization object for consistency - Follows same pattern as user and team extraction Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: revert to using request.organizationId as requested in GitHub comment - Change back from request.organization to request.organizationId - This aligns with how auth strategies actually populate the request object - Addresses GitHub comment from ThyMinimalDev Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
co-authored by
morgan@cal.com
Morgan
parent
95575291ff
commit
21422fe027
@@ -1,3 +1,4 @@
|
||||
import { extractUserContext } from "@/lib/extract-user-context";
|
||||
import { filterReqHeaders } from "@/lib/filterReqHeaders";
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
@@ -39,6 +40,7 @@ export class CalendarServiceExceptionFilter implements ExceptionFilter {
|
||||
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
|
||||
response.setHeader("X-Request-Id", requestId.toString());
|
||||
|
||||
const userContext = extractUserContext(request);
|
||||
this.logger.error(`Calendar Service Exception Filter: ${exception?.message}`, {
|
||||
exception,
|
||||
body: request.body,
|
||||
@@ -46,6 +48,7 @@ export class CalendarServiceExceptionFilter implements ExceptionFilter {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
requestId,
|
||||
...userContext,
|
||||
});
|
||||
|
||||
response.status(400).json({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { extractUserContext } from "@/lib/extract-user-context";
|
||||
import { filterReqHeaders } from "@/lib/filterReqHeaders";
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
@@ -16,6 +17,7 @@ export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
|
||||
const statusCode = exception.getStatus();
|
||||
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
|
||||
response.setHeader("X-Request-Id", requestId.toString());
|
||||
const userContext = extractUserContext(request);
|
||||
this.logger.error(`Http Exception Filter: ${exception?.message}`, {
|
||||
exception,
|
||||
body: request.body,
|
||||
@@ -23,6 +25,7 @@ export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
requestId,
|
||||
...userContext,
|
||||
});
|
||||
|
||||
response.status(statusCode).json({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { extractUserContext } from "@/lib/extract-user-context";
|
||||
import { filterReqHeaders } from "@/lib/filterReqHeaders";
|
||||
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
|
||||
import { Catch, HttpStatus, Logger } from "@nestjs/common";
|
||||
@@ -43,6 +44,7 @@ export class PrismaExceptionFilter implements ExceptionFilter {
|
||||
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
|
||||
response.setHeader("X-Request-Id", requestId.toString());
|
||||
|
||||
const userContext = extractUserContext(request);
|
||||
this.logger.error(`PrismaError: ${error.message}`, {
|
||||
error,
|
||||
body: request.body,
|
||||
@@ -50,6 +52,7 @@ export class PrismaExceptionFilter implements ExceptionFilter {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
requestId,
|
||||
...userContext,
|
||||
});
|
||||
|
||||
let message = "There was an error, please try again later.";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { extractUserContext } from "@/lib/extract-user-context";
|
||||
import { filterReqHeaders } from "@/lib/filterReqHeaders";
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
@@ -120,6 +121,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
|
||||
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
|
||||
response.setHeader("X-Request-Id", requestId.toString());
|
||||
|
||||
const userContext = extractUserContext(request);
|
||||
this.logger.error(`TRPC Exception Filter: ${exception?.message}`, {
|
||||
exception,
|
||||
body: request.body,
|
||||
@@ -127,6 +129,7 @@ export class TRPCExceptionFilter implements ExceptionFilter {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
requestId,
|
||||
...userContext,
|
||||
});
|
||||
|
||||
response.status(statusCode).json({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { extractUserContext } from "@/lib/extract-user-context";
|
||||
import { filterReqHeaders } from "@/lib/filterReqHeaders";
|
||||
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
|
||||
import { Catch, HttpStatus, Logger } from "@nestjs/common";
|
||||
@@ -18,6 +19,7 @@ export class ZodExceptionFilter implements ExceptionFilter {
|
||||
const requestId = request.headers["X-Request-Id"] ?? "unknown-request-id";
|
||||
response.setHeader("X-Request-Id", requestId.toString());
|
||||
|
||||
const userContext = extractUserContext(request);
|
||||
this.logger.error(`ZodError: ${error.message}`, {
|
||||
error,
|
||||
body: request.body,
|
||||
@@ -25,6 +27,7 @@ export class ZodExceptionFilter implements ExceptionFilter {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
requestId,
|
||||
...userContext,
|
||||
});
|
||||
|
||||
response.status(HttpStatus.BAD_REQUEST).json({
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Request } from "express";
|
||||
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
import { ApiAuthGuardUser } from "../modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
|
||||
export interface UserContext {
|
||||
userId?: number;
|
||||
userEmail?: string;
|
||||
organizationId?: number;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
export function extractUserContext(request: Request): UserContext {
|
||||
const context: UserContext = {};
|
||||
|
||||
const user = (request as any).user as ApiAuthGuardUser | undefined;
|
||||
if (user) {
|
||||
context.userId = user.id;
|
||||
context.userEmail = user.email;
|
||||
}
|
||||
|
||||
const organizationId = (request as any).organizationId as number | undefined;
|
||||
if (organizationId) {
|
||||
context.organizationId = organizationId;
|
||||
}
|
||||
|
||||
const team = (request as any).team as Team | undefined;
|
||||
if (team) {
|
||||
context.teamId = team.id;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user