Skip to content
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

feat(remix): Migrate to opentelemetry-instrumentation-remix. #12110

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from

Conversation

onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented May 17, 2024

Ref: #11040

Migrates Remix server-side SDK to opentelemetry-instrumentation-remix.

This PR also keeps the original implementation not the break the developer experience for non-Express Remix projects.
Remix projects using Express are supported as is using the new autoInstrumentRemix option.

Usage with Express:

// instrument.(cjs | mjs)
const Sentry = require('@sentry/remix');

Sentry.init({
  dsn: YOUR_DSN
  // ...
  // auto instrument Remix with OpenTelemetry
  autoInstrumentRemix: true,
  // Optionally capture action formData attributes with errors.
  // This requires `sendDefaultPii` set to true as well.
  captureActionFormDataKeys: {
    file: true,
    text: true,
  },
  // To capture action formData attributes.
  sendDefaultPii: true
});
// server.(cjs | mjs)
// import the Sentry instrumentation file before anything else.
import './instrument.cjs';
// alternatively `require('./instrument.cjs')`

// ...

const app = express();

// ...

Usage with built-in Remix server:

You need to run the Remix server with NODE_OPTIONS=--require(...)` set.

// package.json

// ...
  "scripts": {
    "start": "NODE_OPTIONS='--require=./instrument.server.cjs' remix-serve build"
    // or
    "start": "NODE_OPTIONS='--import=./instrument.server.mjs' remix-serve build"
  }
// ...

This PR removes:

  • Express server adapter.
    There is no need to use wrapExpressCreateRequestHandler anymore even if you don't opt-in to autoInstrumentRemix. wrapExpressCreateRequestHandler is kept exported as a no-op function.

  • Built in HTTP incoming request instrumentation for both legacy and otel implementations. Instead we mark requestHandler spans as the root http.server spans.

When autoInstrumentRemix is set to true, this update replaces:

  • Performance tracing on action / loader / documentRequest functions. Leaving them to be traced by opentelemetry-instrumentation-remix

  • Request handler instrumentation as they are also traced by opentelemetry-instrumentation-remix

  • Auto-instrumentation for http as default integration for Remix SDK.

  • With the new instrumentation, pageload span is the child of the loader span. (Example: Trace)

  • Legacy instrumentation keeps recording pageload span as the child of the http.server span. (Example: Trace)

Also:

Migrates Remix integration tests from Jest to Vitest.

Fixes Backlogged Issues:

@onurtemizkan onurtemizkan force-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895 Compare May 20, 2024 11:13
@onurtemizkan onurtemizkan requested review from mydea and Lms24 May 20, 2024 11:46
@onurtemizkan onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
@@ -10,7 +10,7 @@ test('Sends two linked transactions (server & client) to Sentry', async ({ page
const httpServerTransactionPromise = waitForTransaction('create-remix-app-v2', transactionEvent => {
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, we should fix this to be http.server - this is important I believe!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marked requestHandler spans as http.server 👍

*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we still instrument outgoing requests without this? Do we really need to remove this? Most otel instrumentation is on top of this...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Http instrumentation's functionality is covered by requestHandler spans by the opentelemetry-instrumentation-remix.

In this context, HTTP instrumentation also ends up setting an unparameterised route as the root transaction name as I've seen from the tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the http span become the parent of the remix request handler spans?

If so, could we just extend the HTTP integration for remix to filter out server spans by default? So we still get instrumentation for outgoing requests?

Otherwise I wonder if we should actually vendor in https://github.com/justindsmith/opentelemetry-instrumentations-js/blob/main/packages/instrumentation-remix/src/instrumentation.ts and change it that way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense to me, just extended the Http integration the same way it's done in Next.js. Also marked requestHandler spans as http.server.

@@ -57,7 +57,7 @@ const config: PlaywrightTestConfig = {
port: eventProxyPort,
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to run it like this, does it not work with require on top? Just to clarify!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to run it like this. esbuild does not bundle the instrumentation inside the entry files correctly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I wonder if this qualifies as a breaking change 😬 thoughts cc @AbhiPrasad ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this does qualify as a breaking change 😬

I guess we have to opt-in to this mode, we can't break the people who already put the work in to upgrade their sdk to v8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make this the default during onboarding though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that makes sense. So I would say we do:

  1. Expose remixIntegration but do not add it by default
  2. Expose remixHttpIntegration but do not add it by default
  3. Users have to add both of these if they want to opt-in (the latter "overwrites" the default http integration)

Does that sound good? 🤔

Questions that remain:

a. Can we find a way to make 2 obsolete? Can't think of a good way to do it sadly 😬
b. Should we then (eventually) document both paths, or just the "new" path? I guess the "old" way could remain as an alternative installation method...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we try adding another init option like useOtel to switch to this mode, and initialise the integrations without (or also with) exposing them?

This change also doesn't affect Express servers (or I guess any other custom server), a top-level import on the server file works in those cases. So we can still get rid of the Express adapter instrumentation, and keep the core instrumentation as an alternative for built-in server users (which I also guess is not very popular among Remix users looking at our issue reports)

I have updated the e2e tests using Express custom servers. (0e2f73a)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds good - let's make an option for this and adjust default integrations based on this!

@onurtemizkan onurtemizkan force-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185e Compare May 24, 2024 15:56
@AbhiPrasad
Copy link
Member

@onurtemizkan could you add the new setup instructions to the readme of this PR? and also add a short note about what the migration looks like?

export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's refactor this to something like:

function getRemixDefaultIntegrations() {
  return [...]
}

// further down:

if (options.autoInstrumentRemix) {
  options.defaultIntegrations = getRemixDefaultIntegrations(options);
} else {
  instrumentServer();
}

This way users can opt in to the new behavior, but keep the old behavior working if needed?

This also means that wrapExpressCreateRequestHandler should probably remain as it was before, users need to opt-in to not use that anymore...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping incoming requests worked well both on legacy and otel implementations. I think I managed to keep the old behaviour, without requiring wrapExpressCreateRequestHandler, creating http.server span manually.
Dropping a sample event here

name: 'Remix',
setupOnce() {
addOpenTelemetryInstrumentation(
new RemixInstrumentation({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Let's re-write this based on the changes done in #12213, which means extracting this into a instrumentRemix method which is called in setupOnce. You'll have to put the options into module scope so they can be updated later...!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@onurtemizkan onurtemizkan force-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656 Compare June 10, 2024 15:09
Copy link
Contributor

github-actions bot commented Jun 11, 2024

size-limit report 📦

Path Size
@sentry/browser 22.04 KB (0%)
@sentry/browser (incl. Tracing) 33.23 KB (0%)
@sentry/browser (incl. Tracing, Replay) 68.95 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas) 73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback) 85.14 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics) 86.98 KB (0%)
@sentry/browser (incl. metrics) 26.23 KB (0%)
@sentry/browser (incl. Feedback) 38.21 KB (0%)
@sentry/browser (incl. sendFeedback) 26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync) 31.18 KB (0%)
@sentry/react 24.81 KB (0%)
@sentry/react (incl. Tracing) 36.27 KB (0%)
@sentry/vue 26.05 KB (0%)
@sentry/vue (incl. Tracing) 35.07 KB (0%)
@sentry/svelte 22.17 KB (0%)
CDN Bundle 23.39 KB (0%)
CDN Bundle (incl. Tracing) 34.91 KB (0%)
CDN Bundle (incl. Tracing, Replay) 69.02 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) 74.15 KB (0%)
CDN Bundle - uncompressed 68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed 103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed 213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 226.21 KB (0%)
@sentry/nextjs (client) 35.63 KB (0%)
@sentry/sveltekit (client) 33.86 KB (0%)
@sentry/node 111.93 KB (0%)
@sentry/node - without tracing 89.4 KB (0%)
@sentry/aws-serverless 98.46 KB (0%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants