Skip to content

Commit

Permalink
✨ Implement intake v2
Browse files Browse the repository at this point in the history
  • Loading branch information
amortemousque committed Aug 26, 2021
1 parent 1477bbe commit 561f1a9
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 150 deletions.
27 changes: 6 additions & 21 deletions packages/core/src/domain/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BuildEnv } from '../boot/init'
import { CookieOptions, getCurrentSite } from '../browser/cookie'
import { catchUserErrors } from '../tools/catchUserErrors'
import { includes, ONE_KILO_BYTE, ONE_SECOND } from '../tools/utils'
import { computeTransportConfiguration } from './transportConfiguration'
import { computeTransportConfiguration, TransportConfiguration } from './transportConfiguration'

export const DEFAULT_CONFIGURATION = {
allowedTracingOrigins: [] as Array<string | RegExp>,
Expand Down Expand Up @@ -59,6 +59,8 @@ export interface InitConfiguration {
version?: string

useAlternateIntakeDomains?: boolean
intakeApiVersion?: 1 | 2

useCrossSiteSessionCookie?: boolean
useSecureSessionCookie?: boolean
trackSessionAcrossSubdomains?: boolean
Expand Down Expand Up @@ -86,36 +88,19 @@ export type Configuration = typeof DEFAULT_CONFIGURATION &
isEnabled: (feature: string) => boolean
}

export interface TransportConfiguration {
logsEndpoint: string
rumEndpoint: string
sessionReplayEndpoint: string
internalMonitoringEndpoint?: string
isIntakeUrl: (url: string) => boolean

// only on staging build mode
replica?: ReplicaConfiguration
}

interface ReplicaConfiguration {
applicationId?: string
logsEndpoint: string
rumEndpoint: string
internalMonitoringEndpoint: string
}

export function buildConfiguration(initConfiguration: InitConfiguration, buildEnv: BuildEnv): Configuration {
const enableExperimentalFeatures = Array.isArray(initConfiguration.enableExperimentalFeatures)
? initConfiguration.enableExperimentalFeatures
: []

const isEnabled = (feature: string) => includes(enableExperimentalFeatures, feature)
const configuration: Configuration = {
beforeSend:
initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'),
cookieOptions: buildCookieOptions(initConfiguration),
isEnabled: (feature: string) => includes(enableExperimentalFeatures, feature),
isEnabled,
service: initConfiguration.service,
...computeTransportConfiguration(initConfiguration, buildEnv),
...computeTransportConfiguration(initConfiguration, buildEnv, isEnabled('support-intake-v2')),
...DEFAULT_CONFIGURATION,
}

Expand Down
111 changes: 111 additions & 0 deletions packages/core/src/domain/endpointBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { BuildEnv } from '../boot/init'
import { generateUUID, includes } from '../tools/utils'
import { InitConfiguration } from './configuration'

export const ENDPOINTS = {
alternate: {
logs: 'logs',
rum: 'rum',
sessionReplay: 'session-replay',
},
classic: {
logs: 'browser',
rum: 'rum',
// session-replay has no classic endpoint
sessionReplay: undefined,
},
}

const INTAKE_TRACKS = {
logs: 'logs',
rum: 'rum',
sessionReplay: 'session-replay',
}

export const ENDPOINTS_TYPES = Object.keys(ENDPOINTS['alternate']) as EndpointType[]
export type EndpointType = keyof typeof ENDPOINTS[IntakeType]

export const INTAKE_SITE_US = 'datadoghq.com'
const INTAKE_SITE_US3 = 'us3.datadoghq.com'
const INTAKE_SITE_GOV = 'ddog-gov.com'
const INTAKE_SITE_EU = 'datadoghq.eu'

const CLASSIC_ALLOWED_SITES = [INTAKE_SITE_US, INTAKE_SITE_EU]
const INTAKE_V1_ALLOWED_SITES = [INTAKE_SITE_US, INTAKE_SITE_US3, INTAKE_SITE_EU, INTAKE_SITE_GOV]

type IntakeType = keyof typeof ENDPOINTS

type IntakeApiVersion = 1 | 2

export class EndpointBuilder {
private site: string
private clientToken: string
private env: string | undefined
private proxyHost: string | undefined
private sdkVersion: string
private service: string | undefined
private version: string | undefined
private intakeApiVersion: IntakeApiVersion
private useAlternateIntakeDomains: boolean
private isIntakeV2Enabled: boolean
constructor(initConfiguration: InitConfiguration, buildEnv: BuildEnv, isIntakeV2Enabled?: boolean) {
this.isIntakeV2Enabled = !!isIntakeV2Enabled
this.site = initConfiguration.site || INTAKE_SITE_US
this.clientToken = initConfiguration.clientToken
this.env = initConfiguration.env
this.proxyHost = initConfiguration.proxyHost
this.sdkVersion = buildEnv.sdkVersion
this.service = initConfiguration.service
this.version = initConfiguration.version
this.intakeApiVersion = initConfiguration.intakeApiVersion || 1
this.useAlternateIntakeDomains = !!initConfiguration.useAlternateIntakeDomains
}

supportIntakeV2 = (endpointType?: EndpointType): boolean =>
this.isIntakeV2Enabled &&
(this.intakeApiVersion === 2 || !includes(INTAKE_V1_ALLOWED_SITES, this.site) || endpointType === 'sessionReplay')

supportAlternateDomain = (endpointType?: EndpointType): boolean =>
this.useAlternateIntakeDomains || !includes(CLASSIC_ALLOWED_SITES, this.site) || endpointType === 'sessionReplay'

build(endpointType: EndpointType, source?: string) {
const tags =
`sdk_version:${this.sdkVersion}` +
`${this.env ? `,env:${this.env}` : ''}` +
`${this.service ? `,service:${this.service}` : ''}` +
`${this.version ? `,version:${this.version}` : ''}`
const datadogHost = this.buildHost(endpointType)
const proxyParameter = this.proxyHost ? `ddhost=${datadogHost}&` : ''
const parameters = `${proxyParameter}ddsource=${source || 'browser'}&ddtags=${encodeURIComponent(tags)}`
const newIntakeParameters = this.supportIntakeV2(endpointType)
? `?dd-api-key=${this.clientToken}&` +
`dd-evp-origin-version=${this.sdkVersion}&` +
`dd-evp-origin=browser&` +
`dd-request-id=${generateUUID()}&`
: `${this.clientToken}?`

return `${this.buildIntakeUrl(endpointType)}${newIntakeParameters}${parameters}`
}

buildIntakeUrl(endpointType: EndpointType): string {
const datadogHost = this.buildHost(endpointType)
const host = this.proxyHost ? this.proxyHost : datadogHost
return `https://${host}${this.buildPath(endpointType)}`
}

private buildHost(endpointType: EndpointType) {
if (this.supportAlternateDomain(endpointType)) {
const endpoint = ENDPOINTS.alternate[endpointType]
const domainParts = this.site.split('.')
const extension = domainParts.pop()
const suffix = `${domainParts.join('-')}.${extension!}`
return `${endpoint}.browser-intake-${suffix}`
}
const endpoint = ENDPOINTS.classic[endpointType]!
return `${endpoint}-http-intake.logs.${this.site}`
}

private buildPath(endpointType: EndpointType) {
return this.supportIntakeV2(endpointType) ? `/api/v2/${INTAKE_TRACKS[endpointType]}` : `/v1/input/`
}
}
127 changes: 126 additions & 1 deletion packages/core/src/domain/transportConfiguration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('transportConfiguration', () => {
})
})

describe('isIntakeUrl', () => {
describe('isIntakeUrl when "support-intake-v2" disabled', () => {
it('should not detect non intake request', () => {
const configuration = computeTransportConfiguration({ clientToken }, buildEnv)
expect(configuration.isIntakeUrl('https://www.foo.com')).toBe(false)
Expand Down Expand Up @@ -191,5 +191,130 @@ describe('transportConfiguration', () => {
expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true)
})

it('should force intake v1 when "support-intake-v2" disabled', () => {
const configuration = computeTransportConfiguration({ clientToken, intakeApiVersion: 2 }, buildEnv)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://session-replay.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true)
})
})

describe('isIntakeUrl when "support-intake-v2" enabled', () => {
describe('when RUM or Logs', () => {
describe('on us1 and eu1', () => {
it('should detect classic domains intake v2', () => {
let configuration = computeTransportConfiguration({ clientToken, intakeApiVersion: 2 }, buildEnv, true)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.com/api/v2/logs?xxx')).toBe(true)

configuration = computeTransportConfiguration(
{ clientToken, site: 'datadoghq.eu', intakeApiVersion: 2 },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.eu/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.eu/api/v2/logs?xxx')).toBe(true)
})

it('should detect alternate domains intake v2', () => {
let configuration = computeTransportConfiguration(
{ clientToken, useAlternateIntakeDomains: true, intakeApiVersion: 2 },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.com/api/v2/logs?xxx')).toBe(true)

configuration = computeTransportConfiguration(
{ clientToken, site: 'datadoghq.eu', useAlternateIntakeDomains: true, intakeApiVersion: 2 },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.eu/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.eu/api/v2/logs?xxx')).toBe(true)
})
})

describe('on us3 and gov', () => {
it('should detect alternate domains intake v2', () => {
let configuration = computeTransportConfiguration(
{ clientToken, site: 'us3.datadoghq.com', intakeApiVersion: 2 },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-us3-datadoghq.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-us3-datadoghq.com/api/v2/logs?xxx')).toBe(true)

configuration = computeTransportConfiguration(
{ clientToken, site: 'ddog-gov.com', intakeApiVersion: 2 },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-ddog-gov.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.ddog-gov.com/api/v2/logs?xxx')).toBe(false)
})
})

describe('on us5', () => {
it('should force alternate domains intake v2', () => {
const configuration = computeTransportConfiguration(
{ clientToken, site: 'us5.datadoghq.com' },
buildEnv,
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-us5-datadoghq.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-us5-datadoghq.com/api/v2/logs?xxx')).toBe(true)
})
})
})

describe('when session-replay on all env', () => {
it('should force alternate domains intake v2', () => {
let configuration = computeTransportConfiguration({ clientToken }, buildEnv, true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-datadoghq.com/api/v2/session-replay?xxx')
).toBe(true)

configuration = computeTransportConfiguration({ clientToken, site: 'datadoghq.eu' }, buildEnv, true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-datadoghq.eu/api/v2/session-replay?xxx')
).toBe(true)

configuration = computeTransportConfiguration({ clientToken, site: 'us3.datadoghq.com' }, buildEnv, true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-us3-datadoghq.com/api/v2/session-replay?xxx')
).toBe(true)

configuration = computeTransportConfiguration({ clientToken, site: 'ddog-gov.com' }, buildEnv, true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-ddog-gov.com/api/v2/session-replay?xxx')
).toBe(true)

configuration = computeTransportConfiguration({ clientToken, site: 'us5.datadoghq.com' }, buildEnv, true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-us5-datadoghq.com/api/v2/session-replay?xxx')
).toBe(true)
})
})

it('should detect replica intake request with alternate intake domains version 2', () => {
const configuration = computeTransportConfiguration(
{ clientToken, site: 'foo.com', replica: { clientToken } },
{ ...buildEnv, buildMode: BuildMode.STAGING },
true
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-foo.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-foo.com/api/v2/logs?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://session-replay.browser-intake-foo.com/api/v2/session-replay?xxx')).toBe(
true
)

expect(configuration.isIntakeUrl('https://rum.browser-intake-datadoghq.com/api/v2/rum?xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-datadoghq.com/api/v2/logs?xxx')).toBe(true)
expect(
configuration.isIntakeUrl('https://session-replay.browser-intake-datadoghq.com/api/v2/session-replay?xxx')
).toBe(true)
})
})
})
Loading

0 comments on commit 561f1a9

Please sign in to comment.