Skip to content
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

Add Sentry Span Origin #4066

Merged
merged 9 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add `sentry.origin` to SDK spans to indicated if spans are created by a part of the SDK or manually ([#4066](https://github.com/getsentry/sentry-react-native/pull/4066))

## 6.0.0-alpha.2

- Only internal changes. No SDK changes.
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/js/touchevents.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addBreadcrumb, getClient } from '@sentry/core';
import { addBreadcrumb, getClient, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import type { SeverityLevel } from '@sentry/types';
import { dropUndefinedKeys, logger } from '@sentry/utils';
import * as React from 'react';
Expand All @@ -8,6 +8,7 @@ import { StyleSheet, View } from 'react-native';
import { createIntegration } from './integrations/factory';
import { startUserInteractionSpan } from './tracing/integrations/userInteraction';
import { UI_ACTION_TOUCH } from './tracing/ops';
import { SPAN_ORIGIN_AUTO_INTERACTION } from './tracing/origin';

export type TouchEventBoundaryProps = {
/**
Expand Down Expand Up @@ -195,10 +196,13 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
this._logTouchEvent(touchPath, label);
}

startUserInteractionSpan({
const span = startUserInteractionSpan({
elementId: label,
op: UI_ACTION_TOUCH,
});
if (span) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_INTERACTION);
}
}

/**
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/js/tracing/gesturetracing.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { addBreadcrumb } from '@sentry/core';
import { addBreadcrumb, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import type { Breadcrumb } from '@sentry/types';
import { logger } from '@sentry/utils';

import { startUserInteractionSpan } from './integrations/userInteraction';
import { UI_ACTION } from './ops';
import { SPAN_ORIGIN_AUTO_INTERACTION } from './origin';

export const DEFAULT_BREADCRUMB_CATEGORY = 'gesture';
export const DEFAULT_BREADCRUMB_TYPE = 'user';
Expand Down Expand Up @@ -69,7 +70,10 @@ export function sentryTraceGesture<GestureT>(

const originalOnBegin = gestureCandidate.handlers.onBegin;
(gesture as unknown as Required<BaseGesture>).handlers.onBegin = (event: GestureEvent) => {
startUserInteractionSpan({ elementId: label, op: `${UI_ACTION}.${name}` });
const span = startUserInteractionSpan({ elementId: label, op: `${UI_ACTION}.${name}` });
if (span) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_INTERACTION);
}

addGestureBreadcrumb(`Gesture ${label} begin.`, { event, name });

Expand Down
39 changes: 37 additions & 2 deletions packages/core/src/js/tracing/integrations/appStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getCapturedScopesOnSpan,
getClient,
getCurrentScope,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SentryNonRecordingSpan,
startInactiveSpan,
} from '@sentry/core';
Expand All @@ -22,6 +23,7 @@ import {
APP_START_WARM as APP_START_WARM_OP,
UI_LOAD as UI_LOAD_OP,
} from '../ops';
import { SPAN_ORIGIN_AUTO_APP_START, SPAN_ORIGIN_MANUAL_APP_START } from '../origin';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP } from '../semanticAttributes';
import { createChildSpanJSON, createSpanJSON, getBundleStartTimestampMs } from '../utils';

Expand All @@ -45,19 +47,32 @@ const MAX_APP_START_AGE_MS = 60_000;
const APP_START_TX_NAME = 'App Start';

let recordedAppStartEndTimestampMs: number | undefined = undefined;
let isRecordedAppStartEndTimestampMsManual = false;

let rootComponentCreationTimestampMs: number | undefined = undefined;
let isRootComponentCreationTimestampMsManual = false;

/**
* Records the application start end.
* Used automatically by `Sentry.wrap` and `Sentry.ReactNativeProfiler`.
*/
export async function captureAppStart(): Promise<void> {
export function captureAppStart(): Promise<void> {
return _captureAppStart({ isManual: true });
}

/**
* For internal use only.
*
* @private
*/
export async function _captureAppStart({ isManual }: { isManual: boolean }): Promise<void> {
const client = getClient();
if (!client) {
logger.warn('[AppStart] Could not capture App Start, missing client.');
return;
}

isRecordedAppStartEndTimestampMsManual = isManual;
_setAppStartEndTimestampMs(timestampInSeconds() * 1000);
await client.getIntegrationByName<AppStartIntegration>(INTEGRATION_NAME)?.captureStandaloneAppStart();
}
Expand All @@ -71,6 +86,17 @@ export function setRootComponentCreationTimestampMs(timestampMs: number): void {
logger.warn('Setting Root component creation timestamp after app start end is set.');
rootComponentCreationTimestampMs && logger.warn('Overwriting already set root component creation timestamp.');
rootComponentCreationTimestampMs = timestampMs;
isRootComponentCreationTimestampMsManual = true;
}

/**
* For internal use only.
*
* @private
*/
export function _setRootComponentCreationTimestampMs(timestampMs: number): void {
setRootComponentCreationTimestampMs(timestampMs);
isRootComponentCreationTimestampMsManual = false;
}

/**
Expand Down Expand Up @@ -234,6 +260,10 @@ export const appStartIntegration = ({
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = UI_LOAD_OP;
event.contexts.trace.op = UI_LOAD_OP;

const origin = isRecordedAppStartEndTimestampMsManual ? SPAN_ORIGIN_MANUAL_APP_START : SPAN_ORIGIN_AUTO_APP_START;
event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = origin;
event.contexts.trace.origin = origin;

const appStartTimestampSeconds = appStartTimestampMs / 1000;
event.start_timestamp = appStartTimestampSeconds;

Expand Down Expand Up @@ -269,7 +299,7 @@ export const appStartIntegration = ({
timestamp: appStartEndTimestampSeconds,
trace_id: event.contexts.trace.trace_id,
parent_span_id: event.contexts.trace.span_id,
origin: 'auto',
origin,
});
const jsExecutionSpanJSON = createJSExecutionStartSpan(appStartSpanJSON, rootComponentCreationTimestampMs);

Expand Down Expand Up @@ -335,13 +365,15 @@ function createJSExecutionStartSpan(
description: 'JS Bundle Execution Start',
start_timestamp: bundleStartTimestampMs / 1000,
timestamp: bundleStartTimestampMs / 1000,
origin: SPAN_ORIGIN_AUTO_APP_START,
});
}

return createChildSpanJSON(parentSpan, {
description: 'JS Bundle Execution Before React Root',
start_timestamp: bundleStartTimestampMs / 1000,
timestamp: rootComponentCreationTimestampMs / 1000,
origin: isRootComponentCreationTimestampMsManual ? SPAN_ORIGIN_MANUAL_APP_START : SPAN_ORIGIN_AUTO_APP_START,
});
}

Expand All @@ -358,6 +390,7 @@ function convertNativeSpansToSpanJSON(parentSpan: SpanJSON, nativeSpans: NativeA
description: span.description,
start_timestamp: span.start_timestamp_ms / 1000,
timestamp: span.end_timestamp_ms / 1000,
origin: SPAN_ORIGIN_AUTO_APP_START,
});
});
}
Expand All @@ -377,12 +410,14 @@ function createUIKitSpan(parentSpan: SpanJSON, nativeUIKitSpan: NativeAppStartRe
description: 'UIKit Init to JS Exec Start',
start_timestamp: nativeUIKitSpan.start_timestamp_ms / 1000,
timestamp: bundleStart / 1000,
origin: SPAN_ORIGIN_AUTO_APP_START,
});
} else {
return createChildSpanJSON(parentSpan, {
description: 'UIKit Init',
start_timestamp: nativeUIKitSpan.start_timestamp_ms / 1000,
timestamp: nativeUIKitSpan.end_timestamp_ms / 1000,
origin: SPAN_ORIGIN_AUTO_APP_START,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { logger } from '@sentry/utils';

import type { ReactNativeClientOptions } from '../../options';
import { onlySampleIfChildSpans } from '../onSpanEndUtils';
import { SPAN_ORIGIN_AUTO_INTERACTION } from '../origin';
import { SPAN_ORIGIN_MANUAL_INTERACTION } from '../origin';
import { getCurrentReactNativeTracingIntegration } from '../reactnativetracing';
import { clearActiveSpanFromScope, isSentryInteractionSpan, startIdleSpan } from '../span';

Expand Down Expand Up @@ -86,7 +86,7 @@ export const startUserInteractionSpan = (userInteractionId: {
idleTimeout: tracing.options.idleTimeoutMs,
finalTimeout: tracing.options.finalTimeoutMs,
});
newSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_INTERACTION);
newSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_MANUAL_INTERACTION);
onlySampleIfChildSpans(client, newSpan);
logger.log(`[${INTEGRATION_NAME}] User Interaction Tracing Created ${op} transaction ${name}.`);
return newSpan;
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/js/tracing/origin.ts
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
export const SPAN_ORIGIN_AUTO_INTERACTION = 'auto.interaction';
export const SPAN_ORIGIN_MANUAL_INTERACTION = 'manual.interaction';

export const SPAN_ORIGIN_MANUAL_APP_START = 'manual.app.start';
export const SPAN_ORIGIN_AUTO_APP_START = 'auto.app.start';

export const SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NATIVE_NAVIGATION = 'auto.navigation.react_native_navigation';
export const SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION = 'auto.navigation.react_navigation';
export const SPAN_ORIGIN_AUTO_NAVIGATION_CUSTOM = 'auto.navigation.custom';

export const SPAN_ORIGIN_AUTO_UI_TIME_TO_DISPLAY = 'auto.ui.time_to_display';
export const SPAN_ORIGIN_MANUAL_UI_TIME_TO_DISPLAY = 'manual.ui.time_to_display';
6 changes: 6 additions & 0 deletions packages/core/src/js/tracing/reactnativenavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
addBreadcrumb,
getClient,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
spanToJSON,
} from '@sentry/core';
Expand All @@ -10,6 +11,7 @@ import type { Client, Integration, Span } from '@sentry/types';
import type { EmitterSubscription } from '../utils/rnlibrariesinterface';
import { isSentrySpan } from '../utils/span';
import { ignoreEmptyBackNavigation } from './onSpanEndUtils';
import { SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NATIVE_NAVIGATION } from './origin';
import type { ReactNativeTracingIntegration } from './reactnativetracing';
import { getReactNativeTracingIntegration } from './reactnativetracing';
import {
Expand Down Expand Up @@ -132,6 +134,10 @@ export const reactNativeNavigationIntegration = ({
: getDefaultIdleNavigationSpanOptions(),
idleSpanOptions,
);
latestNavigationSpan?.setAttribute(
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NATIVE_NAVIGATION,
);
if (ignoreEmptyBackNavigationTransactions) {
ignoreEmptyBackNavigation(getClient(), latestNavigationSpan);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/js/tracing/reactnativeprofiler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getClient, Profiler } from '@sentry/react';
import { timestampInSeconds } from '@sentry/utils';

import { createIntegration } from '../integrations/factory';
import { captureAppStart, setRootComponentCreationTimestampMs } from '../tracing/integrations/appStart';
import { _captureAppStart, _setRootComponentCreationTimestampMs } from '../tracing/integrations/appStart';

const ReactNativeProfilerGlobalState = {
appStartReported: false,
Expand All @@ -15,7 +15,7 @@ export class ReactNativeProfiler extends Profiler {
public readonly name: string = 'ReactNativeProfiler';

public constructor(props: ConstructorParameters<typeof Profiler>[0]) {
setRootComponentCreationTimestampMs(timestampInSeconds() * 1000);
_setRootComponentCreationTimestampMs(timestampInSeconds() * 1000);
super(props);
}

Expand Down Expand Up @@ -45,6 +45,6 @@ export class ReactNativeProfiler extends Profiler {

client.addIntegration && client.addIntegration(createIntegration(this.name));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
captureAppStart();
_captureAppStart({ isManual: false });
}
}
7 changes: 7 additions & 0 deletions packages/core/src/js/tracing/reactnavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getActiveSpan,
getClient,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SPAN_STATUS_OK,
spanToJSON,
startInactiveSpan,
Expand All @@ -17,6 +18,7 @@ import { isSentrySpan } from '../utils/span';
import { RN_GLOBAL_OBJ } from '../utils/worldwide';
import { NATIVE } from '../wrapper';
import { ignoreEmptyBackNavigation } from './onSpanEndUtils';
import { SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION } from './origin';
import type { ReactNativeTracingIntegration } from './reactnativetracing';
import { getReactNativeTracingIntegration } from './reactnativetracing';
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from './semanticAttributes';
Expand Down Expand Up @@ -191,6 +193,7 @@ export const reactNavigationIntegration = ({
: getDefaultIdleNavigationSpanOptions(),
idleSpanOptions,
);
latestNavigationSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION);
if (ignoreEmptyBackNavigationTransactions) {
ignoreEmptyBackNavigation(getClient(), latestNavigationSpan);
}
Expand All @@ -201,6 +204,10 @@ export const reactNavigationIntegration = ({
name: 'Navigation processing',
startTime: latestNavigationSpan && spanToJSON(latestNavigationSpan).start_timestamp,
});
navigationProcessingSpan.setAttribute(
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION,
);
}

stateChangeTimeout = setTimeout(_discardLatestTransaction, routeChangeTimeoutMs);
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/js/tracing/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getClient,
getCurrentScope,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SentryNonRecordingSpan,
SPAN_STATUS_ERROR,
spanToJSON,
Expand All @@ -13,7 +14,11 @@ import { generatePropagationContext, logger } from '@sentry/utils';

import { isRootSpan } from '../utils/span';
import { adjustTransactionDuration, cancelInBackground } from './onSpanEndUtils';
import { SPAN_ORIGIN_AUTO_INTERACTION } from './origin';
import {
SPAN_ORIGIN_AUTO_INTERACTION,
SPAN_ORIGIN_AUTO_NAVIGATION_CUSTOM,
SPAN_ORIGIN_MANUAL_INTERACTION,
} from './origin';

export const DEFAULT_NAVIGATION_SPAN_NAME = 'Route Change';

Expand Down Expand Up @@ -75,6 +80,8 @@ export const startIdleNavigationSpan = (
);

adjustTransactionDuration(client, idleSpan, finalTimeout);

idleSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_ORIGIN_AUTO_NAVIGATION_CUSTOM);
return idleSpan;
};

Expand Down Expand Up @@ -118,7 +125,7 @@ export function getDefaultIdleNavigationSpanOptions(): StartSpanOptions {
* Checks if the span is a Sentry User Interaction span.
*/
export function isSentryInteractionSpan(span: Span): boolean {
return spanToJSON(span).origin === SPAN_ORIGIN_AUTO_INTERACTION;
return [SPAN_ORIGIN_AUTO_INTERACTION, SPAN_ORIGIN_MANUAL_INTERACTION].includes(spanToJSON(span).origin);
}

const SCOPE_SPAN_FIELD = '_sentrySpan';
Expand Down
Loading
Loading