Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🐛] Crashlytics log is not working for Android #4772

Closed
3 of 10 tasks
sergey-kozel opened this issue Jan 13, 2021 · 20 comments · Fixed by #4904
Closed
3 of 10 tasks

[🐛] Crashlytics log is not working for Android #4772

sergey-kozel opened this issue Jan 13, 2021 · 20 comments · Fixed by #4904
Labels
help: needs-triage Issue needs additional investigation/triaging. type: bug New bug report

Comments

@sergey-kozel
Copy link
Contributor

Issue

firebase.crashlytics().log() is not working on Android. To be sure I created a new react-native project from scratch with react-native init and just @react-native-firebase/app and @react-native-firebase/crashlytics dependencies added. After initiating a crash I can see the report on the firebase console but there are no logs under the appropriate tab that I passed before the crash with firebase.crashlytics().log() method. BTW I tried with recordError() too but still nothing. IOS logs are working at the same time.


Project Files

Javascript

Click To Expand

package.json:

{
  "name": "test",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "16.13.1",
    "react-native": "0.63.4",
    "@react-native-firebase/app": "10.4.0",
    "@react-native-firebase/crashlytics": "10.4.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "babel-jest": "^26.6.3",
    "eslint": "^7.17.0",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.64.0",
    "react-test-renderer": "16.13.1"
  },
  "jest": {
    "preset": "react-native"
  }
}

App.js:

import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
  TouchableOpacity,
} from 'react-native';

import {
  Header,
  LearnMoreLinks,
  Colors,
  DebugInstructions,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
import firebase from '@react-native-firebase/app';
import '@react-native-firebase/crashlytics';

const App: () => React$Node = () => {
  const onPress = () => {
    firebase.crashlytics().log('Hello Crashlytics!');
    firebase.crashlytics().recordError(new Error('Hello Crashlytics!'));
    setTimeout(() => nothing.getException(), 5000);
  };
  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
          contentInsetAdjustmentBehavior="automatic"
          style={styles.scrollView}>
          <Header />
          {global.HermesInternal == null ? null : (
            <View style={styles.engine}>
              <Text style={styles.footer}>Engine: Hermes</Text>
            </View>
          )}
          <View style={styles.body}>
            <View style={styles.sectionContainer}>
              <TouchableOpacity onPress={onPress}>
                <Text style={[styles.sectionTitle, {color: 'red'}]}>Press to crash</Text>
              </TouchableOpacity>
              <Text style={styles.sectionDescription}>
                Edit <Text style={styles.highlight}>App.js</Text> to change this
                screen and then come back to see your edits.
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>See Your Changes</Text>
              <Text style={styles.sectionDescription}>
                <ReloadInstructions />
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Debug</Text>
              <Text style={styles.sectionDescription}>
                <DebugInstructions />
              </Text>
            </View>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>Learn More</Text>
              <Text style={styles.sectionDescription}>
                Read the docs to discover what to do next:
              </Text>
            </View>
            <LearnMoreLinks />
          </View>
        </ScrollView>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  engine: {
    position: 'absolute',
    right: 0,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
  highlight: {
    fontWeight: '700',
  },
  footer: {
    color: Colors.dark,
    fontSize: 12,
    fontWeight: '600',
    padding: 4,
    paddingRight: 12,
    textAlign: 'right',
  },
});

export default App;

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

buildscript {
    ext {
        buildToolsVersion = "29.0.2"
        minSdkVersion = 16
        compileSdkVersion = 29
        targetSdkVersion = 29
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:3.5.3")
        classpath 'com.google.gms:google-services:4.3.3'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
    }
}

allprojects {
    repositories {
        mavenLocal()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url("$rootDir/../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../node_modules/jsc-android/dist")
        }

        google()
        jcenter()
        maven { url 'https://www.jitpack.io' }
    }
}

android/app/build.gradle:

apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

import com.android.build.OutputFile


project.ext.react = [
    enableHermes: false,  // clean and rebuild if changing
]

apply from: "../../node_modules/react-native/react.gradle"

def enableSeparateBuildPerCPUArchitecture = false

def enableProguardInReleaseBuilds = false

def jscFlavor = 'org.webkit:android-jsc:+'

def enableHermes = project.ext.react.get("enableHermes", false);

android {
    compileSdkVersion rootProject.ext.compileSdkVersion

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        applicationId "com.test"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
        }
    }
    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            // Caution! In production, you need to generate your own keystore file.
            // see https://reactnative.dev/docs/signed-apk-android.
            signingConfig signingConfigs.debug
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }

    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // https://developer.android.com/studio/build/configure-apk-splits.html
            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
            }

        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

    implementation platform('com.google.firebase:firebase-bom:26.2.0')
    
    implementation 'com.google.firebase:firebase-analytics'

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
      exclude group:'com.facebook.fbjni'
    }

    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
    }

    if (enableHermes) {
        def hermesPath = "../../node_modules/hermes-engine/android/";
        debugImplementation files(hermesPath + "hermes-debug.aar")
        releaseImplementation files(hermesPath + "hermes-release.aar")
    } else {
        implementation jscFlavor
    }
}

// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.compile
    into 'libs'
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

android/settings.gradle:

Without changes

MainApplication.java:

Without changes

AndroidManifest.xml:

Without changes


Environment

Click To Expand

react-native info output:

System:
    OS: macOS 10.15.6
    CPU: (8) x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
    Memory: 20.21 MB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 10.23.0 - /usr/local/bin/node
    Yarn: 1.19.1 - /usr/local/bin/yarn
    npm: 6.14.8 - /usr/local/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.10.0 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 13.6, DriverKit 19.0, macOS 10.15, tvOS 13.4, watchOS 6.2
    Android SDK:
      API Levels: 23, 24, 25, 26, 27, 28, 29
      Build Tools: 23.0.1, 23.0.2, 25.0.0, 25.0.1, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 27.0.0, 27.0.1, 27.0.3, 28.0.3, 29.0.2
      System Images: android-27 | Google APIs Intel x86 Atom
      Android NDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6626763
    Xcode: 11.6/11E708 - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_151 - /usr/bin/javac
    Python: 2.7.16 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: 16.13.1 => 16.13.1
    react-native: 0.63.4 => 0.63.4
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • 10.4.0
  • Firebase module(s) you're using that has the issue:
    • "@react-native-firebase/crashlytics": "10.4.1"
  • Are you using TypeScript?
    • N


@sergey-kozel sergey-kozel added help: needs-triage Issue needs additional investigation/triaging. type: bug New bug report labels Jan 13, 2021
@mikehardy
Copy link
Collaborator

Oh that's unexpected. Can you reach in to node_modules javascript add console.logs and android add System.err.println's to trace that through prior to API calls etc? I thought we even had testing around that area but now of course I'm not sure

@sergey-kozel
Copy link
Contributor Author

Could you please clarify where I exactly should insert logging? Files, lines, etc.
But again, it's just a project from scratch. The only files I've changed its App.js and these from firebase installation guides. You can see it here. All that you need to test is just replacing google-services.json (the package is com.test).

@mikehardy
Copy link
Collaborator

Sorry, the idea is that you'll have to roll your sleeves up and determine those things for yourself. The method call names themselves are searchable and will give you the entry point(s) (e.g. cd node_modules/@react-native-firebase/crashlytics && grep -r 'method name here' *) to find the file then get to it. I'm not trying to be irritating, method location is the work of just a few seconds, shouldn't be a barrier

@sergey-kozel
Copy link
Contributor Author

And I don't mind rolling my sleeves up but I am not sure I understand what exactly should I log.

@mikehardy
Copy link
Collaborator

mikehardy commented Jan 14, 2021

We are looking for basic proof that the APIs are even exercised, as a fundamental step, and that they have the parameters we expect, and return success. This will show:

  1. we're not crazy, it's software, it's actually executing at all, and hopefully;
  2. react-native-firebase is correctly calling firebase-android-sdk APIs that it seems like it should (based on their documents), it's carrying in the information we intended to log, and it's not throwing an error

It's a bit shocking how frequently 1 above fails even. So it's worth verifying. 2 above will help show us where in the stack the problem is (is it react-native-firebase? is it firebase-android-sdk? Something else?) which will direct further effort

@sergey-kozel
Copy link
Contributor Author

  1. Yes, true, and here are my debug path screenshots I able to do for log function (jsModule, nativeModule, getFirebaseInstance, wtiteToLog, doWriteToLog)
  2. I am not familiar with firebase's internal structure so finding the place where things are connected together for reporting is a bit hard for me. Here what I was able to find out: it sends report right after the crash and after restart it identifies the previous crash but can't find any reports available. And here is sniffed API call.

@mikehardy
Copy link
Collaborator

Screenshots are unfortunately difficult ways to communicate via github, it is pretty disjointed following those little captures through, I was hoping (as mentioned) for console / System.err statements so we could throw text around.
It looks like we are calling all the API statements correctly, as a firebase-android-sdk API client.

If I'm following the the screen caps correctly, do you see anything that indicates we are not calling the firebase-android-sdk API guidance? In other words, questions 1 and 2 above both seem to be answered in the affirmative, which indicates there is either something in the test setup (using real device? release mode just to make sure?) or firebase-android-sdk code going wrong, but that there may not be a problem in this code. I wonder if you can reproduce with a pure firebase-android-sdk quickstart https://github.com/firebase/quickstart-android/tree/master/crash

@sergey-kozel
Copy link
Contributor Author

Hello @mikehardy
Sorry for the delay. That quickstart example is working as well as my test project when I log messages and generate an exception on the native side.


import android.os.Bundle;

import com.facebook.react.ReactActivity;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends ReactActivity {

  String empty = null;

  @Override
  protected String getMainComponentName() {
    return "test";
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Timer timer = new Timer();
    FirebaseCrashlytics.getInstance().log("Hello");
    timer.schedule(wrap(() -> {
      FirebaseCrashlytics.getInstance().log("crash");
      empty.charAt(1);
    }), 1000 * 60);
  }

  private static TimerTask wrap(Runnable r) {
    return new TimerTask() {

      @Override
      public void run() {
        r.run();
      }
    };
  }
}

But if I try to log in addition to that native crash from RN then these logs do not appear in the console, just the native ones. And in case if I generate a crash on JS then no messages appear at all.

@mikehardy
Copy link
Collaborator

So, if firebase-android-sdk is working and react-native-firebase is just a thin wrapper around it, can you put logging around the API calls we make to the sdk to see where expectations aren't meeting reality? This is a very simple area of the code if I recall correctly, might be just a couple minutes more to find the issue

@sergey-kozel
Copy link
Contributor Author

sergey-kozel commented Feb 3, 2021

Isn't it what I provided with screenshots? And what did you mean by "calls"? Because the only call I do is crashlytics().log. In any case here is the log before calling FirebaseCrashlytics.getInstance().log(message) in your RN module (I/MY CRASH lines are from ReactNativeFirebaseCrashlyticsModule.java)

2021-02-03 14:13:40.647 19910-19910/? I/RNFBCrashlyticsInit: initialization successful
2021-02-03 14:13:40.649 19910-19955/? I/FirebaseCrashlytics: Crashlytics NDK initialization successful
2021-02-03 14:13:43.573 20231-20231/? I/CrashlyticsCore: Initializing Crashlytics 2.3.17.dev
2021-02-03 14:13:43.747 20231-20254/? W/CrashlyticsCore: Received null settings, skipping report submission!
2021-02-03 14:13:47.150 19910-20051/? I/ReactNativeJS: Call "firebase.crashlytics().log('Hello Crashlytics!');" from RN!
2021-02-03 14:13:47.158 19910-20052/? I/MY CRASH: Before FirebaseCrashlytics.getInstance().log(message)
2021-02-03 14:13:47.158 19910-20052/? I/MY CRASH: message: Hello Crashlytics!
2021-02-03 14:13:47.158 19910-20052/? I/MY CRASH: After FirebaseCrashlytics.getInstance().log(message)
2021-02-03 14:14:17.167 19910-20051/? I/ReactNativeJS: [Crashlytics] Generating crash in JS...
2021-02-03 14:14:17.679 19910-19955/? I/FirebaseCrashlytics: Crashlytics NDK initialization successful

@mikehardy
Copy link
Collaborator

Yes, by "calls" I mean in our module ("my" module? It's open source! It's your module! It's our module!) react-native-firebase to firebase-android-sdk - also open source! https://github.com/firebase/firebase-android-sdk/tree/master/firebase-crashlytics)

Interesting that it appears the underlying firebase-android-sdk API is called correctly, perhaps there is some asynchronicity not being respected? Like adding an 1-second sleep just as a test to make sure the log message is digested before crashing could help, but the log timestamps indicate you waited almost 1.5 seconds.

I can't explain these results, sorry

@sergey-kozel
Copy link
Contributor Author

Yes, you are right, it is open source. Sorry if it sounded rude from me. I meant the part which you probably know better than me.
And yes, there is delay in 30 sec before the crash.
Anyway, thanks for your help.

@mikehardy
Copy link
Collaborator

It wasn't rude, you have been quite helpful working to track this down, I just wanted to maintain perspective, especially because I don't have time personally to work on this issue unfortunately though I will do my very best to share with you any thing I know in the area if I know something. Unfortunately I don't have special knowledge here, for me this is still in the standard "trace things / log parameters + results" phase and I'm stumped

@sergey-kozel
Copy link
Contributor Author

Finally, I found out the reason of the issue. The thing is react-native-crashlytics module is generating additional non-fatal issue io.invertase.firebase.crashlytics.JavaScriptError by overriding global error handler. And this non-fatal error takes all logs for its report. And when the android crashlytics module process regular com.facebook.react.common.JavascriptException there is nothing left in logs. And I didn't notice that non-fatal issues in the firebase console because of the filter.
@mikehardy could you please advise whether it is possible to prevent the module from autogenerating these non-fatal issues on JS crashes?

@mikehardy
Copy link
Collaborator

Hmm, that's a highly desired feature - that javascript crashes are sent to crashlytics, it's a vital part of crash logging for a react-native (i.e., javascript) app.

I would advise a documentation update around react-native-crashlytics instead, somewhere in the usage area - where ever it says to view the crashes on the console - that mentions in bold that javascript crashes are logged as "non-fatal" since they don't crash the app, so for react-native-firebase you should not apply the fatal filter when viewing crashes.

That fits the ecosystem here (react-native vs native) better I think? If you agree there's an edit button on the top of every docs page

@mikehardy
Copy link
Collaborator

Great digging by the way, that is subtle!

@sergey-kozel
Copy link
Contributor Author

sergey-kozel commented Feb 8, 2021

Hmm, that's a highly desired feature - that javascript crashes are sent to crashlytics, it's a vital part of crash logging for a react-native (i.e., javascript) app.

But as we can see from my investigation it's working without additional crashes generation from react-native-crashlytics side. React Native itself passes JS exceptions to native side where native crashlytics module does all work. It detects that exception and collect all logs. I think such react-native-crashlytics module behavior should be optional at least.

@mikehardy
Copy link
Collaborator

This is vague, but I believe there was some special processing that react-native-firebase did with regard to javascript frames to bend them into the stack frame format that firebase crashlytics wants, so I believe this is desired still.

If you want to make it optional, PRs are happily merged - in the case of feature toggles, especially if they don't change default behavior. There are a few PRs merged in last couple of weeks that add toggles for base sdk features like "auto collection of performance module data at startup" that can serve as a pattern

@gustavopch
Copy link
Contributor

I spent the last 2 or 3 hours digging into react-native-firebase implementation to understand why my JS errors weren't appearing in the Crashlytics console just to finally discover that they were hidden by the "Crashes" filter 😄. It was just a matter of changing the filter to "Non-fatals" to see them. Just leaving this comment in case it helps someone reading this issue in the future.

@mikehardy
Copy link
Collaborator

@gustavopch oh no - I hate hearing people losing time like that. I have personally been bitten by the fatals / non-fatal thing. I'm mainly replying to say that you are going to love the new firebase crashlytics feature they're implementing for us that allows for dynamic languages that have crashes to be logged as fatal - if you'd had this problem in just a couple days it will probably be merged + released #5047

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help: needs-triage Issue needs additional investigation/triaging. type: bug New bug report
Projects
None yet
3 participants