Skip to content

Commit

Permalink
refactor(react-native): create ReactNativeSpanFactory class
Browse files Browse the repository at this point in the history
  • Loading branch information
yousif-bugsnag committed Dec 18, 2024
1 parent f5d8949 commit 3dabdbe
Show file tree
Hide file tree
Showing 13 changed files with 62 additions and 40 deletions.
31 changes: 20 additions & 11 deletions packages/core/lib/span-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { IdGenerator } from './id-generator'
import type { NetworkSpanOptions } from './network-span'
import type { Processor } from './processor'
import type { ReadonlySampler } from './sampler'
import type { InternalSpanOptions, Span, SpanOptionSchema, SpanOptions } from './span'
import type { InternalSpanOptions, ParentContext, Span, SpanOptionSchema, SpanOptions } from './span'
import { SpanInternal, coreSpanOptionSchema } from './span'
import type { SpanContextStorage } from './span-context'
import { timeToNumber } from './time'
Expand All @@ -25,7 +25,7 @@ export class SpanFactory<C extends Configuration> {
readonly sampler: ReadonlySampler
private readonly idGenerator: IdGenerator
private readonly spanAttributesSource: SpanAttributesSource<C>
private readonly clock: Clock
protected readonly clock: Clock
private readonly spanContextStorage: SpanContextStorage
private logger: Logger
private spanAttributeLimits: SpanAttributesLimits = defaultSpanAttributeLimits
Expand Down Expand Up @@ -64,7 +64,6 @@ export class SpanFactory<C extends Configuration> {

startSpan (name: string, options: SpanOptions) {
const safeStartTime = timeToNumber(this.clock, options.startTime)
const spanId = this.idGenerator.generate(64)

// if the parentContext option is not set use the current context
// if parentContext is explicitly null, or there is no current context,
Expand All @@ -73,16 +72,9 @@ export class SpanFactory<C extends Configuration> {
? options.parentContext
: this.spanContextStorage.current

const parentSpanId = parentContext ? parentContext.id : undefined
const traceId = parentContext ? parentContext.traceId : this.idGenerator.generate(128)

const attributes = new SpanAttributes(new Map(), this.spanAttributeLimits, name, this.logger)

if (typeof options.isFirstClass === 'boolean') {
attributes.set('bugsnag.span.first_class', options.isFirstClass)
}

const span = new SpanInternal(spanId, traceId, name, safeStartTime, attributes, this.clock, parentSpanId)
const span = this.createSpanInternal(name, safeStartTime, parentContext, options.isFirstClass, attributes)

// don't track spans that are started while the app is backgrounded
if (this.isInForeground) {
Expand All @@ -96,6 +88,23 @@ export class SpanFactory<C extends Configuration> {
return span
}

protected createSpanInternal (
name: string,
startTime: number,
parentContext: ParentContext | null | undefined,
isFirstClass: boolean | undefined,
attributes: SpanAttributes) {
const spanId = this.idGenerator.generate(64)
const parentSpanId = parentContext ? parentContext.id : undefined
const traceId = parentContext ? parentContext.traceId : this.idGenerator.generate(128)

if (typeof isFirstClass === 'boolean') {
attributes.set('bugsnag.span.first_class', isFirstClass)
}

return new SpanInternal(spanId, traceId, name, startTime, attributes, this.clock, parentSpanId)
}

startNetworkSpan (options: NetworkSpanOptions) {
const spanName = `[HTTP/${options.method.toUpperCase()}]`
const cleanOptions = this.validateSpanOptions<NetworkSpanOptions>(spanName, options)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type {
Clock,
InternalConfiguration,
Plugin,
SpanFactory
Plugin
} from '@bugsnag/core-performance'
import type { ReactNode } from 'react'
import React from 'react'
import type { AppRegistry, WrapperComponentProvider } from 'react-native'
import type { ReactNativeConfiguration } from '../config'
import { createAppStartSpan } from '../create-app-start-span'
import type { ReactNativeSpanFactory } from '../span-factory'

interface WrapperProps {
children: ReactNode
Expand All @@ -19,13 +19,13 @@ export const isWrapperComponentProvider = (value: unknown): value is WrapperComp

export class AppStartPlugin implements Plugin<ReactNativeConfiguration> {
private readonly appStartTime: number
private readonly spanFactory: SpanFactory<ReactNativeConfiguration>
private readonly spanFactory: ReactNativeSpanFactory
private readonly clock: Clock
private readonly appRegistry: typeof AppRegistry

constructor (
appStartTime: number,
spanFactory: SpanFactory<ReactNativeConfiguration>,
spanFactory: ReactNativeSpanFactory,
clock: Clock,
appRegistry: typeof AppRegistry
) {
Expand Down
6 changes: 4 additions & 2 deletions packages/platforms/react-native/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import resourceAttributesSourceFactory from './resource-attributes-source'
import createRetryQueueFactory from './retry-queue'
import { createSpanAttributesSource } from './span-attributes-source'
import createBrowserBackgroundingListener from './backgrounding-listener'
import { ReactNativeSpanFactory } from './span-factory'

// this is how some internal react native code detects whether the app is running
// in the remote debugger:
Expand Down Expand Up @@ -47,14 +48,15 @@ const BugsnagPerformance = createClient({
idGenerator,
persistence,
plugins: (spanFactory, spanContextStorage) => [
new AppStartPlugin(appStartTime, spanFactory, clock, AppRegistry),
new AppStartPlugin(appStartTime, spanFactory as ReactNativeSpanFactory, clock, AppRegistry),
new NetworkRequestPlugin(spanFactory, spanContextStorage, xhrRequestTracker)
],
resourceAttributesSource,
schema,
spanAttributesSource,
retryQueueFactory: createRetryQueueFactory(FileSystem),
platformExtensions: (spanFactory, spanContextStorage) => platformExtensions(appStartTime, clock, spanFactory, spanContextStorage)
spanFactory: ReactNativeSpanFactory,
platformExtensions: (spanFactory, spanContextStorage) => platformExtensions(appStartTime, clock, spanFactory as ReactNativeSpanFactory, spanContextStorage)
})

BugsnagPerformance.attach = (config) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/platforms/react-native/lib/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface Performance {
now: () => number
}

interface ReactNativeClock extends Clock {
export interface ReactNativeClock extends Clock {
toUnixNanoseconds: (time: number) => number
}

Expand Down
6 changes: 3 additions & 3 deletions packages/platforms/react-native/lib/create-app-start-span.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { SpanFactory, SpanInternal } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from './config'
import type { SpanInternal } from '@bugsnag/core-performance'
import type { ReactNativeSpanFactory } from './span-factory'

let appStartSpan: SpanInternal

export function createAppStartSpan (spanFactory: SpanFactory<ReactNativeConfiguration>, appStartTime: number) {
export function createAppStartSpan (spanFactory: ReactNativeSpanFactory, appStartTime: number) {
if (appStartSpan) {
return appStartSpan
}
Expand Down
5 changes: 3 additions & 2 deletions packages/platforms/react-native/lib/create-navigation-span.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Configuration, SpanFactory, SpanOptions } from '@bugsnag/core-performance'
import type { SpanOptions } from '@bugsnag/core-performance'
import type { ReactNativeSpanFactory } from './span-factory'

export function createNavigationSpan <C extends Configuration> (spanFactory: SpanFactory<C>, routeName: string, spanOptions: SpanOptions) {
export function createNavigationSpan (spanFactory: ReactNativeSpanFactory, routeName: string, spanOptions: SpanOptions) {
// Navigation spans are always first class
spanOptions.isFirstClass = true

Expand Down
1 change: 1 addition & 0 deletions packages/platforms/react-native/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export type { PlatformExtensions } from './platform-extensions'
export default BugsnagPerformance

export { createNavigationSpan } from './create-navigation-span'
export * from './span-factory'
7 changes: 4 additions & 3 deletions packages/platforms/react-native/lib/platform-extensions.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Clock, SpanContextStorage, SpanFactory, SpanOptions } from '@bugsnag/core-performance'
import type { Clock, SpanContextStorage, SpanOptions } from '@bugsnag/core-performance'
import React from 'react'
import type { ReactNativeConfiguration, ReactNativeAttachConfiguration } from './config'
import type { ReactNativeAttachConfiguration } from './config'
import { createAppStartSpan } from './create-app-start-span'
import { createNavigationSpan } from './create-navigation-span'
import type { ReactNativeSpanFactory } from './span-factory'

type NavigationSpanOptions = Omit<SpanOptions, 'isFirstClass'>

export const platformExtensions = (appStartTime: number, clock: Clock, spanFactory: SpanFactory<ReactNativeConfiguration>, spanContextStorage: SpanContextStorage) => ({
export const platformExtensions = (appStartTime: number, clock: Clock, spanFactory: ReactNativeSpanFactory, spanContextStorage: SpanContextStorage) => ({
startNavigationSpan: (routeName: string, spanOptions?: NavigationSpanOptions) => {
const cleanOptions = spanFactory.validateSpanOptions(routeName, spanOptions)
cleanOptions.options.isFirstClass = true
Expand Down
9 changes: 9 additions & 0 deletions packages/platforms/react-native/lib/span-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SpanFactory } from '@bugsnag/core-performance'
import type { SpanAttributes, ParentContext } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from './config'

export class ReactNativeSpanFactory extends SpanFactory<ReactNativeConfiguration> {
protected createSpanInternal (name: string, startTime: number, parentContext: ParentContext | null | undefined, isFirstClass: boolean | undefined, attributes: SpanAttributes) {
return super.createSpanInternal(name, startTime, parentContext, isFirstClass, attributes)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Plugin, SpanFactory, SpanInternal } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import type { ReactNativeConfiguration, ReactNativeSpanFactory } from '@bugsnag/react-native-performance'
import type { NavigationDelegate } from 'react-native-navigation/lib/dist/src/NavigationDelegate'

import { createNavigationSpan } from '@bugsnag/react-native-performance'
Expand All @@ -16,7 +16,7 @@ class BugsnagPluginReactNativeNavigationPerformance implements Plugin<ReactNativ
private startTimeout?: NodeJS.Timeout
private endTimeout?: NodeJS.Timeout
private componentsWaiting = 0
private spanFactory?: SpanFactory<ReactNativeConfiguration>
private spanFactory?: ReactNativeSpanFactory
private previousRoute?: string

constructor (Navigation: NavigationDelegate) {
Expand Down Expand Up @@ -67,7 +67,7 @@ class BugsnagPluginReactNativeNavigationPerformance implements Plugin<ReactNativ
}

configure (configuration: ReactNativeConfiguration, spanFactory: SpanFactory<ReactNativeConfiguration>) {
this.spanFactory = spanFactory
this.spanFactory = spanFactory as ReactNativeSpanFactory

// Potential for a navigation to occur
this.Navigation.events().registerCommandListener((name, params) => {
Expand All @@ -84,7 +84,7 @@ class BugsnagPluginReactNativeNavigationPerformance implements Plugin<ReactNativ
clearTimeout(this.startTimeout)

const routeName = event.componentName
this.currentNavigationSpan = createNavigationSpan(spanFactory, routeName, { startTime: this.startTime })
this.currentNavigationSpan = createNavigationSpan(spanFactory as ReactNativeSpanFactory, routeName, { startTime: this.startTime })
this.currentNavigationSpan.setAttribute('bugsnag.navigation.triggered_by', '@bugsnag/plugin-react-native-navigation-performance')

if (this.previousRoute) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { SpanFactory } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import type { ReactNativeSpanFactory } from '@bugsnag/react-native-performance'
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'
import type { NavigationContainerProps, NavigationContainerRefWithCurrent } from '@react-navigation/native'
import React, { forwardRef, useRef } from 'react'
Expand All @@ -8,10 +7,10 @@ import { NavigationContextProvider } from './navigation-context'
// Prevent rollup plugin from tree shaking NavigationContextProvider
const Provider = NavigationContextProvider

type CreateNavigationContainer = (NavigationContainerComponent: typeof NavigationContainer, spanFactory: SpanFactory<ReactNativeConfiguration>) => typeof NavigationContainer
type CreateNavigationContainer = (NavigationContainerComponent: typeof NavigationContainer, spanFactory: ReactNativeSpanFactory) => typeof NavigationContainer
type NavigationContainerRef = NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>

export const createNavigationContainer: CreateNavigationContainer = (NavigationContainerComponent = NavigationContainer, spanFactory: SpanFactory<ReactNativeConfiguration>) => {
export const createNavigationContainer: CreateNavigationContainer = (NavigationContainerComponent = NavigationContainer, spanFactory: ReactNativeSpanFactory) => {
return forwardRef<NavigationContainerRef, NavigationContainerProps>((props, _ref) => {
const { onStateChange, ...rest } = props

Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-react-navigation/lib/navigation-context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SpanFactory, SpanInternal } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import type { SpanInternal } from '@bugsnag/core-performance'
import type { ReactNativeSpanFactory } from '@bugsnag/react-native-performance'
import type { PropsWithChildren } from 'react'

import React from 'react'
Expand All @@ -13,7 +13,7 @@ export const NavigationContext = React.createContext({

interface Props extends PropsWithChildren {
currentRoute?: string
spanFactory: SpanFactory<ReactNativeConfiguration>
spanFactory: ReactNativeSpanFactory
}

type EndCondition = 'condition' | 'mount' | 'unmount' | 'immediate'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Plugin, SpanFactory } from '@bugsnag/core-performance'
import type { ReactNativeConfiguration } from '@bugsnag/react-native-performance'
import type { ReactNativeConfiguration, ReactNativeSpanFactory } from '@bugsnag/react-native-performance'
import { NavigationContainer } from '@react-navigation/native'
import { createNavigationContainer } from './create-navigation-container'

class BugsnagPluginReactNavigationNativePerformance implements Plugin<ReactNativeConfiguration> {
private spanFactory?: SpanFactory<ReactNativeConfiguration>
private spanFactory?: ReactNativeSpanFactory

configure (_configuration: ReactNativeConfiguration, spanFactory: SpanFactory<ReactNativeConfiguration>) {
this.spanFactory = spanFactory
this.spanFactory = spanFactory as ReactNativeSpanFactory
}

createNavigationContainer = (Container = NavigationContainer) => {
Expand Down

0 comments on commit 3dabdbe

Please sign in to comment.