UNPKG

19.3 kBJavaScriptView Raw
1Object.defineProperty(exports, "__esModule", { value: true });
2var tslib_1 = require("tslib");
3/* eslint-disable max-lines */
4var hub_1 = require("@sentry/hub");
5var types_1 = require("@sentry/types");
6var utils_1 = require("@sentry/utils");
7var integration_1 = require("./integration");
8/**
9 * Base implementation for all JavaScript SDK clients.
10 *
11 * Call the constructor with the corresponding backend constructor and options
12 * specific to the client subclass. To access these options later, use
13 * {@link Client.getOptions}. Also, the Backend instance is available via
14 * {@link Client.getBackend}.
15 *
16 * If a Dsn is specified in the options, it will be parsed and stored. Use
17 * {@link Client.getDsn} to retrieve the Dsn at any moment. In case the Dsn is
18 * invalid, the constructor will throw a {@link SentryException}. Note that
19 * without a valid Dsn, the SDK will not send any events to Sentry.
20 *
21 * Before sending an event via the backend, it is passed through
22 * {@link BaseClient._prepareEvent} to add SDK information and scope data
23 * (breadcrumbs and context). To add more custom information, override this
24 * method and extend the resulting prepared event.
25 *
26 * To issue automatically created events (e.g. via instrumentation), use
27 * {@link Client.captureEvent}. It will prepare the event and pass it through
28 * the callback lifecycle. To issue auto-breadcrumbs, use
29 * {@link Client.addBreadcrumb}.
30 *
31 * @example
32 * class NodeClient extends BaseClient<NodeBackend, NodeOptions> {
33 * public constructor(options: NodeOptions) {
34 * super(NodeBackend, options);
35 * }
36 *
37 * // ...
38 * }
39 */
40var BaseClient = /** @class */ (function () {
41 /**
42 * Initializes this client instance.
43 *
44 * @param backendClass A constructor function to create the backend.
45 * @param options Options for the client.
46 */
47 function BaseClient(backendClass, options) {
48 /** Array of used integrations. */
49 this._integrations = {};
50 /** Number of call being processed */
51 this._processing = 0;
52 this._backend = new backendClass(options);
53 this._options = options;
54 if (options.dsn) {
55 this._dsn = new utils_1.Dsn(options.dsn);
56 }
57 }
58 /**
59 * @inheritDoc
60 */
61 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
62 BaseClient.prototype.captureException = function (exception, hint, scope) {
63 var _this = this;
64 var eventId = hint && hint.event_id;
65 this._process(this._getBackend()
66 .eventFromException(exception, hint)
67 .then(function (event) { return _this._captureEvent(event, hint, scope); })
68 .then(function (result) {
69 eventId = result;
70 }));
71 return eventId;
72 };
73 /**
74 * @inheritDoc
75 */
76 BaseClient.prototype.captureMessage = function (message, level, hint, scope) {
77 var _this = this;
78 var eventId = hint && hint.event_id;
79 var promisedEvent = utils_1.isPrimitive(message)
80 ? this._getBackend().eventFromMessage(String(message), level, hint)
81 : this._getBackend().eventFromException(message, hint);
82 this._process(promisedEvent
83 .then(function (event) { return _this._captureEvent(event, hint, scope); })
84 .then(function (result) {
85 eventId = result;
86 }));
87 return eventId;
88 };
89 /**
90 * @inheritDoc
91 */
92 BaseClient.prototype.captureEvent = function (event, hint, scope) {
93 var eventId = hint && hint.event_id;
94 this._process(this._captureEvent(event, hint, scope).then(function (result) {
95 eventId = result;
96 }));
97 return eventId;
98 };
99 /**
100 * @inheritDoc
101 */
102 BaseClient.prototype.captureSession = function (session) {
103 if (!(typeof session.release === 'string')) {
104 utils_1.logger.warn('Discarded session because of missing or non-string release');
105 }
106 else {
107 this._sendSession(session);
108 // After sending, we set init false to indicate it's not the first occurrence
109 session.update({ init: false });
110 }
111 };
112 /**
113 * @inheritDoc
114 */
115 BaseClient.prototype.getDsn = function () {
116 return this._dsn;
117 };
118 /**
119 * @inheritDoc
120 */
121 BaseClient.prototype.getOptions = function () {
122 return this._options;
123 };
124 /**
125 * @inheritDoc
126 */
127 BaseClient.prototype.flush = function (timeout) {
128 var _this = this;
129 return this._isClientProcessing(timeout).then(function (ready) {
130 return _this._getBackend()
131 .getTransport()
132 .close(timeout)
133 .then(function (transportFlushed) { return ready && transportFlushed; });
134 });
135 };
136 /**
137 * @inheritDoc
138 */
139 BaseClient.prototype.close = function (timeout) {
140 var _this = this;
141 return this.flush(timeout).then(function (result) {
142 _this.getOptions().enabled = false;
143 return result;
144 });
145 };
146 /**
147 * Sets up the integrations
148 */
149 BaseClient.prototype.setupIntegrations = function () {
150 if (this._isEnabled()) {
151 this._integrations = integration_1.setupIntegrations(this._options);
152 }
153 };
154 /**
155 * @inheritDoc
156 */
157 BaseClient.prototype.getIntegration = function (integration) {
158 try {
159 return this._integrations[integration.id] || null;
160 }
161 catch (_oO) {
162 utils_1.logger.warn("Cannot retrieve integration " + integration.id + " from the current Client");
163 return null;
164 }
165 };
166 /** Updates existing session based on the provided event */
167 BaseClient.prototype._updateSessionFromEvent = function (session, event) {
168 var e_1, _a;
169 var crashed = false;
170 var errored = false;
171 var userAgent;
172 var exceptions = event.exception && event.exception.values;
173 if (exceptions) {
174 errored = true;
175 try {
176 for (var exceptions_1 = tslib_1.__values(exceptions), exceptions_1_1 = exceptions_1.next(); !exceptions_1_1.done; exceptions_1_1 = exceptions_1.next()) {
177 var ex = exceptions_1_1.value;
178 var mechanism = ex.mechanism;
179 if (mechanism && mechanism.handled === false) {
180 crashed = true;
181 break;
182 }
183 }
184 }
185 catch (e_1_1) { e_1 = { error: e_1_1 }; }
186 finally {
187 try {
188 if (exceptions_1_1 && !exceptions_1_1.done && (_a = exceptions_1.return)) _a.call(exceptions_1);
189 }
190 finally { if (e_1) throw e_1.error; }
191 }
192 }
193 var user = event.user;
194 if (!session.userAgent) {
195 var headers = event.request ? event.request.headers : {};
196 for (var key in headers) {
197 if (key.toLowerCase() === 'user-agent') {
198 userAgent = headers[key];
199 break;
200 }
201 }
202 }
203 session.update(tslib_1.__assign(tslib_1.__assign({}, (crashed && { status: types_1.SessionStatus.Crashed })), { user: user,
204 userAgent: userAgent, errors: session.errors + Number(errored || crashed) }));
205 this.captureSession(session);
206 };
207 /** Deliver captured session to Sentry */
208 BaseClient.prototype._sendSession = function (session) {
209 this._getBackend().sendSession(session);
210 };
211 /** Waits for the client to be done with processing. */
212 BaseClient.prototype._isClientProcessing = function (timeout) {
213 var _this = this;
214 return new utils_1.SyncPromise(function (resolve) {
215 var ticked = 0;
216 var tick = 1;
217 var interval = setInterval(function () {
218 if (_this._processing == 0) {
219 clearInterval(interval);
220 resolve(true);
221 }
222 else {
223 ticked += tick;
224 if (timeout && ticked >= timeout) {
225 clearInterval(interval);
226 resolve(false);
227 }
228 }
229 }, tick);
230 });
231 };
232 /** Returns the current backend. */
233 BaseClient.prototype._getBackend = function () {
234 return this._backend;
235 };
236 /** Determines whether this SDK is enabled and a valid Dsn is present. */
237 BaseClient.prototype._isEnabled = function () {
238 return this.getOptions().enabled !== false && this._dsn !== undefined;
239 };
240 /**
241 * Adds common information to events.
242 *
243 * The information includes release and environment from `options`,
244 * breadcrumbs and context (extra, tags and user) from the scope.
245 *
246 * Information that is already present in the event is never overwritten. For
247 * nested objects, such as the context, keys are merged.
248 *
249 * @param event The original event.
250 * @param hint May contain additional information about the original exception.
251 * @param scope A scope containing event metadata.
252 * @returns A new event with more information.
253 */
254 BaseClient.prototype._prepareEvent = function (event, scope, hint) {
255 var _this = this;
256 var _a = this.getOptions().normalizeDepth, normalizeDepth = _a === void 0 ? 3 : _a;
257 var prepared = tslib_1.__assign(tslib_1.__assign({}, event), { event_id: event.event_id || (hint && hint.event_id ? hint.event_id : utils_1.uuid4()), timestamp: event.timestamp || utils_1.dateTimestampInSeconds() });
258 this._applyClientOptions(prepared);
259 this._applyIntegrationsMetadata(prepared);
260 // If we have scope given to us, use it as the base for further modifications.
261 // This allows us to prevent unnecessary copying of data if `captureContext` is not provided.
262 var finalScope = scope;
263 if (hint && hint.captureContext) {
264 finalScope = hub_1.Scope.clone(finalScope).update(hint.captureContext);
265 }
266 // We prepare the result here with a resolved Event.
267 var result = utils_1.SyncPromise.resolve(prepared);
268 // This should be the last thing called, since we want that
269 // {@link Hub.addEventProcessor} gets the finished prepared event.
270 if (finalScope) {
271 // In case we have a hub we reassign it.
272 result = finalScope.applyToEvent(prepared, hint);
273 }
274 return result.then(function (evt) {
275 if (typeof normalizeDepth === 'number' && normalizeDepth > 0) {
276 return _this._normalizeEvent(evt, normalizeDepth);
277 }
278 return evt;
279 });
280 };
281 /**
282 * Applies `normalize` function on necessary `Event` attributes to make them safe for serialization.
283 * Normalized keys:
284 * - `breadcrumbs.data`
285 * - `user`
286 * - `contexts`
287 * - `extra`
288 * @param event Event
289 * @returns Normalized event
290 */
291 BaseClient.prototype._normalizeEvent = function (event, depth) {
292 if (!event) {
293 return null;
294 }
295 var normalized = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, event), (event.breadcrumbs && {
296 breadcrumbs: event.breadcrumbs.map(function (b) { return (tslib_1.__assign(tslib_1.__assign({}, b), (b.data && {
297 data: utils_1.normalize(b.data, depth),
298 }))); }),
299 })), (event.user && {
300 user: utils_1.normalize(event.user, depth),
301 })), (event.contexts && {
302 contexts: utils_1.normalize(event.contexts, depth),
303 })), (event.extra && {
304 extra: utils_1.normalize(event.extra, depth),
305 }));
306 // event.contexts.trace stores information about a Transaction. Similarly,
307 // event.spans[] stores information about child Spans. Given that a
308 // Transaction is conceptually a Span, normalization should apply to both
309 // Transactions and Spans consistently.
310 // For now the decision is to skip normalization of Transactions and Spans,
311 // so this block overwrites the normalized event to add back the original
312 // Transaction information prior to normalization.
313 if (event.contexts && event.contexts.trace) {
314 // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
315 normalized.contexts.trace = event.contexts.trace;
316 }
317 return normalized;
318 };
319 /**
320 * Enhances event using the client configuration.
321 * It takes care of all "static" values like environment, release and `dist`,
322 * as well as truncating overly long values.
323 * @param event event instance to be enhanced
324 */
325 BaseClient.prototype._applyClientOptions = function (event) {
326 var options = this.getOptions();
327 var environment = options.environment, release = options.release, dist = options.dist, _a = options.maxValueLength, maxValueLength = _a === void 0 ? 250 : _a;
328 if (!('environment' in event)) {
329 event.environment = 'environment' in options ? environment : 'production';
330 }
331 if (event.release === undefined && release !== undefined) {
332 event.release = release;
333 }
334 if (event.dist === undefined && dist !== undefined) {
335 event.dist = dist;
336 }
337 if (event.message) {
338 event.message = utils_1.truncate(event.message, maxValueLength);
339 }
340 var exception = event.exception && event.exception.values && event.exception.values[0];
341 if (exception && exception.value) {
342 exception.value = utils_1.truncate(exception.value, maxValueLength);
343 }
344 var request = event.request;
345 if (request && request.url) {
346 request.url = utils_1.truncate(request.url, maxValueLength);
347 }
348 };
349 /**
350 * This function adds all used integrations to the SDK info in the event.
351 * @param event The event that will be filled with all integrations.
352 */
353 BaseClient.prototype._applyIntegrationsMetadata = function (event) {
354 var integrationsArray = Object.keys(this._integrations);
355 if (integrationsArray.length > 0) {
356 event.sdk = event.sdk || {};
357 event.sdk.integrations = tslib_1.__spread((event.sdk.integrations || []), integrationsArray);
358 }
359 };
360 /**
361 * Tells the backend to send this event
362 * @param event The Sentry event to send
363 */
364 BaseClient.prototype._sendEvent = function (event) {
365 this._getBackend().sendEvent(event);
366 };
367 /**
368 * Processes the event and logs an error in case of rejection
369 * @param event
370 * @param hint
371 * @param scope
372 */
373 BaseClient.prototype._captureEvent = function (event, hint, scope) {
374 return this._processEvent(event, hint, scope).then(function (finalEvent) {
375 return finalEvent.event_id;
376 }, function (reason) {
377 utils_1.logger.error(reason);
378 return undefined;
379 });
380 };
381 /**
382 * Processes an event (either error or message) and sends it to Sentry.
383 *
384 * This also adds breadcrumbs and context information to the event. However,
385 * platform specific meta data (such as the User's IP address) must be added
386 * by the SDK implementor.
387 *
388 *
389 * @param event The event to send to Sentry.
390 * @param hint May contain additional information about the original exception.
391 * @param scope A scope containing event metadata.
392 * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send.
393 */
394 BaseClient.prototype._processEvent = function (event, hint, scope) {
395 var _this = this;
396 // eslint-disable-next-line @typescript-eslint/unbound-method
397 var _a = this.getOptions(), beforeSend = _a.beforeSend, sampleRate = _a.sampleRate;
398 if (!this._isEnabled()) {
399 return utils_1.SyncPromise.reject(new utils_1.SentryError('SDK not enabled, will not send event.'));
400 }
401 var isTransaction = event.type === 'transaction';
402 // 1.0 === 100% events are sent
403 // 0.0 === 0% events are sent
404 // Sampling for transaction happens somewhere else
405 if (!isTransaction && typeof sampleRate === 'number' && Math.random() > sampleRate) {
406 return utils_1.SyncPromise.reject(new utils_1.SentryError("Discarding event because it's not included in the random sample (sampling rate = " + sampleRate + ")"));
407 }
408 return this._prepareEvent(event, scope, hint)
409 .then(function (prepared) {
410 if (prepared === null) {
411 throw new utils_1.SentryError('An event processor returned null, will not send event.');
412 }
413 var isInternalException = hint && hint.data && hint.data.__sentry__ === true;
414 if (isInternalException || isTransaction || !beforeSend) {
415 return prepared;
416 }
417 var beforeSendResult = beforeSend(prepared, hint);
418 if (typeof beforeSendResult === 'undefined') {
419 throw new utils_1.SentryError('`beforeSend` method has to return `null` or a valid event.');
420 }
421 else if (utils_1.isThenable(beforeSendResult)) {
422 return beforeSendResult.then(function (event) { return event; }, function (e) {
423 throw new utils_1.SentryError("beforeSend rejected with " + e);
424 });
425 }
426 return beforeSendResult;
427 })
428 .then(function (processedEvent) {
429 if (processedEvent === null) {
430 throw new utils_1.SentryError('`beforeSend` returned `null`, will not send event.');
431 }
432 var session = scope && scope.getSession && scope.getSession();
433 if (!isTransaction && session) {
434 _this._updateSessionFromEvent(session, processedEvent);
435 }
436 _this._sendEvent(processedEvent);
437 return processedEvent;
438 })
439 .then(null, function (reason) {
440 if (reason instanceof utils_1.SentryError) {
441 throw reason;
442 }
443 _this.captureException(reason, {
444 data: {
445 __sentry__: true,
446 },
447 originalException: reason,
448 });
449 throw new utils_1.SentryError("Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: " + reason);
450 });
451 };
452 /**
453 * Occupies the client with processing and event
454 */
455 BaseClient.prototype._process = function (promise) {
456 var _this = this;
457 this._processing += 1;
458 void promise.then(function (value) {
459 _this._processing -= 1;
460 return value;
461 }, function (reason) {
462 _this._processing -= 1;
463 return reason;
464 });
465 };
466 return BaseClient;
467}());
468exports.BaseClient = BaseClient;
469//# sourceMappingURL=baseclient.js.map
\No newline at end of file