feat(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe
, '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(node): Add @sentry/node/preload hook - #12213

Merged
mydea merged 4 commits into
developfrom
fn/lazy-init
May 27, 2024
Merged

feat(node): Add @sentry/node/preload hook#12213
mydea merged 4 commits into
developfrom
fn/lazy-init

Conversation

@mydea

Copy link
Copy Markdown
Member

This PR adds a new way to initialize @sentry/node, which allows to use the SDK with performance instrumentation even if you cannot (for whatever reason) call Sentry.init() at the very start of your app.

CJS usage

In CommonJS mode, you can run the SDK like this:

node --require @sentry/node/preload ./app.js
// app.jsconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

ESM usage

in ESM mode, you can run the SDK like this:

node --import @sentry/node/preload ./app.mjs
// app.mjsimportexpressfrom'express';import*asSentryfrom'@sentry/node';constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// express is instrumented even though we initialized Sentry late

Configuration options

This script will by default preload all opentelemetry instrumentation. You can choose to instrument only specific packages like this:

SENTRY_PRELOAD_INTEGRATIONS="Http,Express,Graphql" --import @sentry/node/preload ./app.mjs

You can also enable debug logging for the script via SENTRY_DEBUG=true.

Manually preloading

It is also possible to manually call preloadOpenTelemetry() to achieve the same thing. For example, in a CJS app you could do the following thing if you want to initialize late but don't want to use --require:

// preload.jsconstSentry=require('@sentry/node');Sentry.preloadOpenTelemetry();// app.js// call this first, before any other requires!require('./preload.js');// Then, other stuffconstexpress=require('express');constSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });

@mydeamydea self-assigned this May 24, 2024
@AbhiPrasad

Copy link
Copy Markdown
Contributor

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

@timfish

timfish commented May 24, 2024

Copy link
Copy Markdown
Collaborator

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

@github-actions

github-actionsBot commented May 24, 2024

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize
@sentry/browser21.74 KB (0%)
@sentry/browser (incl. Tracing)32.77 KB (0%)
@sentry/browser (incl. Tracing, Replay)68.24 KB (0%)
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags61.66 KB (0%)
@sentry/browser (incl. Tracing, Replay with Canvas)72.29 KB (0%)
@sentry/browser (incl. Tracing, Replay, Feedback)84.32 KB (0%)
@sentry/browser (incl. Feedback)37.75 KB (0%)
@sentry/browser (incl. sendFeedback)26.31 KB (0%)
@sentry/browser (incl. FeedbackAsync)30.73 KB (0%)
@sentry/react24.43 KB (0%)
@sentry/react (incl. Tracing)35.77 KB (0%)
@sentry/vue25.68 KB (0%)
@sentry/vue (incl. Tracing)34.58 KB (0%)
@sentry/svelte21.88 KB (0%)
CDN Bundle24.28 KB (0%)
CDN Bundle (incl. Tracing)34.22 KB (0%)
CDN Bundle (incl. Tracing, Replay)68.03 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback)73.02 KB (0%)
CDN Bundle - uncompressed71.46 KB (0%)
CDN Bundle (incl. Tracing) - uncompressed101.55 KB (0%)
CDN Bundle (incl. Tracing, Replay) - uncompressed211.46 KB (0%)
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed223.81 KB (0%)
@sentry/nextjs (client)35.12 KB (0%)
@sentry/sveltekit (client)33.36 KB (0%)
@sentry/node114.6 KB (+0.25% 🔺)
@sentry/aws-serverless103.29 KB (+0.09% 🔺)

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

In our tests, import/execution order sadly still mattered. If something was imported before we added the OTEL instrumentation, it did not work :( Actually, the test is not "good" because in this specific test stuff does work. I updated this now, it fails when something is used before Sentry is run. E.g.:

importexpressfrom'express';constapp=express();// setup app...// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

What happens if a user manually initializes OpenTelemetry? Can they still delay calling Sentry.init?

This should all still work, basically this does nothing except setup the instrumentations, it does not actually setup otel itself (e.g. no span processor etc. is created yet).

@andreiborza

Copy link
Copy Markdown
Member

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?

I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

@mydea

Copy link
Copy Markdown
MemberAuthor

How does this differ from using --import @sentry/node/import? I can see the code differenced but why are these differences required?
I assumed --import @sentry/node/import would load import-in-the-middle and then import order in the app doesn't matter, it still instruments libraries.

If I understood correctly, some people didn't have a DSN ready by the time we need to import/init everything for instrumentation to work. This would be a workaround.

You can use @sentry/node/import without DSN, which does setup up import-in-the-middle correctly. but this still requires the otel instrumentation to be loaded before a module is used :/

@timfish

Copy link
Copy Markdown
Collaborator
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

@mydea

Copy link
Copy Markdown
MemberAuthor
// somewhere latersetTimeout(function(){Sentry.init(...);},1000);

In this case, express is already uninstrumented before we run init :/

Ah that does make sense for that case.

Is there any reason not to encourage using an instrument.js instead?

node --require ./instrument.js app.js
// instrument.jsconstSentry=require('@sentry/node');constdsn=awaitgetSentryDsn();Sentry.init({ dsn });// app.jsconstexpress=require('express');

Yeah, we generally do recommend that, but this is not possible if users cannot init at this point - e.g. if they load their DSN from somewhere else (something that we don't recommend, but there are people out there that have setups like this...)

So this is really just an alternative way to get stuff running for these people, and will def. not be the recommended way to init sentry, but just an escape hatch!

@timfishtimfish left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes understood!

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

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

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

@mydea

Copy link
Copy Markdown
MemberAuthor

My only minor concern is that we're racking up quite a few different ways to initialise the SDK.

yeah I also feel this, but I think this is an important thing to expose, so let's release. Maybe we can iterate and combine @sentry/node/preload and @sentry/node/import somehow 🤔.

We could, eventually, make import be a variant of preload with some env var set 🤔 but let's get this out for now and tweak stuff later as needed!

@mydea
mydea merged commit b188e61 into developMay 27, 2024
@mydea
mydea deleted the fn/lazy-init branch May 27, 2024 07:52
@jeengbe

jeengbe commented May 27, 2024

Copy link
Copy Markdown
Contributor

Out of curiosity, what's a use case for this? I can't imagine any situation in which you don't have control over the entry point of your code. Maybe with frameworks like Next?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@mydea@AbhiPrasad@timfish@andreiborza@jeengbe