I have a nestjs application which handles some marketing automation tasks. I have set up datadog and sentry and I keep getting errors. When I replay the bodies locally, I get no errors. I'm a bit stuck here.
The error I get on sentry is the following (i'm stringifying the error as additional data)
{
"success": false,
"message": "Bad Request",
"errorCode": 400,
"context": {
"req": {
"method": "PUT",
"url": "https://api.pipedrive.com/v1/persons/84653?api_token=my_token",
"data": {},
"headers": {
"user-agent": "Pipedrive-SDK-Javascript-16.0.3",
"content-type": "application/json",
"accept": "application/json"
}
},
"header": {
"date": "Thu, 22 Sep 2022 22:01:46 GMT",
"content-type": "application/json",
"transfer-encoding": "chunked",
"connection": "close",
"cf-ray": "74ee5231de24f40b-LHR",
"access-control-allow-origin": "*",
"cache-control": "no-cache",
"content-encoding": "gzip",
"strict-transport-security": "max-age=31536000; includeSubDomains",
"vary": "Accept-Encoding",
"cf-cache-status": "DYNAMIC",
"access-control-expose-headers": "X-RateLimit-Remaining, X-RateLimit-Limit, X-RateLimit-Reset",
"badi": "Routing: eu-central-1=>eu-central-1; Version: 8cce; Host: bari;",
"x-content-type-options": "nosniff",
"x-correlation-id": "5ccf217c-aee7-4d8f-ab89-70277fb5b4f7",
"x-daily-requests-left": "6918",
"x-frame-options": "SAMEORIGIN",
"x-ratelimit-limit": "40",
"x-ratelimit-remaining": "37",
"x-ratelimit-reset": "2",
"x-xss-protection": "1; mode=block",
"set-cookie": [
"__cf_bm=bqPOYf9wW8omTTIvIQnhXgCqxmqexoZCvl0tRkmINBo-1663884106-0-AWQN9mC/Fq+ELcoe7Pfp2aTXcv9JapLJYfrMsUbE3+lOJGgva9hLWHPLGy97S7tDNszaDW5tew4//3R2mVz9bcI=; path=/; expires=Thu, 22-Sep-22 22:31:46 GMT; domain=.pipedrive.com; HttpOnly; Secure; SameSite=None"
],
"server": "cloudflare",
"alt-svc": "h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400"
},
"status": 400,
"text": "{\"success\":false,\"error\":\"Bad request\",\"error_info\":\"Please check developers.pipedrive.com for more information about Pipedrive API.\",\"data\":null,\"additional_data\":null}"
},
"errorInfo": "Please check developers.pipedrive.com for more information about Pipedrive API."
}
It seems related to making a PUT request on pipedrive with an empty body.
However my code for that is the following:
async updatePerson(id: number, person: IPipedrivePersonDto): Promise<PipedrivePerson> {
if (isEmpty(person)) {
throw new Error(`Can't update a person with no body`);
}
const personApi = new this.pipeDriveClient.PersonsApi();
const opts = Pipedrive.UpdatePerson.constructFromObject(person);
if (isEmpty(opts)) {
throw new Error(`Can't update a person with no body: ` + JSON.stringify(person));
}
const { data } = (await personApi.updatePerson(id, opts)) as PersonResponse;
return data;
}
And when I do trick my body to get an empty body it gets caught.
So now, sentry does not give me the entire stack trace, the list of lines of code executed. That might help me.
How can I get that ? How else can I debug something I can't reproduce? I also suspect the pipedrive node js client to make some hidden PUT requests, cause there is only the aforementioned snippet that handles it in my code.
Here is the full code of my http exception filter on nestjs, maybe I can pipm it somehow to get more details:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
import { SentryService } from '@ntegral/nestjs-sentry';
import { Request, Response } from 'express';
import { CustomLoggerService } from 'src/core/logger/logger.service';
import { PipedriveResponseException } from 'src/thirdParties/pipedrive/pipedrive.exception';
import { SlackResponseException } from 'src/thirdParties/slack/slack.exception';
import { injectRecord } from 'src/utils/datadog';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private readonly sentry: SentryService, private readonly logger: CustomLoggerService) {}
private logAndSendResponse(
exception: PipedriveResponseException | HttpException,
request: Request,
response: Response,
jsonResponse: object,
message: string | null,
status: number,
) {
const record = injectRecord({ ...jsonResponse, stack: exception.stack }, 'error');
this.logger.logger.error(`${request.method} ${request.originalUrl}`, record, exception);
this.sentry.instance().setExtra('response', jsonResponse);
this.sentry.instance().setTag('message', message);
response.status(status).json(jsonResponse);
}
catch(exception: PipedriveResponseException | HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const pipeDriveException = exception as PipedriveResponseException;
const slackException = exception as unknown as SlackResponseException;
const globalException = exception as HttpException;
const baseResponse = {
timestamp: new Date().toISOString(),
path: request.url,
};
this.sentry.instance().setTag('url', request.url);
this.sentry.instance().setTag('method', request.method);
this.sentry.instance().setExtra('stringified-error', JSON.stringify(exception));
// Catch slack exceptions
if (slackException.code && slackException.data?.error) {
const status = 400;
const message = slackException?.data?.error || null;
const jsonResponse = {
...baseResponse,
statusCode: status,
message: message,
};
this.logAndSendResponse(exception, request, response, jsonResponse, message, status);
// Catch pipedrive exceptions
} else if (pipeDriveException.context && pipeDriveException.errorCode && pipeDriveException.errorInfo) {
const status = pipeDriveException.errorCode;
const message = pipeDriveException.context?._body?.error || null;
const jsonResponse = {
...baseResponse,
statusCode: status,
message: message,
};
this.logAndSendResponse(exception, request, response, jsonResponse, message, status);
} else {
const jsonResponse = {
...baseResponse,
exception,
};
this.logAndSendResponse(exception, request, response, jsonResponse, globalException.message, 500);
}
this.sentry.instance().captureException(exception);
}
}
Thanks for your help :)