Skip to content

Commit e0c91f1

Browse files
authored
node: raven-node port (#1418)
1 parent f8ae472 commit e0c91f1

File tree

24 files changed

+1174
-474
lines changed

24 files changed

+1174
-474
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@
4242
"ts-jest": "^22.4.4",
4343
"tslint": "^5.9.1",
4444
"tslint-language-service": "^0.9.9",
45-
"typescript": "^2.8.3"
45+
"typescript": "^2.9.2"
4646
}
4747
}

packages/core/src/base.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,15 @@ export abstract class BaseClient<B extends Backend, O extends Options>
112112
*/
113113
public install(): boolean {
114114
if (!this.isEnabled()) {
115-
return false;
115+
return (this.installed = false);
116116
}
117117

118-
if (this.installed === undefined) {
119-
this.installed = this.getBackend().install();
118+
const backend = this.getBackend();
119+
if (!this.installed && backend.install) {
120+
backend.install();
120121
}
121122

122-
return this.installed;
123+
return (this.installed = true);
123124
}
124125

125126
/**
@@ -275,7 +276,7 @@ export abstract class BaseClient<B extends Backend, O extends Options>
275276
// This should be the last thing called, since we want that
276277
// {@link Hub.addEventProcessor} gets the finished prepared event.
277278
if (scope) {
278-
await scope.applyToEvent(
279+
return scope.applyToEvent(
279280
prepared,
280281
Math.min(maxBreadcrumbs, MAX_BREADCRUMBS),
281282
);

packages/core/src/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ export interface Client<O extends Options = Options> {
245245
*/
246246
export interface Backend {
247247
/** Installs the SDK into the environment. */
248-
install(): boolean;
248+
install?(): boolean;
249249

250250
/** Creates a {@link SentryEvent} from an exception. */
251251
eventFromException(exception: any): Promise<SentryEvent>;

packages/hub/src/hub.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export class Hub {
217217
* @param callback will only be called if there is a bound client
218218
*/
219219
public addEventProcessor(
220-
callback: (event: SentryEvent) => Promise<void>,
220+
callback: (event: SentryEvent) => Promise<SentryEvent>,
221221
): void {
222222
const top = this.getStackTop();
223223
if (top.scope && top.client) {

packages/hub/src/scope.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export class Scope {
1212
protected scopeListeners: Array<(scope: Scope) => void> = [];
1313

1414
/** Callback list that will be called after {@link applyToEvent}. */
15-
protected eventProcessors: Array<(scope: SentryEvent) => Promise<void>> = [];
15+
protected eventProcessors: Array<
16+
(scope: SentryEvent) => Promise<SentryEvent>
17+
> = [];
1618

1719
/** Array of breadcrumbs. */
1820
protected breadcrumbs: Breadcrumb[] = [];
@@ -36,7 +38,7 @@ export class Scope {
3638

3739
/** Add new event processor that will be called after {@link applyToEvent}. */
3840
public addEventProcessor(
39-
callback: (scope: SentryEvent) => Promise<void>,
41+
callback: (scope: SentryEvent) => Promise<SentryEvent>,
4042
): void {
4143
this.eventProcessors.push(callback);
4244
}
@@ -59,11 +61,13 @@ export class Scope {
5961
/**
6062
* This will be called after {@link applyToEvent} is finished.
6163
*/
62-
protected async notifyEventProcessors(event: SentryEvent): Promise<void> {
64+
protected async notifyEventProcessors(
65+
event: SentryEvent,
66+
): Promise<SentryEvent> {
6367
return this.eventProcessors.reduce(async (prev, callback) => {
64-
await prev;
65-
return callback(event);
66-
}, Promise.resolve());
68+
const prevEvent = await prev;
69+
return callback(prevEvent);
70+
}, Promise.resolve(event));
6771
}
6872

6973
/**
@@ -170,7 +174,7 @@ export class Scope {
170174
public async applyToEvent(
171175
event: SentryEvent,
172176
maxBreadcrumbs?: number,
173-
): Promise<void> {
177+
): Promise<SentryEvent> {
174178
if (this.extra && Object.keys(this.extra).length) {
175179
event.extra = { ...this.extra, ...event.extra };
176180
}
@@ -195,6 +199,6 @@ export class Scope {
195199
: this.breadcrumbs;
196200
}
197201

198-
await this.notifyEventProcessors(event);
202+
return this.notifyEventProcessors(event);
199203
}
200204
}

packages/hub/test/lib/hub.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { SentryEvent } from '@sentry/types';
2-
import { Hub, Layer, Scope } from '../../src';
2+
import { Hub, Scope } from '../../src';
33

44
const clientFn = jest.fn();
5-
// const asyncClientFn = jest.fn(async () => Promise.resolve({}));
6-
// const asyncClientFn = async () => jest.fn();
75
const asyncClientFn = async () => Promise.reject('error');
86
const scope = new Scope();
97

@@ -236,13 +234,16 @@ describe('Hub', () => {
236234
const hub = new Hub({ a: 'b' }, localScope);
237235
hub.addEventProcessor(async (processedEvent: SentryEvent) => {
238236
expect(processedEvent.extra).toEqual({ a: 'b', b: 3 });
237+
return processedEvent;
239238
});
240239
hub.addEventProcessor(async (processedEvent: SentryEvent) => {
241240
processedEvent.dist = '1';
241+
return processedEvent;
242242
});
243243
hub.addEventProcessor(async (processedEvent: SentryEvent) => {
244244
expect(processedEvent.dist).toEqual('1');
245245
done();
246+
return processedEvent;
246247
});
247248
await localScope.applyToEvent(event);
248249
});
@@ -257,19 +258,21 @@ describe('Hub', () => {
257258
const hub = new Hub({ a: 'b' }, localScope);
258259
hub.addEventProcessor(async (processedEvent: SentryEvent) => {
259260
expect(processedEvent.extra).toEqual({ a: 'b', b: 3 });
261+
return processedEvent;
260262
});
261263
hub.addEventProcessor(
262264
async (processedEvent: SentryEvent) =>
263-
new Promise<void>(resolve => {
265+
new Promise<SentryEvent>(resolve => {
264266
setImmediate(() => {
265267
processedEvent.dist = '1';
266-
resolve();
268+
resolve(processedEvent);
267269
});
268270
}),
269271
);
270272
hub.addEventProcessor(async (processedEvent: SentryEvent) => {
271273
expect(processedEvent.dist).toEqual('1');
272274
done();
275+
return processedEvent;
273276
});
274277
await localScope.applyToEvent(event);
275278
});

packages/node/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@
2020
"@sentry/minimal": "4.0.0-beta.6",
2121
"@sentry/types": "4.0.0-beta.6",
2222
"@sentry/utils": "4.0.0-beta.6",
23-
"raven": "^2.6.0"
23+
"@types/cookie": "0.3.1",
24+
"@types/md5": "2.1.32",
25+
"@types/stack-trace": "0.0.29",
26+
"cookie": "0.3.1",
27+
"lsmod": "1.0.0",
28+
"md5": "2.2.1",
29+
"stack-trace": "0.0.10"
2430
},
2531
"devDependencies": {
2632
"jest": "^22.4.3",

packages/node/src/backend.ts

Lines changed: 51 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
import { Backend, DSN, Options, SentryError } from '@sentry/core';
2-
import { addBreadcrumb, captureEvent } from '@sentry/minimal';
2+
import { getDefaultHub } from '@sentry/hub';
33
import { SentryEvent, SentryResponse } from '@sentry/types';
4-
import { Raven } from './raven';
4+
import { isError, isPlainObject } from '@sentry/utils/is';
5+
import {
6+
limitObjectDepthToSize,
7+
serializeKeysToEventMessage,
8+
} from '@sentry/utils/object';
9+
import * as md5 from 'md5';
10+
import * as stacktrace from 'stack-trace';
11+
import { parseError, parseStack, prepareFramesForEvent } from './parsers';
512
import { HTTPSTransport, HTTPTransport } from './transports';
613

7-
/** Extension to the Function type. */
8-
interface FunctionExt extends Function {
9-
__SENTRY_CAPTURE__?: boolean;
10-
}
11-
12-
/** Prepares an event so it can be send with raven-js. */
13-
function normalizeEvent(ravenEvent: any): SentryEvent {
14-
const event = ravenEvent;
15-
// tslint:disable-next-line:no-unsafe-any
16-
if (ravenEvent.exception && !ravenEvent.exception.values) {
17-
// tslint:disable-next-line:no-unsafe-any
18-
event.exception = { values: ravenEvent.exception };
19-
}
20-
return event as SentryEvent;
21-
}
22-
2314
/**
2415
* Configuration options for the Sentry Node SDK.
2516
* @see NodeClient for more information.
2617
*/
2718
export interface NodeOptions extends Options {
28-
/**
29-
* Whether unhandled Promise rejections should be captured or not. If true,
30-
* this will install an error handler and prevent the process from crashing.
31-
* Defaults to false.
32-
*/
33-
captureUnhandledRejections?: boolean;
34-
3519
/** Callback that is executed when a fatal global error occurs. */
3620
onFatalError?(error: Error): void;
3721
}
@@ -44,77 +28,58 @@ export class NodeBackend implements Backend {
4428
/**
4529
* @inheritDoc
4630
*/
47-
public install(): boolean {
48-
// We are only called by the client if the SDK is enabled and a valid DSN
49-
// has been configured. If no DSN is present, this indicates a programming
50-
// error.
51-
const dsn = this.options.dsn;
52-
if (!dsn) {
53-
throw new SentryError(
54-
'Invariant exception: install() must not be called when disabled',
55-
);
56-
}
57-
58-
Raven.config(dsn, this.options);
59-
60-
// We need to leave it here for now, as we are skipping `install` call,
61-
// due to integrations migration
62-
// TODO: Remove it once we fully migrate our code
63-
const { onFatalError } = this.options;
64-
if (onFatalError) {
65-
Raven.onFatalError = onFatalError;
66-
}
67-
Raven.installed = true;
68-
69-
// Hook into Raven's breadcrumb mechanism. This allows us to intercept both
70-
// breadcrumbs created internally by Raven and pass them to the Client
71-
// first, before actually capturing them.
72-
Raven.captureBreadcrumb = breadcrumb => {
73-
addBreadcrumb(breadcrumb);
74-
};
75-
76-
// Hook into Raven's internal event sending mechanism. This allows us to
77-
// pass events to the client, before they will be sent back here for
78-
// actual submission.
79-
Raven.send = (event, callback) => {
80-
if (callback && (callback as FunctionExt).__SENTRY_CAPTURE__) {
81-
callback(normalizeEvent(event));
31+
public async eventFromException(exception: any): Promise<SentryEvent> {
32+
let stack: stacktrace.StackFrame[] | undefined;
33+
let ex: any = exception;
34+
35+
if (!isError(exception)) {
36+
if (isPlainObject(exception)) {
37+
// This will allow us to group events based on top-level keys
38+
// which is much better than creating new group when any key/value change
39+
const keys = Object.keys(exception as {}).sort();
40+
const message = `Non-Error exception captured with keys: ${serializeKeysToEventMessage(
41+
keys,
42+
)}`;
43+
44+
// TODO: We also set `event.message` here previously, check if it works without it as well
45+
getDefaultHub().configureScope(scope => {
46+
scope.setExtra(
47+
'__serialized__',
48+
limitObjectDepthToSize(exception as {}),
49+
);
50+
scope.setFingerprint([md5(keys.join(''))]);
51+
});
52+
53+
ex = new Error(message);
8254
} else {
83-
// This "if" needs to be here in order for raven-node to propergate
84-
// internal callbacks like in makeErrorHandler -> captureException
85-
// correctly. Also the setTimeout is a dirty hack because in case of a
86-
// fatal error, raven-node exits the process and our consturct of
87-
// hub -> client doesn't have enough time to send the event.
88-
if (callback) {
89-
setTimeout(() => {
90-
callback(event);
91-
}, 1000);
92-
}
93-
captureEvent(normalizeEvent(event));
55+
// This handles when someone does: `throw "something awesome";`
56+
// We synthesize an Error here so we can extract a (rough) stack trace.
57+
ex = new Error(exception as string);
9458
}
95-
};
9659

97-
return true;
98-
}
60+
stack = stacktrace.get();
61+
}
9962

100-
/**
101-
* @inheritDoc
102-
*/
103-
public async eventFromException(exception: any): Promise<SentryEvent> {
104-
return new Promise<SentryEvent>(resolve => {
105-
(resolve as FunctionExt).__SENTRY_CAPTURE__ = true;
106-
Raven.captureException(exception, resolve);
107-
});
63+
const event: SentryEvent = stack
64+
? await parseError(ex as Error, stack)
65+
: await parseError(ex as Error);
66+
67+
return event;
10868
}
10969

11070
/**
11171
* @inheritDoc
11272
*/
11373
public async eventFromMessage(message: string): Promise<SentryEvent> {
114-
return new Promise<SentryEvent>(resolve => {
115-
(resolve as FunctionExt).__SENTRY_CAPTURE__ = true;
116-
Raven.captureMessage(message, resolve);
117-
});
74+
const stack = stacktrace.get();
75+
const frames = await parseStack(stack);
76+
const event: SentryEvent = {
77+
message,
78+
stacktrace: {
79+
frames: prepareFramesForEvent(frames),
80+
},
81+
};
82+
return event;
11883
}
11984

12085
/**

packages/node/src/declarations.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'lsmod';

0 commit comments

Comments
 (0)