-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(node): Add Sentry tRPC middleware #7511
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
Conversation
cc @kamilogorek :P |
packages/node/src/index.ts
Outdated
@@ -65,6 +65,8 @@ const INTEGRATIONS = { | |||
|
|||
export { INTEGRATIONS as Integrations, Handlers }; | |||
|
|||
export { sentryTrpcMiddleware } from './trpc'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if lib-specific code should be exported directly from our core node package tbh. And if so, we can maybe put it under handlers.ts
, where our express-related code lives already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't know where else to put it. I think it is fine here, considering we also have stuff like specific DB integrations exported from the node pkg. Do you know a better place?
Also, I wouldn't put it into handlers. It's not really a handler lol. Also, I don't think it matters too much in which file we put it. I just wanted to be able to find it quickly if I fuzzy search for "trpc".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine here, considering we also have stuff like specific DB integrations exported from the node pkg.
We don't, it's exported from tracing package :P
It's not really a handler lol.
Neither are express request
and tracing
"middlewares" that we call handlers, yet we expose them via Handlers
:P
wdyt @AbhiPrasad? If we leave it here, I'd simplify the name to tRPCMiddleware
as import 'sentrySomething' from 'sentry'
sounds strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't, it's exported from tracing package :P
This will change, we want to rm -rf
the tracing package. #7346
I'd simplify the name to tRPCMiddleware as import 'sentrySomething' from 'sentry' sounds strange.
Sounds fine to me. Only concern we might have is that it might collide with other packages, which might be annoying for vscode autocomplete (not a huge deal, but still some DX we need to think about).
packages/node/src/trpc.ts
Outdated
procedureType: type, | ||
}; | ||
|
||
if (options.attachRpcInput !== undefined ? options.attachRpcInput : clientOptions?.sendDefaultPii) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be somewhat confusing to users. sendDefaultPii
is documented as something that controls headers and cookies, not the user-provided input.
I'd probably change it to options.attachRpcInput
and add default to options: SentryTrpcMiddlewareOptions = {}
, which is not currently set as optional anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
packages/node/src/trpc.ts
Outdated
const sentryTransaction = hub.getScope()?.getTransaction(); | ||
|
||
if (sentryTransaction) { | ||
sentryTransaction.setName(`${path}()`, 'route'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this standardized format for RPC? We use urls directly as route
value in other places, sometimes prepended with a METHOD
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just use the path without added ()
when logging myself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked the team if we have precedence or a convention around transaction names for RPCs. I believe it comes down to preference of how you would like it to look in the product.
Here, for tRPC at least, imo it doesn't make too much sense to show the METHOD
in the transaction name since it is always POST anyways (correct me if I am wrong ^^) and it would just be noise. The method name is still included in the span details if the server is properly instrumented otherwise.
Considering the point about noise above, I think we should drop the ()
. It just makes the transaction name more lengthy without any real value. I believe I added it at first because it denoted quite well that something was a method call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should match OpenTelemetry here, which names their spans as: $package.$service/$method
.
We do this already in our OpenTelemetry transformations across the different SDKs.
packages/node/src/trpc.ts
Outdated
|
||
if (sentryTransaction) { | ||
sentryTransaction.setName(`${path}()`, 'route'); | ||
sentryTransaction.op = 'rpc.server'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md I don't see rpc.server
operation in the spec we follow, there's rpc
only. Do we make a distinction here ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I think it is fine to append stuff to the span op. I think there is a hierarchy to it. The docs are just a guide to set the baseline ops we should use.
Here I decided to append .server
because we could at some point decide that we would like to instrument the client side part of RPCs and there we would probably call it rpc.client
. Similar to how we do it with http.server
and http.client
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is fine, we can update our span operations spec to account for this https://develop.sentry.dev/sdk/performance/span-operations/
Co-authored-by: Kamil Ogórek <kamil.ogorek@gmail.com>
packages/node/src/trpc.ts
Outdated
sentryTransaction.setData('trpc', trpcData); | ||
} | ||
|
||
return next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to log something if the procedure actually fails? https://trpc.io/docs/server/middlewares#logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but for now I don't think we need to add this. People will still need to use this integration with a proper server framework integration like the Express integration for it to work, and those integrations will handle the errors.
We could at some point add an option that allows capturing of non-200 responses.
size-limit report 📦
|
packages/node/src/index.ts
Outdated
@@ -65,6 +65,8 @@ const INTEGRATIONS = { | |||
|
|||
export { INTEGRATIONS as Integrations, Handlers }; | |||
|
|||
export { trpcMiddleware } from './trpc'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h: let's put this under Handlers
for now. I don't think we need to worry about tree-shaking and this matches the pattern we have with the rest of the SDK. We can re-evaluate this at a later time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! f74e161
packages/node/src/handlers.ts
Outdated
trpcData.procedureInput = normalize(rawInput); | ||
} | ||
|
||
sentryTransaction.setData('trpc', trpcData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transaction.setData
does nothing on the event atm - can we add it as context instead? If we do that, can just just add it globally on the scope so that errors also get this info?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we had this exact discussion before ^^ Data definitely appears in the transaction view:
But I agree that a context is more valuable: 6ff778e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah it does appear in trace context!!
Keep forgetting 🤦
@@ -315,6 +316,48 @@ export function errorHandler(options?: { | |||
}; | |||
} | |||
|
|||
interface SentryTrpcMiddlewareOptions { | |||
attachRpcInput?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: can we add a doc string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 8ab4c72
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: IMO we should use snake_vas vars since that matches the convention of every single other context we set. If we want to make it look nice in the UI we can make frontend changes there.
But I'm fine to merge without this.
packages/node/src/handlers.ts
Outdated
}; | ||
|
||
if (options.attachRpcInput !== undefined ? options.attachRpcInput : clientOptions?.sendDefaultPii) { | ||
trpcData.procedureInput = normalize(rawInput); | ||
trpcContext['Input'] = normalize(rawInput); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trpcContext['Input'] = normalize(rawInput); | |
trpcContext['input'] = normalize(rawInput); |
packages/node/src/handlers.ts
Outdated
const trpcData: Record<string, unknown> = { | ||
procedureType: type, | ||
const trpcContext: Record<string, unknown> = { | ||
'Procedure Type': type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'Procedure Type': type, | |
procedure_type: type, |
Couldn't find any cases in the JS repo where we set context with snake case except for in tests but I am fine with it. |
Is there an example repo on how to use this in the t3 stack? |
Hey @hichemfantar - I wrote up a small guide for using Sentry with the t3 stack here: #8500 |
Hello, @AbhiPrasad - I'm trying to have the setup in t3 turbo but it's not working for some reason. import * as Sentry from "@sentry/nextjs";
// export API handler
export default withCors(
createNextApiHandler({
router: businessApiRouter,
createContext: createTRPCContext,
/**
* @link https://trpc.io/docs/error-handling
*/
onError({ error }) {
Sentry.captureException(error);
Sentry.captureMessage("Something went wrong");
if (error.code === 'INTERNAL_SERVER_ERROR') {
// send to bug reporting
console.error('Something went wrong', error);
}
},
// for some reason the 'any' is needed here
}) as any);
```
I also tried setting a middleware in this file `https://github.com/t3-oss/create-t3-turbo/blob/main/packages/api/src/trpc.ts` but it's not working even tho it's returning 500.
```ts
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: "https://4778f57e03c4439ca06b662a083a2d02@o4505510253887488.ingest.sentry.io/4505510279970816",
integrations: [new CaptureConsole()],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
});
const sentryMiddleware = t.middleware(
Sentry.Handlers.trpcMiddleware({
attachRpcInput: true,
})
);
export const publicProcedure = t.procedure.use(sentryMiddleware )
``` |
Hey @hichemfantar, adding the Let's move convo into #8500 please, I'll be locking this issue. |
This PR adds tRPC support.
Why? tRPC usually requires you to define a particular route of your http server to handle RPC requests. If not instrumented, these requests will show up in the performance product under one transaction name.
The Sentry tRPC middleware will take care of meaningfully naming the transactions and attaches some additional information if configured.