-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Integration for FastifyJS #4784
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
Comments
Official support like express would be great! |
I wrote and use the following code for FastifyJS 3, it works but has problems due to global scoping. Might be useful to someone, better than nothing. I would be happy to have official support. function parseRequest(event: Event, req: FastifyRequest) {
event.contexts = {
...event.contexts,
runtime: {
name: 'node',
version: process.version,
},
};
event.request = {
...event.request,
url: `${req.protocol}://${req.hostname}${req.url}`,
method: req.method,
headers: req.headers as Record<string, string>,
query_string: req.query as Record<string, string>,
};
return event;
}
fastify.addHook('onRequest', (req, reply, done) => {
const currentHub = getCurrentHub();
currentHub.pushScope();
currentHub.configureScope((scope) => {
scope.addEventProcessor((event) => parseRequest(event, req));
});
done();
});
fastify.addHook('onResponse', (req, reply, done) => {
const currentHub = getCurrentHub();
currentHub.popScope();
done();
}); fastify.setErrorHandler(async (error: FastifyError, req, reply) => {
if (!error.statusCode || error.statusCode >= 500) {
withScope((scope) => {
captureException(error);
});
return res.send('Something is wrong, please contact support');
}
return res.send(error);
}); |
I would love this. Fastify is a growing framework with over 500k downloads / month. |
@AbhiPrasad Is there any progress in this direction? Can we at least discuss theoretically how to do this? |
You can bind the hub to the current request using domains or async hooks - storing it in async storage. This is what we do in nextjs for example - #3788 It’s not ideal, but using async storage mechanisms is what every observability SDK for node uses. We will revaluate this at a later point. |
@AbhiPrasad Thanks for the link! As far as I can see, there is still a domain in use that is deprecated and "especially" not recommended. However, yes, without a serious rethinking of the Sentry architecture at the moment it is difficult to find another way. |
My first version of a plugin for catching errors in Fastify. I'm interested to see what anyone thinks. Known Issues
Code Register the plugin: await app.register(sentry, {
dsn: process.env.SENTRY_DNS,
});
import { create } from 'domain';
import fastifyPlugin from 'fastify-plugin';
import { init, getCurrentHub, captureException, type NodeOptions, type Event } from '@sentry/node';
import type { FastifyRequest, FastifyPluginCallback, FastifyError } from 'fastify';
function parseRequest(event: Event, req: FastifyRequest) {
event.contexts = {
...event.contexts,
runtime: {
name: 'node',
version: process.version,
},
};
event.request = {
...event.request,
url: `${req.protocol}://${req.hostname}${req.url}`,
method: req.method,
headers: req.headers as Record<string, string>,
query_string: req.query as Record<string, string>,
};
return event;
}
const sentry: FastifyPluginCallback<NodeOptions> = (fastify, options, next) => {
init(options);
fastify.addHook('onRoute', (options) => {
const originalHandler = options.handler;
options.handler = async (req, res) => {
const domain = create();
domain.add(req as never);
domain.add(res as never);
const boundHandler = domain.bind(async () => {
getCurrentHub().configureScope((scope) => {
scope.addEventProcessor((event) => parseRequest(event, req));
});
try {
return await originalHandler.call(fastify, req, res);
} catch (error) {
// If there is no statusCode, it means an internal error that was not fired by Fastify.
// Maybe it's good to catch 5xx errors as well. Can be removed or added as plugin options.
if (!(error as FastifyError).statusCode || (error as FastifyError).statusCode! >= 500) {
captureException(error);
}
throw error;
}
});
await boundHandler();
};
});
next();
};
export default fastifyPlugin(sentry, {
fastify: '4.x',
name: '@sentry/fastify',
}); |
I am using @fastify/middle .
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Sentry = require('@sentry/node');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ProfilingIntegration } = require('@sentry/profiling-node');
// eslint-disable-next-line @typescript-eslint/no-var-requires
// const Tracing = require('@sentry/tracing');
Sentry.init({
dsn: dataSourceName,
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Http({ tracing: true }),
// // enable Express.js middleware tracing
// new Tracing.Integrations.Express({ app: app.getHttpAdapter().getInstance() }),
new ProfilingIntegration(),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
// or pull from params
// tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE),
});
// RequestHandler creates a separate execution context using domains, so that every
// transaction/span/breadcrumb is attached to its own Hub instance
app.use(Sentry.Handlers.requestHandler());
// TracingHandler creates a trace for every incoming request
// app.use(Sentry.Handlers.tracingHandler());
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Sentry = require('@sentry/node');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ProfilingIntegration } = require('@sentry/profiling-node');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Tracing = require('@sentry/tracing');
Sentry.init({
dsn: dataSourceName,
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Http({ tracing: true }),
// enable Express.js middleware tracing
new Tracing.Integrations.Express({ app: app.getHttpAdapter().getInstance() }),
new ProfilingIntegration(),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
// or pull from params
// tracesSampleRate: parseFloat(params.SENTRY_TRACES_SAMPLE_RATE),
});
// RequestHandler creates a separate execution context using domains, so that every
// transaction/span/breadcrumb is attached to its own Hub instance
app.use(Sentry.Handlers.requestHandler());
// TracingHandler creates a trace for every incoming request
app.use(Sentry.Handlers.tracingHandler()); |
Hi I'm assisting an organization that could benefit from this. |
@thinkocapo import fastifyPlugin from 'fastify-plugin';
import {
init,
getCurrentHub,
captureException,
runWithAsyncContext,
type NodeOptions,
type Event,
} from '@sentry/node';
import type { FastifyRequest, FastifyPluginCallback, FastifyError } from 'fastify';
function parseRequest(event: Event, req: FastifyRequest) {
event.contexts = {
...event.contexts,
runtime: {
name: 'node',
version: process.version,
},
};
event.request = {
...event.request,
url: `${req.protocol}://${req.hostname}${req.url}`,
method: req.method,
headers: req.headers as Record<string, string>,
query_string: req.query as Record<string, string>,
};
return event;
}
const sentry: FastifyPluginCallback<NodeOptions> = (fastify, options, next) => {
init(options);
fastify.addHook('onRoute', (options) => {
const originalHandler = options.handler;
options.handler = async (req, res) => {
return runWithAsyncContext(async () => {
getCurrentHub().configureScope((scope) => {
scope.addEventProcessor((event) => parseRequest(event, req));
});
try {
return await originalHandler.call(fastify, req, res);
} catch (error) {
// If there is no statusCode, it means an internal error that was not fired by Fastify.
// Maybe it's good to catch 5xx errors as well. Can be removed or added as plugin options.
if (!(error as FastifyError).statusCode || (error as FastifyError).statusCode! >= 500) {
captureException(error);
console.log('onError', error);
}
throw error;
}
});
};
});
next();
};
export default fastifyPlugin(sentry, {
fastify: '4.x',
name: '@sentry/fastify',
}); P.S. I have been using it for production for a long time without any issues. |
Very open to help work on this, is there anything that needs assistance with? I work for a major company which uses Fastify extensively and recently started evaluating Sentry. Not having native official support is a major roadblock for general adoption currently. |
Coming soon in v8 of the sdk (alpha out now: https://www.npmjs.com/package/@sentry/node/v/8.0.0-alpha.9) const Sentry = require('@sentry/node');
Sentry.init({
dsn: process.env.E2E_TEST_DSN,
// distributed tracing + performance monitoring automatically enabled for fastify
tracesSampleRate: 1,
});
// Make sure Sentry is initialized before you require fastify
const { fastify } = require('fastify');
const app = fastify();
// add error handler to whatever fastify router instances you want (we recommend all of them!)
Sentry.setupFastifyErrorHandler(app); |
![]() @AbhiPrasad Just heads up that we are getting a type error with Sentry alpha-9 and Fastify 4.24.3. |
shoot we're on it! Thanks @gajus |
As per #4784 (comment) make sure fastify types use any is that we don't collide with what fastify types expect. Also convert e2e tests to use typescript to validate build.
Can I write my criticism?
await app.register(Sentry.setupFastify, {
dsn: process.env.SENTRY_DNS,
environment: process.env.NODE_ENV,
// ...etc
});
|
Thank you for the feedback! For 1., we would not want to do that because Sentry should be initialised before fastify is initialised, as otherwise we may miss errors in Fastify initialisation itself. For 2., especially in v8 this will not really be that much of an issue anymore, because fastify will be auto-instrumented without any config. So no need to do anything fastify specific, except to add an error handler. Having a dedicated package just for that feels a bit overkill IMHO :) |
@mydea Thank you very much for the explanation.
|
Some applications have complicated conditional logic (or run differently in production) during initialization. This is also important for getting accurate traces/spans for performance monitoring.
We avoid this via a very small public API surface. We have a general SDK philosophy to stay compatible with as many things as possible, and we'll make sure our integrations can work with multiple majors as much as possible (see what we do with nextjs as an example). |
As per getsentry#4784 (comment) make sure fastify types use any is that we don't collide with what fastify types expect. Also convert e2e tests to use typescript to validate build.
v8 is out and has proper fastify support! 🎉 |
I am using v8 with Fastify and the problem is that breadcrumbs include a lot of information that is not related to user's request. e.g. Those ping checks cannot be part of the error/user session because they are happening in background. Fastify is instantiated with: const app = fastify();
setupFastifyErrorHandler(app); Sentry is loaded before |
@mydea same issue ^ Lots of errors are just associated with wrong users/wrong requests/wrong breadcrumbs. |
@punkpeye mind opening a new GH issue? And adding the output of debug logs when you add We can investigate further then, we should be isolating the errors to each individual request. |
Problem Statement
https://www.fastify.io/
https://www.npmjs.com/package/fastify
Solution Brainstorm
Build an integration for Fastify, similar to what we do for Express
The text was updated successfully, but these errors were encountered: