sentry-vir 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/event-context/event-context.d.ts +6 -0
- package/dist/event-context/extra-context.error.d.ts +2 -1
- package/dist/event-context/extra-context.error.js +8 -1
- package/dist/event-context/extra-event-context.d.ts +16 -0
- package/dist/event-context/extra-event-context.js +23 -0
- package/dist/init-sentry/base-sentry-init.js +2 -0
- package/dist/logging/handle-error.js +28 -9
- package/dist/logging/send-log.d.ts +21 -2
- package/dist/logging/send-log.js +71 -10
- package/dist/processing/handle-sentry-send.js +12 -3
- package/dist/processing/throttling.d.ts +61 -2
- package/dist/processing/throttling.js +76 -23
- package/package.json +1 -1
|
@@ -17,6 +17,12 @@ export type EventContextAndTags = PartialWithUndefined<{
|
|
|
17
17
|
context: EventExtraContext;
|
|
18
18
|
tags: EventTags;
|
|
19
19
|
attachments: ReadonlyArray<Attachment>;
|
|
20
|
+
/**
|
|
21
|
+
* Per-event throttle threshold override. When set, throttling for this event uses the minimum
|
|
22
|
+
* of this value and the globally-configured `throttleThreshold`, allowing individual log calls
|
|
23
|
+
* to be throttled more aggressively than the global default.
|
|
24
|
+
*/
|
|
25
|
+
throttleThreshold: number;
|
|
20
26
|
}>;
|
|
21
27
|
/** Function that generates extra event context. */
|
|
22
28
|
export type EventExtraContextCreator = () => EventExtraContext;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Attachment } from '@sentry/core';
|
|
2
2
|
import { type EventContextAndTags, type EventExtraContext, type EventTags } from './event-context.js';
|
|
3
|
-
import { extraEventAttachmentsSymbol, extraEventContextSymbol, extraEventTagsSymbol } from './extra-event-context.js';
|
|
3
|
+
import { extraEventAttachmentsSymbol, extraEventContextSymbol, extraEventTagsSymbol, extraEventThrottleThresholdSymbol } from './extra-event-context.js';
|
|
4
4
|
/**
|
|
5
5
|
* Constructs an error with extra event context attached to it in the same way that
|
|
6
6
|
* throwWithExtraContext attaches data.
|
|
@@ -16,6 +16,7 @@ export declare class ExtraContextError extends Error {
|
|
|
16
16
|
readonly [extraEventContextSymbol]: EventExtraContext | undefined;
|
|
17
17
|
readonly [extraEventTagsSymbol]: EventTags | undefined;
|
|
18
18
|
readonly [extraEventAttachmentsSymbol]: ReadonlyArray<Attachment> | undefined;
|
|
19
|
+
readonly [extraEventThrottleThresholdSymbol]: number | undefined;
|
|
19
20
|
constructor(message: string, extraData: EventContextAndTags);
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureError } from '@augment-vir/common';
|
|
2
|
-
import { extraEventAttachmentsSymbol, extraEventContextSymbol, extraEventTagsSymbol, } from './extra-event-context.js';
|
|
2
|
+
import { extraEventAttachmentsSymbol, extraEventContextSymbol, extraEventTagsSymbol, extraEventThrottleThresholdSymbol, } from './extra-event-context.js';
|
|
3
3
|
/**
|
|
4
4
|
* Constructs an error with extra event context attached to it in the same way that
|
|
5
5
|
* throwWithExtraContext attaches data.
|
|
@@ -15,6 +15,7 @@ export class ExtraContextError extends Error {
|
|
|
15
15
|
[extraEventContextSymbol];
|
|
16
16
|
[extraEventTagsSymbol];
|
|
17
17
|
[extraEventAttachmentsSymbol];
|
|
18
|
+
[extraEventThrottleThresholdSymbol];
|
|
18
19
|
constructor(message, extraData) {
|
|
19
20
|
super(message);
|
|
20
21
|
if (extraData.context) {
|
|
@@ -26,6 +27,9 @@ export class ExtraContextError extends Error {
|
|
|
26
27
|
if (extraData.attachments) {
|
|
27
28
|
this[extraEventAttachmentsSymbol] = extraData.attachments;
|
|
28
29
|
}
|
|
30
|
+
if (extraData.throttleThreshold != undefined) {
|
|
31
|
+
this[extraEventThrottleThresholdSymbol] = extraData.throttleThreshold;
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
@@ -43,5 +47,8 @@ export function throwWithExtraContext(originalError, extraData) {
|
|
|
43
47
|
if (extraData.attachments) {
|
|
44
48
|
error[extraEventAttachmentsSymbol] = extraData.attachments;
|
|
45
49
|
}
|
|
50
|
+
if (extraData.throttleThreshold != undefined) {
|
|
51
|
+
error[extraEventThrottleThresholdSymbol] = extraData.throttleThreshold;
|
|
52
|
+
}
|
|
46
53
|
throw error;
|
|
47
54
|
}
|
|
@@ -15,6 +15,11 @@ export declare const extraEventTagsSymbol: unique symbol;
|
|
|
15
15
|
* for attaching file data to thrown errors.
|
|
16
16
|
*/
|
|
17
17
|
export declare const extraEventAttachmentsSymbol: unique symbol;
|
|
18
|
+
/**
|
|
19
|
+
* Symbol used to attach a per-event throttle threshold override. When present, throttling uses the
|
|
20
|
+
* minimum of this value and the globally-configured `throttleThreshold`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const extraEventThrottleThresholdSymbol: unique symbol;
|
|
18
23
|
/** Simply describes an object that has extra event context. */
|
|
19
24
|
export type HasExtraContext = {
|
|
20
25
|
[extraEventContextSymbol]: EventExtraContext;
|
|
@@ -27,12 +32,18 @@ export type HasExtraTags = {
|
|
|
27
32
|
export type HasExtraAttachments = {
|
|
28
33
|
[extraEventAttachmentsSymbol]: ReadonlyArray<Attachment>;
|
|
29
34
|
};
|
|
35
|
+
/** Simply describes an object that has a per-event throttle threshold override. */
|
|
36
|
+
export type HasExtraThrottleThreshold = {
|
|
37
|
+
[extraEventThrottleThresholdSymbol]: number;
|
|
38
|
+
};
|
|
30
39
|
/** Type guard for whether any given input has extra event context. */
|
|
31
40
|
export declare function hasExtraEventContext(input: unknown): input is HasExtraContext;
|
|
32
41
|
/** Type guard for whether any given input has extra event tags. */
|
|
33
42
|
export declare function hasExtraEventTags(input: unknown): input is HasExtraTags;
|
|
34
43
|
/** Type guard for whether any given input has extra event attachments. */
|
|
35
44
|
export declare function hasExtraEventAttachments(input: unknown): input is HasExtraAttachments;
|
|
45
|
+
/** Type guard for whether any given input carries a per-event throttle threshold override. */
|
|
46
|
+
export declare function hasExtraEventThrottleThreshold(input: unknown): input is HasExtraThrottleThreshold;
|
|
36
47
|
/**
|
|
37
48
|
* Checks if extra event context has been injected into the input via extraEventContextSymbol and,
|
|
38
49
|
* if so, extracts it.
|
|
@@ -63,3 +74,8 @@ export declare function extractExtraEventTags(event: EventHint | Event): EventTa
|
|
|
63
74
|
* there are no extra event attachments.
|
|
64
75
|
*/
|
|
65
76
|
export declare function extractExtraEventAttachments(event: EventHint | Event): ReadonlyArray<Attachment> | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Tries to extract a per-event throttle threshold via extraEventThrottleThresholdSymbol from the
|
|
79
|
+
* input itself or its originalException. Returns undefined if none is set.
|
|
80
|
+
*/
|
|
81
|
+
export declare function extractExtraEventThrottleThreshold(event: EventHint | Event): number | undefined;
|
|
@@ -14,6 +14,11 @@ export const extraEventTagsSymbol = Symbol('extra-event-tags');
|
|
|
14
14
|
* for attaching file data to thrown errors.
|
|
15
15
|
*/
|
|
16
16
|
export const extraEventAttachmentsSymbol = Symbol('extra-event-attachments');
|
|
17
|
+
/**
|
|
18
|
+
* Symbol used to attach a per-event throttle threshold override. When present, throttling uses the
|
|
19
|
+
* minimum of this value and the globally-configured `throttleThreshold`.
|
|
20
|
+
*/
|
|
21
|
+
export const extraEventThrottleThresholdSymbol = Symbol('extra-event-throttle-threshold');
|
|
17
22
|
/** Type guard for whether any given input has extra event context. */
|
|
18
23
|
export function hasExtraEventContext(input) {
|
|
19
24
|
return check.hasKey(input, extraEventContextSymbol) && !!input[extraEventContextSymbol];
|
|
@@ -26,6 +31,11 @@ export function hasExtraEventTags(input) {
|
|
|
26
31
|
export function hasExtraEventAttachments(input) {
|
|
27
32
|
return check.hasKey(input, extraEventAttachmentsSymbol) && !!input[extraEventAttachmentsSymbol];
|
|
28
33
|
}
|
|
34
|
+
/** Type guard for whether any given input carries a per-event throttle threshold override. */
|
|
35
|
+
export function hasExtraEventThrottleThreshold(input) {
|
|
36
|
+
return (check.hasKey(input, extraEventThrottleThresholdSymbol) &&
|
|
37
|
+
check.isNumber(input[extraEventThrottleThresholdSymbol]));
|
|
38
|
+
}
|
|
29
39
|
/**
|
|
30
40
|
* Checks if extra event context has been injected into the input via extraEventContextSymbol and,
|
|
31
41
|
* if so, extracts it.
|
|
@@ -122,3 +132,16 @@ export function extractExtraEventAttachments(event) {
|
|
|
122
132
|
return undefined;
|
|
123
133
|
}
|
|
124
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Tries to extract a per-event throttle threshold via extraEventThrottleThresholdSymbol from the
|
|
137
|
+
* input itself or its originalException. Returns undefined if none is set.
|
|
138
|
+
*/
|
|
139
|
+
export function extractExtraEventThrottleThreshold(event) {
|
|
140
|
+
const fromRoot = hasExtraEventThrottleThreshold(event)
|
|
141
|
+
? event[extraEventThrottleThresholdSymbol]
|
|
142
|
+
: undefined;
|
|
143
|
+
const fromException = 'originalException' in event && hasExtraEventThrottleThreshold(event.originalException)
|
|
144
|
+
? event.originalException[extraEventThrottleThresholdSymbol]
|
|
145
|
+
: undefined;
|
|
146
|
+
return fromRoot ?? fromException;
|
|
147
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setSentryClientForLogging } from '../logging/sentry-client-for-logging.js';
|
|
2
2
|
import { processSentryEvent } from '../processing/event-processor.js';
|
|
3
|
+
import { setActiveThrottleOptions } from '../processing/throttling.js';
|
|
3
4
|
import { createSentryConfig } from './sentry-config.js';
|
|
4
5
|
/**
|
|
5
6
|
* Base Sentry init. Requires the Sentry module to already have been imported. Setup a sentry client
|
|
@@ -25,6 +26,7 @@ export async function baseInitSentry({ dsn, releaseEnv, releaseName, sentryConfi
|
|
|
25
26
|
});
|
|
26
27
|
sentryDep.init(finalSentryConfig);
|
|
27
28
|
sentryDep.addEventProcessor((event, hint) => processSentryEvent(event, hint, createUniversalContext));
|
|
29
|
+
setActiveThrottleOptions(throttleOptions);
|
|
28
30
|
void setSentryClientForLogging(sentryDep);
|
|
29
31
|
return sentryDep;
|
|
30
32
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { extractErrorMessage } from '@augment-vir/common';
|
|
2
2
|
import { convertEventDetailsToSentryContext, } from '../event-context/event-context.js';
|
|
3
3
|
import { EventSeverityEnum } from '../event-context/event-severity.js';
|
|
4
|
-
import { extractExtraAttachmentsFromSymbol } from '../event-context/extra-event-context.js';
|
|
4
|
+
import { extractExtraAttachmentsFromSymbol, extractExtraEventThrottleThreshold, } from '../event-context/extra-event-context.js';
|
|
5
5
|
import { LoggingState, logToConsoleWithoutSentry } from '../processing/log-to-console.js';
|
|
6
|
+
import { skipBeforeSendThrottleContextKey } from '../processing/throttling.js';
|
|
6
7
|
import { addPrematureEvent } from './premature-events.js';
|
|
8
|
+
import { checkActiveThrottle } from './send-log.js';
|
|
7
9
|
import { sentryClientForLogging } from './sentry-client-for-logging.js';
|
|
8
10
|
/** Record an error to Sentry without throwing it. */
|
|
9
11
|
export function handleError(error, eventOptions) {
|
|
@@ -30,6 +32,22 @@ function internalHandleError(error, eventOptions, options) {
|
|
|
30
32
|
]);
|
|
31
33
|
return undefined;
|
|
32
34
|
}
|
|
35
|
+
const perCallThresholdCandidates = [
|
|
36
|
+
extractExtraEventThrottleThreshold({
|
|
37
|
+
originalException: error,
|
|
38
|
+
}),
|
|
39
|
+
eventOptions?.throttleThreshold,
|
|
40
|
+
].filter((value) => value != undefined);
|
|
41
|
+
const perCallThrottleThreshold = perCallThresholdCandidates.length
|
|
42
|
+
? Math.min(...perCallThresholdCandidates)
|
|
43
|
+
: undefined;
|
|
44
|
+
if (checkActiveThrottle({
|
|
45
|
+
message: extractErrorMessage(error),
|
|
46
|
+
}, {
|
|
47
|
+
originalException: error,
|
|
48
|
+
}, perCallThrottleThreshold)) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
33
51
|
const scopeContext = convertEventDetailsToSentryContext({
|
|
34
52
|
extraContext: eventOptions?.context,
|
|
35
53
|
tags: eventOptions?.tags,
|
|
@@ -40,14 +58,15 @@ function internalHandleError(error, eventOptions, options) {
|
|
|
40
58
|
...(eventOptions?.attachments || []),
|
|
41
59
|
...(extractExtraAttachmentsFromSymbol(error) || []),
|
|
42
60
|
];
|
|
43
|
-
const eventId =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
})
|
|
50
|
-
|
|
61
|
+
const eventId = client.withScope((scope) => {
|
|
62
|
+
scope.setContext(skipBeforeSendThrottleContextKey, {
|
|
63
|
+
skipThrottle: true,
|
|
64
|
+
});
|
|
65
|
+
allAttachments.forEach((attachment) => {
|
|
66
|
+
scope.addAttachment(attachment);
|
|
67
|
+
});
|
|
68
|
+
return client.captureException(error, scopeContext);
|
|
69
|
+
});
|
|
51
70
|
return eventId;
|
|
52
71
|
}
|
|
53
72
|
catch (caught) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type ErrorEvent, type EventHint, type Event as SentryEvent, type TransactionEvent } from '@sentry/core';
|
|
2
3
|
import { type ContextOptions, type EventContextAndTags, type EventDetails } from '../event-context/event-context.js';
|
|
4
|
+
import { type ThrottleOptions } from '../processing/throttling.js';
|
|
3
5
|
/** A Sentry event object without the fields that are set by the logging context. */
|
|
4
6
|
export type SendLogEvent = Omit<SentryEvent, 'extra' | 'level'>;
|
|
5
7
|
/** All accepted input types for `sendLog`. Strings, Error objects, and raw Sentry events. */
|
|
@@ -13,5 +15,22 @@ export declare const sendLog: {
|
|
|
13
15
|
/** Sends an even to Sentry with warning severity. */
|
|
14
16
|
readonly warning: (info: Parameters<typeof sendLogToSentry>[0], eventOptions?: EventContextAndTags) => string | undefined;
|
|
15
17
|
};
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Runs the throttle decision and, if a state transition just occurred (`'started'` or `'ended'`)
|
|
20
|
+
* and `disableThrottleLog` is not set, emits the corresponding `Throttling started: ...` /
|
|
21
|
+
* `Throttling ended after suppressing N events: ...` warning via `sendLog.warning`. Returns whether
|
|
22
|
+
* the event should be throttled (i.e. dropped).
|
|
23
|
+
*
|
|
24
|
+
* @category Internal
|
|
25
|
+
*/
|
|
26
|
+
export declare function throttleEventWithLogging(event: Pick<TransactionEvent | ErrorEvent, 'message'>, hint: Readonly<Pick<EventHint, 'originalException'>> | undefined, options: Readonly<PartialWithUndefined<ThrottleOptions>>): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Synchronous pre-capture throttle check used by `sendLog` and `handleError`. Returns `true` when
|
|
29
|
+
* the event should be dropped. Returns `false` (i.e. "send it") when no active throttle options
|
|
30
|
+
* have been registered yet, so events sent before Sentry init aren't accidentally throttled.
|
|
31
|
+
*
|
|
32
|
+
* @category Internal
|
|
33
|
+
*/
|
|
34
|
+
export declare function checkActiveThrottle(event: Pick<TransactionEvent | ErrorEvent, 'message'>, hint: Readonly<Pick<EventHint, 'originalException'>> | undefined, perCallThreshold: number | undefined): boolean;
|
|
35
|
+
declare function sendLogToSentry(logInfo: SendLogInfo, eventDetails: EventDetails, options: ContextOptions, perCallThrottleThreshold: number | undefined): string | undefined;
|
|
17
36
|
export {};
|
package/dist/logging/send-log.js
CHANGED
|
@@ -4,6 +4,7 @@ import { convertEventDetailsToSentryContext, } from '../event-context/event-cont
|
|
|
4
4
|
import { EventSeverityEnum } from '../event-context/event-severity.js';
|
|
5
5
|
import { extractOriginalMessage } from '../processing/event-processor.js';
|
|
6
6
|
import { LoggingState, logToConsoleWithoutSentry } from '../processing/log-to-console.js';
|
|
7
|
+
import { combineThrottleThreshold, defaultThrottleOptions, getActiveThrottleOptions, shouldThrottleEvent, skipBeforeSendThrottleContextKey, } from '../processing/throttling.js';
|
|
7
8
|
import { addPrematureEvent } from './premature-events.js';
|
|
8
9
|
import { sentryClientForLogging } from './sentry-client-for-logging.js';
|
|
9
10
|
/** Send non-error events to Sentry. */
|
|
@@ -15,6 +16,56 @@ export const sendLog = {
|
|
|
15
16
|
/** Sends an even to Sentry with warning severity. */
|
|
16
17
|
[EventSeverityEnum.Warning]: wrapLogWithSeverity(EventSeverityEnum.Warning),
|
|
17
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Runs the throttle decision and, if a state transition just occurred (`'started'` or `'ended'`)
|
|
21
|
+
* and `disableThrottleLog` is not set, emits the corresponding `Throttling started: ...` /
|
|
22
|
+
* `Throttling ended after suppressing N events: ...` warning via `sendLog.warning`. Returns whether
|
|
23
|
+
* the event should be throttled (i.e. dropped).
|
|
24
|
+
*
|
|
25
|
+
* @category Internal
|
|
26
|
+
*/
|
|
27
|
+
export function throttleEventWithLogging(event, hint, options) {
|
|
28
|
+
const result = shouldThrottleEvent(event, hint, options);
|
|
29
|
+
const disableLog = options.disableThrottleLog ?? defaultThrottleOptions.disableThrottleLog;
|
|
30
|
+
if (!disableLog && result.errorKey != undefined) {
|
|
31
|
+
if (result.transition.kind === 'started') {
|
|
32
|
+
sendLog.warning(`Throttling started: ${result.errorKey}`, {
|
|
33
|
+
context: {
|
|
34
|
+
suppressedErrorKey: result.errorKey,
|
|
35
|
+
},
|
|
36
|
+
tags: {
|
|
37
|
+
suppressedErrorKey: result.errorKey,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else if (result.transition.kind === 'ended') {
|
|
42
|
+
sendLog.warning(`Throttling ended after suppressing ${result.transition.suppressedCount} events: ${result.errorKey}`, {
|
|
43
|
+
context: {
|
|
44
|
+
suppressedErrorKey: result.errorKey,
|
|
45
|
+
suppressedCount: result.transition.suppressedCount,
|
|
46
|
+
},
|
|
47
|
+
tags: {
|
|
48
|
+
suppressedErrorKey: result.errorKey,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result.shouldThrottle;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Synchronous pre-capture throttle check used by `sendLog` and `handleError`. Returns `true` when
|
|
57
|
+
* the event should be dropped. Returns `false` (i.e. "send it") when no active throttle options
|
|
58
|
+
* have been registered yet, so events sent before Sentry init aren't accidentally throttled.
|
|
59
|
+
*
|
|
60
|
+
* @category Internal
|
|
61
|
+
*/
|
|
62
|
+
export function checkActiveThrottle(event, hint, perCallThreshold) {
|
|
63
|
+
const active = getActiveThrottleOptions();
|
|
64
|
+
if (!active) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return throttleEventWithLogging(event, hint, combineThrottleThreshold(active, perCallThreshold));
|
|
68
|
+
}
|
|
18
69
|
function wrapLogWithSeverity(severity) {
|
|
19
70
|
return (info, eventOptions) => {
|
|
20
71
|
return sendLogToSentry(info, {
|
|
@@ -24,10 +75,10 @@ function wrapLogWithSeverity(severity) {
|
|
|
24
75
|
severity,
|
|
25
76
|
}, {
|
|
26
77
|
wasSentPrematurely: false,
|
|
27
|
-
});
|
|
78
|
+
}, eventOptions?.throttleThreshold);
|
|
28
79
|
};
|
|
29
80
|
}
|
|
30
|
-
function sendLogToSentry(logInfo, eventDetails, options) {
|
|
81
|
+
function sendLogToSentry(logInfo, eventDetails, options, perCallThrottleThreshold) {
|
|
31
82
|
try {
|
|
32
83
|
/**
|
|
33
84
|
* `Error.message` is not enumerable, so spreading an `Error` into `captureEvent` would lose
|
|
@@ -51,9 +102,18 @@ function sendLogToSentry(logInfo, eventDetails, options) {
|
|
|
51
102
|
{
|
|
52
103
|
wasSentPrematurely: true,
|
|
53
104
|
},
|
|
105
|
+
perCallThrottleThreshold,
|
|
54
106
|
]);
|
|
55
107
|
return undefined;
|
|
56
108
|
}
|
|
109
|
+
const throttleMessage = check.isString(resolvedLogInfo)
|
|
110
|
+
? resolvedLogInfo
|
|
111
|
+
: extractOriginalMessage(resolvedLogInfo, undefined);
|
|
112
|
+
if (checkActiveThrottle({
|
|
113
|
+
message: throttleMessage,
|
|
114
|
+
}, undefined, perCallThrottleThreshold)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
57
117
|
const scopeContext = convertEventDetailsToSentryContext(eventDetails, options);
|
|
58
118
|
const client = sentryClientForLogging;
|
|
59
119
|
function captureWithClient() {
|
|
@@ -64,14 +124,15 @@ function sendLogToSentry(logInfo, eventDetails, options) {
|
|
|
64
124
|
...scopeContext,
|
|
65
125
|
});
|
|
66
126
|
}
|
|
67
|
-
const eventId =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
})
|
|
74
|
-
|
|
127
|
+
const eventId = client.withScope((scope) => {
|
|
128
|
+
scope.setContext(skipBeforeSendThrottleContextKey, {
|
|
129
|
+
skipThrottle: true,
|
|
130
|
+
});
|
|
131
|
+
eventDetails.attachments?.forEach((attachment) => {
|
|
132
|
+
scope.addAttachment(attachment);
|
|
133
|
+
});
|
|
134
|
+
return captureWithClient();
|
|
135
|
+
});
|
|
75
136
|
return eventId;
|
|
76
137
|
}
|
|
77
138
|
catch (caught) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { extractExtraEventThrottleThreshold } from '../event-context/extra-event-context.js';
|
|
2
|
+
import { throttleEventWithLogging } from '../logging/send-log.js';
|
|
1
3
|
import { LoggingState, logToConsoleFromSentry } from './log-to-console.js';
|
|
2
|
-
import {
|
|
4
|
+
import { combineThrottleThreshold, skipBeforeSendThrottleContextKey, } from './throttling.js';
|
|
3
5
|
/** Creates a handler for Sentry events based on the given env. */
|
|
4
6
|
export function createSentryHandler({ isDev, isSilent, throttleOptions, }) {
|
|
5
7
|
/** The actual function that gets called when handling Sentry events. */
|
|
@@ -8,8 +10,15 @@ export function createSentryHandler({ isDev, isSilent, throttleOptions, }) {
|
|
|
8
10
|
event,
|
|
9
11
|
/** The EventHint generated by Sentry. */
|
|
10
12
|
hint) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const wasPreThrottled = event.contexts?.[skipBeforeSendThrottleContextKey]?.skipThrottle === true;
|
|
14
|
+
if (event.contexts && skipBeforeSendThrottleContextKey in event.contexts) {
|
|
15
|
+
delete event.contexts[skipBeforeSendThrottleContextKey];
|
|
16
|
+
}
|
|
17
|
+
if (!wasPreThrottled) {
|
|
18
|
+
const perEventThreshold = extractExtraEventThrottleThreshold(hint);
|
|
19
|
+
if (throttleEventWithLogging(event, hint, combineThrottleThreshold(throttleOptions, perEventThreshold))) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
13
22
|
}
|
|
14
23
|
if (!event.extra?.wasSentPrematurely) {
|
|
15
24
|
logToConsoleFromSentry(event, hint, isDev ? LoggingState.Dev : LoggingState.Prod, isSilent);
|
|
@@ -49,7 +49,36 @@ export type ThrottleOptions = {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare const defaultThrottleOptions: ThrottleOptions;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* The state transition (if any) that just occurred for an error key. Used by callers to decide
|
|
53
|
+
* whether to emit a `"Throttling started"` or `"Throttling ended after suppressing N events"` log.
|
|
54
|
+
*
|
|
55
|
+
* @category Internal
|
|
56
|
+
*/
|
|
57
|
+
export type ThrottleTransition = {
|
|
58
|
+
kind: 'none';
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'started';
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'ended';
|
|
63
|
+
suppressedCount: number;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Result of a single {@link shouldThrottleEvent} call: whether the event should be dropped, the
|
|
67
|
+
* fuzzy cluster key it was bucketed into, and the state transition (if any) that the caller may
|
|
68
|
+
* want to surface as a log.
|
|
69
|
+
*
|
|
70
|
+
* @category Internal
|
|
71
|
+
*/
|
|
72
|
+
export type ThrottleResult = {
|
|
73
|
+
shouldThrottle: boolean;
|
|
74
|
+
/** Undefined when throttling is disabled (the event isn't bucketed). */
|
|
75
|
+
errorKey: FuzzyIndexKey | undefined;
|
|
76
|
+
transition: ThrottleTransition;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Determines whether an event should be throttled based on previous event counts.This does not emit
|
|
80
|
+
* any logs itself, the caller is expected to interpret `result.transition` and emit logs as
|
|
81
|
+
* appropriate.
|
|
53
82
|
*
|
|
54
83
|
* @category Internal
|
|
55
84
|
*/
|
|
@@ -57,4 +86,34 @@ export declare function shouldThrottleEvent(
|
|
|
57
86
|
/** Event from Sentry. */
|
|
58
87
|
event: Pick<TransactionEvent | ErrorEvent, 'message'>,
|
|
59
88
|
/** EventHint generated by Sentry. */
|
|
60
|
-
hint?: Readonly<Pick<EventHint, 'originalException'>> | undefined, userOptions?: Readonly<PartialWithUndefined<ThrottleOptions>>):
|
|
89
|
+
hint?: Readonly<Pick<EventHint, 'originalException'>> | undefined, userOptions?: Readonly<PartialWithUndefined<ThrottleOptions>>): ThrottleResult;
|
|
90
|
+
/**
|
|
91
|
+
* Reserved key under `event.contexts` that `sendLog` and `handleError` set via `withScope` to tell
|
|
92
|
+
* `handleSentrySend` to skip its own throttle check — they've already done the check synchronously
|
|
93
|
+
* and the event passing through is one they chose to forward. Stripped from the event in
|
|
94
|
+
* `handleSentrySend` so it never reaches Sentry.
|
|
95
|
+
*
|
|
96
|
+
* @category Internal
|
|
97
|
+
*/
|
|
98
|
+
export declare const skipBeforeSendThrottleContextKey = "__sentryVirSkipThrottle";
|
|
99
|
+
/**
|
|
100
|
+
* Sets the throttle options consulted by `shouldThrottleSync` (in `send-log.ts`). Called by
|
|
101
|
+
* `baseInitSentry`.
|
|
102
|
+
*
|
|
103
|
+
* @category Internal
|
|
104
|
+
*/
|
|
105
|
+
export declare function setActiveThrottleOptions(options: Readonly<PartialWithUndefined<ThrottleOptions>> | undefined): void;
|
|
106
|
+
/**
|
|
107
|
+
* Returns the throttle options registered by the most recent `baseInitSentry` call, or `undefined`
|
|
108
|
+
* if Sentry has not yet been initialized.
|
|
109
|
+
*
|
|
110
|
+
* @category Internal
|
|
111
|
+
*/
|
|
112
|
+
export declare function getActiveThrottleOptions(): Readonly<PartialWithUndefined<ThrottleOptions>> | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Combines a per-call throttle threshold with the active global throttle options, taking the
|
|
115
|
+
* minimum of the two so the per-call threshold can only ever tighten throttling.
|
|
116
|
+
*
|
|
117
|
+
* @category Internal
|
|
118
|
+
*/
|
|
119
|
+
export declare function combineThrottleThreshold(base: Readonly<PartialWithUndefined<ThrottleOptions>> | undefined, perCallThreshold: number | undefined): Readonly<PartialWithUndefined<ThrottleOptions>>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getOrSetFromMap, mergeDefinedProperties, } from '@augment-vir/common';
|
|
2
2
|
import { calculateRelativeDate, getNowInUtcTimezone, isDateAfter, } from 'date-vir';
|
|
3
3
|
import { FuzzyIndex } from 'fuzzy-vir';
|
|
4
|
-
import { sendLog } from '../logging/send-log.js';
|
|
5
4
|
import { extractOriginalMessage } from './event-processor.js';
|
|
6
5
|
/**
|
|
7
6
|
* The current throttle cache, keyed by a fuzzy cluster key so near-duplicate error messages share a
|
|
@@ -35,7 +34,9 @@ export const defaultThrottleOptions = {
|
|
|
35
34
|
throttleThreshold: 500,
|
|
36
35
|
};
|
|
37
36
|
/**
|
|
38
|
-
* Determines
|
|
37
|
+
* Determines whether an event should be throttled based on previous event counts.This does not emit
|
|
38
|
+
* any logs itself, the caller is expected to interpret `result.transition` and emit logs as
|
|
39
|
+
* appropriate.
|
|
39
40
|
*
|
|
40
41
|
* @category Internal
|
|
41
42
|
*/
|
|
@@ -46,7 +47,13 @@ event,
|
|
|
46
47
|
hint, userOptions = defaultThrottleOptions) {
|
|
47
48
|
const options = mergeDefinedProperties(defaultThrottleOptions, userOptions);
|
|
48
49
|
if (options.disableThrottling) {
|
|
49
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
shouldThrottle: false,
|
|
52
|
+
errorKey: undefined,
|
|
53
|
+
transition: {
|
|
54
|
+
kind: 'none',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
50
57
|
}
|
|
51
58
|
const errorKey = fuzzyErrorIndex.insert(extractOriginalMessage(event, hint));
|
|
52
59
|
const now = getNowInUtcTimezone();
|
|
@@ -56,21 +63,17 @@ hint, userOptions = defaultThrottleOptions) {
|
|
|
56
63
|
intervalStartAt: now,
|
|
57
64
|
};
|
|
58
65
|
});
|
|
66
|
+
const transitionCandidates = [];
|
|
59
67
|
const intervalNeedsRestart = isDateAfter({
|
|
60
68
|
fullDate: now,
|
|
61
69
|
relativeTo: calculateRelativeDate(errorThrottleData.intervalStartAt, options.thresholdInterval),
|
|
62
70
|
});
|
|
63
71
|
if (intervalNeedsRestart) {
|
|
64
72
|
const suppressedCount = errorThrottleData.intervalCount - options.throttleThreshold;
|
|
65
|
-
if (suppressedCount > 0
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
suppressedCount,
|
|
70
|
-
},
|
|
71
|
-
tags: {
|
|
72
|
-
suppressedErrorKey: errorKey,
|
|
73
|
-
},
|
|
73
|
+
if (suppressedCount > 0) {
|
|
74
|
+
transitionCandidates.push({
|
|
75
|
+
kind: 'ended',
|
|
76
|
+
suppressedCount,
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
errorThrottleData.intervalStartAt = now;
|
|
@@ -78,17 +81,67 @@ hint, userOptions = defaultThrottleOptions) {
|
|
|
78
81
|
}
|
|
79
82
|
errorThrottleData.intervalCount++;
|
|
80
83
|
const shouldThrottle = errorThrottleData.intervalCount > options.throttleThreshold;
|
|
81
|
-
if (shouldThrottle &&
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
sendLog.warning(`Throttling started: ${errorKey}`, {
|
|
85
|
-
context: {
|
|
86
|
-
suppressedErrorKey: errorKey,
|
|
87
|
-
},
|
|
88
|
-
tags: {
|
|
89
|
-
suppressedErrorKey: errorKey,
|
|
90
|
-
},
|
|
84
|
+
if (shouldThrottle && errorThrottleData.intervalCount === options.throttleThreshold + 1) {
|
|
85
|
+
transitionCandidates.push({
|
|
86
|
+
kind: 'started',
|
|
91
87
|
});
|
|
92
88
|
}
|
|
93
|
-
return
|
|
89
|
+
return {
|
|
90
|
+
shouldThrottle,
|
|
91
|
+
errorKey,
|
|
92
|
+
transition: transitionCandidates[0] ?? {
|
|
93
|
+
kind: 'none',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reserved key under `event.contexts` that `sendLog` and `handleError` set via `withScope` to tell
|
|
99
|
+
* `handleSentrySend` to skip its own throttle check — they've already done the check synchronously
|
|
100
|
+
* and the event passing through is one they chose to forward. Stripped from the event in
|
|
101
|
+
* `handleSentrySend` so it never reaches Sentry.
|
|
102
|
+
*
|
|
103
|
+
* @category Internal
|
|
104
|
+
*/
|
|
105
|
+
export const skipBeforeSendThrottleContextKey = '__sentryVirSkipThrottle';
|
|
106
|
+
/**
|
|
107
|
+
* The throttle options currently in effect, set by {@link setActiveThrottleOptions} during Sentry
|
|
108
|
+
* initialization. Used by `sendLog` and `handleError` for their synchronous pre-capture throttle
|
|
109
|
+
* checks. `undefined` means Sentry has not yet been initialized.
|
|
110
|
+
*
|
|
111
|
+
* @category Internal
|
|
112
|
+
*/
|
|
113
|
+
let activeThrottleOptions;
|
|
114
|
+
/**
|
|
115
|
+
* Sets the throttle options consulted by `shouldThrottleSync` (in `send-log.ts`). Called by
|
|
116
|
+
* `baseInitSentry`.
|
|
117
|
+
*
|
|
118
|
+
* @category Internal
|
|
119
|
+
*/
|
|
120
|
+
export function setActiveThrottleOptions(options) {
|
|
121
|
+
activeThrottleOptions = options;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Returns the throttle options registered by the most recent `baseInitSentry` call, or `undefined`
|
|
125
|
+
* if Sentry has not yet been initialized.
|
|
126
|
+
*
|
|
127
|
+
* @category Internal
|
|
128
|
+
*/
|
|
129
|
+
export function getActiveThrottleOptions() {
|
|
130
|
+
return activeThrottleOptions;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Combines a per-call throttle threshold with the active global throttle options, taking the
|
|
134
|
+
* minimum of the two so the per-call threshold can only ever tighten throttling.
|
|
135
|
+
*
|
|
136
|
+
* @category Internal
|
|
137
|
+
*/
|
|
138
|
+
export function combineThrottleThreshold(base, perCallThreshold) {
|
|
139
|
+
if (perCallThreshold == undefined) {
|
|
140
|
+
return base ?? {};
|
|
141
|
+
}
|
|
142
|
+
const baseThreshold = base?.throttleThreshold ?? defaultThrottleOptions.throttleThreshold;
|
|
143
|
+
return {
|
|
144
|
+
...base,
|
|
145
|
+
throttleThreshold: Math.min(baseThreshold, perCallThreshold),
|
|
146
|
+
};
|
|
94
147
|
}
|