-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exceptions from not sampled transactions are added to non-related transactions #4947
Comments
Hey, thanks for writing in! We recommend using something like sentry-javascript/packages/node/src/handlers.ts Lines 420 to 455 in c518955
|
This issue has gone three weeks without activity. In another week, I will close it. But! If you comment or otherwise update it, I will reset the clock, and if you label it "A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀 |
@AbhiPrasad I'm having some issues trying to understand how I can implement this properly. I have two things that are happening namely HTTP requests coming in and internal cron jobs running. I'm using NestJS but found that the @ntegral/nestjs-sentry is not the right fit for my situation and a bit outdated. Desired outcomeWhat I'm trying to achieve is to have logs that are related to a single request or process like a cron job to be sent along when an error occurs. Currently all of the logs from my cron job are being sent whenever a HTTP request is throwing an error. So they are not relevant. When not running any cron job the scoping is going fine and it will only send relevant logs of only the request when an error occurs. This is because I'm using the SetupWithout going into detail, I have a service that gives me the current process context, this context is used in other services and is setup using ProcessContextService@Injectable()
export class ProcessContextService {
/**
* Determine type of the current context that is used
*/
getType() {
if (RequestContext.currentContext) {
return 'request';
}
if (ManualProcessContext.currentContext) {
return 'process';
}
}
/**
* Get the contents of the current context
*/
getContents() {
const type = this.getType();
// Via HTTP
if (type === 'request') {
const req: Request = RequestContext.currentContext.req;
return req.meta;
}
// Manually created (cron jobs, ...)
else if (type === 'process') {
return ManualProcessContext.currentContext;
}
}
} ManualProcessContextimport { Injectable } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
import cuid from 'cuid';
import { ProcessContextContents } from './process-context.service';
@Injectable()
export class ManualProcessContext {
static cls = new AsyncLocalStorage<ProcessContextContents>();
static get currentContext() {
return this.cls.getStore();
}
static create() {
const id = cuid();
this.cls.enterWith({ contextId: id });
return id;
}
constructor() { }
} LoggerServiceimport { Injectable, LoggerService as NestLoggerService } from '@nestjs/common';
import { addBreadcrumb } from '@sentry/node';
@Injectable()
export class LoggerService implements NestLoggerService {
// ...
public log(message: any, context?: string): any {
// ...
addBreadcrumb({ message, level: 'info' });
// ...
}
// ...
} Cron job exampleimport { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TaskSchedulerService {
// ...
@Cron(CronExpression.EVERY_10_SECONDS)
async throwError() {
this.logger.log('Should only be sent when an error in this cron job occurs');
throw new Error('Cron job erroring');
}
} QuestionHow can I implement similar logic so that logs are properly scoped for my cron jobs like it is done for HTTP requests? |
@nealoke you'll have to use domains to isolate context, which is what the sentry-javascript/packages/node/src/handlers.ts Lines 183 to 187 in f8b74f0
|
@AbhiPrasad thanks for the answer but for me it is still unclear 😢. I mean I see the using of domain but don't really know how to go from there. My main questions are:
Thanks for your time and effort 🙏 |
As mentioned by @AbhiPrasad, you indeed use the import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TaskSchedulerService {
// ...
@Cron(CronExpression.EVERY_10_SECONDS)
async throwError() {
const local = domain.create();
local.run(() => {
this.logger.log('Should only be sent when an error in this cron job occurs');
throw new Error('this is an error');
});
}
} Doesn't work like thisWhat I don't understand however is that the following doesn't work, I'm essentially doing the same thing but the result here is that nothing of the TaskSchedulerServiceimport { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TaskSchedulerService {
// ...
@Cron(CronExpression.EVERY_10_SECONDS)
@ProvideManualProcess()
async throwError() {
this.logger.log('Should only be sent when an error in this cron job occurs');
throw new Error('this is an error');
}
} ProvideManualProcessimport * as domain from 'domain';
export const ProvideManualProcess = (): MethodDecorator => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const local = domain.create();
return local.run(() => {
return originalMethod.apply(this, [...args]);
});
};
return descriptor;
};
}; |
My previous solution wasn't great because as soon as I used async inside the Simplified solutionBy using import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TaskSchedulerService {
// ...
@Cron(CronExpression.EVERY_10_SECONDS)
@ProvideManualProcess()
async throwError() {
const local = domain.create();
local.enter(); // From this point on, everything is in this domain
this.logger.log('Should only be sent when an error in this cron job occurs');
throw new Error('this is an error');
}
} With a decorator (ProvideManualProcess)import * as domain from 'domain';
export const ProvideManualProcess = (): MethodDecorator => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const local = domain.create();
local.enter();
return originalMethod.apply(this, [processId, ...args]);
};
return descriptor;
};
}; |
Hey thank you for your patience while we got around to looking at this! To answer some questions:
In a temporary process you will to make sure Sentry has the time to flush events. You can use the
Yup, Sentry will use the correct domain under the hood for this.
The domain should be garbage collected if you use
Something we need to do better with - opened a GH issue to track: getsentry/sentry-docs#6541 As for using async code inside a domain, you can use
What I'm curious about is this code - there is nothing async about it, so I can't see why it wouldn't work with
I would shy away from using |
Thanks for the answer, and good that you made an issue for adjusting the docs. I can't be the only one 🙈
In the example code, is there a place where I would need to call this then? For now I can see that everything is working as it should.
It is simplified code that's why there is no
When I don't exit it but the process finished, it won't do it automatically? |
Ah yes if the process will be finished it will all be cleaned up, so no problems there, use |
Our usage of the |
This gives me nothing as result 😅 |
|
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which package are you using?
@sentry/node
SDK Version
6.19.6
Framework Version
6.19.6
Link to Sentry event
No response
Steps to Reproduce
I am trying to understand how to use transactions on a high concurrency environment (the server is going to get a lot of request that are going to overlap with each other). So I created a sandbox to test my use case this is what I did
I created a very simple Nest JS project with a single GET endpoint. This endpoint takes 1 second to return a response. I also started a transaction every time the endpoint is hit. The endpoint will randomly fail and in that case I will send an exception to Sentry. My main goal is to test what happens if there is an exception in a transaction that was not selected for sampling (I set the tracesSampleRate to 0.5 in the init method)
So to test this I used K6 and ran
k6 run --vus 10 --duration 2s k6.js
so I will get 10 requests concurrently.Here is the link to the repo
https://github.com/geraldosalazar16/sentry-concurrency
Expected Result
Well the expected result is that those exceptions will get related to some transaction (even if it did not happened during that transaction) I am not sure if this is intended or a bug. I see multiple exceptions linked to one transaction
Actual Result
I am not sure what the behaviour should be for exceptions captured during a non traced transaction but it is very confusing (at least to me) the way they show right now
The text was updated successfully, but these errors were encountered: