@sentry/react-native
Version:
Official Sentry SDK for react-native
152 lines • 6.41 kB
JavaScript
import { debug, getSpanDescendants, SPAN_STATUS_ERROR, spanToJSON } from '@sentry/core';
import { AppState } from 'react-native';
import { isRootSpan, isSentrySpan } from '../utils/span';
/**
* Hooks on span end event to execute a callback when the span ends.
*/
export function onThisSpanEnd(client, span, callback) {
client.on('spanEnd', (endedSpan) => {
if (span !== endedSpan) {
return;
}
callback(endedSpan);
});
}
export const adjustTransactionDuration = (client, span, maxDurationMs) => {
if (!isRootSpan(span)) {
debug.warn('Not sampling empty back spans only works for Sentry Transactions (Root Spans).');
return;
}
client.on('spanEnd', (endedSpan) => {
if (endedSpan !== span) {
return;
}
const endTimestamp = spanToJSON(span).timestamp;
const startTimestamp = spanToJSON(span).start_timestamp;
if (!endTimestamp || !startTimestamp) {
return;
}
const diff = endTimestamp - startTimestamp;
const isOutdatedTransaction = endTimestamp && (diff > maxDurationMs || diff < 0);
if (isOutdatedTransaction) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
// TODO: check where was used, might be possible to delete
span.setAttribute('maxTransactionDurationExceeded', 'true');
}
});
};
/**
* Helper function to filter out auto-instrumentation child spans.
*/
function getMeaningfulChildSpans(span) {
const children = getSpanDescendants(span);
return children.filter(child => child.spanContext().spanId !== span.spanContext().spanId &&
spanToJSON(child).op !== 'ui.load.initial_display' &&
spanToJSON(child).op !== 'navigation.processing');
}
/**
* Generic helper to discard empty navigation spans based on a condition.
*/
function discardEmptyNavigationSpan(client, span, shouldDiscardFn, onDiscardFn) {
if (!client) {
debug.warn('Could not hook on spanEnd event because client is not defined.');
return;
}
if (!span) {
debug.warn('Could not hook on spanEnd event because span is not defined.');
return;
}
if (!isRootSpan(span) || !isSentrySpan(span)) {
debug.warn('Not sampling empty navigation spans only works for Sentry Transactions (Root Spans).');
return;
}
client.on('spanEnd', (endedSpan) => {
if (endedSpan !== span) {
return;
}
if (!shouldDiscardFn(span)) {
return;
}
const meaningfulChildren = getMeaningfulChildSpans(span);
if (meaningfulChildren.length <= 0) {
onDiscardFn(span);
span['_sampled'] = false;
}
});
}
export const ignoreEmptyBackNavigation = (client, span) => {
discardEmptyNavigationSpan(client, span,
// Only discard if route has been seen before
span => { var _a; return ((_a = spanToJSON(span).data) === null || _a === void 0 ? void 0 : _a['route.has_been_seen']) === true; },
// Log message when discarding
() => {
debug.log('Not sampling transaction as route has been seen before. Pass ignoreEmptyBackNavigationTransactions = false to disable this feature.');
});
};
/**
* Discards empty "Route Change" transactions that never received route information.
* This happens when navigation library emits a route change event but getCurrentRoute() returns undefined.
* Such transactions don't contain any useful information and should not be sent to Sentry.
*
* This function must be called with a reference tracker function that can check if the span
* was cleared from the integration's tracking (indicating it went through the state listener).
*/
export const ignoreEmptyRouteChangeTransactions = (client, span, defaultNavigationSpanName, isSpanStillTracked) => {
discardEmptyNavigationSpan(client, span,
// Only discard if:
// 1. Still has default name
// 2. No route information was set
// 3. Still being tracked (state listener never called)
span => {
var _a;
const spanJSON = spanToJSON(span);
return (spanJSON.description === defaultNavigationSpanName && !((_a = spanJSON.data) === null || _a === void 0 ? void 0 : _a['route.name']) && isSpanStillTracked());
},
// Log and record dropped event
_span => {
debug.log(`Discarding empty "${defaultNavigationSpanName}" transaction that never received route information.`);
client === null || client === void 0 ? void 0 : client.recordDroppedEvent('sample_rate', 'transaction');
});
};
/**
* Idle Transaction callback to only sample transactions with child spans.
* To avoid side effects of other callbacks this should be hooked as the last callback.
*/
export const onlySampleIfChildSpans = (client, span) => {
if (!isRootSpan(span) || !isSentrySpan(span)) {
debug.warn('Not sampling childless spans only works for Sentry Transactions (Root Spans).');
return;
}
client.on('spanEnd', (endedSpan) => {
if (endedSpan !== span) {
return;
}
const children = getSpanDescendants(span);
if (children.length <= 1) {
// Span always has at lest one child, itself
debug.log(`Not sampling as ${spanToJSON(span).op} transaction has no child spans.`);
span['_sampled'] = false;
}
});
};
/**
* Hooks on AppState change to cancel the span if the app goes background.
*/
export const cancelInBackground = (client, span) => {
const subscription = AppState.addEventListener('change', (newState) => {
if (newState === 'background') {
debug.log(`Setting ${spanToJSON(span).op} transaction to cancelled because the app is in the background.`);
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' });
span.end();
}
});
subscription &&
client.on('spanEnd', (endedSpan) => {
var _a;
if (endedSpan === span) {
debug.log(`Removing AppState listener for ${spanToJSON(span).op} transaction.`);
(_a = subscription === null || subscription === void 0 ? void 0 : subscription.remove) === null || _a === void 0 ? void 0 : _a.call(subscription);
}
});
};
//# sourceMappingURL=onSpanEndUtils.js.map