8000 all: Pass around from the initial callsite to get a usable stack trace · phthhieu/sentry-javascript@8853d2b · GitHub
[go: up one dir, main page]

Skip to content

Commit 8853d2b

Browse files
committed
all: Pass around from the initial callsite to get a usable stack trace
1 parent ddc6d04 commit 8853d2b

File tree

8 files changed

+109
-109
lines changed

8 files changed

+109
-109
lines changed

packages/browser/src/backend.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class BrowserBackend implements Backend {
5757
/**
5858
* @inheritDoc
5959
*/
60-
public async eventFromException(exception: any): Promise<SentryEvent> {
60+
public async eventFromException(exception: any, syntheticException: Error | null): Promise<SentryEvent> {
6161
if (isErrorEvent(exception as ErrorEvent) && (exception as ErrorEvent).error) {
6262
// If it is an ErrorEvent with `error` property, extract it to get actual Error
6363
const ex = 6D40 exception as ErrorEvent;
@@ -71,7 +71,7 @@ export class BrowserBackend implements Backend {
7171
const name = ex.name || (isDOMError(ex) ? 'DOMError' : 'DOMException');
7272
const message = ex.message ? `${name}: ${ex.message}` : name;
7373

74-
return this.eventFromMessage(message);
74+
return this.eventFromMessage(message, syntheticException);
7575
} else if (isError(exception as Error)) {
7676
// we have a real Error object, do nothing
7777
} else if (isPlainObject(exception as {})) {
@@ -89,12 +89,12 @@ export class BrowserBackend implements Backend {
8989
// it's not an Error
9090
// So bail out and capture it as a simple message:
9191
const ex = exception as string;
92-
return this.eventFromMessage(ex);
92+
return this.eventFromMessage(ex, syntheticException);
9393
}
9494

95-
const event = eventFromStacktrace(computeStackTrace(exception as Error));
95+
let event: SentryEvent = eventFromStacktrace(computeStackTrace(exception as Error));
9696

97-
return {
97+
event = {
9898
...event,
9999
exception: {
100100
...event.exception,
@@ -104,39 +104,32 @@ export class BrowserBackend implements Backend {
104104
},
105105
},
106106
};
107+
108+
console.log(event);
109+
110+
return event;
107111
}
108112

109113
/**
110114
* @inheritDoc
111115
*/
112-
public async eventFromMessage(message: string): Promise<SentryEvent> {
113-
message = String(message); // tslint:disable-line:no-parameter-reassignment
114-
115-
// Generate a "synthetic" stack trace from this point.
116-
// NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative
117-
// of a bug with Raven.js. Sentry generates synthetic traces either by configuration,
118-
// or if it catches a thrown object without a "stack" property.
119-
// Neither DOMError or DOMException provide stacktrace and we most likely wont get it this way as well
120 F438 -
// but it's barely any overhead so we may at least try
121-
let syntheticException: Error;
122-
try {
123-
throw new Error(message);
124-
} catch (exception) {
125-
syntheticException = exception as Error;
126-
// null exception name so `Error` isn't prefixed to msg
127-
(syntheticException as any).name = null; // tslint:disable-line:no-null-keyword
128-
}
129-
130-
const stacktrace = computeStackTrace(syntheticException);
131-
const frames = prepareFramesForEvent(stacktrace.stack);
132-
133-
return {
116+
public async eventFromMessage(message: string, syntheticException: Error | null): Promise<SentryEvent> {
117+
const event: SentryEvent = {
134118
fingerprint: [message],
135119
message,
136-
stacktrace: {
137-
frames,
138-
},
139120
};
121+
122+
if (syntheticException) {
123+
const stacktrace = computeStackTrace(syntheticException);
124+
const frames = prepareFramesForEvent(stacktrace.stack);
125+
event.stacktrace = {
126+
frames,
127+
};
128+
}
129+
130+
console.log(event);
131+
132+
return event;
140133
}
141134

142135
/**

packages/browser/src/integrations/globalhandlers.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { logger } from '@sentry/core';
12
import { captureEvent } from '@sentry/minimal';
23
import { Integration, SentryEvent } from '@sentry/types';
34
import { eventFromStacktrace } from '../parsers';
@@ -14,34 +15,52 @@ export class GlobalHandlers implements Integration {
1415
* @inheritDoc
1516
*/
1617
public name: string = 'GlobalHandlers';
17-
/**
18-
* @inheritDoc
19-
*/
20-
public install(
21-
options: {
18+
public constructor(
19+
private options: {
2220
onerror: boolean;
2321
onunhandledpromiserejection: boolean;
2422
} = {
2523
onerror: true,
2624
onunhandledpromiserejection: true,
2725
},
28-
): void {
29-
if (options.onerror) {
26+
) {}
27+
/**
28+
* @inheritDoc
29+
*/
30+
public install(): void {
31+
subscribe((stack: TraceKitStackTrace) => {
32+
// TODO: use stack.context to get a valuable information from TraceKit, eg.
33+
// [
34+
// 0: " })"
35+
// 1: ""
36+
// 2: " function foo () {"
37+
// 3: " Sentry.captureException('some error')"
38+
// 4: " Sentry.captureMessage('some message')"
39+
// 5: " throw 'foo'"
40+
// 6: " }"
41+
// 7: ""
42+
// 8: " function bar () {"
43+
// 9: " foo();"
44+
// 10: " }"
45+
// ]
46+
captureEvent(this.eventFromGlobalHandler(stack));
47+
});
48+
49+
if (this.options.onerror) {
50+
logger.log('Global Handler attached: onerror');
3051
installGlobalHandler();
3152
}
3253

33-
if (options.onunhandledpromiserejection) {
54+
if (this.options.onunhandledpromiserejection) {
55+
logger.log('Global Handler attached: onunhandledpromiserejection');
3456
installGlobalUnhandledRejectionHandler();
3557
}
36-
37-
subscribe((stack: TraceKitStackTrace) => {
38-
captureEvent(this.eventFromGlobalHandler(stack));
39-
});
4058
}
4159

4260
/** TODO */
4361
private eventFromGlobalHandler(stacktrace: TraceKitStackTrace): SentryEvent {
4462
const event = eventFromStacktrace(stacktrace);
63+
console.log(event);
4564
// TODO: Make a distinction between 'onunhandledrejection' and 'onerror'
4665
return {
4766
...event,

packages/browser/src/parsers.ts

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,53 +57,19 @@ export function prepareFramesForEvent(stack: TraceKitStackFrame[]): StackFrame[]
5757
return [];
5858
}
5959

60-
const topFrameUrl = stack[0].url;
61-
62-
return (
63-
stack
64-
// TODO: REMOVE ME, TESTING ONLY
65-
// Remove frames that don't have filename, colno and lineno.
66-
// Things like `new Promise` called by generated code
67-
// eg. async/await from regenerator
68-
.filter(frame => {
69-
if (frame.url.includes('packages/browser/build/bundle.min.js')) {
70-
return false;
71-
}
72-
if (frame.url === '<anonymous>' && !frame.column && !frame.line) {
73-
return false;
74-
}
75-
return true;
76-
})
77-
.map(
78-
(frame: TraceKitStackFrame): StackFrame => ({
79-
// normalize the frames data
80-
// Case when we don't have any information about the error
81-
// E.g. throwing a string or raw object, instead of an `Error` in Firefox
82-
// Generating synthetic error doesn't add any value here
83-
//
84-
// We should probably somehow let a user know that they should fix their code
85-
86-
// e.g. frames captured via captureMessage throw
87-
// for (let j = 0; j < options.trimHeadFrames && j < frames.length; j++) {
88-
// frames[j].in_app = false;
89-
// }
90-
91-
// TODO: This has to be fixed
92-
// determine if an exception came from outside of our app
93-
// first we check the global includePaths list.
94-
// Now we check for fun, if the function name is Raven or TraceKit
95-
// finally, we do a last ditch effort and check for raven.min.js
96-
// normalized.in_app = !(
97-
// /(Sentry|TraceKit)\./.test(normalized.function) ||
98-
// /raven\.(min\.)?js$/.test(normalized.filename)
99-
// );
100-
colno: frame.column,
101-
filename: frame.url || topFrameUrl,
102-
function: frame.func || '?',
103-
in_app: true,
104-
lineno: frame.line,
105-
}),
106-
)
107-
.slice(0, STACKTRACE_LIMIT)
108-
);
60+
return stack
61+
.filter(
62+
// TODO: This could be smarter
63+
frame => !frame.func.includes('captureMessage') && !frame.func.includes('captureException'),
64+
)
65+
.map(
66+
(frame: TraceKitStackFrame): StackFrame => ({
67+
colno: frame.column,
68+
filename: frame.url || stack[0].url,
69+
function: frame.func || '?',
70+
in_app: true,
71+
lineno: frame.line,
72+
}),
73+
)
74+
.slice(0, STACKTRACE_LIMIT);
10975
}

packages/core/src/base.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,16 @@ export abstract class BaseClient<B extends Backend, O extends Options> implement
116116
/**
117117
* @inheritDoc
118118
*/
119-
public async captureException(exception: any, scope?: Scope): Promise<void> {
120-
const event = await this.getBackend().eventFromException(exception);
119+
public async captureException(exception: any, syntheticException: Error | null, scope?: Scope): Promise<void> {
120+
const event = await this.getBackend().eventFromException(exception, syntheticException);
121121
await this.captureEvent(event, scope);
122122
}
123123

124124
/**
125125
* @inheritDoc
126126
*/
127-
public async captureMessage(message: string, scope?: Scope): Promise<void> {
128-
const event = await this.getBackend().eventFromMessage(message);
127+
public async captureMessage(message: string, syntheticException: Error | null, scope?: Scope): Promise<void> {
128+
const event = await this.getBackend().eventFromMessage(message, syntheticException);
129129
await this.captureEvent(event, scope);
130130
}
131131

packages/core/src/interfaces.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,20 @@ export interface Client<O extends Options = Options> {
175175
*
176176
* @param exception An exception-like object.
177177
* @param scope An optional scope containing event metadata.
178+
* @param syntheticException Manually thrown exception at the very top, to get _any_ valuable stack trace
178179
* @returns The created event id.
179180
*/
180-
captureException(exception: any, scope?: Scope): Promise<void>;
181+
captureException(exception: any, syntheticException: Error | null, scope?: Scope): Promise<void>;
181182

182183
/**
183184
* Captures a message event and sends it to Sentry.
184185
*
185186
* @param message The message to send to Sentry.
186187
* @param scope An optional scope containing event metadata.
188+
* @param syntheticException Manually thrown exception at the very top, to get _any_ valuable stack trace
187189
* @returns The created event id.
188190
*/
189-
captureMessage(message: string, scope?: Scope): Promise<void>;
191+
captureMessage(message: string, syntheticException: Error | null, scope?: Scope): Promise<void>;
190192

191193
/**
192194
* Captures a manually created event and sends it to Sentry.
@@ -242,10 +244,10 @@ export interface Backend {
242244
install?(): boolean;
243245

244246
/** Creates a {@link SentryEvent} from an exception. */
245-
eventFromException(exception: any): Promise<SentryEvent>;
247+
eventFromException(exception: any, syntheticException: Error | null): Promise<SentryEvent>;
246248

247249
/** Creates a {@link SentryEvent} from a plain message. */
248-
eventFromMessage(message: string): Promise<SentryEvent>;
250+
eventFromMessage(message: string, syntheticException: Error | null): Promise<SentryEvent>;
249251

250252
/** Submits the event to Sentry */
251253
sendEvent(event: SentryEvent): Promise<SentryResponse>;

packages/hub/src/hub.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,20 @@ export class Hub {
164164
* Captures an exception event and sends it to Sentry.
165165
*
166166
* @param exception An exception-like object.
167+
* @param syntheticException Manually thrown exception at the very top, to get _any_ valuable stack trace
167168
*/
168-
public captureException(exception: any): void {
169-
this.invokeClientAsync('captureException', exception);
169+
public captureException(exception: any, syntheticException: Error | null = null): void {
170+
this.invokeClientAsync('captureException', exception, syntheticException);
170171
}
171172

172173
/**
173174
* Captures a message event and sends it to Sentry.
174175
*
175176
* @param message The message to send to Sentry.
177+
* @param syntheticException Manually thrown exception at the very top, to get _any_ valuable stack trace
176178
*/
177-
public captureMessage(message: string): void {
178-
this.invokeClientAsync('captureMessage', message);
179+
public captureMessage(message: string, syntheticException: Error | null = null): void {
180+
this.invokeClientAsync('captureMessage', message, syntheticException);
179181
}
180182

181183
/**

packages/minimal/src/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ function callOnHub(method: string, ...args: any[]): void {
1919
* @param exception An exception-like object.
2020
*/
2121
export function captureException(exception: any): void {
22-
callOnHub('captureException', exception);
22+
let syntheticException: Error;
23+
try {
24+
// TODO: Get message from captureException call in case we pass it a non-Error type?
25+
throw new Error('Sentry syntheticException');
26+
} catch (exception) {
27+
syntheticException = exception as Error;
28+
}
29+
callOnHub('captureException', exception, syntheticException);
2330
}
2431

2532
/**
@@ -28,7 +35,13 @@ export function captureException(exception: any): void {
2835
* @param message The message to send to Sentry.
2936
*/
3037
export function captureMessage(message: string): void {
31-
callOnHub('captureMessage', message);
38+
let syntheticException: Error;
39+
try {
40+
throw new Error(message);
41+
} catch (exception) {
42+
syntheticException = exception as Error;
43+
}
44+
callOnHub('captureMessage', message, syntheticException);
3245
}
3346

3447
/**

packages/node/src/backend.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class NodeBackend implements Backend {
2525
/**
2626
* @inheritDoc
2727
*/
28-
public async eventFromException(exception: any): Promise<SentryEvent> {
28+
public async eventFromException(exception: any, syntheticException: Error | null): Promise<SentryEvent> {
2929
let stack: stacktrace.StackFrame[] | undefined;
3030
let ex: any = exception;
3131

@@ -42,11 +42,12 @@ export class NodeBackend implements Backend {
4242
scope.setFingerprint([md5(keys.join(''))]);
4343
});
4444

45+
// TODO: Use syntheticException here as well
4546
ex = new Error(message);
4647
} else {
4748
// This handles when someone does: `throw "something awesome";`
48-
// We synthesize an Error here so we can extract a (rough) stack trace.
49-
ex = new Error(exception as string);
49+
// We use synthesized Error here so we can extract a (rough) stack trace.
50+
ex = syntheticException || new Error(exception as string);
5051
}
5152

5253
stack = stacktrace.get();
@@ -60,10 +61,14 @@ export class NodeBackend implements Backend {
6061
/**
6162
* @inheritDoc
6263
*/
63-
public async eventFromMessage(message: string): Promise<SentryEvent> {
64+
public async eventFromMessage(message: string, syntheticException: Error | null 70FB ): Promise<SentryEvent> {
65+
// TODO: Use syntheticException to get a stack
6466
const stack = stacktrace.get();
6567
const frames = await parseStack(stack);
6668
const event: SentryEvent = {
69+
extra: {
70+
TODOmakeLinterHappyAKARemoveIt: syntheticException,
71+
},
6772
message,
6873
stacktrace: {
6974
frames: prepareFramesForEvent(frames),

0 commit comments

Comments
 (0)
0