diff --git a/plugins/node/instrumentation-react-native-navigation/README.md b/plugins/node/instrumentation-react-native-navigation/README.md index a079c20c20..2f498aa596 100644 --- a/plugins/node/instrumentation-react-native-navigation/README.md +++ b/plugins/node/instrumentation-react-native-navigation/README.md @@ -162,7 +162,7 @@ As mentioned before, relies on `@react-native/navigation` ### Note `useProvider` hook in this example returns an instance of a configured provided. -It doesn't matter which provider you choose; you just need to pass down one (if needed) with all your configurations. To create that provider, you may want to refer to the official [OpenTelemetry JS documentation](https://github.com/open-telemetry/opentelemetry-js). You can also review our suggested implementation (`experimental/testUtils/hooks/useProvider.ts`), but keep in mind that this is the simplest provider with minimal configurations. +It doesn't matter which provider you choose; you just need to pass down one (if needed) with all your configurations. To create that provider, you may want to refer to the official [OpenTelemetry JS documentation](https://github.com/open-telemetry/opentelemetry-js). You can also review our suggested implementation (`./test/hooks/useProvider.ts`), but keep in mind that this is the simplest provider with minimal configurations. ## Useful links diff --git a/plugins/node/instrumentation-react-native-navigation/src/components/NativeNavigationTracker.tsx b/plugins/node/instrumentation-react-native-navigation/src/components/NativeNavigationTracker.tsx index 2f3eaf61ee..111f8b8a25 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/components/NativeNavigationTracker.tsx +++ b/plugins/node/instrumentation-react-native-navigation/src/components/NativeNavigationTracker.tsx @@ -16,7 +16,7 @@ import { forwardRef, ReactNode } from 'react'; import { TracerProvider } from '@opentelemetry/api'; -import useTraceRef from '../utils/hooks/useTracerRef'; +import useTracerRef from '../utils/hooks/useTracerRef'; import useNativeNavigationTracker, { NativeNavRef, } from '../hooks/useNativeNavigationTracker'; @@ -36,7 +36,7 @@ const NativeNavigationTracker = forwardRef< NativeNavigationTrackerProps >(({ children, provider, config }, ref) => { // Initializing a Trace instance - const tracer = useTraceRef(provider, config); + const tracer = useTracerRef(provider, config); useNativeNavigationTracker(ref, tracer, config); diff --git a/plugins/node/instrumentation-react-native-navigation/src/components/NavigationTracker.tsx b/plugins/node/instrumentation-react-native-navigation/src/components/NavigationTracker.tsx index fc646c3a36..f783cf8529 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/components/NavigationTracker.tsx +++ b/plugins/node/instrumentation-react-native-navigation/src/components/NavigationTracker.tsx @@ -16,7 +16,7 @@ import { forwardRef, ReactNode } from 'react'; import { TracerProvider } from '@opentelemetry/api'; -import useTraceRef from '../utils/hooks/useTracerRef'; +import useTracerRef from '../utils/hooks/useTracerRef'; import useNavigationTracker, { NavRef } from '../hooks/useNavigationTracker'; import { NavigationTrackerConfig } from '../types/navigation'; @@ -34,7 +34,7 @@ const NavigationTracker = forwardRef< NavigationTrackerProps >(({ children, provider, config }, ref) => { // Initializing a Trace instance - const tracer = useTraceRef(provider, config); + const tracer = useTracerRef(provider, config); useNavigationTracker(ref, tracer, config); diff --git a/plugins/node/instrumentation-react-native-navigation/src/hooks/useAppStateListener.ts b/plugins/node/instrumentation-react-native-navigation/src/hooks/useAppStateListener.ts index 0e66913faf..f1173aa846 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/hooks/useAppStateListener.ts +++ b/plugins/node/instrumentation-react-native-navigation/src/hooks/useAppStateListener.ts @@ -14,14 +14,40 @@ * limitations under the License. */ import { AppState, AppStateStatus } from 'react-native'; -import { useEffect } from 'react'; +import { MutableRefObject, useCallback, useEffect } from 'react'; +import { spanCreatorAppState } from '../utils/spanFactory'; +import { TracerRef } from '../utils/hooks/useTracerRef'; +import { SpanRef } from '../utils/hooks/useSpanRef'; +import { Attributes } from '@opentelemetry/api'; -type CallbackFn = (currentState: AppStateStatus) => void; +const useAppStateListener = ( + tracer: TracerRef, + span: SpanRef, + view: MutableRefObject, + attributes?: Attributes +) => { + /** + * App State Span Factory + */ + const initAppStateSpan = useCallback( + (currentState: AppStateStatus) => { + const appStateHandler = spanCreatorAppState(tracer, span, attributes); -const useAppStateListener = (callback?: CallbackFn) => { + if (view?.current === null) { + return; + } + + appStateHandler(view?.current, currentState); + }, + [span, tracer, attributes] + ); + + /** + * App State Listener changes + */ useEffect(() => { const handleAppStateChange = (currentState: AppStateStatus) => { - callback?.(currentState); + initAppStateSpan(currentState); }; const subscription = AppState.addEventListener( @@ -32,7 +58,7 @@ const useAppStateListener = (callback?: CallbackFn) => { return () => { subscription.remove(); }; - }, [callback]); + }, [initAppStateSpan]); }; export default useAppStateListener; diff --git a/plugins/node/instrumentation-react-native-navigation/src/hooks/useNativeNavigationTracker.ts b/plugins/node/instrumentation-react-native-navigation/src/hooks/useNativeNavigationTracker.ts index a233d49303..10aae52c23 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/hooks/useNativeNavigationTracker.ts +++ b/plugins/node/instrumentation-react-native-navigation/src/hooks/useNativeNavigationTracker.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import { AppStateStatus } from 'react-native'; -import { ForwardedRef, useCallback, useEffect, useMemo, useRef } from 'react'; +import { ForwardedRef, useEffect, useMemo, useRef } from 'react'; -import spanCreator, { - spanCreatorAppState, - spanEnd, -} from '../utils/spanCreator'; +import { spanCreator, spanEnd } from '../utils/spanFactory'; import { TracerRef } from '../utils/hooks/useTracerRef'; import useSpanRef from '../utils/hooks/useSpanRef'; import { @@ -38,19 +34,29 @@ const useNativeNavigationTracker = ( tracer: TracerRef, config?: NavigationTrackerConfig ) => { - const { attributes: customAttributes, debug } = config ?? {}; - const console = useConsole(!!debug); - const navigationElRef = useMemo(() => { const isMutableRef = ref !== null && typeof ref !== 'function'; return isMutableRef ? ref.current : undefined; }, [ref]); - const navView = useRef(null); + const { attributes: customAttributes, debug } = config ?? {}; + const console = useConsole(!!debug); - // Initializing a Span + const view = useRef(null); const span = useSpanRef(); + /** + * Navigation Span Factory + */ + const initNativeNavigationSpan = useMemo( + () => spanCreator(tracer, span, view, customAttributes), + [customAttributes] + ); + + /** + * Registering the componentDidAppear and componentDidDisappear listeners + * to start and end spans depending on the navigation lifecycle + */ useEffect(() => { if (!navigationElRef) { console.warn( @@ -71,7 +77,7 @@ const useNativeNavigationTracker = ( return; } - spanCreator(tracer, span, navView, componentName, customAttributes); + initNativeNavigationSpan(componentName); }); navigationElRef.registerComponentDidDisappearListener( @@ -88,35 +94,23 @@ const useNativeNavigationTracker = ( spanEnd(span); } ); - }, [navigationElRef, span, tracer, customAttributes]); + }, [navigationElRef, span, initNativeNavigationSpan]); + /** + * Start and end spans depending on the app state changes + */ + useAppStateListener(tracer, span, view, customAttributes); + + /** + * Ending the final span depending on the app lifecycle + */ useEffect( () => () => { // making sure the final span is ended when the app is unmounted - const isFinalView = true; - spanEnd(span, undefined, isFinalView); + spanEnd(span, undefined, true); }, [span] ); - - const handleAppStateListener = useCallback( - (currentState: AppStateStatus) => { - const appStateHandler = spanCreatorAppState( - tracer, - span, - customAttributes - ); - - if (navView?.current === null) { - return; - } - - appStateHandler(navView?.current, currentState); - }, - [span, tracer, customAttributes] - ); - - useAppStateListener(handleAppStateListener); }; export default useNativeNavigationTracker; diff --git a/plugins/node/instrumentation-react-native-navigation/src/hooks/useNavigationTracker.ts b/plugins/node/instrumentation-react-native-navigation/src/hooks/useNavigationTracker.ts index e11fb3321e..20e7bf77fe 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/hooks/useNavigationTracker.ts +++ b/plugins/node/instrumentation-react-native-navigation/src/hooks/useNavigationTracker.ts @@ -14,13 +14,9 @@ * limitations under the License. */ -import { AppStateStatus } from 'react-native'; -import { ForwardedRef, useCallback, useEffect, useMemo, useRef } from 'react'; +import { ForwardedRef, useEffect, useMemo, useRef } from 'react'; -import spanCreator, { - spanCreatorAppState, - spanEnd, -} from '../utils/spanCreator'; +import { spanCreator, spanEnd } from '../utils/spanFactory'; import { TracerRef } from '../utils/hooks/useTracerRef'; import useSpanRef from '../utils/hooks/useSpanRef'; import { @@ -38,20 +34,29 @@ const useNavigationTracker = ( tracer: TracerRef, config?: NavigationTrackerConfig ) => { - const { attributes: customAttributes, debug } = config ?? {}; - const console = useConsole(!!debug); - const navigationElRef = useMemo(() => { const isMutableRef = ref !== null && typeof ref !== 'function'; return isMutableRef ? ref.current : undefined; }, [ref]); - // tracking specific (no otel related) - const navView = useRef(null); + const { attributes: customAttributes, debug } = config ?? {}; + const console = useConsole(!!debug); - // Initializing a Span const span = useSpanRef(); + const view = useRef(null); + + /** + * Native Navigation Span Factory + */ + const initNavigationSpan = useMemo( + () => spanCreator(tracer, span, view, customAttributes), + [customAttributes] + ); + /** + * Registering the Navigation 'state' Listener + * to start and end spans depending on the navigation lifecycle + */ useEffect(() => { if (!navigationElRef) { console.warn( @@ -74,38 +79,26 @@ const useNavigationTracker = ( return; } - spanCreator(tracer, span, navView, routeName, customAttributes); + initNavigationSpan(routeName); }); } - }, [navigationElRef, span, tracer, customAttributes]); + }, [navigationElRef, initNavigationSpan]); + + /** + * Start and end spans depending on the app state changes + */ + useAppStateListener(tracer, span, view, customAttributes); + /** + * Ending the final span depending on the app lifecycle + */ useEffect( () => () => { // making sure the final span is ended when the app is unmounted - const isFinalView = true; - spanEnd(span, undefined, isFinalView); + spanEnd(span, undefined, true); }, [span] ); - - const handleAppStateListener = useCallback( - (currentState: AppStateStatus) => { - const appStateHandler = spanCreatorAppState( - tracer, - span, - customAttributes - ); - - if (navView?.current === null) { - return; - } - - appStateHandler(navView?.current, currentState); - }, - [span, tracer, customAttributes] - ); - - useAppStateListener(handleAppStateListener); }; export default useNavigationTracker; diff --git a/plugins/node/instrumentation-react-native-navigation/src/utils/hooks/useTracerRef.ts b/plugins/node/instrumentation-react-native-navigation/src/utils/hooks/useTracerRef.ts index 4a36ef80f6..5b86ae3778 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/utils/hooks/useTracerRef.ts +++ b/plugins/node/instrumentation-react-native-navigation/src/utils/hooks/useTracerRef.ts @@ -29,7 +29,6 @@ const useTracerRef = ( const tracerRef = useRef(null); const console = useConsole(!!debug); - // using the layout effect to make sure the tracer is initialized before the component is rendered useEffect(() => { if (tracerRef.current === null) { if (!provider) { diff --git a/plugins/node/instrumentation-react-native-navigation/src/utils/spanCreator.ts b/plugins/node/instrumentation-react-native-navigation/src/utils/spanFactory.ts similarity index 67% rename from plugins/node/instrumentation-react-native-navigation/src/utils/spanCreator.ts rename to plugins/node/instrumentation-react-native-navigation/src/utils/spanFactory.ts index 867dff506d..fe2681cef7 100644 --- a/plugins/node/instrumentation-react-native-navigation/src/utils/spanCreator.ts +++ b/plugins/node/instrumentation-react-native-navigation/src/utils/spanFactory.ts @@ -42,10 +42,12 @@ const spanStart = ( // Starting the span span.current = tracer.current.startSpan(currentRouteName); - // it should create the first span knowing there is not a previous view - span.current.setAttribute(ATTRIBUTES.initialView, !!isLaunch); - // it should set the view name in case it's useful have this as attr - span.current.setAttribute(ATTRIBUTES.viewName, currentRouteName); + span.current.setAttributes({ + // it should create the first span knowing there is not a previous view + [ATTRIBUTES.initialView]: !!isLaunch, + // it should set the view name in case it's useful have this as attr + [ATTRIBUTES.viewName]: currentRouteName, + }); if (customAttributes) { span.current.setAttributes(customAttributes); @@ -71,6 +73,35 @@ const spanEnd = ( } }; +const spanCreator = + ( + tracer: TracerRef, + span: SpanRef, + view: MutableRefObject, + customAttributes?: Attributes + ) => + (currentRouteName: string) => { + if (!tracer.current) { + // do nothing in case for some reason the tracer is not initialized + return; + } + + const isInitialView = view.current === null; + + const shouldEndCurrentSpan = + view.current !== null && view.current !== currentRouteName; + + // it means the view has changed and we are ending the previous span + if (shouldEndCurrentSpan) { + spanEnd(span); + } + + spanStart(tracer, span, currentRouteName, customAttributes, isInitialView); + + // last step before it changes the view + view.current = currentRouteName; + }; + const spanCreatorAppState = (tracer: TracerRef, span: SpanRef, customAttributes?: Attributes) => (currentRouteName: string, currentState: AppStateStatus) => { @@ -85,33 +116,4 @@ const spanCreatorAppState = } }; -const spanCreator = ( - tracer: TracerRef, - span: SpanRef, - view: MutableRefObject, - currentRouteName: string, - customAttributes?: Attributes -) => { - if (!tracer.current) { - // do nothing in case for some reason the tracer is not initialized - return; - } - - const isInitialView = view.current === null; - - const shouldEndCurrentSpan = - view.current !== null && view.current !== currentRouteName; - - // it means the view has changed and we are ending the previous span - if (shouldEndCurrentSpan) { - spanEnd(span); - } - - spanStart(tracer, span, currentRouteName, customAttributes, isInitialView); - - // last step before it changes the view - view.current = currentRouteName; -}; - -export default spanCreator; -export { spanStart, spanEnd, spanCreatorAppState, ATTRIBUTES }; +export { spanCreator, spanCreatorAppState, spanStart, spanEnd, ATTRIBUTES }; diff --git a/plugins/node/instrumentation-react-native-navigation/test/NativeNavigationTracker.test.tsx b/plugins/node/instrumentation-react-native-navigation/test/NativeNavigationTracker.test.tsx index 09e4046fdc..03b96eaa60 100644 --- a/plugins/node/instrumentation-react-native-navigation/test/NativeNavigationTracker.test.tsx +++ b/plugins/node/instrumentation-react-native-navigation/test/NativeNavigationTracker.test.tsx @@ -16,7 +16,7 @@ import { AppState } from 'react-native'; import React, { FC, useRef } from 'react'; import { render } from '@testing-library/react'; -import { ATTRIBUTES } from '../src/utils/spanCreator'; +import { ATTRIBUTES } from '../src/utils/spanFactory'; import sinon from 'sinon'; import { NativeNavigationTracker } from '../src'; import useProvider from './hooks/useProvider'; diff --git a/plugins/node/instrumentation-react-native-navigation/test/NavigationTracker.test.tsx b/plugins/node/instrumentation-react-native-navigation/test/NavigationTracker.test.tsx index 0a53244413..478c1dfbe3 100644 --- a/plugins/node/instrumentation-react-native-navigation/test/NavigationTracker.test.tsx +++ b/plugins/node/instrumentation-react-native-navigation/test/NavigationTracker.test.tsx @@ -19,7 +19,7 @@ import React, { render } from '@testing-library/react'; import useProvider from './hooks/useProvider'; import { NavRef } from '../src/hooks/useNavigationTracker'; -import { ATTRIBUTES } from '../src/utils/spanCreator'; +import { ATTRIBUTES } from '../src/utils/spanFactory'; import sinon from 'sinon'; import { NavigationTracker } from '../src';