Skip to content

Commit 58faad5

Browse files
authored
feat: support setting attributes after handler start (#10)
1 parent 787af7d commit 58faad5

8 files changed

Lines changed: 195 additions & 3 deletions

File tree

app/src/main/cpp/native-lib.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
#include <string>
44
#include <unistd.h>
55
#include "client/crashpad_client.h"
6+
#include "client/crashpad_info.h"
67
#include "client/crash_report_database.h"
78
#include "client/settings.h"
9+
#include "client/simple_string_dictionary.h"
810
#include "include/bugsplat_utils.h"
911

1012
using namespace base;
1113
using namespace crashpad;
1214
using namespace std;
1315

16+
static SimpleStringDictionary* g_simple_annotations = nullptr;
17+
1418
// Forward declarations of JNI functions
1519
extern "C" JNIEXPORT jboolean JNICALL
1620
Java_com_bugsplat_android_BugSplatBridge_jniInitBugSplat(JNIEnv *env, jclass clazz,
@@ -25,6 +29,14 @@ Java_com_bugsplat_android_BugSplatBridge_jniInitBugSplat(JNIEnv *env, jclass cla
2529
extern "C" JNIEXPORT void JNICALL
2630
Java_com_bugsplat_android_BugSplatBridge_jniCrash(JNIEnv *env, jclass clazz);
2731

32+
extern "C" JNIEXPORT void JNICALL
33+
Java_com_bugsplat_android_BugSplatBridge_jniSetAttribute(JNIEnv *env, jclass clazz,
34+
jstring key, jstring value);
35+
36+
extern "C" JNIEXPORT void JNICALL
37+
Java_com_bugsplat_android_BugSplatBridge_jniRemoveAttribute(JNIEnv *env, jclass clazz,
38+
jstring key);
39+
2840
// JNI implementation
2941
extern "C" JNIEXPORT jboolean JNICALL
3042
Java_com_bugsplat_android_BugSplatBridge_jniInitBugSplat(JNIEnv *env, jclass clazz,
@@ -50,7 +62,7 @@ Java_com_bugsplat_android_BugSplatBridge_jniInitBugSplat(JNIEnv *env, jclass cla
5062
string url = "https://" + databaseString + ".bugsplat.com/post/bp/crash/crashpad.php";
5163
__android_log_print(ANDROID_LOG_INFO, "bugsplat-android", "Url: %s", url.c_str());
5264

53-
// Crashpad annotations
65+
// Crashpad annotations (passed to StartHandlerAtCrash for upload metadata)
5466
map<string, string> annotations;
5567
annotations["format"] = "minidump";
5668
annotations["database"] = databaseString;
@@ -60,6 +72,15 @@ Java_com_bugsplat_android_BugSplatBridge_jniInitBugSplat(JNIEnv *env, jclass cla
6072
// Create custom attributes
6173
createAttributes(env, attributes_map, annotations);
6274

75+
// Register a SimpleStringDictionary on CrashpadInfo for runtime-updatable annotations.
76+
// Unlike the annotations map passed to StartHandlerAtCrash, these live in process memory
77+
// and can be modified at any time — the crash handler reads them directly at crash time.
78+
g_simple_annotations = new SimpleStringDictionary();
79+
for (const auto& entry : annotations) {
80+
g_simple_annotations->SetKeyValue(entry.first, entry.second);
81+
}
82+
CrashpadInfo::GetCrashpadInfo()->set_simple_annotations(g_simple_annotations);
83+
6384
// Crashpad arguments
6485
vector<string> arguments;
6586
arguments.emplace_back("--no-rate-limit");
@@ -147,6 +168,38 @@ void createAttributes(JNIEnv *env, jobject attributes_map, map<string, string>&
147168
env->DeleteLocalRef(entrySet);
148169
}
149170

171+
extern "C" JNIEXPORT void JNICALL
172+
Java_com_bugsplat_android_BugSplatBridge_jniSetAttribute(JNIEnv *env, jclass clazz,
173+
jstring key, jstring value) {
174+
if (g_simple_annotations == nullptr) {
175+
__android_log_print(ANDROID_LOG_WARN, "bugsplat-android", "setAttribute called before init");
176+
return;
177+
}
178+
179+
const char* keyStr = env->GetStringUTFChars(key, nullptr);
180+
const char* valueStr = env->GetStringUTFChars(value, nullptr);
181+
182+
g_simple_annotations->SetKeyValue(keyStr, valueStr);
183+
184+
env->ReleaseStringUTFChars(key, keyStr);
185+
env->ReleaseStringUTFChars(value, valueStr);
186+
}
187+
188+
extern "C" JNIEXPORT void JNICALL
189+
Java_com_bugsplat_android_BugSplatBridge_jniRemoveAttribute(JNIEnv *env, jclass clazz,
190+
jstring key) {
191+
if (g_simple_annotations == nullptr) {
192+
__android_log_print(ANDROID_LOG_WARN, "bugsplat-android", "removeAttribute called before init");
193+
return;
194+
}
195+
196+
const char* keyStr = env->GetStringUTFChars(key, nullptr);
197+
198+
g_simple_annotations->RemoveKey(keyStr);
199+
200+
env->ReleaseStringUTFChars(key, keyStr);
201+
}
202+
150203
vector<FilePath> createAttachments(JNIEnv *env, jobjectArray attachments) {
151204
vector<FilePath> attachmentPaths;
152205

app/src/main/java/com/bugsplat/android/BugSplat.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ public static void init(Activity activity, String database, String application,
7070
public static void crash() {
7171
BugSplatBridge.crash();
7272
}
73+
74+
/**
75+
* Set a custom attribute that will be included in crash reports.
76+
* This can be called at any time after init, and the value will be
77+
* captured in the next crash report.
78+
*
79+
* <p>The backing store supports a maximum of 64 entries with keys and
80+
* values each limited to 255 bytes. Entries that exceed these limits
81+
* may be silently truncated or dropped.</p>
82+
*
83+
* <p>Passing a null value is equivalent to calling {@link #removeAttribute(String)}.</p>
84+
*
85+
* @param key The attribute key (must not be null or blank)
86+
* @param value The attribute value, or null to remove the attribute
87+
* @throws IllegalArgumentException if key is null or blank
88+
*/
89+
public static void setAttribute(String key, String value) {
90+
BugSplatBridge.setAttribute(key, value);
91+
}
92+
93+
/**
94+
* Remove a custom attribute so it is no longer included in crash reports.
95+
*
96+
* @param key The attribute key to remove (must not be null or blank)
97+
* @throws IllegalArgumentException if key is null or blank
98+
*/
99+
public static void removeAttribute(String key) {
100+
BugSplatBridge.removeAttribute(key);
101+
}
73102

74103
/**
75104
* Upload debug symbols for native libraries (.so files) in the specified directory.

app/src/main/java/com/bugsplat/android/BugSplatBridge.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,32 @@ public static void crash() {
4141
jniCrash();
4242
}
4343

44+
public static void setAttribute(String key, String value) {
45+
validateAttributeKey(key);
46+
if (value == null) {
47+
jniRemoveAttribute(key);
48+
return;
49+
}
50+
jniSetAttribute(key, value);
51+
}
52+
53+
public static void removeAttribute(String key) {
54+
validateAttributeKey(key);
55+
jniRemoveAttribute(key);
56+
}
57+
58+
private static void validateAttributeKey(String key) {
59+
if (key == null || key.trim().isEmpty()) {
60+
throw new IllegalArgumentException("Attribute key must not be null or blank");
61+
}
62+
}
63+
4464
static native boolean jniInitBugSplat(String dataDir, String libDir, String database, String application,
4565
String version, Map<String, String> attributes, String[] attachments);
4666

4767
static native void jniCrash();
68+
69+
static native void jniSetAttribute(String key, String value);
70+
71+
static native void jniRemoveAttribute(String key);
4872
}

example/src/main/java/com/bugsplat/example/MainActivity.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class MainActivity extends AppCompatActivity {
2929
private TextView statusTextView;
3030
private Button crashButton;
3131
private Button feedbackButton;
32+
private Button setAttributeButton;
3233

3334
@Override
3435
protected void onCreate(Bundle savedInstanceState) {
@@ -39,6 +40,7 @@ protected void onCreate(Bundle savedInstanceState) {
3940
statusTextView = findViewById(R.id.statusTextView);
4041
crashButton = findViewById(R.id.crashButton);
4142
feedbackButton = findViewById(R.id.feedbackButton);
43+
setAttributeButton = findViewById(R.id.setAttributeButton);
4244

4345
// Log native library directories
4446
logNativeLibraryInfo();
@@ -68,6 +70,35 @@ public void onClick(View v) {
6870

6971
// Set up click listener for feedback button
7072
feedbackButton.setOnClickListener(v -> showFeedbackDialog());
73+
74+
// Set up click listener for set attribute button
75+
setAttributeButton.setOnClickListener(v -> showSetAttributeDialog());
76+
}
77+
78+
private void showSetAttributeDialog() {
79+
View dialogView = getLayoutInflater().inflate(R.layout.dialog_attribute, null);
80+
EditText keyInput = dialogView.findViewById(R.id.attributeKey);
81+
EditText valueInput = dialogView.findViewById(R.id.attributeValue);
82+
83+
new AlertDialog.Builder(this)
84+
.setTitle("Set Attribute")
85+
.setView(dialogView)
86+
.setPositiveButton("Set", (dialog, which) -> {
87+
String key = keyInput.getText().toString().trim();
88+
String value = valueInput.getText().toString().trim();
89+
90+
if (key.isEmpty()) {
91+
Toast.makeText(this, "Key is required", Toast.LENGTH_SHORT).show();
92+
return;
93+
}
94+
95+
BugSplat.setAttribute(key, value);
96+
statusTextView.setText("Attribute set: " + key + " = " + value);
97+
Toast.makeText(this, "Attribute set!", Toast.LENGTH_SHORT).show();
98+
Log.d(TAG, "setAttribute: " + key + " = " + value);
99+
})
100+
.setNegativeButton("Cancel", null)
101+
.show();
71102
}
72103

73104
private void showFeedbackDialog() {

example/src/main/res/layout/activity_main.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,23 @@
5252
android:layout_marginTop="16dp"
5353
android:text="@string/feedback_button"
5454
style="@style/Widget.BugSplat.Button.Feedback"
55-
app:layout_constraintBottom_toTopOf="@+id/statusTextView"
55+
app:layout_constraintBottom_toTopOf="@+id/setAttributeButton"
5656
app:layout_constraintEnd_toEndOf="parent"
5757
app:layout_constraintStart_toStartOf="parent"
5858
app:layout_constraintTop_toBottomOf="@+id/crashButton" />
5959

60+
<Button
61+
android:id="@+id/setAttributeButton"
62+
android:layout_width="wrap_content"
63+
android:layout_height="wrap_content"
64+
android:layout_marginTop="16dp"
65+
android:text="@string/set_attribute_button"
66+
style="@style/Widget.BugSplat.Button.Attribute"
67+
app:layout_constraintBottom_toTopOf="@+id/statusTextView"
68+
app:layout_constraintEnd_toEndOf="parent"
69+
app:layout_constraintStart_toStartOf="parent"
70+
app:layout_constraintTop_toBottomOf="@+id/feedbackButton" />
71+
6072
<TextView
6173
android:id="@+id/statusTextView"
6274
android:layout_width="0dp"
@@ -70,6 +82,6 @@
7082
app:layout_constraintBottom_toBottomOf="parent"
7183
app:layout_constraintEnd_toEndOf="parent"
7284
app:layout_constraintStart_toStartOf="parent"
73-
app:layout_constraintTop_toBottomOf="@+id/feedbackButton" />
85+
app:layout_constraintTop_toBottomOf="@+id/setAttributeButton" />
7486

7587
</androidx.constraintlayout.widget.ConstraintLayout>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
android:orientation="vertical"
6+
android:padding="24dp">
7+
8+
<com.google.android.material.textfield.TextInputLayout
9+
android:layout_width="match_parent"
10+
android:layout_height="wrap_content"
11+
android:hint="Key">
12+
13+
<com.google.android.material.textfield.TextInputEditText
14+
android:id="@+id/attributeKey"
15+
android:layout_width="match_parent"
16+
android:layout_height="wrap_content"
17+
android:inputType="text"
18+
android:maxLines="1" />
19+
20+
</com.google.android.material.textfield.TextInputLayout>
21+
22+
<com.google.android.material.textfield.TextInputLayout
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:layout_marginTop="16dp"
26+
android:hint="Value">
27+
28+
<com.google.android.material.textfield.TextInputEditText
29+
android:id="@+id/attributeValue"
30+
android:layout_width="match_parent"
31+
android:layout_height="wrap_content"
32+
android:inputType="text"
33+
android:maxLines="1" />
34+
35+
</com.google.android.material.textfield.TextInputLayout>
36+
37+
</LinearLayout>

example/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
<string name="app_name">BugSplat Example</string>
33
<string name="crash_button">Crash App</string>
44
<string name="feedback_button">Send Feedback</string>
5+
<string name="set_attribute_button">Set Attribute</string>
56
</resources>

example/src/main/res/values/styles.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@
2121
<style name="Widget.BugSplat.Button.Feedback">
2222
<item name="backgroundTint">@color/brand_blue</item>
2323
</style>
24+
25+
<!-- Set Attribute button style -->
26+
<style name="Widget.BugSplat.Button.Attribute">
27+
<item name="backgroundTint">@color/brand_green</item>
28+
</style>
2429
</resources>

0 commit comments

Comments
 (0)