Skip to content

Commit

Permalink
Use v2 API format (#111)
Browse files Browse the repository at this point in the history
* Use v2 format

* Use ‘manual’ in place of custom

* Enforce size limit

* Document breadcrumb types

* Update example

* Add a breadcrumb when an error occurs

* Add a Makefile

* Update payload version

* Cleanup types
  • Loading branch information
kattrali committed Jul 22, 2016
1 parent c764c91 commit 3ec4c8e
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 20 deletions.
12 changes: 12 additions & 0 deletions Makefile
@@ -0,0 +1,12 @@
all: build

.PHONY: build test clean

build:
./gradlew build

clean:
./gradlew clean

test:
./gradlew :connectedCheck
Expand Up @@ -10,6 +10,7 @@
import com.bugsnag.android.MetaData;
import com.bugsnag.android.Severity;
import com.bugsnag.android.other.Other;
import com.bugsnag.android.BreadcrumbType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -47,7 +48,7 @@ public boolean run(Error error) {
Bugsnag.addToTab("user", "age", 31);
Bugsnag.addToTab("custom", "account", "something");

Bugsnag.leaveBreadcrumb("onCreate");
Bugsnag.leaveBreadcrumb("onCreate", BreadcrumbType.NAVIGATION, new HashMap<String, String>());

new Thread(new Runnable() {
public void run() {
Expand Down
43 changes: 38 additions & 5 deletions src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.java
@@ -1,9 +1,11 @@
package com.bugsnag.android;

import java.io.IOException;
import java.util.HashMap;

import org.json.JSONException;
import org.json.JSONArray;
import org.json.JSONObject;

public class BreadcrumbsTest extends BugsnagTestCase {
public void testSerialization() throws JSONException, IOException {
Expand All @@ -14,7 +16,7 @@ public void testSerialization() throws JSONException, IOException {

JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals(3, breadcrumbsJson.length());
assertEquals("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim", breadcrumbsJson.getJSONArray(2).get(1));
assertEquals("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim", breadcrumbsJson.getJSONObject(2).getJSONObject("metaData").get("message"));
}

public void testSizeLimit() throws JSONException, IOException {
Expand All @@ -29,8 +31,8 @@ public void testSizeLimit() throws JSONException, IOException {

JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals(5, breadcrumbsJson.length());
assertEquals("2", breadcrumbsJson.getJSONArray(0).get(1));
assertEquals("6", breadcrumbsJson.getJSONArray(4).get(1));
assertEquals("2", breadcrumbsJson.getJSONObject(0).getJSONObject("metaData").get("message"));
assertEquals("6", breadcrumbsJson.getJSONObject(4).getJSONObject("metaData").get("message"));
}

public void testResize() throws JSONException, IOException {
Expand All @@ -45,8 +47,8 @@ public void testResize() throws JSONException, IOException {

JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals(5, breadcrumbsJson.length());
assertEquals("2", breadcrumbsJson.getJSONArray(0).get(1));
assertEquals("6", breadcrumbsJson.getJSONArray(4).get(1));
assertEquals("2", breadcrumbsJson.getJSONObject(0).getJSONObject("metaData").get("message"));
assertEquals("6", breadcrumbsJson.getJSONObject(4).getJSONObject("metaData").get("message"));
}

public void testClear() throws JSONException, IOException {
Expand All @@ -59,4 +61,35 @@ public void testClear() throws JSONException, IOException {
JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals(0, breadcrumbsJson.length());
}

public void testType() throws JSONException, IOException {
Breadcrumbs breadcrumbs = new Breadcrumbs();
breadcrumbs.add("1");
JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals("manual", breadcrumbsJson.getJSONObject(0).get("type"));
}

public void testPayloadSizeLimit() throws JSONException, IOException {
Breadcrumbs breadcrumbs = new Breadcrumbs();
HashMap<String, String> metadata = new HashMap<String, String>();
for (int i = 0; i < 400; i++) {
metadata.put(String.format("%d", i), "!!");
}
breadcrumbs.add("Rotated Menu", BreadcrumbType.STATE, metadata);
JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);
assertEquals(0, breadcrumbsJson.length());
}

public void testPayloadType() throws JSONException, IOException {
Breadcrumbs breadcrumbs = new Breadcrumbs();
HashMap<String, String> metadata = new HashMap<String, String>();
metadata.put("direction", "left");
breadcrumbs.add("Rotated Menu", BreadcrumbType.STATE, metadata);
JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs);

assertEquals("Rotated Menu", breadcrumbsJson.getJSONObject(0).get("name"));
assertEquals("state", breadcrumbsJson.getJSONObject(0).get("type"));
assertEquals("left", breadcrumbsJson.getJSONObject(0).getJSONObject("metaData").get("direction"));
assertEquals(1, breadcrumbsJson.length());
}
}
2 changes: 1 addition & 1 deletion src/androidTest/java/com/bugsnag/android/ErrorTest.java
Expand Up @@ -39,7 +39,7 @@ public void testBasicSerialization() throws JSONException, IOException {

JSONObject errorJson = streamableToJson(error);
assertEquals("warning", errorJson.get("severity"));
assertEquals("2", errorJson.get("payloadVersion"));
assertEquals("3", errorJson.get("payloadVersion"));
assertNotNull(errorJson.get("severity"));
assertNotNull(errorJson.get("metaData"));
assertNotNull(errorJson.get("threads"));
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/com/bugsnag/android/BreadcrumbType.java
@@ -0,0 +1,48 @@
package com.bugsnag.android;

/**
* Recognized types of breadcrumbs
*/
public enum BreadcrumbType {
/**
* An error was sent to Bugsnag (internal use only)
*/
ERROR ("error"),
/**
* A log message
*/
LOG ("log"),
/**
* A manual invocation of `leaveBreadcrumb` (default)
*/
MANUAL ("manual"),
/**
* A navigation event, such as a window opening or closing
*/
NAVIGATION ("navigation"),
/**
* A background process such as a database query
*/
PROCESS ("process"),
/**
* A network request
*/
REQUEST ("request"),
/**
* A change in application state, such as launch or memory warning
*/
STATE ("state"),
/**
* A user action, such as tapping a button
*/
USER ("user");

private final String type;

BreadcrumbType(String type) {
this.type = type;
}

String serialize() { return type; }
}

81 changes: 69 additions & 12 deletions src/main/java/com/bugsnag/android/Breadcrumbs.java
Expand Up @@ -3,45 +3,86 @@
import android.support.annotation.NonNull;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Date;
import java.util.Queue;
import java.util.Map;
import java.util.Collections;
import java.util.concurrent.ConcurrentLinkedQueue;


class Breadcrumbs implements JsonStream.Streamable {
private static class Breadcrumb {
private static class Breadcrumb implements JsonStream.Streamable {
private static final int MAX_MESSAGE_LENGTH = 140;
private static final String DEFAULT_NAME = "manual";
private static final String MESSAGE_METAKEY = "message";
private final String TIMESTAMP_KEY = "timestamp";
private final String NAME_KEY = "name";
private final String METADATA_KEY = "metaData";
private final String TYPE_KEY = "type";
final String timestamp;
final String message;
final String name;
final BreadcrumbType type;
final Map<String, String> metadata;

Breadcrumb(@NonNull String message) {
this.timestamp = DateUtils.toISO8601(new Date());
this.message = message.substring(0, Math.min(message.length(), MAX_MESSAGE_LENGTH));
this.type = BreadcrumbType.MANUAL;
this.metadata = Collections.singletonMap(MESSAGE_METAKEY, message.substring(0, Math.min(message.length(), MAX_MESSAGE_LENGTH)));
this.name = DEFAULT_NAME;
}

Breadcrumb(@NonNull String name, BreadcrumbType type, Map<String, String> metadata) {
this.timestamp = DateUtils.toISO8601(new Date());
this.type = type;
this.metadata = metadata;
this.name = name;
}

public void toStream(@NonNull JsonStream writer) throws IOException {
writer.beginObject();
writer.name(TIMESTAMP_KEY).value(this.timestamp);
writer.name(NAME_KEY).value(this.name);
writer.name(TYPE_KEY).value(this.type.serialize());
writer.name(METADATA_KEY);
writer.beginObject();
for (Map.Entry<String, String> entry : this.metadata.entrySet()) {
writer.name(entry.getKey()).value(entry.getValue());
}
writer.endObject();
writer.endObject();
}

public int payloadSize() throws IOException {
StringWriter writer = new StringWriter();
JsonStream jsonStream = new JsonStream(writer);
toStream(jsonStream);

return writer.toString().length();
}
}

private static final int DEFAULT_MAX_SIZE = 20;
private static final int MAX_PAYLOAD_SIZE = 4096;
private final Queue<Breadcrumb> store = new ConcurrentLinkedQueue<>();
private int maxSize = DEFAULT_MAX_SIZE;

public void toStream(@NonNull JsonStream writer) throws IOException {
writer.beginArray();

for (Breadcrumb breadcrumb : store) {
writer.beginArray();
writer.value(breadcrumb.timestamp);
writer.value(breadcrumb.message);
writer.endArray();
breadcrumb.toStream(writer);
}

writer.endArray();
}

void add(@NonNull String message) {
if (store.size() >= maxSize) {
// Remove oldest breadcrumb
store.poll();
}
store.add(new Breadcrumb(message));
addToStore(new Breadcrumb(message));
}

void add(@NonNull String name, BreadcrumbType type, Map<String, String> metadata) {
addToStore(new Breadcrumb(name, type, metadata));
}

void clear() {
Expand All @@ -58,4 +99,20 @@ void setSize(int size) {
}
}
}

private void addToStore(Breadcrumb breadcrumb) {
try {
if (breadcrumb.payloadSize() > MAX_PAYLOAD_SIZE) {
Logger.warn("Dropping breadcrumb because payload exceeds 4KB limit");
return;
}
if (store.size() >= maxSize) {
// Remove oldest breadcrumb
store.poll();
}
store.add(breadcrumb);
} catch (IOException ex) {
Logger.warn("Dropping breadcrumb because it could not be serialized", ex);
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/bugsnag/android/Bugsnag.java
@@ -1,5 +1,7 @@
package com.bugsnag.android;

import java.util.Map;

import android.content.Context;

/**
Expand Down Expand Up @@ -372,6 +374,18 @@ public static void leaveBreadcrumb(String message) {
getClient().leaveBreadcrumb(message);
}

/**
* Leave a "breadcrumb" log message representing an action or event which
* occurred in your app, to aid with debugging
*
* @param name A short label (max 32 chars)
* @param type A category for the breadcrumb
* @param metadata Additional diagnostic information about the app environment
*/
public static void leaveBreadcrumb(String name, BreadcrumbType type, Map<String, String> metadata) {
getClient().leaveBreadcrumb(name, type, metadata);
}

/**
* Set the maximum number of breadcrumbs to keep and sent to Bugsnag.
* By default, we'll keep and send the 20 most recent breadcrumb log
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/bugsnag/android/Client.java
Expand Up @@ -7,6 +7,8 @@
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.util.Locale;
import java.util.Collections;
import java.util.Map;

/**
* A Bugsnag Client instance allows you to use Bugsnag in your Android app.
Expand Down Expand Up @@ -536,6 +538,10 @@ public void leaveBreadcrumb(String breadcrumb) {
breadcrumbs.add(breadcrumb);
}

public void leaveBreadcrumb(String name, BreadcrumbType type, Map<String, String> metadata) {
breadcrumbs.add(name, type, metadata);
}

/**
* Set the maximum number of breadcrumbs to keep and sent to Bugsnag.
* By default, we'll keep and send the 20 most recent breadcrumb log
Expand Down Expand Up @@ -580,6 +586,9 @@ private void notify(final Error error, boolean blocking) {
return;
}

// Add a breadcrumb for this error occurring
breadcrumbs.add(error.getExceptionName(), BreadcrumbType.ERROR, Collections.singletonMap("message", error.getExceptionMessage()));

// Capture the state of the app and device and attach diagnostics to the error
error.setAppData(appData);
error.setDeviceData(deviceData);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/bugsnag/android/Error.java
Expand Up @@ -16,7 +16,7 @@
* @see BeforeNotify
*/
public class Error implements JsonStream.Streamable {
private static final String PAYLOAD_VERSION = "2";
private static final String PAYLOAD_VERSION = "3";

private final Configuration config;
private AppData appData;
Expand Down

0 comments on commit 3ec4c8e

Please sign in to comment.