diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index a56569896751..8baa9a91079d 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -1,13 +1,4 @@ -import { - Event, - SdkInfo, - SentryRequest, - SentryRequestType, - Session, - SessionAggregates, - SessionEnvelope, - SessionItem, -} from '@sentry/types'; +import { Event, SdkInfo, SentryRequest, Session, SessionAggregates, SessionEnvelope, SessionItem } from '@sentry/types'; import { createEnvelope, dsnToString, normalize, serializeEnvelope } from '@sentry/utils'; import { APIDetails, getEnvelopeEndpointWithUrlEncodedAuth, getStoreEndpointWithUrlEncodedAuth } from './api'; @@ -138,39 +129,21 @@ export function eventToSentryRequest(event: Event, api: APIDetails): SentryReque // deserialization. Instead, we only implement a minimal subset of the spec to // serialize events inline here. if (useEnvelope) { - const envelopeHeaders = JSON.stringify({ - event_id: event.event_id, + const envelopeHeaders = { + event_id: event.event_id as string, sent_at: new Date().toISOString(), ...(sdkInfo && { sdk: sdkInfo }), ...(!!api.tunnel && { dsn: dsnToString(api.dsn) }), - }); - const itemHeaders = JSON.stringify({ - type: eventType, - - // TODO: Right now, sampleRate may or may not be defined (it won't be in the cases of inheritance and - // explicitly-set sampling decisions). Are we good with that? - sample_rates: [{ id: samplingMethod, rate: sampleRate }], - - // The content-type is assumed to be 'application/json' and not part of - // the current spec for transaction items, so we don't bloat the request - // body with it. - // - // content_type: 'application/json', - // - // The length is optional. It must be the number of bytes in req.Body - // encoded as UTF-8. Since the server can figure this out and would - // otherwise refuse events that report the length incorrectly, we decided - // not to send the length to avoid problems related to reporting the wrong - // size and to reduce request body size. - // - // length: new TextEncoder().encode(req.body).length, - }); - // The trailing newline is optional. We intentionally don't send it to avoid - // sending unnecessary bytes. - // - // const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}\n`; - const envelope = `${envelopeHeaders}\n${itemHeaders}\n${req.body}`; - req.body = envelope; + }; + const eventItem: EventItem = [ + { + type: eventType, + sample_rates: [{ id: samplingMethod, rate: sampleRate }], + }, + req.body, + ]; + const envelope = createEnvelope(envelopeHeaders, [eventItem]); + req.body = serializeEnvelope(envelope); } return req; diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index aa7655db9cea..b03d42eb3ea8 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -2,6 +2,7 @@ import { ClientReport } from './clientreport'; import { Event } from './event'; import { SdkInfo } from './sdkinfo'; import { Session, SessionAggregates } from './session'; +import { TransactionSamplingMethod } from './transaction'; import { UserFeedback } from './user'; // Based on: https://develop.sentry.dev/sdk/envelopes/ @@ -25,14 +26,20 @@ type BaseEnvelope; +// TODO(v7): Remove the string union from `Event | string` +// We have to allow this hack for now as we pre-serialize events because we support +// both store and envelope endpoints. +export type EventItem = BaseEnvelopeItem; export type AttachmentItem = BaseEnvelopeItem; export type UserFeedbackItem = BaseEnvelopeItem; export type SessionItem = diff --git a/packages/utils/src/envelope.ts b/packages/utils/src/envelope.ts index 7552bd339784..70294de1b21c 100644 --- a/packages/utils/src/envelope.ts +++ b/packages/utils/src/envelope.ts @@ -1,5 +1,7 @@ import { Envelope } from '@sentry/types'; +import { isPrimitive } from './is'; + /** * Creates an envelope. * Make sure to always explicitly provide the generic to this function @@ -33,6 +35,8 @@ export function serializeEnvelope(envelope: Envelope): string { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (items as any[]).reduce((acc, item: typeof items[number]) => { const [itemHeaders, payload] = item; - return `${acc}\n${JSON.stringify(itemHeaders)}\n${JSON.stringify(payload)}`; + // We do not serialize payloads that are primitives + const serializedPayload = isPrimitive(payload) ? String(payload) : JSON.stringify(payload); + return `${acc}\n${JSON.stringify(itemHeaders)}\n${serializedPayload}`; }, serializedHeaders); }