Skip to content

Commit

Permalink
RUM-2910: Support error.time_since_app_start attribute for NDK crashes
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnm committed Apr 2, 2024
1 parent 3674e1b commit 62aaed6
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ internal class DatadogNdkCrashHandler(
"type" to "ndk_crash",
"sourceType" to nativeCrashSourceType,
"timestamp" to ndkCrashLog.timestamp,
"timeSinceAppStartMs" to ndkCrashLog.timeSinceAppStartMs,
"signalName" to ndkCrashLog.signalName,
"stacktrace" to ndkCrashLog.stacktrace,
"message" to errorLogMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.datadog.android.ndk.internal

import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonParser
Expand All @@ -14,6 +15,7 @@ import java.lang.IllegalStateException
internal data class NdkCrashLog(
val signal: Int,
val timestamp: Long,
val timeSinceAppStartMs: Long?,
val signalName: String,
val message: String,
val stacktrace: String
Expand All @@ -24,6 +26,7 @@ internal data class NdkCrashLog(
jsonObject.addProperty(SIGNAL_KEY_NAME, signal)
jsonObject.addProperty(SIGNAL_NAME_KEY_NAME, signalName)
jsonObject.addProperty(TIMESTAMP_KEY_NAME, timestamp)
jsonObject.addProperty(TIME_SINCE_APP_START_MS_NAME, timeSinceAppStartMs)
jsonObject.addProperty(MESSAGE_KEY_NAME, message)
jsonObject.addProperty(STACKTRACE_KEY_NAME, stacktrace)
return jsonObject.toString()
Expand All @@ -33,6 +36,7 @@ internal data class NdkCrashLog(

internal const val SIGNAL_KEY_NAME = "signal"
internal const val TIMESTAMP_KEY_NAME = "timestamp"
internal const val TIME_SINCE_APP_START_MS_NAME = "time_since_app_start_ms"
internal const val MESSAGE_KEY_NAME = "message"
internal const val SIGNAL_NAME_KEY_NAME = "signal_name"
internal const val STACKTRACE_KEY_NAME = "stacktrace"
Expand All @@ -44,6 +48,8 @@ internal data class NdkCrashLog(
return NdkCrashLog(
jsonObject.get(SIGNAL_KEY_NAME).asInt,
jsonObject.get(TIMESTAMP_KEY_NAME).asLong,
jsonObject.get(TIME_SINCE_APP_START_MS_NAME)
?.let { if (it is JsonNull) null else it.asLong },
jsonObject.get(SIGNAL_NAME_KEY_NAME).asString,
jsonObject.get(MESSAGE_KEY_NAME).asString,
jsonObject.get(STACKTRACE_KEY_NAME).asString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import java.util.concurrent.ExecutorService
@ForgeConfiguration(Configurator::class)
internal class DatadogNdkCrashHandlerTest {

lateinit var testedHandler: DatadogNdkCrashHandler
private lateinit var testedHandler: DatadogNdkCrashHandler

@Mock
lateinit var mockExecutorService: ExecutorService
Expand Down Expand Up @@ -527,6 +527,7 @@ internal class DatadogNdkCrashHandlerTest {
"type" to "ndk_crash",
"sourceType" to "ndk",
"timestamp" to ndkCrashLog.timestamp,
"timeSinceAppStartMs" to ndkCrashLog.timeSinceAppStartMs,
"signalName" to ndkCrashLog.signalName,
"stacktrace" to ndkCrashLog.stacktrace,
"message" to DatadogNdkCrashHandler.LOG_CRASH_MSG
Expand Down Expand Up @@ -570,6 +571,7 @@ internal class DatadogNdkCrashHandlerTest {
"type" to "ndk_crash",
"sourceType" to "ndk+il2cpp",
"timestamp" to ndkCrashLog.timestamp,
"timeSinceAppStartMs" to ndkCrashLog.timeSinceAppStartMs,
"signalName" to ndkCrashLog.signalName,
"stacktrace" to ndkCrashLog.stacktrace,
"message" to DatadogNdkCrashHandler.LOG_CRASH_MSG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import org.mockito.quality.Strictness
@ForgeConfiguration(Configurator::class)
internal class NdkCrashLogDeserializerTest {

lateinit var testedDeserializer: NdkCrashLogDeserializer
private lateinit var testedDeserializer: NdkCrashLogDeserializer

@Mock
lateinit var mockInternalLogger: InternalLogger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ internal class NdkCrashLogForgeryFactory :
ForgeryFactory<NdkCrashLog> {
override fun getForgery(forge: Forge): NdkCrashLog {
return NdkCrashLog(
forge.anInt(min = 1),
System.currentTimeMillis(),
forge.anAlphabeticalString(),
forge.anAlphabeticalString(),
forge.anAlphabeticalString()
signal = forge.anInt(min = 1),
timestamp = System.currentTimeMillis(),
timeSinceAppStartMs = forge.aNullable { aPositiveLong() },
signalName = forge.anAlphabeticalString(),
message = forge.anAlphabeticalString(),
stacktrace = forge.anAlphabeticalString()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,18 @@ class NdkTests {
val fakeErrorStack = forge.anAlphabeticalString()
initNdkErrorHandler(temporaryFolder.root.absolutePath)
updateTrackingConsent(1)
val fakeAppStartTimeMs = forge.aLong(min = 0L, max = System.currentTimeMillis())
updateAppStartTime(fakeAppStartTimeMs)

val expectedTimestamp = System.currentTimeMillis()
val expectedTimeSinceAppStartMs = expectedTimestamp - fakeAppStartTimeMs
simulateSignalInterception(
fakeSignal,
fakeSignalName,
fakeErrorMessage,
fakeErrorStack
)

val expectedTimestamp = System.currentTimeMillis()

// we need to give time to native part to write the file
// otherwise we will get into race condition issues
ConditionWatcher {
Expand All @@ -84,7 +87,12 @@ class NdkTests {
assertThat(jsonObject).hasField(
"timestamp",
expectedTimestamp,
Offset.offset(TimeUnit.SECONDS.toMillis(10))
Offset.offset(TimeUnit.SECONDS.toMillis(2))
)
assertThat(jsonObject).hasField(
"time_since_app_start_ms",
expectedTimeSinceAppStartMs,
Offset.offset(TimeUnit.SECONDS.toMillis(2))
)
}
true
Expand Down Expand Up @@ -222,5 +230,9 @@ class NdkTests {
consent: Int
)

private external fun updateAppStartTime(
appStartTimeMs: Long
)

// endregion
}
13 changes: 10 additions & 3 deletions features/dd-sdk-android-ndk/src/main/cpp/datadog-native-lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static pthread_mutex_t handler_mutex = PTHREAD_MUTEX_INITIALIZER;
static const uint8_t tracking_consent_pending = 0;
static const uint8_t tracking_consent_granted = 1;
static uint8_t tracking_consent = tracking_consent_pending; // 0 - PENDING, 1 - GRANTED, 2 - NOT-GRANTED
static uint64_t global_app_start_time_millis = 0;

#ifndef NDEBUG

Expand All @@ -46,11 +47,12 @@ void unlockMutex() {
#endif

std::string serialize_crash_report(int signum, uint64_t timestamp, const char* signal_name, const char* error_message, const char* error_stacktrace) {
static const char* json_formatter = R"({"signal":%s,"timestamp":%s,"signal_name":"%s","message":"%s","stacktrace":"%s"})";

static const char* json_formatter = R"({"signal":%s,"timestamp":%s,"time_since_app_start_ms":%s,"signal_name":"%s","message":"%s","stacktrace":"%s"})";
const uint64_t time_since_app_start = timestamp - global_app_start_time_millis;
std::string serialized_log = stringutils::format(json_formatter,
std::to_string(signum).c_str(),
std::to_string(timestamp).c_str(),
std::to_string(time_since_app_start).c_str(),
signal_name,
error_message,
error_stacktrace);
Expand Down Expand Up @@ -125,17 +127,22 @@ void update_tracking_consent(jint consent) {
tracking_consent = (uint8_t) consent;
}

void update_app_start_time_millis(jlong time_ms) {
global_app_start_time_millis = time_ms;
}

/// Jni bindings
extern "C" JNIEXPORT void JNICALL
Java_com_datadog_android_ndk_internal_NdkCrashReportsFeature_registerSignalHandler(
JNIEnv *env,
jobject /* this */,
jstring storage_path,
jint consent) {
jint consent,
jlong app_start_time_millis) {

update_main_context(env, storage_path);
update_tracking_consent(consent);
update_app_start_time_millis(app_start_time_millis);
start_monitoring();
}

Expand Down
2 changes: 2 additions & 0 deletions features/dd-sdk-android-ndk/src/main/cpp/datadog-native-lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ void update_main_context(JNIEnv *env,

void update_tracking_consent(jint consent);

void update_app_start_time_millis(jlong time_ms);

void write_crash_report(int signum,
const char *signal_name,
const char *error_message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import com.datadog.android.privacy.TrackingConsent
import com.datadog.android.privacy.TrackingConsentProviderCallback
import java.io.File
import java.lang.NullPointerException
import java.util.concurrent.TimeUnit

/**
* An implementation of the [Feature] which will allow to intercept and report the
* NDK crashes to our logs dashboard.
*/
internal class NdkCrashReportsFeature(private val sdkCore: FeatureSdkCore) :
Feature,
TrackingConsentProviderCallback {
internal class NdkCrashReportsFeature(
private val sdkCore: FeatureSdkCore
) : Feature, TrackingConsentProviderCallback {
private var nativeLibraryLoaded = false

override val name: String = Feature.NDK_CRASH_REPORTS_FEATURE_NAME
Expand Down Expand Up @@ -62,9 +63,12 @@ internal class NdkCrashReportsFeature(private val sdkCore: FeatureSdkCore) :
)
return
}
val appStartTimestamp =
TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) - System.nanoTime() + sdkCore.appStartTimeNs
registerSignalHandler(
ndkCrashesDirs.absolutePath,
consentToInt(internalSdkCore.trackingConsent)
consentToInt(internalSdkCore.trackingConsent),
TimeUnit.NANOSECONDS.toMillis(appStartTimestamp)
)
}

Expand Down Expand Up @@ -124,7 +128,8 @@ internal class NdkCrashReportsFeature(private val sdkCore: FeatureSdkCore) :

private external fun registerSignalHandler(
storagePath: String,
consent: Int
consent: Int,
appStartTimeMs: Long
)

private external fun unregisterSignalHandler()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,14 @@ Java_com_datadog_android_ndk_NdkTests_updateTrackingConsent(
update_tracking_consent(consent);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_datadog_android_ndk_NdkTests_updateAppStartTime(
JNIEnv *env,
jobject /* this */,
jlong app_start_time_ms) {
update_app_start_time_millis(app_start_time_ms);
}



4 changes: 4 additions & 0 deletions features/dd-sdk-android-ndk/src/test/cpp/test-crash-log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ TEST test_will_serialise_the_crash_log(void) {
const char* fake_error_stacktrace = "an error stacktrace";
const int fake_error_signal = 2;
const uint64_t fake_timestamp = 100;
const uint64_t fake_app_start_timestamp = 10;
update_app_start_time_millis(fake_app_start_timestamp);
const char* fake_signal_name = "a signal name";
const string serialized_log = serialize_crash_report(fake_error_signal,
fake_timestamp,
Expand All @@ -31,6 +33,8 @@ TEST test_will_serialise_the_crash_log(void) {
.append(to_string(fake_error_signal))
.append(",\"timestamp\":")
.append(to_string(fake_timestamp))
.append(",\"time_since_app_start_ms\":")
.append(to_string(fake_timestamp - fake_app_start_timestamp))
.append(",\"signal_name\":")
.append("\"")
.append(fake_signal_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal class DatadogLateCrashReporter(

val sourceType = event["sourceType"] as? String
val timestamp = event["timestamp"] as? Long
val timeSinceAppStartMs = event["timeSinceAppStartMs"] as? Long
val signalName = event["signalName"] as? String
val stacktrace = event["stacktrace"] as? String
val errorLogMessage = event["message"] as? String
Expand All @@ -81,6 +82,7 @@ internal class DatadogLateCrashReporter(
ErrorEvent.Category.EXCEPTION,
errorLogMessage,
timestamp,
timeSinceAppStartMs,
stacktrace,
signalName,
null,
Expand Down Expand Up @@ -128,13 +130,14 @@ internal class DatadogLateCrashReporter(
val threadDumps = readThreadsDump(anrExitInfo)
if (threadDumps.isEmpty()) return@withWriteContext

// TODO RUM-3780 support reporting `error.time_since_app_start` for fatal ANRs
val toSendErrorEvent = resolveErrorEventFromViewEvent(
datadogContext,
ErrorEvent.SourceType.ANDROID,
ErrorEvent.Category.ANR,
ANRDetectorRunnable.ANR_MESSAGE,
anrExitInfo.timestamp,
// TODO RUM-3780 support reporting `error.time_since_app_start` for fatal ANRs
null,
threadDumps.mainThread?.stack.orEmpty(),
ANRException::class.java.canonicalName.orEmpty(),
threadDumps,
Expand Down Expand Up @@ -164,6 +167,7 @@ internal class DatadogLateCrashReporter(
category: ErrorEvent.Category,
errorLogMessage: String,
timestamp: Long,
timeSinceAppStartMs: Long?,
stacktrace: String,
errorType: String,
threadDumps: List<ThreadDump>?,
Expand Down Expand Up @@ -250,7 +254,8 @@ internal class DatadogLateCrashReporter(
it.stack,
it.state
)
}
},
timeSinceAppStart = timeSinceAppStartMs
),
version = viewEvent.version
)
Expand Down

0 comments on commit 62aaed6

Please sign in to comment.