Skip to content
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

✨ [RUMF-764] Use new intake domain for US #616

Merged
merged 23 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
569e0aa
🏷️ [RUM] Use enum for endpoint type
webNeat Nov 13, 2020
723bd92
✨ [RUM] Use new intake domain for US
webNeat Nov 13, 2020
7f89562
🎨 [RUM] Refactor isIntakeRequest()
webNeat Nov 16, 2020
3a75832
✨ [RUM] Support legacy endpoints
webNeat Nov 16, 2020
cc99182
🎨 [RUM] Use indexOf() instead of includes() to support IE 11
webNeat Nov 17, 2020
73f4c35
🎨 [RUM] Precompute intake urls
webNeat Nov 20, 2020
7ccedc6
🎨 [RUM] Use .some() instead of for..of
webNeat Nov 23, 2020
87228b9
🎨 [RUM] Remove unused imports
webNeat Nov 23, 2020
9d75102
🎨 [RUM] Replace isIntakeRequest by configuration.isIntakeUrl
webNeat Nov 23, 2020
d187810
✨ [RUM] Add configuration option to use new intake domains
webNeat Nov 25, 2020
ea48e31
🎨 [RUM] Remove uneeded lines
webNeat Nov 26, 2020
16d83b8
🎨 [RUM] Use either new or old intake domains
webNeat Nov 26, 2020
5bee248
🎨 [RUM] Rename configuration option to useAlternateIntakeDomains
webNeat Nov 27, 2020
c843fb3
Merge branch 'master' into amine/use-public-intake
webNeat Nov 27, 2020
927d7df
✅ [RUM] Fix unit tests
webNeat Nov 27, 2020
4fc29bc
👌 Remove list of allowed sites in favor of specifying them in docs
webNeat Nov 27, 2020
7fd2e40
🏷️ Refactor types to account for alternate intake
webNeat Nov 30, 2020
fd3c833
🎨 Refactor code to eliminate duplication
webNeat Dec 1, 2020
0758cb1
👌 Remove uneeded property
webNeat Dec 1, 2020
48a0ffc
👌 Handle sites with subdomains
webNeat Dec 2, 2020
4ba25a4
👌 Support classic intake for sites with subdomains
webNeat Dec 2, 2020
e3fe145
👌 Simplify the code
webNeat Dec 2, 2020
812b230
Merge branch 'master' into amine/use-public-intake
webNeat Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/domain/automaticErrorCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { resetXhrProxy, startXhrProxy, XhrCompleteContext } from '../browser/xhr
import { ErrorSource, formatUnknownError, RawError, toStackTraceString } from '../tools/error'
import { Observable } from '../tools/observable'
import { jsonStringify, ONE_MINUTE, RequestType } from '../tools/utils'
import { Configuration, isIntakeRequest } from './configuration'
import { Configuration } from './configuration'
import { monitor } from './internalMonitoring'
import { computeStackTrace, Handler, report, StackTrace } from './tracekit'

Expand Down Expand Up @@ -94,7 +94,7 @@ export function trackNetworkError(configuration: Configuration, errorObservable:
startFetchProxy().onRequestComplete((context) => handleCompleteRequest(RequestType.FETCH, context))

function handleCompleteRequest(type: RequestType, request: XhrCompleteContext | FetchCompleteContext) {
if (!isIntakeRequest(request.url, configuration) && (isRejected(request) || isServerError(request))) {
if (!configuration.isIntakeUrl(request.url) && (isRejected(request) || isServerError(request))) {
errorObservable.notify({
message: `${format(type)} error ${request.method} ${request.url}`,
resource: {
Expand Down
84 changes: 67 additions & 17 deletions packages/core/src/domain/configuration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BuildEnv, BuildMode, Datacenter } from '../boot/init'
import { buildConfiguration, isIntakeRequest } from './configuration'
import { buildConfiguration } from './configuration'

describe('configuration', () => {
const clientToken = 'some_client_token'
Expand Down Expand Up @@ -52,9 +52,9 @@ describe('configuration', () => {

describe('proxyHost', () => {
it('should replace endpoint host add set it as a query parameter', () => {
const configuration = buildConfiguration({ clientToken, proxyHost: 'proxy.io' }, usEnv)
const configuration = buildConfiguration({ clientToken, site: 'datadoghq.eu', proxyHost: 'proxy.io' }, usEnv)
expect(configuration.rumEndpoint).toMatch(/^https:\/\/proxy\.io\//)
expect(configuration.rumEndpoint).toContain('?ddhost=rum-http-intake.logs.datadoghq.com&')
expect(configuration.rumEndpoint).toContain('?ddhost=rum-http-intake.logs.datadoghq.eu&')
})
})

Expand Down Expand Up @@ -104,44 +104,94 @@ describe('configuration', () => {
})
})

describe('isIntakeRequest', () => {
describe('isIntakeUrl', () => {
it('should not detect non intake request', () => {
const configuration = buildConfiguration({ clientToken }, usEnv)
expect(isIntakeRequest('https://www.foo.com', configuration)).toBe(false)
expect(configuration.isIntakeUrl('https://www.foo.com')).toBe(false)
})

it('should detect intake request', () => {
it('should detect intake request for EU site', () => {
const configuration = buildConfiguration({ clientToken, site: 'datadoghq.eu' }, usEnv)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.datadoghq.eu/v1/input/xxx')).toBe(true)
})

it('should detect intake request for US site', () => {
const configuration = buildConfiguration({ clientToken }, usEnv)
expect(isIntakeRequest('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true)
expect(isIntakeRequest('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true)
expect(isIntakeRequest('https://public-trace-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(

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://public-trace-http-intake.logs.datadoghq.com/v1/input/xxx')).toBe(true)
})

it('should detect alternate intake domains for US site', () => {
const configuration = buildConfiguration({ clientToken, useAlternateIntakeDomains: true }, usEnv)
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)
expect(configuration.isIntakeUrl('https://trace.browser-intake-datadoghq.com/v1/input/xxx')).toBe(true)
})

it('should handle sites with subdomains and classic intake', () => {
const configuration = buildConfiguration({ clientToken, site: 'us3.datadoghq.com' }, usEnv)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.us3.datadoghq.com/v1/input/xxx')).toBe(
true
)
})

it('should handle sites with subdomains and alternate intake', () => {
const configuration = buildConfiguration(
{ clientToken, site: 'us3.datadoghq.com', useAlternateIntakeDomains: true },
usEnv
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://trace.browser-intake-us3-datadoghq.com/v1/input/xxx')).toBe(true)
})

it('should detect proxy intake request', () => {
let configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com' }, usEnv)
expect(isIntakeRequest('https://www.proxy.com/v1/input/xxx', configuration)).toBe(true)
expect(configuration.isIntakeUrl('https://www.proxy.com/v1/input/xxx')).toBe(true)
configuration = buildConfiguration(
{ clientToken, proxyHost: 'www.proxy.com', useAlternateIntakeDomains: true },
usEnv
)
expect(configuration.isIntakeUrl('https://www.proxy.com/v1/input/xxx')).toBe(true)
configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com/custom/path' }, usEnv)
expect(isIntakeRequest('https://www.proxy.com/custom/path/v1/input/xxx', configuration)).toBe(true)
expect(configuration.isIntakeUrl('https://www.proxy.com/custom/path/v1/input/xxx')).toBe(true)
})

it('should not detect request done on the same host as the proxy', () => {
const configuration = buildConfiguration({ clientToken, proxyHost: 'www.proxy.com' }, usEnv)
expect(isIntakeRequest('https://www.proxy.com/foo', configuration)).toBe(false)
expect(configuration.isIntakeUrl('https://www.proxy.com/foo')).toBe(false)
})

it('should detect replica intake request', () => {
const configuration = buildConfiguration(
{ clientToken, site: 'foo.com', replica: { clientToken } },
{ ...usEnv, buildMode: BuildMode.STAGING }
)
expect(isIntakeRequest('https://rum-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true)
expect(isIntakeRequest('https://browser-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true)
expect(isIntakeRequest('https://public-trace-http-intake.logs.foo.com/v1/input/xxx', configuration)).toBe(true)
expect(configuration.isIntakeUrl('https://rum-http-intake.logs.foo.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://browser-http-intake.logs.foo.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://public-trace-http-intake.logs.foo.com/v1/input/xxx')).toBe(true)

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)
})

it('should detect replica intake request with alternate intake domains', () => {
const configuration = buildConfiguration(
{ clientToken, site: 'foo.com', replica: { clientToken }, useAlternateIntakeDomains: true },
{ ...usEnv, buildMode: BuildMode.STAGING }
)
expect(configuration.isIntakeUrl('https://rum.browser-intake-foo.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://logs.browser-intake-foo.com/v1/input/xxx')).toBe(true)
expect(configuration.isIntakeUrl('https://trace.browser-intake-foo.com/v1/input/xxx')).toBe(true)

expect(isIntakeRequest('https://rum-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true)
expect(isIntakeRequest('https://browser-http-intake.logs.datadoghq.com/v1/input/xxx', configuration)).toBe(true)
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)
})
})
})
86 changes: 64 additions & 22 deletions packages/core/src/domain/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BuildEnv, BuildMode, Datacenter, INTAKE_SITE } from '../boot/init'
import { CookieOptions, getCurrentSite } from '../browser/cookie'
import { getPathName, haveSameOrigin } from '../tools/urlPolyfill'
import { includes, ONE_KILO_BYTE, ONE_SECOND } from '../tools/utils'

export const DEFAULT_CONFIGURATION = {
Expand Down Expand Up @@ -55,6 +54,7 @@ export interface UserConfiguration {
env?: string
version?: string

useAlternateIntakeDomains?: boolean
useCrossSiteSessionCookie?: boolean
useSecureSessionCookie?: boolean
trackSessionAcrossSubdomains?: boolean
Expand All @@ -79,6 +79,7 @@ export type Configuration = typeof DEFAULT_CONFIGURATION & {
service?: string

isEnabled: (feature: string) => boolean
isIntakeUrl: (url: string) => boolean

// only on staging build mode
replica?: ReplicaConfiguration
Expand All @@ -104,6 +105,21 @@ interface TransportConfiguration {
version?: string
}

const ENDPOINTS = {
alternate: {
logs: 'logs',
rum: 'rum',
trace: 'trace',
},
classic: {
logs: 'browser',
rum: 'rum',
trace: 'public-trace',
},
}
type IntakeType = keyof typeof ENDPOINTS
type EndpointType = keyof (typeof ENDPOINTS)[IntakeType]

export function buildConfiguration(userConfiguration: UserConfiguration, buildEnv: BuildEnv): Configuration {
const transportConfiguration: TransportConfiguration = {
applicationId: userConfiguration.applicationId,
Expand All @@ -121,21 +137,26 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
? userConfiguration.enableExperimentalFeatures
: []

const intakeType: IntakeType = userConfiguration.useAlternateIntakeDomains ? 'alternate' : 'classic'
const intakeUrls = getIntakeUrls(intakeType, transportConfiguration, userConfiguration.replica !== undefined)
const configuration: Configuration = {
cookieOptions: buildCookieOptions(userConfiguration),
isEnabled: (feature: string) => {
return includes(enableExperimentalFeatures, feature)
},
logsEndpoint: getEndpoint('browser', transportConfiguration),
logsEndpoint: getEndpoint(intakeType, 'logs', transportConfiguration),
proxyHost: userConfiguration.proxyHost,
rumEndpoint: getEndpoint('rum', transportConfiguration),
rumEndpoint: getEndpoint(intakeType, 'rum', transportConfiguration),
service: userConfiguration.service,
traceEndpoint: getEndpoint('public-trace', transportConfiguration),
traceEndpoint: getEndpoint(intakeType, 'trace', transportConfiguration),

isIntakeUrl: (url) => intakeUrls.some((intakeUrl) => url.indexOf(intakeUrl) === 0),
...DEFAULT_CONFIGURATION,
}
if (userConfiguration.internalMonitoringApiKey) {
configuration.internalMonitoringEndpoint = getEndpoint(
'browser',
intakeType,
'logs',
transportConfiguration,
'browser-agent-internal-monitoring'
)
Expand Down Expand Up @@ -174,12 +195,13 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
configuration.replica = {
applicationId: userConfiguration.replica.applicationId,
internalMonitoringEndpoint: getEndpoint(
'browser',
intakeType,
'logs',
replicaTransportConfiguration,
'browser-agent-internal-monitoring'
),
logsEndpoint: getEndpoint('browser', replicaTransportConfiguration),
rumEndpoint: getEndpoint('rum', replicaTransportConfiguration),
logsEndpoint: getEndpoint(intakeType, 'logs', replicaTransportConfiguration),
rumEndpoint: getEndpoint(intakeType, 'rum', replicaTransportConfiguration),
}
}
}
Expand All @@ -200,13 +222,18 @@ export function buildCookieOptions(userConfiguration: UserConfiguration) {
return cookieOptions
}

function getEndpoint(type: string, conf: TransportConfiguration, source?: string) {
function getEndpoint(
intakeType: IntakeType,
endpointType: EndpointType,
conf: TransportConfiguration,
source?: string
) {
const tags =
`sdk_version:${conf.sdkVersion}` +
`${conf.env ? `,env:${conf.env}` : ''}` +
`${conf.service ? `,service:${conf.service}` : ''}` +
`${conf.version ? `,version:${conf.version}` : ''}`
const datadogHost = `${type}-http-intake.logs.${conf.site}`
const datadogHost = getHost(intakeType, endpointType, conf.site)
const host = conf.proxyHost ? conf.proxyHost : datadogHost
const proxyParameter = conf.proxyHost ? `ddhost=${datadogHost}&` : ''
const applicationIdParameter = conf.applicationId ? `_dd.application_id=${conf.applicationId}&` : ''
Expand All @@ -215,18 +242,33 @@ function getEndpoint(type: string, conf: TransportConfiguration, source?: string
return `https://${host}/v1/input/${conf.clientToken}?${parameters}`
}

export function isIntakeRequest(url: string, configuration: Configuration) {
return (
getPathName(url).indexOf('/v1/input/') !== -1 &&
(haveSameOrigin(url, configuration.logsEndpoint) ||
haveSameOrigin(url, configuration.rumEndpoint) ||
haveSameOrigin(url, configuration.traceEndpoint) ||
(!!configuration.internalMonitoringEndpoint && haveSameOrigin(url, configuration.internalMonitoringEndpoint)) ||
(!!configuration.replica &&
(haveSameOrigin(url, configuration.replica.logsEndpoint) ||
haveSameOrigin(url, configuration.replica.rumEndpoint) ||
haveSameOrigin(url, configuration.replica.internalMonitoringEndpoint))))
)
function getHost(intakeType: IntakeType, endpointType: EndpointType, site: string) {
const endpoint = ENDPOINTS[intakeType][endpointType]
if (intakeType === 'classic') {
return `${endpoint}-http-intake.logs.${site}`
}
const domainParts = site.split('.')
const extension = domainParts.pop()
const suffix = `${domainParts.join('-')}.${extension}`
return `${endpoint}.browser-intake-${suffix}`
}

function getIntakeUrls(intakeType: IntakeType, conf: TransportConfiguration, withReplica: boolean) {
if (conf.proxyHost) {
return [`https://${conf.proxyHost}/v1/input/`]
}
const sites = [conf.site]
if (conf.buildMode === BuildMode.STAGING && withReplica) {
sites.push(INTAKE_SITE[Datacenter.US])
}
const urls = []
const endpointTypes = Object.keys(ENDPOINTS[intakeType]) as EndpointType[]
for (const site of sites) {
for (const endpointType of endpointTypes) {
urls.push(`https://${getHost(intakeType, endpointType, site)}/v1/input/`)
}
}
return urls
}

function mustUseSecureCookie(userConfiguration: UserConfiguration) {
Expand Down
8 changes: 1 addition & 7 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
export {
DEFAULT_CONFIGURATION,
Configuration,
UserConfiguration,
isIntakeRequest,
buildCookieOptions,
} from './domain/configuration'
export { DEFAULT_CONFIGURATION, Configuration, UserConfiguration, buildCookieOptions } from './domain/configuration'
export { startAutomaticErrorCollection, ErrorObservable } from './domain/automaticErrorCollection'
export { computeStackTrace } from './domain/tracekit'
export {
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/tools/specHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ export const SPEC_ENDPOINTS: Partial<Configuration> = {
logsEndpoint: 'https://logs-intake.com/v1/input/abcde?foo=bar',
rumEndpoint: 'https://rum-intake.com/v1/input/abcde?foo=bar',
traceEndpoint: 'https://trace-intake.com/v1/input/abcde?foo=bar',

isIntakeUrl: (url: string) => {
const intakeUrls = [
'https://monitoring-intake.com/v1/input/',
'https://logs-intake.com/v1/input/',
'https://rum-intake.com/v1/input/',
'https://trace-intake.com/v1/input/',
]
return intakeUrls.some((intakeUrl) => url.indexOf(intakeUrl) === 0)
},
}

export function isSafari() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
Configuration,
getPathName,
includes,
isIntakeRequest,
isValidUrl,
msToNs,
ResourceType,
Expand Down Expand Up @@ -204,5 +203,5 @@ export function computeSize(entry: RumPerformanceResourceTiming) {
}

export function isAllowedRequestUrl(configuration: Configuration, url: string) {
return url && !isIntakeRequest(url, configuration)
return url && !configuration.isIntakeUrl(url)
}