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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Merged
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration
Jun 13, 2024
Merged

feat(remix): Migrate to opentelemetry-instrumentation-remix.#12110
AbhiPrasad merged 27 commits into
developfrom
onur/remix-otel-migration

Conversation

@onurtemizkan

@onurtemizkanonurtemizkan commented May 17, 2024

Copy link
Copy Markdown
Contributor

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)constSentry=require('@sentry/remix');Sentry.init({dsn: YOUR_DSN// ...// auto instrument Remix with OpenTelemetryautoInstrumentRemix: 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')`// ...constapp=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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 3 times, most recently from 81e86d3 to 8941895CompareMay 20, 2024 11:13
@onurtemizkan
onurtemizkan requested review from Lms24 and mydeaMay 20, 2024 11:46
@onurtemizkan
onurtemizkan marked this pull request as ready for review May 20, 2024 11:46
Comment threadpackages/remix/src/index.server.ts
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from e5d54a3 to fb620b8CompareMay 21, 2024 10:06
@mydea
mydea requested a review from AbhiPrasadMay 22, 2024 07:28
Comment thread.github/workflows/build.yml
return (
transactionEvent.type === 'transaction' &&
transactionEvent.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.op === 'http' &&

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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 👍

Comment threadpackages/remix/src/index.server.ts Outdated
*/
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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.

Comment threadpackages/remix/src/utils/integrations/opentelemetry.ts Outdated
},
{
command: `PORT=${port} pnpm start`,
command: `PORT=${port} NODE_OPTIONS='--require ./instrument.server.cjs' pnpm start`,

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
onurtemizkanforce-pushed the onur/remix-otel-migration branch 2 times, most recently from 3ec1138 to 06a185eCompareMay 24, 2024 15:56
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 06a185e to 0e2f73aCompareMay 29, 2024 17:35
@AbhiPrasad

Copy link
Copy Markdown
Contributor

@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?

@mydeamydea mentioned this pull request Jun 4, 2024
2 tasks
export function getDefaultIntegrations(options: RemixOptions): Integration[] {
return [
...getDefaultNodeIntegrations(options).filter(integration => integration.name !== 'Http'),
httpIntegration(),

Copy link
Copy Markdown
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:

functiongetRemixDefaultIntegrations(){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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done 👍

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch 6 times, most recently from 39d7148 to cd0d656CompareJune 10, 2024 15:09
@github-actions

github-actionsBot commented Jun 11, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser22.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 flags62.27 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)73.02 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)85.17 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback, metrics)87.01 KB (0%)
@sentry/browser (incl. metrics)26.23 KB (0%)
@sentry/browser (incl. Feedback)38.24 KB (0%)
@sentry/browser (incl. sendFeedback)26.63 KB (0%)
@sentry/browser (incl. FeedbackAsync)31.18 KB (0%)
@sentry/react24.81 KB (0%)
@sentry/react (incl. Tracing)36.27 KB (0%)
@sentry/vue26.05 KB (0%)
@sentry/vue (incl. Tracing)35.08 KB (0%)
@sentry/svelte22.17 KB (0%)
CDN Bundle23.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 - uncompressed68.71 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed103.3 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed213.75 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed226.21 KB (0%)
@sentry/nextjs (client)35.63 KB (0%)
@sentry/sveltekit (client)33.86 KB (0%)
@sentry/node111.96 KB (0%)
@sentry/node - without tracing89.43 KB (+0.01% 🔺)
@sentry/aws-serverless98.49 KB (+0.01% 🔺)

@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from 0bd6f5e to 8c9330aCompareJune 11, 2024 09:40
@onurtemizkan
onurtemizkanforce-pushed the onur/remix-otel-migration branch from f61216b to b44dd02CompareJune 13, 2024 14:31

@AbhiPrasadAbhiPrasad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔥

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@onurtemizkan@AbhiPrasad@mydea