Skip to content

Commit

Permalink
feat(instrumentation-react-native-navigation) tweaks and improvements…
Browse files Browse the repository at this point in the history
… + adding logs
  • Loading branch information
facostaembrace committed Aug 5, 2024
1 parent a7265bf commit c47c8c1
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 143 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ typings/

# Optional npm cache directory
.npm
.npmrc

# Optional eslint cache
.eslintcache
Expand Down
21 changes: 13 additions & 8 deletions plugins/node/instrumentation-react-native-navigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ This module provides instrumentation for [react-native/nagivation](https://react

## Installation
```
npm i @embrace-io/react-native
npm i @opentelemetry/instrumentation-react-native-navigation @opentelemetry/api
```

or if you use yarn

```
yarn add @embrace-io/react-native
yarn add @opentelemetry/instrumentation-react-native-navigation @opentelemetry/api
```

## Supported Versions
Expand All @@ -27,11 +27,14 @@ If you are using `expo-router` or `react-native/navigation` you need to wrap you
```javascript
import {FC} from 'react';
import {Stack, useNavigationContainerRef} from 'expo-router';
import {NavigationTracker} from '@embrace/react-native/experimental/navigation';
import {NavigationTracker} from '@opentelemetry/instrumentation-react-native-navigation';

const App: FC = () => {
const navigationRef = useNavigationContainerRef(); // if you do not use `expo-router` the same hook is also available in `@react-navigation/native` since `expo-router` is built on top of it
const provider = useProvider(); // the provider is something you need to configure and pass down as prop into the `NavigationTracker` component
const navigationRef = useNavigationContainerRef(); // if you do not use `expo-router` the same hook is also available in `@react-navigation/native` since `expo-router` is built on top of it. Just make sure this ref is passed also to the navigation container at the root of your app (if not, the ref would be empty and you will get a console.warn message instead).

const provider = useProvider(); // the provider is something you need to configure and pass down as prop into the `NavigationTracker` component (this hook is not part of the package, it is just used here as a reference)
// If your choise is not to pass any custom tracer provider, the <NavigationTracker /> component will use the global one.
// In both cases you have to make sure a tracer provider is registered BEFORE you attempt to record the first span.

return (
<NavigationTracker ref={navigationRef} provider={provider}>
Expand Down Expand Up @@ -73,7 +76,7 @@ Navigation.events().registerAppLaunchedListener(async () => {

const HomeScreen: FC = () => {
const navigationRef = useRef(Navigation.events()); // this is the important part. Make sure you pass a reference with the return of Navigation.events();
const provider = useProvider(); // again, the provider should be passed down into the `NativeNavigationTracker` with the selected exporter and processor configured
const provider = useProvider(); // again, the provider should be passed down into the `NativeNavigationTracker` with the selected exporter and processor configured (this hook is not part of the package, it is just used here as a reference)

return (
<NativeNavigationTracker ref={navigationRef} provider={provider}>
Expand Down Expand Up @@ -103,18 +106,20 @@ For instance, when the application starts and the user navigates to a new sectio
traceId: 'a3280f7e6afab1e5b7f4ecfc12ec059f',
parentId: undefined,
traceState: undefined,
name: 'initial-test-view',
name: 'home',
id: '270509763b408343',
kind: 0,
timestamp: 1718975153696000,
duration: 252.375,
attributes: { initial_view: true },
attributes: { launch: true, state.end: 'active' },
status: { code: 0 },
events: [],
links: []
}
```

If you dig into the attributes, `launch` refers to the moment the app is launched. It will be `true` only the first time the app mounts. Changing the status between background/foreground won't modify this attribute. For this case the `state.end` is used, and will and it can contain two possible values: `active` and `background`.

### NOTE

`useProvider` hook in this example returns an instance of a configured provided.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
{
"name": "@opentelemetry/instrumentation-react-native-navigation",
"version": "0.1.0",
"description": "OpenTelemetry instrumentation for the `@react-navigation/native` views for React Native applications",
"description": "OpenTelemetry instrumentation for `@react-navigation/native`, `expo-router` and `wix/react-native-navigation` route changes for React Native applications",
"keywords": [
"opentelemetry"
"navigation",
"react-native",
"instrumentation",
"nodejs",
"opentelemetry",
"profiling",
"tracing"
],
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/instrumentation-react-native-navigation#readme",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef, ReactNode } from 'react';
import { TracerProvider } from '@opentelemetry/api';
import { TracerOptions, TracerProvider } from '@opentelemetry/api';

import useTrace from '../utils/hooks/useTrace';
import useNativeNavigationTracker, {
NativeNavRef,
} from '../hooks/useNativeNavigationTracker';
import { NavigationTrackerConfig } from '../types/navigation';

export type NativeNavigationTrackerRef = NativeNavRef;
type NativeNavigationTrackerRef = NativeNavRef;

interface NativeNavigationTrackerProps {
children: ReactNode;
// selected provider, should be configured by the app consumer
provider?: TracerProvider;
// in case a provider is passed
options?: TracerOptions;
config?: NavigationTrackerConfig;
}

const NativeNavigationTracker = forwardRef<
NativeNavigationTrackerRef,
NativeNavigationTrackerProps
>(({ children, provider }, ref) => {
>(({ children, provider, options, config }, ref) => {
// Initializing a Trace instance
const tracer = useTrace(
{ name: 'native-navigation', version: '0.1.0' },
provider
);
const tracer = useTrace(provider, options);

useNativeNavigationTracker(ref, tracer);
useNativeNavigationTracker(ref, tracer, config);

return <>{children}</>;
});

NativeNavigationTracker.displayName = 'NativeNavigationTracker';
export default NativeNavigationTracker;
export type { NativeNavigationTrackerRef };
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef, ReactNode } from 'react';
import { TracerProvider } from '@opentelemetry/api';
import { TracerOptions, TracerProvider } from '@opentelemetry/api';

import useTrace from '../utils/hooks/useTrace';
import useNavigationTracker, { NavRef } from '../hooks/useNavigationTracker';
import { NavigationTrackerConfig } from '../types/navigation';

type NavigationTrackerRef = NavRef;

interface NavigationTrackerProps {
children: ReactNode;
// selected provider, configured by the app consumer if global tracer is not enough
provider?: TracerProvider;
// in case a provider is passed
options?: TracerOptions;
config?: NavigationTrackerConfig;
}

const NavigationTracker = forwardRef<
NavigationTrackerRef,
NavigationTrackerProps
>(({ children, provider }, ref) => {
>(({ children, provider, options, config }, ref) => {
// Initializing a Trace instance
const tracer = useTrace(
{ name: 'native-navigation', version: '0.1.0' },
provider
);
const tracer = useTrace(provider, options);

useNavigationTracker(ref, tracer);
useNavigationTracker(ref, tracer, config);

return <>{children}</>;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@
import { AppState, AppStateStatus } from 'react-native';
import { useEffect } from 'react';

// import pkg from 'react-native';
// const { AppState } = pkg;

// import react from 'react';
// const { useEffect } = react;

type CallbackFn = (currentState: AppStateStatus) => void;

const useAppStateListener = (callback?: CallbackFn) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,22 @@ import spanCreator, {
} from '../utils/spanCreator';
import { TracerRef } from '../utils/hooks/useTrace';
import useSpan from '../utils/hooks/useSpan';
import { INativeNavigationContainer } from '../types/navigation';
import {
INativeNavigationContainer,
NavigationTrackerConfig,
} from '../types/navigation';

import useAppStateListener from './useAppStateListener';

export type NativeNavRef = INativeNavigationContainer;

const useNativeNavigationTracker = (
ref: ForwardedRef<NativeNavRef>,
tracer: TracerRef
tracer: TracerRef,
config?: NavigationTrackerConfig
) => {
const { attributes: customAttributes } = config ?? {};

const navigationElRef = useMemo(() => {
const isMutableRef = ref !== null && typeof ref !== 'function';
return isMutableRef ? ref.current : undefined;
Expand All @@ -45,50 +51,67 @@ const useNativeNavigationTracker = (

useEffect(() => {
if (!navigationElRef) {
console.warn(
'Navigation ref is not available. Make sure this is properly configured.'
);

// do nothing in case for some reason there is no navigationElRef
return;
}

navigationElRef.registerComponentDidAppearListener(({ componentName }) => {
if (!componentName) {
console.warn(
'Navigation component name is not available. Make sure this is properly configured.'
);

// do nothing in case for some reason there is no route
return;
}

spanCreator(tracer, span, navView, componentName);
spanCreator(tracer, span, navView, componentName, customAttributes);
});

navigationElRef.registerComponentDidDisappearListener(
({ componentName }) => {
if (!componentName) {
console.warn(
'Navigation component name is not available. Make sure this is properly configured.'
);

// do nothing in case for some reason there is no route
return;
}

spanEnd(span);
}
);
}, [navigationElRef, span, tracer]);
}, [navigationElRef, span, tracer, customAttributes]);

useEffect(
() => () => {
// making sure the final span is ended when the app is unmounted
spanEnd(span);
const isFinalView = true;
spanEnd(span, undefined, isFinalView);
},
[span]
);

const handleAppStateListener = useCallback(
(currentState: AppStateStatus) => {
const appStateHandler = spanCreatorAppState(tracer, span);
const appStateHandler = spanCreatorAppState(
tracer,
span,
customAttributes
);

if (navView?.current === null) {
return;
}

appStateHandler(navView?.current, currentState);
},
[span, tracer]
[span, tracer, customAttributes]
);

useAppStateListener(handleAppStateListener);
Expand Down
Loading

0 comments on commit c47c8c1

Please sign in to comment.