package com.bugsnag.android; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import java.io.IOException; /** * Information and associated diagnostics relating to a handled or unhandled * Exception. * * <p>This object is made available in BeforeNotify callbacks, so you can * inspect and modify it before it is delivered to Bugsnag. * * @see BeforeNotify */ public class Error implements JsonStream.Streamable { private static final String PAYLOAD_VERSION = "3"; final Configuration config; private AppData appData; private DeviceData deviceData; private AppState appState; private DeviceState deviceState; private Breadcrumbs breadcrumbs; private User user; private Throwable exception; private Severity severity = Severity.WARNING; private MetaData metaData = new MetaData(); private String groupingHash; private String context; Error(Configuration config, Throwable exception) { this.config = config; this.exception = exception; } Error(Configuration config, String name, String message, StackTraceElement[] frames) { this.config = config; this.exception = new BugsnagException(name, message, frames); } public void toStream(@NonNull JsonStream writer) throws IOException { // Merge error metaData into global metadata and apply filters MetaData mergedMetaData = MetaData.merge(config.getMetaData(), metaData); // Write error basics writer.beginObject(); writer.name("payloadVersion").value(PAYLOAD_VERSION); writer.name("context").value(getContext()); writer.name("severity").value(severity); writer.name("metaData").value(mergedMetaData); if(config.getProjectPackages() != null) { writer.name("projectPackages").beginArray(); for (String projectPackage : config.getProjectPackages()) { writer.value(projectPackage); } writer.endArray(); } // Write exception info writer.name("exceptions").value(new Exceptions(config, exception)); // Write user info writer.name("user").value(user); // Write diagnostics writer.name("app").value(appData); writer.name("appState").value(appState); writer.name("device").value(deviceData); writer.name("deviceState").value(deviceState); writer.name("breadcrumbs").value(breadcrumbs); writer.name("groupingHash").value(groupingHash); if(config.getSendThreads()) { writer.name("threads").value(new ThreadState(config)); } writer.endObject(); } /** * Override the context sent to Bugsnag with this Error. By default we'll * attempt to detect the name of the top-most Activity when this error * occurred, and use this as the context, but sometimes this is not * possible. * * @param context what was happening at the time of a crash */ public void setContext(String context) { this.context = context; } /** * Get the context associated with this Error. */ @Nullable public String getContext() { if(context != null && !TextUtils.isEmpty(context)) { return context; } else if (config.getContext() != null) { return config.getContext(); } else if (appState != null){ return AppState.getActiveScreenClass(context); } else { return null; } } /** * Set a custom grouping hash to use when grouping this Error on the * Bugsnag dashboard. By default, we use a combination of error class * and top-most stacktrace line to calculate this, and we do not recommend * you override this. * * @param groupingHash a string to use when grouping errors */ public void setGroupingHash(String groupingHash) { this.groupingHash = groupingHash; } /** * Set the Severity of this Error. * * By default, unhandled exceptions will be Severity.ERROR and handled * exceptions sent with bugsnag.notify will be Severity.WARNING. * * @param severity the severity of this error * @see Severity */ public void setSeverity(Severity severity) { if(severity != null) { this.severity = severity; } } /** * Get the Severity of this Error. * * @see Severity */ public Severity getSeverity() { return severity; } /** * Set user information associated with this Error * * @param id the id of the user * @param email the email address of the user * @param name the name of the user */ public void setUser(String id, String email, String name) { this.user = new User(id, email, name); } /** * @return user information associated with this Error */ public User getUser() { return user; } /** * Set user id associated with this Error * * @param id the id of the user */ public void setUserId(String id) { this.user = new User(this.user); this.user.setId(id); } /** * Set user email address associated with this Error * * @param email the email address of the user */ public void setUserEmail(String email) { this.user = new User(this.user); this.user.setEmail(email); } /** * Set user name associated with this Error * * @param name the name of the user */ public void setUserName(String name) { this.user = new User(this.user); this.user.setName(name); } /** * Add additional diagnostic information to send with this Error. * Diagnostic information is collected in "tabs" on your dashboard. * * For example: * * error.addToTab("account", "name", "Acme Co."); * error.addToTab("account", "payingCustomer", true); * * @param tabName the dashboard tab to add diagnostic data to * @param key the name of the diagnostic information * @param value the contents of the diagnostic information */ public void addToTab(String tabName, String key, Object value) { metaData.addToTab(tabName, key, value); } /** * Remove a tab of app-wide diagnostic information from this Error * * @param tabName the dashboard tab to remove diagnostic data from */ public void clearTab(String tabName) { metaData.clearTab(tabName); } /** * Get any additional diagnostic MetaData currently attached to this Error. * * This will contain any MetaData set by setMetaData or addToTab. * * @see Error#setMetaData * @see Error#addToTab */ public MetaData getMetaData() { return metaData; } /** * Set additional diagnostic MetaData to send with this Error. This will * be merged with any global MetaData you set on the Client. * * Note: This will overwrite any MetaData you provided using * Bugsnag.notify, so it is recommended to use addToTab instead. * * @param metaData additional diagnostic data to send with this Error * @see Error#addToTab * @see Error#getMetaData */ public void setMetaData(MetaData metaData) { this.metaData = metaData; } /** * Get the class name from the exception contained in this Error report. */ public String getExceptionName() { if(exception instanceof BugsnagException) { return ((BugsnagException)exception).getName(); } else { return exception.getClass().getName(); } } /** * Get the message from the exception contained in this Error report. */ public String getExceptionMessage() { return exception.getLocalizedMessage(); } /** * The {@linkplain Throwable exception} which triggered this Error report. */ public Throwable getException() { return exception; } void setAppData(AppData appData) { this.appData = appData; } void setDeviceData(DeviceData deviceData) { this.deviceData = deviceData; } void setAppState(AppState appState) { this.appState = appState; } void setDeviceState(DeviceState deviceState) { this.deviceState = deviceState; } void setUser(User user) { this.user = user; } void setBreadcrumbs(Breadcrumbs breadcrumbs) { this.breadcrumbs = breadcrumbs; } boolean shouldIgnoreClass() { return config.shouldIgnoreClass(getExceptionName()); } }