Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jpmunz/embr 4790 pass js stacktrace to ios #79

Merged
merged 3 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public void clearAllUserPersonas(Promise promise) {

@ReactMethod
public void logMessageWithSeverityAndProperties(String message, String severity, ReadableMap properties,
Promise promise) {
String stacktrace, Promise promise) {
try {
final Map<String, Object> props = properties != null ? properties.toHashMap() : null;
if (severity.equals("info")) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/ios/RNEmbrace/EmbraceManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ @interface RCT_EXTERN_MODULE(EmbraceManager, NSObject)
RCT_EXTERN_METHOD(logMessageWithSeverityAndProperties:(NSString *)message
severity:(NSString *)severity
properties:(NSDictionary)properties
stacktrace:(NSString *)stacktrace
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)

Expand Down
10 changes: 8 additions & 2 deletions packages/core/ios/RNEmbrace/EmbraceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,20 +301,26 @@ class EmbraceManager: NSObject {
}
}

@objc(logMessageWithSeverityAndProperties:severity:properties:resolver:rejecter:)
@objc(logMessageWithSeverityAndProperties:severity:properties:stacktrace:resolver:rejecter:)
func logMessageWithSeverityAndProperties(
_ message: String,
severity: String,
properties: NSDictionary,
stacktrace: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {

let severityValue = self.severityFromString(from: severity)
guard let attributes = properties as? [String: String] else {
guard var attributes = properties as? [String: String] else {
reject("LOG_MESSAGE_INVALID_PROPERTIES", "Properties should be [String: String]", nil)
return
}

if (!stacktrace.isEmpty) {
attributes.updateValue(stacktrace, forKey: "exception.stacktrace")
}

Embrace.client?.log(
message,
severity: severityValue,
Expand Down
74 changes: 55 additions & 19 deletions packages/core/src/__tests__/embrace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ jest.mock("react-native", () => ({
message: string,
severity: string,
properties: Record<string, any>,
stacktrace: string,
) =>
mockLogMessageWithSeverityAndProperties(message, severity, properties),
mockLogMessageWithSeverityAndProperties(
message,
severity,
properties,
stacktrace,
),
logHandledError: (message: string, properties?: Record<string, any>) =>
mockLogHandledError(message, properties),
addUserPersona: (persona: string) => mockAddUserPersona(persona),
Expand Down Expand Up @@ -133,8 +139,15 @@ jest.mock("react-native", () => ({
},
}));

const mockSt = "this is a fake stack trace";
const testView = "View";

const mockGenerateStackTrace = jest.fn();
jest.mock("../utils/ErrorUtil", () => ({
...jest.requireActual("../utils/ErrorUtil"),
generateStackTrace: () => mockGenerateStackTrace(),
}));

describe("User Identifier Tests", () => {
const testUserId = "testUser";
beforeEach(() => {
Expand Down Expand Up @@ -182,6 +195,10 @@ describe("Logs Test", () => {
const INFO = "info";
const ERROR = "error";

beforeEach(() => {
mockGenerateStackTrace.mockReturnValue(mockSt);
});

test("addBreadcrumb", async () => {
await addBreadcrumb(testView);
expect(mockAddBreadcrumb).toHaveBeenCalledWith(testView);
Expand All @@ -199,26 +216,34 @@ describe("Logs Test", () => {
const testProps = {foo: "bar"};

test.each`
message | severity | properties
${testMessage} | ${INFO} | ${testProps}
${testMessage} | ${WARNING} | ${testProps}
${testMessage} | ${ERROR} | ${testProps}
`("should run $severity log", async ({message, severity, properties}) => {
await logMessage(message, severity, properties);
expect(mockLogMessageWithSeverityAndProperties).toHaveBeenCalledWith(
message,
severity,
properties,
);
});
message | severity | properties | stackTrace
${testMessage} | ${INFO} | ${testProps} | ${""}
${testMessage} | ${INFO} | ${testProps} | ${""}
${testMessage} | ${WARNING} | ${testProps} | ${mockSt}
${testMessage} | ${WARNING} | ${testProps} | ${mockSt}
${testMessage} | ${ERROR} | ${testProps} | ${mockSt}
${testMessage} | ${ERROR} | ${testProps} | ${mockSt}
`(
"should run $severity log",
async ({message, severity, properties, stackTrace}) => {
await logMessage(message, severity, properties);
expect(mockLogMessageWithSeverityAndProperties).toHaveBeenCalledWith(
message,
severity,
properties,
stackTrace,
);
},
);
});

test("logInfo", async () => {
await logInfo("test message");
expect(mockLogMessageWithSeverityAndProperties).toHaveBeenCalledWith(
`test message`,
INFO,
{},
undefined,
"",
);
});

Expand All @@ -227,7 +252,8 @@ describe("Logs Test", () => {
expect(mockLogMessageWithSeverityAndProperties).toHaveBeenCalledWith(
`test message`,
WARNING,
{},
undefined,
mockSt,
);
});

Expand All @@ -236,7 +262,8 @@ describe("Logs Test", () => {
expect(mockLogMessageWithSeverityAndProperties).toHaveBeenCalledWith(
`test message`,
ERROR,
{},
undefined,
mockSt,
);
});
});
Expand Down Expand Up @@ -289,7 +316,9 @@ describe("Custom Views Tests", () => {
});

test("endView", async () => {
await endView(testView);
const promiseToResolve = endView(testView);
jest.runAllTimers();
await promiseToResolve;
expect(mockEndView).toHaveBeenCalledWith(testView);
});
});
Expand All @@ -308,12 +337,19 @@ describe("Session Properties Tests", () => {

describe("Payers Test", () => {
test("setUserAsPayer", async () => {
const result = await setUserAsPayer();
const promiseToResolve = setUserAsPayer();
jest.runAllTimers();
const result = await promiseToResolve;

expect(mockSetUserAsPayer).toHaveBeenCalled();
expect(result).toBe(false);
});
test("clearUserAsPayer", async () => {
const result = await clearUserAsPayer();
const promiseToResolve = clearUserAsPayer();

jest.runAllTimers();
const result = await promiseToResolve;

expect(mockClearUserAsPayer).toHaveBeenCalled();
expect(result).toBe(false);
});
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {NativeModules, Platform} from "react-native";

import * as embracePackage from "../package.json";

import {handleGlobalError} from "./utils/ErrorUtil";
import {generateStackTrace, handleGlobalError} from "./utils/ErrorUtil";
import {ApplyInterceptorStrategy} from "./networkInterceptors/ApplyInterceptor";
import {SessionStatus} from "./interfaces/Types";
import {
Expand Down Expand Up @@ -121,15 +121,18 @@ export const initialize = async ({
allRejections: true,
onUnhandled: (_: unknown, error: Error) => {
let message = `${UNHANDLED_PROMISE_REJECTION_PREFIX}: ${error}`;
let stackTrace = "";

if (error instanceof Error) {
message = `${UNHANDLED_PROMISE_REJECTION_PREFIX}: ${error.message}`;
stackTrace = error.stack || "";
}

return NativeModules.EmbraceManager.logMessageWithSeverityAndProperties(
message,
ERROR,
{},
stackTrace,
);
},
onHandled: noOp,
Expand Down Expand Up @@ -205,10 +208,13 @@ export const logMessage = (
severity: "info" | "warning" | "error" = "error",
properties?: Properties,
): Promise<boolean> => {
const stacktrace = severity === INFO ? "" : generateStackTrace();

return NativeModules.EmbraceManager.logMessageWithSeverityAndProperties(
message,
severity,
properties || {},
properties,
stacktrace,
);
};

Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/utils/ErrorUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@
handleError(error, callback);
};

export {handleGlobalError};
const generateStackTrace = (): string => {
const err = new Error();

Check warning on line 19 in packages/core/src/utils/ErrorUtil.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/utils/ErrorUtil.ts#L19

Added line #L19 was not covered by tests
return err.stack || "";
};

export {handleGlobalError, generateStackTrace};
2 changes: 1 addition & 1 deletion packages/core/test-project/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,7 @@ SPEC CHECKSUMS:
React-utils: 4476b7fcbbd95cfd002f3e778616155241d86e31
ReactCommon: ecad995f26e0d1e24061f60f4e5d74782f003f12
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: ae3c32c514802d30f687a04a6a35b348506d411f
Yoga: 2f71ecf38d934aecb366e686278102a51679c308

PODFILE CHECKSUM: 8a247e59201368a342e5530a540a10f61aa7e0f3

Expand Down
23 changes: 21 additions & 2 deletions packages/core/test-project/ios/RNEmbraceTests/RNEmbraceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class EmbraceLogsTests: XCTestCase {

func testLogMessageWithSeverity() async throws {
module.logMessageWithSeverityAndProperties("my log message", severity:"warning", properties: NSDictionary(),
stacktrace: "",
resolver: promise.resolve, rejecter: promise.reject)

let exportedLogs = try await getExportedLogs()
Expand All @@ -200,14 +201,16 @@ class EmbraceLogsTests: XCTestCase {
XCTAssertEqual(exportedLogs[0].severity?.description, "WARN")
XCTAssertEqual(exportedLogs[0].body?.description, "my log message")
XCTAssertEqual(exportedLogs[0].attributes["emb.type"]!.description, "sys.log")
XCTAssertNil(exportedLogs[0].attributes["exception.stacktrace"])
}


func testLogMessageWithSeverityAndProperties() async throws {
module.logMessageWithSeverityAndProperties("my log message", severity:"error", properties: NSDictionary(dictionary: [
"prop1": "foo",
"prop2": "bar"
]), resolver: promise.resolve, rejecter: promise.reject)
]),
stacktrace: "",
resolver: promise.resolve, rejecter: promise.reject)

let exportedLogs = try await getExportedLogs()

Expand All @@ -218,9 +221,25 @@ class EmbraceLogsTests: XCTestCase {
XCTAssertEqual(exportedLogs[0].attributes["emb.type"]!.description, "sys.log")
XCTAssertEqual(exportedLogs[0].attributes["prop1"]!.description, "foo")
XCTAssertEqual(exportedLogs[0].attributes["prop2"]!.description, "bar")
XCTAssertNil(exportedLogs[0].attributes["exception.stacktrace"])
}


func testLogMessageWithStackTrace() async throws {
module.logMessageWithSeverityAndProperties("my log message", severity:"warning", properties: NSDictionary(),
stacktrace: "my stack trace",
resolver: promise.resolve, rejecter: promise.reject)

let exportedLogs = try await getExportedLogs()

XCTAssertEqual(promise.resolveCalls.count, 1)
XCTAssertEqual(exportedLogs.count, 1)
XCTAssertEqual(exportedLogs[0].severity?.description, "WARN")
XCTAssertEqual(exportedLogs[0].body?.description, "my log message")
XCTAssertEqual(exportedLogs[0].attributes["emb.type"]!.description, "sys.log")
XCTAssertEqual(exportedLogs[0].attributes["exception.stacktrace"]!.description, "my stack trace")
}

}

class EmbraceSpansTests: XCTestCase {
Expand Down
Loading