diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca03883cd..52c09496c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,22 +163,40 @@ use_frameworks! ``` **NOTE:** You do **NOT** need to add `DdSdkReactNative` here manually, `pod install` should find and install it automatically -Now you can go back to your `App.js/tsx` and use `@flashcatcloud/mobile-react-native` from there +Now you can go back to your `App.js/tsx` and use `@flashcatcloud/mobile-react-native` from there. + +Wrap your app root in `DatadogProvider`. It installs the JavaScript auto-instrumentation +during its own render pass and initializes the native SDK afterwards, so requests made while +the app starts up are still collected. Initializing by hand with `DdSdkReactNative.initialize()` +reverses that order and those requests produce no resource event at all — see +[migrating to DatadogProvider](./docs/migrating_to_datadog_provider.md). + Example code: ``` -import { DdSdkReactNative, DdSdkReactNativeConfiguration } from '@flashcatcloud/mobile-react-native'; +import { + DatadogProvider, + DatadogProviderConfiguration, + TrackingConsent, +} from '@flashcatcloud/mobile-react-native'; + +const config = new DatadogProviderConfiguration( + "", + "", + "", + true, // track User interactions (e.g.: Tap on buttons) + true, // track XHR Resources + true, // track Errors + TrackingConsent.GRANTED +) +config.firstPartyHosts = ['example.com']; const App: () => React$Node = () => { - const config = new DdSdkReactNativeConfiguration( - "", - "", - "", - true, // track User interactions (e.g.: Tap on buttons) - true, // track XHR Resources - true // track Errors - ) - DdSdkReactNative.initialize(config); - ... + return ( + + ... + + ); +} ``` Then your project should work without problems ✅ @@ -190,7 +208,7 @@ If it doesn't, you should fix it before shipping ❌ Many great ideas for new features come from the community, and we'd be happy to consider yours! -To share your request, you can open an [issue](https://github.com/flashcat/fc-sdk-reactnative/issues/new) +To share your request, you can open an [issue](https://github.com/flashcatcloud/fc-sdk-reactnative/issues/new) with the details about what you'd like to see. At a minimum, please provide: - The goal of the new feature; @@ -205,7 +223,7 @@ or UI, contact our support team via https://docs.datadoghq.com/help/ for direct, faster assistance. You may submit bug reports concerning the Datadog SDK for Android by -[opening a Github issue](https://github.com/flashcat/fc-sdk-reactnative/issues/new). +[opening a Github issue](https://github.com/flashcatcloud/fc-sdk-reactnative/issues/new). At a minimum, please provide: - A description of the problem; @@ -233,20 +251,20 @@ the bug are best. ## Have a patch? We welcome code contributions to the library, which you can -[submit as a pull request](https://github.com/flashcat/fc-sdk-reactnative/pull/new/master). +[submit as a pull request](https://github.com/flashcatcloud/fc-sdk-reactnative/compare). Before you submit a PR, make sure that you first create an Issue to explain the bug or the feature your patch covers, and make sure another Issue or PR doesn't already exist. To create a pull request: -1. **Fork the repository** from https://github.com/flashcat/fc-sdk-reactnative ; +1. **Fork the repository** from https://github.com/flashcatcloud/fc-sdk-reactnative ; 2. **Make any changes** for your patch; 3. **Write tests** that demonstrate how the feature works or how the bug is fixed; -4. **Update any documentation** such as `docs/GettingStarted.md`, especially for - new features; +4. **Update any documentation** — the [core package README](./packages/core/README.md) + and the guides under `docs/`, especially for new features; 5. **Submit the pull request** from your fork back to this - [repository](https://github.com/flashcat/fc-sdk-reactnative) . + [repository](https://github.com/flashcatcloud/fc-sdk-reactnative) . The pull request will be run through our CI pipeline, and a project member will diff --git a/README.md b/README.md index e705c77af..b116d53e3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,47 @@ yarn add @flashcatcloud/mobile-react-native Then initialize the SDK with your Flashcat client token and RUM application ID. Data is sent to the Flashcat `CN` site by default; use the `STAGING` site or `customEndpoints` for other environments. +```javascript +import { + DatadogProvider, + DatadogProviderConfiguration, + TrackingConsent +} from '@flashcatcloud/mobile-react-native'; + +const config = new DatadogProviderConfiguration( + '', + '', + '', + true, // track user interactions (taps) + true, // track XHR / fetch resources + true, // track JS errors + TrackingConsent.GRANTED +); +config.site = 'CN'; +config.serviceName = ''; +config.nativeCrashReportEnabled = true; +// Your own backend hosts. Only requests matching these carry tracing headers, which is +// what links a resource in RUM to its backend trace. +config.firstPartyHosts = ['example.com']; + +export default function App() { + return ( + + + + ); +} +``` + +Wrap the app root, as high in the tree as you can. `DatadogProvider` installs the JavaScript +auto-instrumentation during its own render pass and initializes the native SDK afterwards, +buffering whatever is reported meanwhile — so requests made while the app starts up are +collected. Initializing by hand with `DdSdkReactNative.initialize()` reverses that order and +those requests produce no resource event at all; see [migrating to DatadogProvider][10] for +why, and for the react-native-navigation (Wix) case where the manual call is still required. + +The [core package reference][8] documents every configuration option, view tracking and data storage. + The RUM React Native SDK supports [Expo][2]. The RUM React Native SDK supports monitoring hybrid applications. @@ -47,8 +88,10 @@ Pull requests are welcome. First, open an issue to discuss what you would like t For more information, see [Apache License, v2.0][7] [2]: https://docs.expo.dev/ -[4]: https://github.com/flashcat/fc-sdk-reactnative/blob/develop/TROUBLESHOOTING.md -[5]: https://github.com/flashcat/fc-sdk-reactnative/issues?q=is%3Aissue -[6]: https://github.com/flashcat/fc-sdk-reactnative/blob/develop/CONTRIBUTING.md -[7]: https://github.com/flashcat/fc-sdk-reactnative/blob/main/LICENSE +[4]: ./TROUBLESHOOTING.md +[5]: https://github.com/flashcatcloud/fc-sdk-reactnative/issues?q=is%3Aissue +[6]: ./CONTRIBUTING.md +[7]: ./LICENSE +[8]: ./packages/core/README.md [9]: https://opentelemetry.io/ +[10]: ./docs/migrating_to_datadog_provider.md diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index b0657086a..7dab26376 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,12 +1,12 @@ # TROUBLESHOOTING -### No data is being sent to Datadog +### No data is being sent See the [dedicated troubleshooting guide](./docs/troubleshooting_no_data.md). ### `Undefined symbols: Swift` -Original issue: https://github.com/flashcat/fc-sdk-reactnative/issues/41 +Original issue: https://github.com/flashcatcloud/fc-sdk-reactnative/issues/41 If you have the following error message: diff --git a/docs/migrating_to_datadog_provider.md b/docs/migrating_to_datadog_provider.md index cf38ace7c..2fd3f4a44 100644 --- a/docs/migrating_to_datadog_provider.md +++ b/docs/migrating_to_datadog_provider.md @@ -1,5 +1,26 @@ ## Overview +`DatadogProvider` replaces the manual `DdSdkReactNative.initialize()` call. This is a +correctness change, not a style preference — the two do the same work in the opposite order. + +`DdSdkReactNative.initialize()` awaits the native SDK across the bridge and installs the +JavaScript auto-instrumentation only after that resolves. The XHR proxy behind +`track XHR / fetch resources` is a live patch on `XMLHttpRequest.prototype`, not a buffered +call: a request that starts before the patch is applied produces no resource event at all. +It is not dropped later and cannot be recovered — the SDK never sees it. Because most apps +fetch their first screen while starting up, that window usually swallows the requests you +most want to look at. Calling `initialize()` earlier narrows that window but does not close it +from inside a React tree: children mount before any effect of yours can await the call. Closing +it that way means awaiting initialization in your entry file before the app is registered, which +trades startup latency for the coverage the provider gives you for free. + +`DatadogProvider` installs the instrumentation during its own render pass, before any child +renders, and initializes the native SDK afterwards. Anything reported meanwhile goes into a +bounded buffer and is flushed once the native SDK is up, so the window is closed rather than +merely narrowed. + +The same ordering applies to user interactions and JS errors raised during the first render. + ## Change the configuration class Change your configuration from a `DdSdkReactNativeConfiguration` to a `DatadogProviderConfiguration` instance: @@ -28,6 +49,9 @@ export default function App() { } ``` +Wrap the app root, as high in the tree as you can. The provider only covers what renders +below it, so every level you push it down is a level whose startup requests go uncollected. + ## Remove call to DdSdkReactNative.initialize Remove the call to `DdSdkReactNative.initialize` in your code. @@ -53,6 +77,23 @@ export default function App() { ### Delaying the initialization -See the [documentation on asynchronous initialization][1]. +Set `initializationMode` on the configuration. `InitializationMode.SYNC` (the default) +initializes the native SDK as the provider renders. `InitializationMode.ASYNC` defers that +native initialization until after the current interactions and animations have finished, so +it does not compete with your first screen: + +```javascript +import { InitializationMode } from '@flashcatcloud/mobile-react-native'; + +config.initializationMode = InitializationMode.ASYNC; +``` + +Both modes install the JavaScript auto-instrumentation immediately — `ASYNC` delays only the +native initialization, and events reported in the meantime are buffered either way. + +### react-native-navigation (Wix) -[1]: https://github.com/flashcat/fc-sdk-reactnative/blob/develop/docs/advanced_configuration.md#delaying-the-initialization +`DatadogProvider` needs a single React root to wrap, and react-native-navigation does not +have one — each screen is registered separately. Keep `DdSdkReactNative.initialize()` there, +and call it at module scope in your entry file, before registering any screen, so the +uninstrumented window is as small as that setup allows. diff --git a/docs/troubleshooting_no_data.md b/docs/troubleshooting_no_data.md new file mode 100644 index 000000000..646f839c7 --- /dev/null +++ b/docs/troubleshooting_no_data.md @@ -0,0 +1,86 @@ +# No data is being sent + +Work down this list in order — each step narrows where the data is being lost. + +## 1. Confirm the SDK started + +Turn on internal logs and read them before changing anything else: + +```javascript +import { SdkVerbosity } from '@flashcatcloud/mobile-react-native'; + +config.verbosity = SdkVerbosity.DEBUG; +``` + +You are looking for two separate lines. `Datadog SDK was initialized` means the native SDK is +up. `Datadog SDK is tracking XHR resources` means the network instrumentation was installed — +it is only printed when `trackResources` is enabled, and its absence is the single most common +reason an app reports views but no API calls. (Both strings still carry the upstream name this +SDK was forked from; grep for them verbatim.) + +If neither line appears, initialization never ran: check that the provider is actually mounted, +and that no exception is being swallowed around it. + +## 2. Views and crashes arrive, but no API calls + +This is almost always initialization order. + +`DdSdkReactNative.initialize()` awaits the native SDK across the bridge and installs the +JavaScript instrumentation only after that resolves. That instrumentation is a live patch on +`XMLHttpRequest.prototype`, not a buffered call: a request that starts before the patch is +applied produces no resource event at all. It is not queued and delivered late — the SDK never +sees it, and nothing can recover it afterwards. + +RUM calls such as `DdRum.startView` *are* buffered before initialization, which is why views +and errors survive while resources do not. That asymmetry is what makes this look like a +resource-specific bug when it is really a timing one. + +Use `DatadogProvider` instead. It installs the instrumentation during its own render pass, +before any child renders, and initializes the native SDK afterwards. Wrap the app root, as high +in the tree as you can — the provider only covers what renders below it. See +[migrating to DatadogProvider](./migrating_to_datadog_provider.md). + +react-native-navigation (Wix) has no single React root to wrap, so it must keep the manual +call. Put it at module scope in your entry file, before any screen is registered. + +## 3. Resources arrive but are not linked to backend traces + +Set `firstPartyHosts`. The SDK adds tracing headers only to requests whose host matches, so +with it unset every resource is a dead end: + +```javascript +config.firstPartyHosts = ['example.com']; // matches example.com and its subdomains +``` + +Pass bare hosts, not URLs — no scheme, port or path. Also check +`resourceTracingSamplingRate`, which defaults to `20`: at that value four out of five matching +requests carry no tracing headers by design. + +## 4. Nothing arrives at all + +- **Wrong destination.** `site` accepts `'CN'` (default) and `'STAGING'`. For a private + deployment leave `site` alone and set `customEndpoints` to your own intake URLs instead; + each value is the complete URL including its path, and is passed to the native SDKs as is. +- **Credentials.** The client token and RUM application ID must come from the same application + in the console. A token that is valid but belongs to another application produces a silent + no-op, not an error. +- **Sampling.** `sessionSamplingRate` is a percentage of *sessions*. At a low value most test + runs legitimately report nothing; set it to `100` while integrating. +- **Consent.** Nothing is collected under `TrackingConsent.NOT_GRANTED`, and events collected + under `PENDING` are discarded unless consent is later granted. + +## 5. Only in development + +Two request kinds are filtered on purpose in dev builds, and only in dev builds: the Expo +`/logs` endpoint and the React Native packager's `/symbolicate`. Both are noise from the +tooling rather than from your app — the first would otherwise loop, since logging an API call +is itself an API call. Your own requests are never filtered. + +Also check whether your own configuration disables collection outside release builds. Guards +such as `trackResources: !__DEV__` are easy to forget and behave exactly like a broken SDK. + +## Still stuck + +Open an [issue](https://github.com/flashcatcloud/fc-sdk-reactnative/issues/new) with the +`SdkVerbosity.DEBUG` output, your configuration with the credentials removed, and the SDK and +React Native versions. diff --git a/example/src/ddUtils.tsx b/example/src/ddUtils.tsx index 3751c2ba5..f384ee1e7 100644 --- a/example/src/ddUtils.tsx +++ b/example/src/ddUtils.tsx @@ -11,7 +11,12 @@ import { // (APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT). import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials'; -// New SDK Setup - not available for react-native-navigation +// Preferred setup: hand this configuration to a wrapping the app root. +// The provider installs the JS auto-instrumentation as it renders and initializes the native +// SDK afterwards, so requests made during startup are still collected. +// +// Not usable with react-native-navigation, which has no single React root to wrap - see +// initializeDatadog below. export function getDatadogConfig(trackingConsent: TrackingConsent) { const config = new DatadogProviderConfiguration( CLIENT_TOKEN, @@ -38,7 +43,12 @@ export function getDatadogConfig(trackingConsent: TrackingConsent) { DdSdkReactNative.setAttributes({campaign: "ad-network"}) } -// Legacy SDK Setup +// Manual setup. Only correct for react-native-navigation, where there is no single React root +// for to wrap - every other app should use getDatadogConfig above. +// +// initialize() awaits the native SDK before it patches XMLHttpRequest, so requests issued +// before that resolves produce no resource event at all. Call it at module scope in the entry +// file, before registering any screen, to keep that window as small as this setup allows. export function initializeDatadog(trackingConsent: TrackingConsent) { const config = new DdSdkReactNativeConfiguration( diff --git a/packages/codepush/README.md b/packages/codepush/README.md index 8dd7e7717..aa5d5618a 100644 --- a/packages/codepush/README.md +++ b/packages/codepush/README.md @@ -46,5 +46,5 @@ If you use `datadog-ci react-native upload` to upload your CodePush bundle and s - `version` to completely override the version [1]: https://github.com/microsoft/react-native-code-push -[2]: https://github.com/flashcat/fc-sdk-reactnative/tree/main/packages/core +[2]: ../core [3]: https://github.com/DataDog/datadog-ci/tree/master/src/commands/react-native#codepush diff --git a/packages/core/README.md b/packages/core/README.md index 7305b816a..12b14b8bb 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,6 +1,6 @@ # React-Native Monitoring -Datadog Real User Monitoring (RUM) enables you to visualize and analyze the real-time performance and user journeys of your application’s individual users. +Flashcat Real User Monitoring (RUM) enables you to visualize and analyze the real-time performance and user journeys of your application’s individual users. ## Setup @@ -18,17 +18,17 @@ yarn add @flashcatcloud/mobile-react-native **Minimum React Native version**: SDK supports React Native version 0.63.4 or higher. Compatibility with older versions is not guaranteed out of the box. -Versions `1.0.0-rc5` and higher require you to have `compileSdkVersion = 31` in the Android application setup, which implies that you should use Build Tools version 31, Android Gradle Plugin version 7, and Gradle version 7 or higher. To modify the versions, change the values in the `buildscript.ext` block of your application's top-level `build.gradle` file. Datadog recommends using React Native version 0.67 or higher. +Versions `1.0.0-rc5` and higher require you to have `compileSdkVersion = 31` in the Android application setup, which implies that you should use Build Tools version 31, Android Gradle Plugin version 7, and Gradle version 7 or higher. To modify the versions, change the values in the `buildscript.ext` block of your application's top-level `build.gradle` file. Flashcat recommends using React Native version 0.67 or higher. ### Specify application details in UI -1. In the [Datadog app][1], select **UX Monitoring > RUM Applications > New Application**. -2. Choose `react-native` as your Application Type. -3. Provide a new application name to generate a unique Datadog application ID and client token. +1. In the [Flashcat console][1], open **RUM > Applications** and create a new application. +2. Choose `react-native` as your application type. +3. Name the application to generate its RUM application ID and client token. ![image][2] -To ensure the safety of your data, you must use a client token. You cannot use only [Datadog API keys][3] to configure the `@flashcatcloud/mobile-react-native` library, because they would be exposed client-side. For more information about setting up a client token, see the [Client Token documentation][4]. +To ensure the safety of your data, you must use a client token. Never configure `@flashcatcloud/mobile-react-native` with an API key: the client bundle is shipped to your users, so anything in it is readable by them. A client token can only write RUM events, which is why it is safe to embed. ### Initialize the library with application context @@ -46,19 +46,22 @@ const datadogConfiguration = new DatadogProviderConfiguration( true, // track XHR Resources true // track Errors ); -// Optional: Select your Datadog website (one of "US1", "US3", "US5", "EU1", "AP1", "AP2", or "US1_FED"). Default is "US1". -datadogConfiguration.site = 'US1'; +// Optional: select the Flashcat site, one of "CN" (default) or "STAGING". For a private +// deployment, leave `site` alone and set `customEndpoints` to your own intake URLs instead. +datadogConfiguration.site = 'CN'; // Optional: enable or disable native crash reports datadogConfiguration.nativeCrashReportEnabled = true; -// Optional: sample RUM sessions (here, 80% of session will be sent to Datadog. Default = 100%) +// Optional: sample RUM sessions (here, 80% of sessions are reported. Default = 100%) datadogConfiguration.sessionSamplingRate = 80; -// Optional: sample tracing integrations for network calls between your app and your backend (here, 80% of calls to your instrumented backend will be linked from the RUM view to the APM view. Default = 20%) +// Optional: sample the tracing integration for calls between your app and your backend (here, 80% of +// calls to your instrumented backend are linked from the RUM view to the trace. Default = 20%) // You need to specify the hosts of your backends to enable tracing with these backends datadogConfiguration.resourceTracingSamplingRate = 80; datadogConfiguration.firstPartyHosts = ['example.com']; // matches 'example.com' and subdomains like 'api.example.com' // Optional: set the reported service name (by default, it'll use the package name / bundleIdentifier of your Android / iOS app respectively) datadogConfiguration.serviceName = 'com.example.reactnative'; -// Optional: let the SDK print internal logs (above or equal to the provided level. Default = undefined (meaning no logs)) +// Optional: let the SDK print internal logs (at or above the provided level. Default = undefined, meaning no logs). +// Worth turning on while integrating: it prints whether resource tracking actually started. datadogConfiguration.verbosity = SdkVerbosity.WARN; export default function App() { @@ -70,6 +73,22 @@ export default function App() { } ``` +Wrap the app root, as high in the tree as you can. `DatadogProvider` installs the JavaScript +auto-instrumentation during its own render pass, before any child renders, and initializes the +native SDK afterwards — events reported meanwhile are buffered and flushed once it is up. + +Do not initialize by hand with `DdSdkReactNative.initialize()` instead. That call awaits the +native SDK first and only then patches `XMLHttpRequest`, so requests made while your app is +starting up produce no resource event at all: they are never recorded, and nothing can recover +them afterwards. Calling it earlier narrows that window but does not close it from inside a +React tree, since children mount before any effect of yours can await the call. The one place it +is still required is react-native-navigation (Wix), which has no single React root to wrap — +see [migrating to DatadogProvider][5]. + +`firstPartyHosts` is what links a resource to its backend trace: the SDK adds tracing headers +only to requests whose host matches. Left unset, resources are still reported but none of them +can be correlated with the backend. + ### Track view navigation Because React Native offers a wide range of libraries to create screen navigation, by default only manual View tracking is supported. You can manually start and stop a View using the following `startView()` and `stopView` methods. @@ -94,17 +113,18 @@ DdRum.stopView('ViewKey', Date.now(), { 'custom.bar': 42 }); ### Android -Before data is uploaded to Datadog, it is stored in cleartext in your application's cache directory. +Before data is uploaded, it is stored in cleartext in your application's cache directory. This cache folder is protected by [Android's Application Sandbox][3], meaning that on most devices this data can't be read by other applications. However, if the mobile device is rooted, or someone tempers with the linux kernel, the stored data might become readable. ### iOS -Before data is uploaded to Datadog, it is stored in cleartext in the cache directory (`Library/Caches`) +Before data is uploaded, it is stored in cleartext in the cache directory (`Library/Caches`) of your [application sandbox][4], which can't be read by any other app installed on the device. -[1]: https://app.datadoghq.com/rum/application/create -[2]: https://raw.githubusercontent.com/flashcat/fc-sdk-reactnative/main/docs/image_reactnative.png +[1]: https://console.flashcat.cloud/rum/apps +[2]: ../../docs/image_reactnative.png [3]: https://source.android.com/security/app-sandbox [4]: https://support.apple.com/guide/security/security-of-runtime-process-sec15bfe098e/web +[5]: ../../docs/migrating_to_datadog_provider.md diff --git a/packages/react-native-navigation/README.md b/packages/react-native-navigation/README.md index 6eeaa74f3..91026f7e8 100644 --- a/packages/react-native-navigation/README.md +++ b/packages/react-native-navigation/README.md @@ -36,5 +36,5 @@ DdRumReactNativeNavigationTracking.startTracking(viewNamePredicate); ``` [1]: https://github.com/wix/react-native-navigation -[2]: https://github.com/flashcat/fc-sdk-reactnative/tree/main/packages/core +[2]: ../core [3]: https://wix.github.io/react-native-navigation/api/events/#componentdidappear diff --git a/packages/react-navigation/README.md b/packages/react-navigation/README.md index 3b7b6e924..a162c582b 100644 --- a/packages/react-navigation/README.md +++ b/packages/react-navigation/README.md @@ -50,4 +50,4 @@ function App() { [1]: https://github.com/react-navigation/react-navigation -[2]: https://github.com/flashcat/fc-sdk-reactnative/tree/main/packages/core +[2]: ../core