|
1 | 1 | /* eslint-disable max-lines */
|
2 |
| -import { Primitive, Span as SpanInterface, SpanContext, Transaction } from '@sentry/types'; |
3 |
| -import { dropUndefinedKeys, timestampWithMs, uuid4 } from '@sentry/utils'; |
| 2 | +import { getCurrentHub, Hub } from '@sentry/hub'; |
| 3 | +import { Primitive, Span as SpanInterface, SpanContext, TraceHeaders, Transaction } from '@sentry/types'; |
| 4 | +import { dropUndefinedKeys, logger, timestampWithMs, uuid4 } from '@sentry/utils'; |
4 | 5 |
|
5 | 6 | import { SpanStatus } from './spanstatus';
|
| 7 | +import { computeTracestateValue } from './utils'; |
6 | 8 |
|
7 | 9 | /**
|
8 | 10 | * Keeps track of finished spans for a given transaction
|
@@ -284,6 +286,26 @@ export class Span implements SpanInterface {
|
284 | 286 | return this;
|
285 | 287 | }
|
286 | 288 |
|
| 289 | + /** |
| 290 | + * @inheritDoc |
| 291 | + */ |
| 292 | + public getTraceHeaders(): TraceHeaders { |
| 293 | + // if this span is part of a transaction, but that transaction doesn't yet have a tracestate value, create one |
| 294 | + if (this.transaction && !this.transaction?.metadata.tracestate?.sentry) { |
| 295 | + this.transaction.metadata.tracestate = { |
| 296 | + ...this.transaction.metadata.tracestate, |
| 297 | + sentry: this._getNewTracestate(), |
| 298 | + }; |
| 299 | + } |
| 300 | + |
| 301 | + const tracestate = this._toTracestate(); |
| 302 | + |
| 303 | + return { |
| 304 | + 'sentry-trace': this._toSentrytrace(), |
| 305 | + ...(tracestate && { tracestate }), |
| 306 | + }; |
| 307 | + } |
| 308 | + |
287 | 309 | /**
|
288 | 310 | * @inheritDoc
|
289 | 311 | */
|
@@ -339,4 +361,67 @@ export class Span implements SpanInterface {
|
339 | 361 | trace_id: this.traceId,
|
340 | 362 | });
|
341 | 363 | }
|
| 364 | + |
| 365 | + /** |
| 366 | + * Create a new Sentry tracestate header entry (i.e. `sentry=xxxxxx`) |
| 367 | + * |
| 368 | + * @returns The new Sentry tracestate entry, or undefined if there's no client or no dsn |
| 369 | + */ |
| 370 | + protected _getNewTracestate(): string | undefined { |
| 371 | + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any |
| 372 | + const hub = ((this.transaction as any)?._hub as Hub) || getCurrentHub(); |
| 373 | + const client = hub.getClient(); |
| 374 | + const { id: userId, segment: userSegment } = hub.getScope()?.getUser() || {}; |
| 375 | + const dsn = client?.getDsn(); |
| 376 | + |
| 377 | + if (!client || !dsn) { |
| 378 | + return; |
| 379 | + } |
| 380 | + |
| 381 | + const { environment, release } = client.getOptions() || {}; |
| 382 | + |
| 383 | + // only define a `user` object if there's going to be something in it (note: prettier insists on removing the |
| 384 | + // parentheses, but since it's easy to misinterpret this, imagine `()` around `userId || userSegment`) |
| 385 | + const user = userId || userSegment ? { id: userId, segment: userSegment } : undefined; |
| 386 | + |
| 387 | + // TODO - the only reason we need the non-null assertion on `dsn.publicKey` (below) is because `dsn.publicKey` has |
| 388 | + // to be optional while we transition from `dsn.user` -> `dsn.publicKey`. Once `dsn.user` is removed, we can make |
| 389 | + // `dsn.publicKey` required and remove the `!`. |
| 390 | + |
| 391 | + return `sentry=${computeTracestateValue({ |
| 392 | + trace_id: this.traceId, |
| 393 | + environment, |
| 394 | + release, |
| 395 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 396 | + public_key: dsn.publicKey!, |
| 397 | + user, |
| 398 | + })}`; |
| 399 | + } |
| 400 | + |
| 401 | + /** |
| 402 | + * Return a traceparent-compatible header string. |
| 403 | + */ |
| 404 | + private _toSentrytrace(): string { |
| 405 | + let sampledString = ''; |
| 406 | + if (this.sampled !== undefined) { |
| 407 | + sampledString = this.sampled ? '-1' : '-0'; |
| 408 | + } |
| 409 | + return `${this.traceId}-${this.spanId}${sampledString}`; |
| 410 | + } |
| 411 | + |
| 412 | + /** |
| 413 | + * Return a tracestate-compatible header string, including both sentry and third-party data (if any). Returns |
| 414 | + * undefined if there is no client or no DSN. |
| 415 | + */ |
| 416 | + private _toTracestate(): string | undefined { |
| 417 | + // if this is an orphan span, create a new tracestate value |
| 418 | + const sentryTracestate = this.transaction?.metadata?.tracestate?.sentry || this._getNewTracestate(); |
| 419 | + let thirdpartyTracestate = this.transaction?.metadata?.tracestate?.thirdparty; |
| 420 | + |
| 421 | + // if there's third-party data, add a leading comma; otherwise, convert from `undefined` to the empty string, so the |
| 422 | + // end result doesn’t come out as `sentry=xxxxxundefined` |
| 423 | + thirdpartyTracestate = thirdpartyTracestate ? `,${thirdpartyTracestate}` : ''; |
| 424 | + |
| 425 | + return `${sentryTracestate}${thirdpartyTracestate}`; |
| 426 | + } |
342 | 427 | }
|
0 commit comments