Skip to content

Commit

Permalink
feat(react-native): support for starting native spans on android
Browse files Browse the repository at this point in the history
  • Loading branch information
yousif-bugsnag committed Nov 28, 2024
1 parent 2cf5d15 commit 39925f3
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.Callable;

import com.bugsnag.android.performance.BugsnagPerformance;
import com.bugsnag.android.performance.SpanContext;
import com.bugsnag.android.performance.SpanOptions;

import com.bugsnag.android.performance.internal.EncodingUtils;
import com.bugsnag.android.performance.internal.SpanFactory;
import com.bugsnag.android.performance.internal.SpanImpl;
import com.bugsnag.android.performance.internal.processing.ImmutableConfig;

class NativeBugsnagPerformanceImpl {
Expand Down Expand Up @@ -111,7 +120,7 @@ public WritableMap getNativeConfiguration() {
result.putString("releaseStage", nativeConfig.getReleaseStage());
result.putString("serviceName", nativeConfig.getServiceName());
result.putInt("attributeCountLimit", nativeConfig.getAttributeCountLimit());
result.putInt("attriubuteStringValueLimit", nativeConfig.getAttributeStringValueLimit());
result.putInt("attributeStringValueLimit", nativeConfig.getAttributeStringValueLimit());
result.putInt("attributeArrayLengthLimit", nativeConfig.getAttributeArrayLengthLimit());

var appVersion = nativeConfig.getAppVersion();
Expand All @@ -130,9 +139,95 @@ public WritableMap getNativeConfiguration() {
}

return result;
}

}
@Nullable
public WritableMap startNativeSpan(String name, ReadableMap options) {
if (!isNativePerformanceAvailable) {
return null;
}

SpanOptions spanOptions = jsSpanOptionsToNativeSpanOptions(options);
SpanFactory spanFactory = BugsnagPerformance.INSTANCE.getInstrumentedAppState$internal().getSpanFactory();
SpanImpl nativeSpan = spanFactory.createCustomSpan(name, spanOptions);

nativeSpan.getAttributes().getEntries$internal().clear();

WritableMap span = nativeSpanToJsSpan(nativeSpan);
return span;
}

private WritableMap nativeSpanToJsSpan(SpanImpl nativeSpan) {
WritableMap span = Arguments.createMap();
span.putString("name", nativeSpan.getName());
span.putString("id", EncodingUtils.toHexString(nativeSpan.getSpanId()));
span.putString("traceId", EncodingUtils.toHexString(nativeSpan.getTraceId()));
span.putDouble("startTime", (double)nativeSpan.getStartTime$internal());
Long parentSpanId = nativeSpan.getParentSpanId();
if (parentSpanId > 0L) {
span.putString("parentSpanId", EncodingUtils.toHexString(parentSpanId));
}

return span;
}

private SpanOptions jsSpanOptionsToNativeSpanOptions(ReadableMap options) {
SpanOptions spanOptions = SpanOptions.DEFAULTS
.setFirstClass(true)
.makeCurrentContext(false);

if (options.hasKey("startTime")) {
double startTime = options.getDouble("startTime");
spanOptions = spanOptions.startTime((long)startTime);
}

ReadableMap parentContext = null;
if (options.hasKey("parentContext") && (parentContext = options.getMap("parentContext")) != null) {
SpanContext nativeParentContext = jsSpanContextToNativeSpanContext(parentContext);
spanOptions = spanOptions.within(nativeParentContext);
} else {
spanOptions = spanOptions.within(null);
}

return spanOptions;
}

private SpanContext jsSpanContextToNativeSpanContext(ReadableMap spanContext) {
String spanId = spanContext.getString("id");
String traceId = spanContext.getString("traceId");

long nativeSpanId = Long.parseUnsignedLong(spanId, 16);
UUID nativeTraceId = new UUID(Long.parseUnsignedLong(traceId.substring(0, 16), 16), Long.parseUnsignedLong(traceId.substring(16), 16));

SpanContext nativeSpanContext = new SpanContext() {
@Override
public long getSpanId() {
return nativeSpanId;
}

@NonNull
@Override
public UUID getTraceId() {
return nativeTraceId;
}

@NonNull
@Override
public Runnable wrap(@NonNull Runnable runnable) {
return null;
}

@NonNull
@Override
public <T> Callable<T> wrap(@NonNull Callable<T> callable) {
return null;
}
};

return nativeSpanContext;
}

@Nullable
private String abiToArchitecture(@Nullable String abi) {
if (abi == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.bugsnag.reactnative.performance.NativeBugsnagPerformanceSpec;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

public class BugsnagReactNativePerformance extends NativeBugsnagPerformanceSpec {
Expand Down Expand Up @@ -44,5 +45,9 @@ public boolean isNativePerformanceAvailable() {
public WritableMap getNativeConfiguration() {
return impl.getNativeConfiguration();
}

@Override public WritableMap startNativeSpan(String name, ReadableMap options) {
return impl.startNativeSpan(name, options);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

import java.util.Map;
Expand Down Expand Up @@ -46,4 +47,9 @@ public boolean isNativePerformanceAvailable() {
public WritableMap getNativeConfiguration() {
return impl.getNativeConfiguration();
}

@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap startNativeSpan(String name, ReadableMap options) {
return impl.startNativeSpan(name, options);
}
}
28 changes: 23 additions & 5 deletions packages/platforms/react-native/lib/NativeBugsnagPerformance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'
import { TurboModuleRegistry } from 'react-native'

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type DeviceInfo = {
arch?: string
model?: string
Expand All @@ -10,26 +10,44 @@ export type DeviceInfo = {
bundleIdentifier?: string
}

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type NativeConfiguration = {
apiKey: string
endpoint: string
samplingProbability?: number
appVersion?: string
samplingProbability: number | undefined
appVersion: string | undefined
releaseStage: string
enabledReleaseStages?: string[]
enabledReleaseStages: string[] | undefined
serviceName: string
attributeCountLimit: number
attributeStringValueLimit: number
attributeArrayLengthLimit: number
}

export type ParentContext = {
id: string
traceId: string
}

export type NativeSpanOptions = {
startTime: number | undefined
parentContext: ParentContext | null
}

export type NativeSpan = {
name: string
id: string
traceId: string
startTime: number
parentSpanId: string
}

export interface Spec extends TurboModule {
getDeviceInfo: () => DeviceInfo
requestEntropy: () => string
requestEntropyAsync: () => Promise<string>
isNativePerformanceAvailable: () => boolean
getNativeConfiguration: () => NativeConfiguration | null
startNativeSpan: (name: string, options: NativeSpanOptions) => NativeSpan
}

export default TurboModuleRegistry.get<Spec>(
Expand Down

0 comments on commit 39925f3

Please sign in to comment.