-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Introduce New Transports API #4716
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f0b49b4
feat(core): Introduce New Transports API
AbhiPrasad a8f56b8
re-arrange
AbhiPrasad cbde60d
remove required URL
AbhiPrasad 11a0a69
fix lint
AbhiPrasad 5690250
Merge branch 'master' into abhi-brand-new-transports
AbhiPrasad c842977
make flush timeout optional
AbhiPrasad 058d00e
Merge branch 'master' into abhi-brand-new-transports
AbhiPrasad 606b76d
address PR feedback
AbhiPrasad b709e98
explicitly write out rate limit headers
AbhiPrasad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import { Envelope, EventStatus } from '@sentry/types'; | ||
import { | ||
disabledUntil, | ||
eventStatusFromHttpCode, | ||
isRateLimited, | ||
makePromiseBuffer, | ||
PromiseBuffer, | ||
RateLimits, | ||
rejectedSyncPromise, | ||
resolvedSyncPromise, | ||
serializeEnvelope, | ||
updateRateLimits, | ||
} from '@sentry/utils'; | ||
|
||
export const ERROR_TRANSPORT_CATEGORY = 'error'; | ||
|
||
export const TRANSACTION_TRANSPORT_CATEGORY = 'transaction'; | ||
|
||
export const ATTACHMENT_TRANSPORT_CATEGORY = 'attachment'; | ||
|
||
export const SESSION_TRANSPORT_CATEGORY = 'session'; | ||
|
||
type TransportCategory = | ||
| typeof ERROR_TRANSPORT_CATEGORY | ||
| typeof TRANSACTION_TRANSPORT_CATEGORY | ||
| typeof ATTACHMENT_TRANSPORT_CATEGORY | ||
| typeof SESSION_TRANSPORT_CATEGORY; | ||
|
||
export type TransportRequest = { | ||
body: string; | ||
category: TransportCategory; | ||
}; | ||
|
||
export type TransportMakeRequestResponse = { | ||
body?: string; | ||
headers?: { | ||
[key: string]: string | null; | ||
'x-sentry-rate-limits': string | null; | ||
'retry-after': string | null; | ||
}; | ||
reason?: string; | ||
statusCode: number; | ||
lforst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
export type TransportResponse = { | ||
status: EventStatus; | ||
reason?: string; | ||
}; | ||
|
||
interface InternalBaseTransportOptions { | ||
bufferSize?: number; | ||
} | ||
|
||
export interface BaseTransportOptions extends InternalBaseTransportOptions { | ||
// url to send the event | ||
lforst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// transport does not care about dsn specific - client should take care of | ||
// parsing and figuring that out | ||
url: string; | ||
} | ||
|
||
// TODO: Move into Browser Transport | ||
export interface BrowserTransportOptions extends BaseTransportOptions { | ||
// options to pass into fetch request | ||
fetchParams: Record<string, string>; | ||
headers?: Record<string, string>; | ||
sendClientReports?: boolean; | ||
} | ||
|
||
// TODO: Move into Node transport | ||
export interface NodeTransportOptions extends BaseTransportOptions { | ||
// Set a HTTP proxy that should be used for outbound requests. | ||
httpProxy?: string; | ||
// Set a HTTPS proxy that should be used for outbound requests. | ||
httpsProxy?: string; | ||
// HTTPS proxy certificates path | ||
caCerts?: string; | ||
} | ||
|
||
export interface NewTransport { | ||
// If `$` is set, we know that this is a new transport. | ||
// TODO(v7): Remove this as we will no longer have split between | ||
// old and new transports. | ||
$: boolean; | ||
send(request: Envelope, category: TransportCategory): PromiseLike<TransportResponse>; | ||
flush(timeout?: number): PromiseLike<boolean>; | ||
} | ||
|
||
export type TransportRequestExecutor = (request: TransportRequest) => PromiseLike<TransportMakeRequestResponse>; | ||
|
||
export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30; | ||
|
||
/** | ||
* Creates a `NewTransport` | ||
* | ||
* @param options | ||
* @param makeRequest | ||
*/ | ||
export function createTransport( | ||
options: InternalBaseTransportOptions, | ||
makeRequest: TransportRequestExecutor, | ||
buffer: PromiseBuffer<TransportResponse> = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE), | ||
): NewTransport { | ||
let rateLimits: RateLimits = {}; | ||
|
||
const flush = (timeout?: number): PromiseLike<boolean> => buffer.drain(timeout); | ||
|
||
function send(envelope: Envelope, category: TransportCategory): PromiseLike<TransportResponse> { | ||
const request: TransportRequest = { | ||
category, | ||
body: serializeEnvelope(envelope), | ||
}; | ||
|
||
// Don't add to buffer if transport is already rate-limited | ||
if (isRateLimited(rateLimits, category)) { | ||
return rejectedSyncPromise({ | ||
status: 'rate_limit', | ||
reason: getRateLimitReason(rateLimits, category), | ||
}); | ||
} | ||
|
||
const requestTask = (): PromiseLike<TransportResponse> => | ||
makeRequest(request).then(({ body, headers, reason, statusCode }): PromiseLike<TransportResponse> => { | ||
const status = eventStatusFromHttpCode(statusCode); | ||
if (headers) { | ||
rateLimits = updateRateLimits(rateLimits, headers); | ||
} | ||
if (status === 'success') { | ||
return resolvedSyncPromise({ status, reason }); | ||
} | ||
return rejectedSyncPromise({ | ||
status, | ||
reason: | ||
reason || | ||
body || | ||
(status === 'rate_limit' ? getRateLimitReason(rateLimits, category) : 'Unknown transport error'), | ||
}); | ||
}); | ||
|
||
return buffer.add(requestTask); | ||
} | ||
|
||
return { | ||
$: true, | ||
send, | ||
flush, | ||
}; | ||
} | ||
|
||
function getRateLimitReason(rateLimits: RateLimits, category: TransportCategory): string { | ||
return `Too many ${category} requests, backing off until: ${new Date( | ||
disabledUntil(rateLimits, category), | ||
).toISOString()}`; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.