-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: Add span creators to @sentry/tracing package #2736
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
Changes from 11 commits
2647f18
64d1efc
644fb78
51ec856
a9b91c5
bede869
a55c8be
ea0686e
3e55bf6
0a5e72a
ee5b4c4
e8c5d00
da4b39a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { getGlobalObject, logger } from '@sentry/utils'; | ||
|
||
import { IdleTransaction } from '../idletransaction'; | ||
import { SpanStatus } from '../spanstatus'; | ||
|
||
import { getActiveTransaction } from './utils'; | ||
|
||
const global = getGlobalObject<Window>(); | ||
|
||
/** | ||
* Add a listener that cancels and finishes a transaction when the global | ||
* document is hidden. | ||
*/ | ||
export function registerBackgroundTabDetection(): void { | ||
if (global && global.document) { | ||
global.document.addEventListener('visibilitychange', () => { | ||
const activeTransaction = getActiveTransaction() as IdleTransaction; | ||
if (global.document.hidden && activeTransaction) { | ||
logger.log( | ||
`[Tracing] Transaction: ${SpanStatus.Cancelled} -> since tab moved to the background, op: ${ | ||
activeTransaction.op | ||
}`, | ||
); | ||
activeTransaction.setStatus(SpanStatus.Cancelled); | ||
activeTransaction.setTag('visibilitychange', 'document.hidden'); | ||
activeTransaction.finish(); | ||
} | ||
}); | ||
} else { | ||
logger.warn('[Tracing] Could not set up background tab detection due to lack of global document'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,25 @@ import { EventProcessor, Integration, Transaction as TransactionType, Transactio | |
import { logger } from '@sentry/utils'; | ||
|
||
import { startIdleTransaction } from '../hubextensions'; | ||
import { DEFAULT_IDLE_TIMEOUT } from '../idletransaction'; | ||
import { DEFAULT_IDLE_TIMEOUT, IdleTransaction } from '../idletransaction'; | ||
import { Span } from '../span'; | ||
|
||
import { SpanStatus } from '../spanstatus'; | ||
|
||
import { registerBackgroundTabDetection } from './backgroundtab'; | ||
import { registerErrorInstrumentation } from './errors'; | ||
import { MetricsInstrumentation } from './metrics'; | ||
import { | ||
defaultRequestInstrumentionOptions, | ||
registerRequestInstrumentation, | ||
RequestInstrumentationOptions, | ||
} from './request'; | ||
import { defaultBeforeNavigate, defaultRoutingInstrumentation } from './router'; | ||
import { secToMs } from './utils'; | ||
|
||
export const DEFAULT_MAX_TRANSACTION_DURATION__SECONDS = 600; | ||
|
||
/** Options for Browser Tracing integration */ | ||
export interface BrowserTracingOptions { | ||
export interface BrowserTracingOptions extends RequestInstrumentationOptions { | ||
/** | ||
* The time to wait in ms until the transaction will be finished. The transaction will use the end timestamp of | ||
* the last finished span as the endtime for the transaction. | ||
|
@@ -50,6 +62,24 @@ export interface BrowserTracingOptions { | |
startTransactionOnPageLoad?: boolean, | ||
startTransactionOnLocationChange?: boolean, | ||
): void; | ||
|
||
/** | ||
* The maximum duration of a transaction before it will be marked as "deadline_exceeded". | ||
* If you never want to mark a transaction set it to 0. | ||
* Time is in seconds. | ||
* | ||
* Default: 600 | ||
*/ | ||
maxTransactionDuration: number; | ||
|
||
/** | ||
* Flag Transactions where tabs moved to background with "cancelled". Browser background tab timing is | ||
* not suited towards doing precise measurements of operations. By default, we recommend that this option | ||
* be enabled as background transactions can mess up your statistics in nondeterministic ways. | ||
* | ||
* Default: true | ||
*/ | ||
markBackgroundTransactions: boolean; | ||
} | ||
|
||
/** | ||
|
@@ -69,9 +99,12 @@ export class BrowserTracing implements Integration { | |
public options: BrowserTracingOptions = { | ||
beforeNavigate: defaultBeforeNavigate, | ||
idleTimeout: DEFAULT_IDLE_TIMEOUT, | ||
markBackgroundTransactions: true, | ||
maxTransactionDuration: DEFAULT_MAX_TRANSACTION_DURATION__SECONDS, | ||
routingInstrumentation: defaultRoutingInstrumentation, | ||
startTransactionOnLocationChange: true, | ||
startTransactionOnPageLoad: true, | ||
...defaultRequestInstrumentionOptions, | ||
}; | ||
|
||
/** | ||
|
@@ -81,12 +114,28 @@ export class BrowserTracing implements Integration { | |
|
||
private _getCurrentHub?: () => Hub; | ||
|
||
// navigationTransactionInvoker() -> Uses history API NavigationTransaction[] | ||
private readonly _metrics: MetricsInstrumentation = new MetricsInstrumentation(); | ||
|
||
private readonly _emitOptionsWarning: boolean = false; | ||
|
||
public constructor(_options?: Partial<BrowserTracingOptions>) { | ||
let tracingOrigins = defaultRequestInstrumentionOptions.tracingOrigins; | ||
// NOTE: Logger doesn't work in constructors, as it's initialized after integrations instances | ||
if ( | ||
_options && | ||
_options.tracingOrigins && | ||
Array.isArray(_options.tracingOrigins) && | ||
_options.tracingOrigins.length !== 0 | ||
) { | ||
tracingOrigins = _options.tracingOrigins; | ||
} else { | ||
this._emitOptionsWarning = true; | ||
} | ||
|
||
this.options = { | ||
...this.options, | ||
..._options, | ||
tracingOrigins, | ||
}; | ||
} | ||
|
||
|
@@ -96,13 +145,40 @@ export class BrowserTracing implements Integration { | |
public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { | ||
this._getCurrentHub = getCurrentHub; | ||
|
||
const { routingInstrumentation, startTransactionOnLocationChange, startTransactionOnPageLoad } = this.options; | ||
if (this._emitOptionsWarning) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I will refactor the logic here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this is here because we cannot call it in the constructor, and we want to warn the user if they haven't manually set the |
||
logger.warn( | ||
'[Tracing] You need to define `tracingOrigins` in the options. Set an array of urls or patterns to trace.', | ||
); | ||
logger.warn( | ||
`[Tracing] We added a reasonable default for you: ${defaultRequestInstrumentionOptions.tracingOrigins}`, | ||
); | ||
} | ||
|
||
const { | ||
routingInstrumentation, | ||
startTransactionOnLocationChange, | ||
startTransactionOnPageLoad, | ||
markBackgroundTransactions, | ||
traceFetch, | ||
traceXHR, | ||
tracingOrigins, | ||
shouldCreateSpanForRequest, | ||
} = this.options; | ||
|
||
routingInstrumentation( | ||
(context: TransactionContext) => this._createRouteTransaction(context), | ||
startTransactionOnPageLoad, | ||
startTransactionOnLocationChange, | ||
); | ||
|
||
// TODO: Should this be default behaviour? | ||
AbhiPrasad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
registerErrorInstrumentation(); | ||
|
||
if (markBackgroundTransactions) { | ||
registerBackgroundTabDetection(); | ||
} | ||
|
||
registerRequestInstrumentation({ traceFetch, traceXHR, tracingOrigins, shouldCreateSpanForRequest }); | ||
} | ||
|
||
/** Create routing idle transaction. */ | ||
|
@@ -112,12 +188,13 @@ export class BrowserTracing implements Integration { | |
return undefined; | ||
} | ||
|
||
const { beforeNavigate, idleTimeout } = this.options; | ||
const { beforeNavigate, idleTimeout, maxTransactionDuration } = this.options; | ||
|
||
// if beforeNavigate returns undefined, we should not start a transaction. | ||
const ctx = beforeNavigate({ | ||
...context, | ||
...getHeaderContext(), | ||
trimEnd: true, | ||
}); | ||
|
||
if (ctx === undefined) { | ||
|
@@ -126,8 +203,14 @@ export class BrowserTracing implements Integration { | |
} | ||
|
||
const hub = this._getCurrentHub(); | ||
logger.log(`[Tracing] starting ${ctx.op} idleTransaction on scope with context:`, ctx); | ||
return startIdleTransaction(hub, ctx, idleTimeout, true) as TransactionType; | ||
logger.log(`[Tracing] starting ${ctx.op} idleTransaction on scope`); | ||
const idleTransaction = startIdleTransaction(hub, ctx, idleTimeout, true); | ||
idleTransaction.registerBeforeFinishCallback((transaction, endTimestamp) => { | ||
this._metrics.addPerformanceEntires(transaction); | ||
adjustTransactionDuration(secToMs(maxTransactionDuration), transaction, endTimestamp); | ||
}); | ||
|
||
return idleTransaction as TransactionType; | ||
} | ||
} | ||
|
||
|
@@ -155,3 +238,13 @@ export function getMetaContent(metaName: string): string | null { | |
const el = document.querySelector(`meta[name=${metaName}]`); | ||
return el ? el.getAttribute('content') : null; | ||
} | ||
|
||
/** Adjusts transaction value based on max transaction duration */ | ||
function adjustTransactionDuration(maxDuration: number, transaction: IdleTransaction, endTimestamp: number): void { | ||
const diff = endTimestamp - transaction.startTimestamp; | ||
const isOutdatedTransaction = endTimestamp && (diff > maxDuration || diff < 0); | ||
if (isOutdatedTransaction) { | ||
transaction.setStatus(SpanStatus.DeadlineExceeded); | ||
transaction.setTag('maxTransactionDurationExceeded', 'true'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { addInstrumentationHandler, logger } from '@sentry/utils'; | ||
|
||
import { SpanStatus } from '../spanstatus'; | ||
|
||
import { getActiveTransaction } from './utils'; | ||
|
||
/** | ||
* Configures global error listeners | ||
*/ | ||
export function registerErrorInstrumentation(): void { | ||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'error', | ||
}); | ||
addInstrumentationHandler({ | ||
callback: errorCallback, | ||
type: 'unhandledrejection', | ||
}); | ||
} | ||
|
||
/** | ||
* If an error or unhandled promise occurs, we mark the active transaction as failed | ||
*/ | ||
function errorCallback(): void { | ||
const activeTransaction = getActiveTransaction(); | ||
if (activeTransaction) { | ||
logger.log(`[Tracing] Transaction: ${SpanStatus.InternalError} -> Global error occured`); | ||
activeTransaction.setStatus(SpanStatus.InternalError); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.