|
| 1 | +--- |
| 2 | +title: React Native App |
| 3 | +cSpell:ignore: typeof |
| 4 | +--- |
| 5 | + |
| 6 | +The React Native app provides a mobile UI for users on Android and iOS devices |
| 7 | +to interact with the demo's services. It is built with |
| 8 | +[Expo](https://docs.expo.dev/get-started/introduction/) and uses Expo's |
| 9 | +file-based routing to layout the screens for the app. |
| 10 | + |
| 11 | +[React Native app source](https://github.com/open-telemetry/opentelemetry-demo/blob/main/src/react-native-app/) |
| 12 | + |
| 13 | +## Instrumentation |
| 14 | + |
| 15 | +The application uses the OpenTelemetry packages to instrument the application at |
| 16 | +the JS layer. |
| 17 | + |
| 18 | +{{% alert title="Important" color="warning" %}} |
| 19 | + |
| 20 | +The JS OTel packages are supported for node and web environments. While they |
| 21 | +work for React Native as well, they are not explicitly supported for that |
| 22 | +environment, where they might break compatibility with minor version updates or |
| 23 | +require workarounds. Building JS OTel package support for React Native is an |
| 24 | +area of active development. |
| 25 | + |
| 26 | +{{% /alert %}} |
| 27 | + |
| 28 | +The main entry point for the application is `app/_layout.tsx` where a hook is |
| 29 | +used to initialize the instrumentation and make sure it is loaded before |
| 30 | +displaying the UI: |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { useTracer } from '@/hooks/useTracer'; |
| 34 | + |
| 35 | +const { loaded: tracerLoaded } = useTracer(); |
| 36 | +``` |
| 37 | + |
| 38 | +`hooks/useTracer.ts` contains all the code for setting up instrumentation |
| 39 | +including initializing a TracerProvider, establishing an OTLP export, |
| 40 | +registering trace context propagators, and registering auto-instrumentation of |
| 41 | +network requests. |
| 42 | + |
| 43 | +```typescript |
| 44 | +import { |
| 45 | + CompositePropagator, |
| 46 | + W3CBaggagePropagator, |
| 47 | + W3CTraceContextPropagator, |
| 48 | +} from '@opentelemetry/core'; |
| 49 | +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; |
| 50 | +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; |
| 51 | +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; |
| 52 | +import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; |
| 53 | +import { registerInstrumentations } from '@opentelemetry/instrumentation'; |
| 54 | +import { Resource } from '@opentelemetry/resources'; |
| 55 | +import { |
| 56 | + ATTR_DEVICE_ID, |
| 57 | + ATTR_OS_NAME, |
| 58 | + ATTR_OS_VERSION, |
| 59 | + ATTR_SERVICE_NAME, |
| 60 | + ATTR_SERVICE_VERSION, |
| 61 | +} from '@opentelemetry/semantic-conventions/incubating'; |
| 62 | +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; |
| 63 | +import getLocalhost from '@/utils/Localhost'; |
| 64 | +import { useEffect, useState } from 'react'; |
| 65 | +import { |
| 66 | + getDeviceId, |
| 67 | + getSystemVersion, |
| 68 | + getVersion, |
| 69 | +} from 'react-native-device-info'; |
| 70 | +import { Platform } from 'react-native'; |
| 71 | +import { SessionIdProcessor } from '@/utils/SessionIdProcessor'; |
| 72 | + |
| 73 | +const Tracer = async () => { |
| 74 | + const localhost = await getLocalhost(); |
| 75 | + |
| 76 | + const resource = new Resource({ |
| 77 | + [ATTR_SERVICE_NAME]: 'react-native-app', |
| 78 | + [ATTR_OS_NAME]: Platform.OS, |
| 79 | + [ATTR_OS_VERSION]: getSystemVersion(), |
| 80 | + [ATTR_SERVICE_VERSION]: getVersion(), |
| 81 | + [ATTR_DEVICE_ID]: getDeviceId(), |
| 82 | + }); |
| 83 | + |
| 84 | + const provider = new WebTracerProvider({ |
| 85 | + resource, |
| 86 | + spanProcessors: [ |
| 87 | + new BatchSpanProcessor( |
| 88 | + new OTLPTraceExporter({ |
| 89 | + url: `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}/otlp-http/v1/traces`, |
| 90 | + }), |
| 91 | + { |
| 92 | + scheduledDelayMillis: 500, |
| 93 | + }, |
| 94 | + ), |
| 95 | + new SessionIdProcessor(), |
| 96 | + ], |
| 97 | + }); |
| 98 | + |
| 99 | + provider.register({ |
| 100 | + propagator: new CompositePropagator({ |
| 101 | + propagators: [ |
| 102 | + new W3CBaggagePropagator(), |
| 103 | + new W3CTraceContextPropagator(), |
| 104 | + ], |
| 105 | + }), |
| 106 | + }); |
| 107 | + |
| 108 | + registerInstrumentations({ |
| 109 | + instrumentations: [ |
| 110 | + // Some tiptoeing required here, propagateTraceHeaderCorsUrls is required to make the instrumentation |
| 111 | + // work in the context of a mobile app even though we are not making CORS requests. `clearTimingResources` must |
| 112 | + // be turned off to avoid using the web-only Performance API |
| 113 | + new FetchInstrumentation({ |
| 114 | + propagateTraceHeaderCorsUrls: /.*/, |
| 115 | + clearTimingResources: false, |
| 116 | + }), |
| 117 | + |
| 118 | + // The React Native implementation of fetch is simply a polyfill on top of XMLHttpRequest: |
| 119 | + // https://github.com/facebook/react-native/blob/7ccc5934d0f341f9bc8157f18913a7b340f5db2d/packages/react-native/Libraries/Network/fetch.js#L17 |
| 120 | + // Because of this when making requests using `fetch` there will an additional span created for the underlying |
| 121 | + // request made with XMLHttpRequest. Since in this demo calls to /api/ are made using fetch, turn off |
| 122 | + // instrumentation for that path to avoid the extra spans. |
| 123 | + new XMLHttpRequestInstrumentation({ |
| 124 | + ignoreUrls: [/\/api\/.*/], |
| 125 | + }), |
| 126 | + ], |
| 127 | + }); |
| 128 | +}; |
| 129 | + |
| 130 | +export interface TracerResult { |
| 131 | + loaded: boolean; |
| 132 | +} |
| 133 | + |
| 134 | +export const useTracer = (): TracerResult => { |
| 135 | + const [loaded, setLoaded] = useState<boolean>(false); |
| 136 | + |
| 137 | + useEffect(() => { |
| 138 | + if (!loaded) { |
| 139 | + Tracer() |
| 140 | + .catch(() => console.warn('failed to setup tracer')) |
| 141 | + .finally(() => setLoaded(true)); |
| 142 | + } |
| 143 | + }, [loaded]); |
| 144 | + |
| 145 | + return { |
| 146 | + loaded, |
| 147 | + }; |
| 148 | +}; |
| 149 | +``` |
0 commit comments