React Native App
React Native 应用为 Android 和 iOS 设备上的用户提供了与演示服务交互的移动 UI。它使用 Expo 构建,并使用 Expo 的基于文件的路由来布局应用程序的屏幕。
仪表
该应用程序使用 OpenTelemetry 包在 JS 层对应用程序进行检测。
重要提示
JS OTel 包支持 node 和 web 环境。虽然它们也适用于 React Native,但并未明确支持该环境,在其中它们可能会破坏与次要版本更新的兼容性或需要变通方法。为 React Native 构建 JS OTel 包支持是一个积极开发中的领域。
应用程序的主入口点是 app/_layout.tsx,其中使用了一个 hook 来初始化检测,并确保在显示 UI 之前已加载。
import { useTracer } from '@/hooks/useTracer';
const { loaded: tracerLoaded } = useTracer();
hooks/useTracer.ts 包含设置检测的所有代码,包括初始化 TracerProvider、建立 OTLP 导出、注册跟踪上下文传播器以及注册网络请求的自动检测。
import {
CompositePropagator,
W3CBaggagePropagator,
W3CTraceContextPropagator,
} from '@opentelemetry/core';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
ATTR_DEVICE_ID,
ATTR_OS_NAME,
ATTR_OS_VERSION,
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions/incubating';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import getLocalhost from '@/utils/Localhost';
import { useEffect, useState } from 'react';
import {
getDeviceId,
getSystemVersion,
getVersion,
} from 'react-native-device-info';
import { Platform } from 'react-native';
import { SessionIdProcessor } from '@/utils/SessionIdProcessor';
const Tracer = async () => {
const localhost = await getLocalhost();
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'react-native-app',
[ATTR_OS_NAME]: Platform.OS,
[ATTR_OS_VERSION]: getSystemVersion(),
[ATTR_SERVICE_VERSION]: getVersion(),
[ATTR_DEVICE_ID]: getDeviceId(),
});
const provider = new WebTracerProvider({
resource,
spanProcessors: [
new BatchSpanProcessor(
new OTLPTraceExporter({
url: `http://${localhost}:${process.env.EXPO_PUBLIC_FRONTEND_PROXY_PORT}/otlp-http/v1/traces`,
}),
{
scheduledDelayMillis: 500,
},
),
new SessionIdProcessor(),
],
});
provider.register({
propagator: new CompositePropagator({
propagators: [
new W3CBaggagePropagator(),
new W3CTraceContextPropagator(),
],
}),
});
registerInstrumentations({
instrumentations: [
// Some tiptoeing required here, propagateTraceHeaderCorsUrls is required to make the instrumentation
// work in the context of a mobile app even though we are not making CORS requests. `clearTimingResources` must
// be turned off to avoid using the web-only Performance API
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: /.*/,
clearTimingResources: false,
}),
// The React Native implementation of fetch is simply a polyfill on top of XMLHttpRequest:
// https://github.com/facebook/react-native/blob/7ccc5934d0f341f9bc8157f18913a7b340f5db2d/packages/react-native/Libraries/Network/fetch.js#L17
// Because of this when making requests using `fetch` there will an additional span created for the underlying
// request made with XMLHttpRequest. Since in this demo calls to /api/ are made using fetch, turn off
// instrumentation for that path to avoid the extra spans.
new XMLHttpRequestInstrumentation({
ignoreUrls: [/\/api\/.*/],
}),
],
});
};
export interface TracerResult {
loaded: boolean;
}
export const useTracer = (): TracerResult => {
const [loaded, setLoaded] = useState<boolean>(false);
useEffect(() => {
if (!loaded) {
Tracer()
.catch(() => console.warn('failed to setup tracer'))
.finally(() => setLoaded(true));
}
}, [loaded]);
return {
loaded,
};
};