Skip to content

Commit

Permalink
[RUMF-617] integrate tracing (#461)
Browse files Browse the repository at this point in the history
* Introduce tracer for XHR requests

* Handle fetch requests with tracer

* Introduce trace collection

* Integrate tracer and trace collection in rum

* Handle request error in collected trace

* Delegate tracing to dd-trace-js if active

* Add tracing configuration parameters

* 👌 phrasing

* 👌 improve naming

* 👌 improve tracing API

* 👌 improve proxy API context type

* 👌 add TraceIdentifier spec

* 👌 check if dd trace is installed rather than have an active scope

* 👌 remove dd trace js check

currently, trace id is not used in rum ui and association does not work perfectly
beta customers will need to remove dd trace js and enable tracing from rum

* 👌 use same configuration as dd-trace-js to ease the onboarding

* 👌 remove enable configuration + remove default same domain tracing
  • Loading branch information
bcaudan authored Aug 14, 2020
1 parent 37dea22 commit 5d7a689
Show file tree
Hide file tree
Showing 22 changed files with 757 additions and 82 deletions.
19 changes: 19 additions & 0 deletions packages/core/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BuildEnv, BuildMode, Datacenter, INTAKE_SITE } from './init'
import { includes, ONE_KILO_BYTE, ONE_SECOND } from './utils'

export const DEFAULT_CONFIGURATION = {
allowedTracingOrigins: [] as Array<string | RegExp>,
isCollectingError: true,
maxErrorsByMinute: 3000,
maxInternalMonitoringMessagesPerPage: 15,
Expand Down Expand Up @@ -40,6 +41,7 @@ export interface UserConfiguration {
applicationId?: string
internalMonitoringApiKey?: string
isCollectingError?: boolean
allowedTracingOrigins?: Array<string | RegExp>
sampleRate?: number
resourceSampleRate?: number
datacenter?: Datacenter // deprecated
Expand All @@ -60,6 +62,7 @@ export interface UserConfiguration {
internalMonitoringEndpoint?: string
logsEndpoint?: string
rumEndpoint?: string
traceEndpoint?: string
}

interface ReplicaUserConfiguration {
Expand All @@ -73,6 +76,10 @@ export type Configuration = typeof DEFAULT_CONFIGURATION & {
traceEndpoint: string
internalMonitoringEndpoint?: string

service?: string
env?: string
version?: string

isEnabled: (feature: string) => boolean

// only on staging build mode
Expand All @@ -83,6 +90,7 @@ interface ReplicaConfiguration {
applicationId?: string
logsEndpoint: string
rumEndpoint: string
traceEndpoint: string
internalMonitoringEndpoint: string
}

Expand Down Expand Up @@ -117,12 +125,15 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
: []

const configuration: Configuration = {
env: userConfiguration.env,
isEnabled: (feature: string) => {
return includes(enableExperimentalFeatures, feature)
},
logsEndpoint: getEndpoint('browser', transportConfiguration),
rumEndpoint: getEndpoint('rum', transportConfiguration),
service: userConfiguration.service,
traceEndpoint: getEndpoint('public-trace', transportConfiguration),
version: userConfiguration.version,
...DEFAULT_CONFIGURATION,
}
if (userConfiguration.internalMonitoringApiKey) {
Expand All @@ -133,6 +144,10 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
)
}

if ('allowedTracingOrigins' in userConfiguration) {
configuration.allowedTracingOrigins = userConfiguration.allowedTracingOrigins!
}

if ('isCollectingError' in userConfiguration) {
configuration.isCollectingError = !!userConfiguration.isCollectingError
}
Expand All @@ -159,6 +174,9 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
if (userConfiguration.rumEndpoint !== undefined) {
configuration.rumEndpoint = userConfiguration.rumEndpoint
}
if (userConfiguration.traceEndpoint !== undefined) {
configuration.traceEndpoint = userConfiguration.traceEndpoint
}
}

if (transportConfiguration.buildMode === BuildMode.STAGING) {
Expand All @@ -178,6 +196,7 @@ export function buildConfiguration(userConfiguration: UserConfiguration, buildEn
),
logsEndpoint: getEndpoint('browser', replicaTransportConfiguration),
rumEndpoint: getEndpoint('rum', replicaTransportConfiguration),
traceEndpoint: getEndpoint('public-trace', replicaTransportConfiguration),
}
}
}
Expand Down
35 changes: 25 additions & 10 deletions packages/core/src/fetchProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import { monitor } from './internalMonitoring'
import { computeStackTrace } from './tracekit'
import { normalizeUrl } from './urlPolyfill'

export interface FetchProxy {
beforeSend: (callback: (context: Partial<FetchContext>) => void) => void
onRequestComplete: (callback: (context: FetchContext) => void) => void
export interface FetchProxy<T extends FetchContext = FetchContext> {
beforeSend: (callback: (context: Partial<T>) => void) => void
onRequestComplete: (callback: (context: T) => void) => void
}

export interface FetchContext {
method: string
startTime: number
init?: RequestInit
duration: number
url: string
status: number
response: string
error?: {
name?: string
message: string
stack: string
}
responseType?: string

/**
Expand All @@ -25,10 +31,10 @@ export interface FetchContext {

let fetchProxySingleton: FetchProxy | undefined
let originalFetch: typeof window.fetch
const beforeSendCallbacks: Array<(xhr: Partial<FetchContext>) => void> = []
const onRequestCompleteCallbacks: Array<(xhr: FetchContext) => void> = []
const beforeSendCallbacks: Array<(fetch: Partial<FetchContext>) => void> = []
const onRequestCompleteCallbacks: Array<(fetch: FetchContext) => void> = []

export function startFetchProxy(): FetchProxy {
export function startFetchProxy<T extends FetchContext = FetchContext>(): FetchProxy<T> {
if (!fetchProxySingleton) {
proxyFetch()
fetchProxySingleton = {
Expand All @@ -40,7 +46,7 @@ export function startFetchProxy(): FetchProxy {
},
}
}
return fetchProxySingleton
return fetchProxySingleton as FetchProxy<T>
}

export function resetFetchProxy() {
Expand All @@ -62,20 +68,29 @@ function proxyFetch() {
// tslint:disable promise-function-async
window.fetch = monitor(function(this: WindowOrWorkerGlobalScope['fetch'], input: RequestInfo, init?: RequestInit) {
const method = (init && init.method) || (typeof input === 'object' && input.method) || 'GET'
const url = normalizeUrl((typeof input === 'object' && input.url) || (input as string))
const startTime = performance.now()

const context: Partial<FetchContext> = {
init,
method,
startTime,
url,
}

const reportFetch = async (response: Response | Error) => {
context.duration = performance.now() - context.startTime!
context.url = normalizeUrl((typeof input === 'object' && input.url) || (input as string))

if ('stack' in response || response instanceof Error) {
const stackTrace = computeStackTrace(response)
const stackTraceString = toStackTraceString(stackTrace)
context.status = 0
context.response = toStackTraceString(computeStackTrace(response))
context.error = {
message: stackTrace.message,
name: stackTrace.name,
stack: stackTraceString,
}
context.response = stackTraceString

onRequestCompleteCallbacks.forEach((callback) => callback(context as FetchContext))
} else if ('status' in response) {
Expand All @@ -94,7 +109,7 @@ function proxyFetch() {
}
beforeSendCallbacks.forEach((callback) => callback(context))

const responsePromise = originalFetch.call(this, input, init)
const responsePromise = originalFetch.call(this, input, context.init)
responsePromise.then(monitor(reportFetch), monitor(reportFetch))
return responsePromise
})
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/xhrProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ interface BrowserXHR extends XMLHttpRequest {
_datadog_xhr: Partial<XhrContext>
}

export interface XhrProxy {
beforeSend: (callback: (context: Partial<XhrContext>) => void) => void
onRequestComplete: (callback: (context: XhrContext) => void) => void
export interface XhrProxy<T extends XhrContext = XhrContext> {
beforeSend: (callback: (context: Partial<T>, xhr: XMLHttpRequest) => void) => void
onRequestComplete: (callback: (context: T) => void) => void
}

export interface XhrContext {
Expand All @@ -25,24 +25,24 @@ export interface XhrContext {
}

let xhrProxySingleton: XhrProxy | undefined
const beforeSendCallbacks: Array<(context: Partial<XhrContext>) => void> = []
const beforeSendCallbacks: Array<(context: Partial<XhrContext>, xhr: XMLHttpRequest) => void> = []
const onRequestCompleteCallbacks: Array<(context: XhrContext) => void> = []
let originalXhrOpen: typeof XMLHttpRequest.prototype.open
let originalXhrSend: typeof XMLHttpRequest.prototype.send

export function startXhrProxy(): XhrProxy {
export function startXhrProxy<T extends XhrContext = XhrContext>(): XhrProxy<T> {
if (!xhrProxySingleton) {
proxyXhr()
xhrProxySingleton = {
beforeSend(callback: (context: Partial<XhrContext>) => void) {
beforeSend(callback: (context: Partial<XhrContext>, xhr: XMLHttpRequest) => void) {
beforeSendCallbacks.push(callback)
},
onRequestComplete(callback: (context: XhrContext) => void) {
onRequestCompleteCallbacks.push(callback)
},
}
}
return xhrProxySingleton
return xhrProxySingleton as XhrProxy<T>
}

export function resetXhrProxy() {
Expand Down Expand Up @@ -99,7 +99,7 @@ function proxyXhr() {

this.addEventListener('loadend', monitor(reportXhr))

beforeSendCallbacks.forEach((callback) => callback(this._datadog_xhr))
beforeSendCallbacks.forEach((callback) => callback(this._datadog_xhr, this))
}

return originalXhrSend.apply(this, arguments as any)
Expand Down
3 changes: 3 additions & 0 deletions packages/core/test/fetchProxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ describe('fetch proxy', () => {
expect(request.url).toEqual(FAKE_URL)
expect(request.status).toEqual(0)
expect(request.response).toMatch(/Error: fetch error/)
expect(request.error!.name).toBe('Error')
expect(request.error!.message).toBe('fetch error')
expect(request.error!.stack).toMatch(/Error: fetch error/)
done()
})
})
Expand Down
2 changes: 2 additions & 0 deletions packages/rum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ datadogRum.init({
- `service`: name of the corresponding service
- `env`: environment of the service
- `version`: version of the service
- `allowedTracingOrigins`: list of string or regexp of request origins in which to inject tracing headers

```
init(configuration: {
Expand All @@ -57,6 +58,7 @@ datadogRum.init({
service?: string,
env?: string,
version?: string,
allowedTracingOrigins?: Array<String|Regexp>,
})
```

Expand Down
Loading

0 comments on commit 5d7a689

Please sign in to comment.