Skip to content

Instantly share code, notes, and snippets.

@jrichardlai
Last active February 7, 2018 20:44
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jrichardlai/072d2f13098f38df504a7fe031a803bb to your computer and use it in GitHub Desktop.
Save jrichardlai/072d2f13098f38df504a7fe031a803bb to your computer and use it in GitHub Desktop.
Bugsnag integration with React Native

To setup Bugsnag import ErrorManager and require configureErrorManager.js or configureErrorManagerWithSourcemap.js. Set up your user with ErrorManager.setIdentifier.

const ErrorManager = NativeModules.ErrorManager;

ErrorManager.setIdentifier({
  id: account.data.id,
  email: account.data.email,
  full_name: account.data.full_name,
});

To trigger an error manually you can call notifyError(error)

const ErrorUtils = require('ErrorUtils');
import { NativeModules } from 'react-native';
import parseErrorStack from 'parseErrorStack';
import _ from 'underscore';
const ErrorManager = NativeModules.ErrorManager;
let exceptionID = 0;
if (ErrorManager && ErrorUtils._globalHandler) {
const previousGlobalHandler = ErrorUtils._globalHandler;
const wrapGlobalHandler = (error, isFatal) => {
let currentExceptionID = ++exceptionID;
const stack = parseErrorStack(error);
const timeoutPromise = new Promise((resolve) => {
global.setTimeout(() => {
resolve();
}, 1000);
});
const reportExceptionPromise = new Promise((resolve) => {
ErrorManager.reportException(error.message, stack, currentExceptionID, {}, resolve);
});
return Promise.race([reportExceptionPromise, timeoutPromise]).then(() => {
previousGlobalHandler(error, isFatal);
});
};
ErrorUtils.setGlobalHandler(wrapGlobalHandler);
}
global.notifyError = (error, errorData) => {
if (error instanceof Error) {
console.log('notifyError', error, errorData);
let currentExceptionID = ++exceptionID;
const stack = parseErrorStack(error);
ErrorManager.reportException(
error.message,
stack,
currentExceptionID,
// Make all values string
_.mapObject(errorData || {}, (val, key) => (val || 'NULL').toString()),
() => {}
);
} else {
console.warn('attempt to call notifyError without an Error', error, errorData);
}
}
const ErrorUtils = require('ErrorUtils');
import {
NativeModules,
} from 'react-native';
import parseErrorStack from 'parseErrorStack';
import _ from 'underscore';
import sourceMap from 'source-map';
const base64 = require('base-64');
const utf8 = require('utf8');
const ErrorManager = NativeModules.ErrorManager;
// set to noop
global.notifyError = () => {};
if (ErrorManager && ErrorUtils._globalHandler) {
let exceptionID = 0;
const getSourceMapInstance = () => {
return new Promise((resolve, reject) => {
try {
ErrorManager.getSourceMaps((data) => {
try {
const content = utf8.decode(base64.decode(data));
resolve(new sourceMap.SourceMapConsumer(JSON.parse(content)));
} catch (e) {
reject(e);
}
});
} catch (e) {
console.warn('Error parsing the error from the sourcemap', e.message);
reject(e);
}
});
};
const parseErrorStackPromise = (error) => {
return new Promise((resolve) => {
global.setTimeout(() => {
resolve(parseErrorStack(error));
}, 8000);
getSourceMapInstance().then((sourceMapInstance) => {
resolve(parseErrorStack(error, [sourceMapInstance]));
}).catch((e) => {
console.warn(e);
resolve(parseErrorStack(error));
});
});
};
const previousGlobalHandler = ErrorUtils._globalHandler;
const wrapGlobalHandler = async (error, isFatal) => {
let currentExceptionID = ++exceptionID;
const stack = await parseErrorStackPromise(error);
const timeoutPromise = new Promise((resolve) => {
global.setTimeout(() => {
resolve();
}, 10000);
});
const reportExceptionPromise = new Promise((resolve) => {
ErrorManager.reportException(error.message, stack, currentExceptionID, {}, resolve);
});
return Promise.race([reportExceptionPromise, timeoutPromise]).then(() => {
previousGlobalHandler(error, isFatal);
});
};
ErrorUtils.setGlobalHandler(wrapGlobalHandler);
global.notifyError = async (error, errorData) => {
if (error instanceof Error) {
console.log('notifyError', error, errorData);
let currentExceptionID = ++exceptionID;
const stack = await parseErrorStack(error);
ErrorManager.reportException(
error.message,
stack,
currentExceptionID,
// Make all values string
_.mapObject(errorData || {}, (val, key) => (val || 'NULL').toString()),
() => {}
);
} else {
console.warn('attempt to call notifyError without an Error', error, errorData);
}
}
}
import android.util.Base64;
import android.util.Log;
import com.bugsnag.android.Bugsnag;
import com.bugsnag.android.MetaData;
import com.bugsnag.android.Severity;
import com.facebook.react.bridge.*;
import java.io.File;
import java.io.InputStream;
public class ErrorManager extends ReactContextBaseJavaModule {
public ErrorManager(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "ErrorManager";
}
@ReactMethod
public void setIdentifier(ReadableMap params) {
if (!BuildConfig.NOTIFY_ERRORS) { return; }
Bugsnag.setUser(params.getString("id"), params.getString("email"), params.getString("full_name"));
}
@ReactMethod
public void getSourceMaps(Callback callback) {
try {
InputStream inputStream = getReactApplicationContext().getAssets().open("sourcemap.js");
int size = inputStream.available();
byte[] buffer = new byte[size];
inputStream.read(buffer, 0, size);
inputStream.close();
String base64Content = Base64.encodeToString(buffer, Base64.NO_WRAP);
callback.invoke(base64Content);
} catch (Exception ex) {
ex.printStackTrace();
}
}
@ReactMethod
public void reportException(String title, ReadableArray details, int exceptionId, ReadableMap errorData, Callback callback) {
if (!BuildConfig.NOTIFY_ERRORS) {
callback.invoke();
return;
}
Error error = new Error(title);
error.setStackTrace(stackTraceToStackTraceElement(details));
MetaData metaData = new MetaData();
metaData.addToTab("Custom", "Stacktrace", stackTraceToString(details));
ReadableMapKeySetIterator iterator = errorData.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
metaData.addToTab("Custom", key, errorData.getString(key));
}
Bugsnag.notify(title, title, stackTraceToStackTraceElement(details), Severity.ERROR, metaData);
callback.invoke();
}
@ReactMethod
public void crash() {
}
private StackTraceElement[] stackTraceToStackTraceElement(ReadableArray stack) {
StackTraceElement[] stackTraceElements = new StackTraceElement[stack.size()];
for (int i = 0; i < stack.size(); i++) {
ReadableMap frame = stack.getMap(i);
stackTraceElements[i] = new StackTraceElement(
"ReactJS",
frame.getString("methodName"),
new File(frame.getString("file")).getName(),
frame.getInt("lineNumber")
);
}
return stackTraceElements;
}
private String stackTraceToString(ReadableArray stack) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < stack.size(); i++) {
ReadableMap frame = stack.getMap(i);
stringBuilder.append(frame.getString("methodName"));
stringBuilder.append("\n ");
stringBuilder.append(new File(frame.getString("file")).getName());
stringBuilder.append(":");
stringBuilder.append(frame.getInt("lineNumber"));
if (frame.hasKey("column") && !frame.isNull("column")) {
stringBuilder
.append(":")
.append(frame.getInt("column"));
}
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
}
//
// ErrorManager.m
//
#import "Bugsnag.h"
#import "ErrorManager.h"
@implementation NSArray (Map)
- (NSArray *)rnfs_mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[result addObject:block(obj, idx)];
}];
return result;
}
@end
@implementation ErrorManager
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(setIdentifier:(NSDictionary*)attributes)
{
#ifdef DEBUG
return;
#endif
[[Bugsnag configuration] setUser:attributes[@"id"] withName:attributes[@"email"] andEmail:attributes[@"full_name"]];
}
RCT_EXPORT_METHOD(getSourceMaps:(RCTResponseSenderBlock)callback)
{
NSString *filePath = [NSString stringWithFormat:@"%@/sourcemap.js", [[NSBundle mainBundle] bundlePath]];
NSData *content = [[NSFileManager defaultManager] contentsAtPath:filePath];
NSString *base64Content = [content base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
if (!base64Content) {
callback(@[]);
return;
}
callback(@[base64Content]);
}
RCT_EXPORT_METHOD(reportException:(NSString *)message
stack:(NSArray<NSDictionary *> *)stack
exceptionId:(nonnull NSNumber *)exceptionId
errorData:(NSDictionary *)errorData
callback:(RCTResponseSenderBlock)callback)
{
#ifdef DEBUG
callback(@[]);
return;
#endif
NSMutableArray *stringFrameArray = [[NSMutableArray alloc] init];
for (NSDictionary *stackFrame in stack) {
NSString *fileName = [NSString stringWithFormat:@"%@ @ %zd:%zd",
[stackFrame[@"file"] lastPathComponent],
[stackFrame[@"lineNumber"] integerValue],
[stackFrame[@"column"] integerValue]];
[stringFrameArray addObject:[NSString stringWithFormat:@"%@ %@", fileName, stackFrame[@"methodName"]]];
}
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString([stringFrameArray componentsJoinedByString:@"\n"], nil),
};
NSMutableDictionary *allErrorData = [errorData mutableCopy];
[allErrorData addEntriesFromDictionary:@{@"Stacktrace": [stringFrameArray componentsJoinedByString:@"\n"]}];
[Bugsnag notify:[NSException exceptionWithName:message reason:[stringFrameArray componentsJoinedByString:@"\n"] userInfo:userInfo]
withData:allErrorData atSeverity:BugsnagSeverityError];
callback(@[]);
}
RCT_EXPORT_METHOD(crash)
{
#ifdef DEBUG
return;
#endif
}
@end
@guns2410
Copy link

What should be in ErrorManager.h?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment