-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(opentelemetry): Add new @sentry/opentelemetry
package
#9238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* @hidden | ||
*/ | ||
const _INTERNAL = { | ||
addOriginToSpan, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to keep this private until we decided on getsentry/rfcs#116, only then I'd like to expose the internal attribute names etc. somehow.
* const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager); | ||
* const contextManager = new SentryContextManager(); | ||
*/ | ||
export function wrapContextManagerClass<ContextManagerInstance extends ContextManager>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been generalized so we can wrap any context manager with our sentry specific behavior. Really we don't care what context manager is used, this should also work with browser-specific context managers (e.g. the is a zonejs based context manager etc).
* const OpenTelemetryClient = getWrappedClientClass(NodeClient); | ||
* const client = new OpenTelemetryClient(options); | ||
*/ | ||
export function wrapClientClass< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been generalized so you can wrap any client, e.g. either a node or a browser client.
* These are internal and are not supposed to be used/depended on by external parties. | ||
* No guarantees apply to these attributes, and the may change/disappear at any time. | ||
*/ | ||
export const InternalSentrySemanticAttributes = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of constants, keeping this similar to how OTEL handles their semantic attributes. Eventually we should align this with getsentry/rfcs#116.
d6bf541
to
bd3fa18
Compare
*/ | ||
export function startSpan<T>(spanContext: NodeExperimentalSpanContext, callback: (span: Span | undefined) => T): T { | ||
export function startSpan<T>(spanContext: OpenTelemetrySpanContext, callback: (span: Span) => T): T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: The API here has changed!! to what it was before.
Previously, the callback received span: Span | undefined
, where Span
was the span class from @opentelemetry/sdk-trace-base
. This is not actually the same as you get from tracer.startActiveSpan()
, and lead to some incompatibilities.
Instead, we now simply pass the proper span through, to keep this aligned. If the span is not sampled, you'll get a NonRecordingSpan (which still has all the write APIs but as noops). This also means users do not need to guard against span
being undefined, which should simplify user code.
* Try to get the Propagation Context from the given OTEL context. | ||
* This requires the SentryPropagator to be registered. | ||
*/ | ||
export function getPropagationContextFromContext(context: Context): PropagationContext | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expose these as functions instead of exposing the constants, which should encapsulate this better.
"@prisma/instrumentation": "~5.3.1", | ||
"@opentelemetry/core": "~1.17.1", | ||
"@opentelemetry/context-async-hooks": "~1.17.1", | ||
"@opentelemetry/instrumentation": "0.44.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hard-pinned all instrumentation dependencies, and pinned the "core" dependencies to bugfixes. LMK if you think we should hard pin everything 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is fine - we can always adjust it afterwards too if it ends up breaking something.
98dcd62
to
9bfa893
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
@@ -161,7 +158,7 @@ export class Http implements Integration { | |||
return; | |||
} | |||
|
|||
const data = getRequestSpanData(span); | |||
const data = _INTERNAL.getRequestSpanData(span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: This _INTERNAL
thing seems very smelly to me. Can we not hoist this into utils or smth?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine because it's dependent on us making some decisions around schema/naming. Internal is sufficiently scary enough that things shouldn't be used with it, and it will be going away very soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah so this should definitely go away, I just want to avoid exposing things publicly that we need right now because then we can't remove them easily later :) once the semantic attributes stuff is merged and agreed upon this can/should just be public API!
@@ -24,6 +24,10 @@ targets: | |||
- name: npm | |||
id: '@sentry/replay' | |||
includeNames: /^sentry-replay-\d.*\.tgz$/ | |||
## 1.6. OpenTelemetry package | |||
- name: npm | |||
id: '@sentry/opentelemetry' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We prob also need to add this to the verdaccio config for the e2e tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this not automatically be picked up there? 🤔 based on the namespace...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No unfortunately not:
sentry-javascript/packages/e2e-tests/verdaccio-config/config.yaml
Lines 29 to 39 in cadeefe
# To not use a proxy (e.g. npm) but instead use verdaccio for package hosting we need to define rules here without the | |
# `proxy` field. Sadly we can't use a wildcard like "@sentry/*" because we have some dependencies (@sentry/cli, | |
# @sentry/webpack-plugin) that fall under that wildcard but don't live in this repository. If we were to use that | |
# wildcard, we would get a 404 when attempting to install them, since they weren't uploaded to verdaccio, and also | |
# don't have a proxy configuration. | |
'@sentry/angular': | |
access: $all | |
publish: $all | |
unpublish: $all | |
# proxy: npmjs # Don't proxy for E2E tests! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
damn, good catch! I'll actually add this here as well, is missing there so far: https://github.com/getsentry/sentry-javascript/blob/develop/docs/new-sdk-release-checklist.md
"@prisma/instrumentation": "~5.3.1", | ||
"@opentelemetry/core": "~1.17.1", | ||
"@opentelemetry/context-async-hooks": "~1.17.1", | ||
"@opentelemetry/instrumentation": "0.44.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is fine - we can always adjust it afterwards too if it ends up breaking something.
@@ -161,7 +158,7 @@ export class Http implements Integration { | |||
return; | |||
} | |||
|
|||
const data = getRequestSpanData(span); | |||
const data = _INTERNAL.getRequestSpanData(span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine because it's dependent on us making some decisions around schema/naming. Internal is sufficiently scary enough that things shouldn't be used with it, and it will be going away very soon.
@@ -30,7 +30,7 @@ if (NODE_VERSION.major && NODE_VERSION.major >= 16) { | |||
*/ | |||
export function init(options: NodeExperimentalOptions | undefined = {}): void { | |||
// Ensure we register our own global hub before something else does | |||
// This will register the NodeExperimentalHub as the global hub | |||
// This will register the OpenTelemetryHub as the global hub | |||
getCurrentHub(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait does it? Don't we have to setup an async context strategy first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually no, this was kind of a footgun I found while working on this - the first time getCurrentHub
is called anywhere it will setup a global hub (the async context strategy also uses the global hub as a fallback), and since a bunch of the general init public APIs call getCurrentHub
I had to call this here to ensure the global hub is actually our custom otel hub and not the regular one 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, what may work (??) is setting up the otel async context strategy first 🤔 not sure if that may run into problems with otel not being setup (yet) but I'll give it a try!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so some more testing: this sadly doesn't fix the issue, we still get a different global hub (from core). BUT I still refactored this so that setOpenTelemetryContextAsyncContextStrategy
just calls getCurrentHub
under the hood, so this is hidden away from the user and they don't need to manually call getCurrentHub
which is a bit weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even more testing: This has weird side effects, so instead I actually export a new function setupGlobalHub
to be called, to make it clearer what it does.
1. Add `SentrySpanProcessor` as span processor | ||
1. Add a context manager wrapped via `wrapContextManagerClass` | ||
1. Add `SentryPropagator` as propagator | ||
1. Setup OTEL-powered async context strategy for Sentry via `setOpenTelemetryContextAsyncContextStrategy()` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: think we need to fix numbers here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a somewhat "weird" markdown trick that you can generate a numbered list with all 1.
which will be shown as a regular, correctly numbered list, but making it much easier to reorder the list or add items in between 😅
packages/opentelemetry/package.json
Outdated
"author": "Sentry", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=8" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: Do we have to bump this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually yes, we should! will do that,
/** Get the OTEL tracer. */ | ||
public get tracer(): Tracer { | ||
if (this._tracer) { | ||
return this._tracer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will we ever expose users setting their own tracer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not right now - really there is no need to do that, they can just use any tracer directly as well separate from this. This is just a nice helper to make this a bit easier.
|
||
// Remove `captureContext` hint and instead clone already here | ||
if (hint && hint.captureContext) { | ||
actualScope = OpenTelemetryScope.clone(scope); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should just restructure all of the process event code in v8, I dislike we have to do this here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, agreed!
Apply suggestions from code review Co-authored-by: Abhijeet Prasad <[email protected]> PR feedback PR feedback: Expose proper `setupGlobalHub` method pr feedback & new release docs
e7c725c
to
68bf926
Compare
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
This extracts the code from
node-experimental
in a more reusable@sentry/opentelemetry
package.This package has no direct (otel) dependencies, but only peer dependencies. This means it can be used by anybody with an existing OTEL setup without messing with their dependencies etc.
In turn,
node-experimental
uses this package to implement a concrete and opinionated implementation of this for node.This new package should also be usable for non-node OTEL implementations, and concisously does not include any node specific stuff.