Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

web: initial Datadog RUM integration #34063

Merged
merged 5 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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: 3 additions & 1 deletion client/web/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ReloadIcon from 'mdi-react/ReloadIcon'
import { asError } from '@sourcegraph/common'
import { Button } from '@sourcegraph/wildcard'

import { isWebpackChunkError } from '../sentry/shouldErrorBeReported'
import { DatadogClient, isWebpackChunkError } from '../monitoring'

import { HeroPage } from './HeroPage'

Expand Down Expand Up @@ -61,6 +61,8 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
Sentry.captureException(error)
})
}

DatadogClient.addError(error, { errorInfo, originalException: error })
}

public componentDidUpdate(previousProps: Props): void {
Expand Down
2 changes: 1 addition & 1 deletion client/web/src/enterprise/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import '@sourcegraph/shared/src/polyfills'

import '../sentry/init'
import '../monitoring/initMonitoring'

import React from 'react'

Expand Down
6 changes: 6 additions & 0 deletions client/web/src/jscontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export interface SourcegraphContext extends Pick<Required<SiteConfiguration>, 'e

readonly sentryDSN: string | null

/* Configuration required for Datadog RUM (https://docs.datadoghq.com/real_user_monitoring/browser/#setup). */
valerybugakov marked this conversation as resolved.
Show resolved Hide resolved
readonly datadog?: {
clientToken: string
applicationId: string
}

/** Externally accessible URL for Sourcegraph (e.g., https://sourcegraph.com or http://localhost:3080). */
externalURL: string

Expand Down
2 changes: 1 addition & 1 deletion client/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import '@sourcegraph/shared/src/polyfills'

import './sentry/init'
import './monitoring/initMonitoring'

import React from 'react'

Expand Down
23 changes: 23 additions & 0 deletions client/web/src/monitoring/datadog/datadogClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ErrorInfo } from 'react'

// Ensures consistent `ErrorContext` across `addError` calls.
// https://docs.datadoghq.com/real_user_monitoring/browser/modifying_data_and_context/?tab=npm#enrich-and-control-rum-data
valerybugakov marked this conversation as resolved.
Show resolved Hide resolved
interface ErrorContext {
// Used to ignore some errors in the `beforeSend` hook.
originalException: unknown
errorInfo?: ErrorInfo
}

export function isDatadogRumAvailable(): boolean {
return typeof DD_RUM !== 'undefined'
}

export const DatadogClient = {
addError: (error: unknown, context: ErrorContext): void => {
// Temporary solution for checking the availability of the
// Datadog SDK until we decide to move forward with this service.
if (isDatadogRumAvailable()) {
DD_RUM.addError(error, context)
}
},
}
75 changes: 75 additions & 0 deletions client/web/src/monitoring/datadog/initDatadog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Import only types to avoid adding `@datadog/browser-rum-slim` to our bundle.
import type { RumGlobal } from '@datadog/browser-rum-slim'

import { authenticatedUser } from '../../auth'
import { shouldErrorBeReported } from '../shouldErrorBeReported'

import { isDatadogRumAvailable } from './datadogClient'

declare global {
const DD_RUM: RumGlobal
}

/**
* Datadog is initialized only if:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loving these comments!

* 1. The SDK script is included into the `index.html` template (app.html).
* 2. Datadog RUM is configured using Sourcegraph site configuration.
* 3. `ENABLE_MONITORING || NODE_ENV === 'production'` to prevent log spam in the development environment.
*/
export function initDatadog(): void {
if (
isDatadogRumAvailable() &&
window.context.datadog &&
(process.env.NODE_ENV === 'production' || process.env.ENABLE_MONITORING)
) {
const {
datadog: { applicationId, clientToken },
version,
} = window.context

// The SDK is loaded asynchronously via an async script defined in the `app.html`.
// https://docs.datadoghq.com/real_user_monitoring/browser/#cdn-async
DD_RUM.onReady(() => {
// Initialization parameters: https://docs.datadoghq.com/real_user_monitoring/browser/#configuration
DD_RUM.init({
clientToken,
applicationId,
env: process.env.NODE_ENV,
// Sanitize the development version to meet Datadog tagging requirements.
// https://docs.datadoghq.com/getting_started/tagging/#defining-tags
version: version.replace('+', '_'),
// A relative sampling (in percent) to the number of sessions collected.
// https://docs.datadoghq.com/real_user_monitoring/browser/modifying_data_and_context/?tab=npm#sampling
sampleRate: 100,
// We can enable it later after verifying that basic RUM functionality works.
// https://docs.datadoghq.com/real_user_monitoring/browser/tracking_user_actions
trackInteractions: false,
// It's identical to Sentry `beforeSend` hook for now. When we decide to drop
// one of the services, we can start using more Datadog-specific properties to filter out logs.
// https://docs.datadoghq.com/real_user_monitoring/browser/modifying_data_and_context/?tab=npm#enrich-and-control-rum-data
beforeSend(event) {
const { type, context } = event

// Use `originalException` to check if we want to ignore the error.
if (type === 'error') {
return shouldErrorBeReported(context?.originalException)
}

return true
},
})

// Datadog RUM is never un-initialized so there's no need to handle this subscription.
// eslint-disable-next-line rxjs/no-ignored-subscription
authenticatedUser.subscribe(user => {
// Add user information to a RUM session.
// https://docs.datadoghq.com/real_user_monitoring/browser/modifying_data_and_context/?tab=npm#identify-user-sessions
if (user) {
DD_RUM.setUser(user)
} else {
DD_RUM.removeUser()
}
})
})
}
}
2 changes: 2 additions & 0 deletions client/web/src/monitoring/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DatadogClient } from './datadog/datadogClient'
export { isWebpackChunkError } from './shouldErrorBeReported'
21 changes: 21 additions & 0 deletions client/web/src/monitoring/initMonitoring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { initDatadog } from './datadog/initDatadog'
import { initSentry } from './sentry/initSentry'

window.addEventListener('error', error => {
/**
* The "ResizeObserver loop limit exceeded" error means that `ResizeObserver` was not
* able to deliver all observations within a single animation frame. It doesn't break
* the functionality of the application. The W3C considers converting this error to a warning:
* https://github.com/w3c/csswg-drafts/issues/5023
* We can safely ignore it in the production environment to avoid hammering Sentry and other
* libraries relying on `window.addEventListener('error', callback)`.
*/
const isResizeObserverLoopError = error.message === 'ResizeObserver loop limit exceeded'
valerybugakov marked this conversation as resolved.
Show resolved Hide resolved

if (process.env.NODE_ENV === 'production' && isResizeObserverLoopError) {
error.stopImmediatePropagation()
}
})

initSentry()
initDatadog()
51 changes: 51 additions & 0 deletions client/web/src/monitoring/sentry/initSentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Import only types to avoid adding `@sentry/browser` to our bundle.
import type { Hub, init, onLoad } from '@sentry/browser'

import { authenticatedUser } from '../../auth'
import { shouldErrorBeReported } from '../shouldErrorBeReported'

export type SentrySDK = Hub & {
init: typeof init
onLoad: typeof onLoad
}

declare global {
const Sentry: SentrySDK
}

export function initSentry(): void {
if (
typeof Sentry !== 'undefined' &&
window.context.sentryDSN &&
(process.env.NODE_ENV === 'production' || process.env.ENABLE_MONITORING)
) {
const { sentryDSN, version } = window.context

// Wait for Sentry to lazy-load from the script tag defined in the `app.html`.
// https://sentry-docs-git-patch-1.sentry.dev/platforms/javascript/guides/react/install/lazy-load-sentry/
Sentry.onLoad(() => {
Sentry.init({
dsn: sentryDSN,
release: 'frontend@' + version,
beforeSend(event, hint) {
// Use `originalException` to check if we want to ignore the error.
if (!hint || shouldErrorBeReported(hint.originalException)) {
return event
}

return null
},
})

// Sentry is never un-initialized.
// eslint-disable-next-line rxjs/no-ignored-subscription
authenticatedUser.subscribe(user => {
Sentry.configureScope(scope => {
if (user) {
scope.setUser({ id: user.id })
}
})
})
})
}
}
68 changes: 0 additions & 68 deletions client/web/src/sentry/init.ts

This file was deleted.

19 changes: 13 additions & 6 deletions cmd/frontend/internal/app/jscontext/jscontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ type JSContext struct {

IsAuthenticatedUser bool `json:"isAuthenticatedUser"`

SentryDSN *string `json:"sentryDSN"`
SiteID string `json:"siteID"`
SiteGQLID string `json:"siteGQLID"`
Debug bool `json:"debug"`
NeedsSiteInit bool `json:"needsSiteInit"`
EmailEnabled bool `json:"emailEnabled"`
Datadog schema.RUM `json:"datadog,omitempty"`
SentryDSN *string `json:"sentryDSN"`
SiteID string `json:"siteID"`
SiteGQLID string `json:"siteGQLID"`
Debug bool `json:"debug"`
NeedsSiteInit bool `json:"needsSiteInit"`
EmailEnabled bool `json:"emailEnabled"`

Site schema.SiteConfiguration `json:"site"` // public subset of site configuration
LikelyDockerOnMac bool `json:"likelyDockerOnMac"`
Expand Down Expand Up @@ -146,6 +147,11 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
sentryDSN = &siteConfig.Log.Sentry.Dsn
}

var datadogRUM schema.RUM
if siteConfig.ObservabilityLogging != nil && siteConfig.ObservabilityLogging.Datadog != nil && siteConfig.ObservabilityLogging.Datadog.RUM != nil {
datadogRUM = *siteConfig.ObservabilityLogging.Datadog.RUM
}

var githubAppCloudSlug string
var githubAppCloudClientID string
if envvar.SourcegraphDotComMode() && siteConfig.Dotcom != nil && siteConfig.Dotcom.GithubAppCloud != nil {
Expand All @@ -165,6 +171,7 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
AssetsRoot: assetsutil.URL("").String(),
Version: version.Version(),
IsAuthenticatedUser: actor.IsAuthenticated(),
Datadog: datadogRUM,
SentryDSN: sentryDSN,
Debug: env.InsecureDev,
SiteID: siteID,
Expand Down
8 changes: 8 additions & 0 deletions cmd/frontend/internal/app/ui/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-TB4NLS7');</script>
<!-- End Google Tag Manager -->
<!-- Sentry -->
<script src='https://js.sentry-cdn.com/ae2f74442b154faf90b5ff0f7cd1c618.min.js' crossorigin="anonymous"></script>
jasongornall marked this conversation as resolved.
Show resolved Hide resolved
<!-- End Sentry -->
<!-- Datadog RUM -->
<script ignore-csp>(function(h,o,u,n,d) { h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
d=o.createElement(u);d.async=1;d.src=n;n=o.getElementsByTagName(u)[0];
n.parentNode.insertBefore(d,n)})(window,document,'script',
'https://www.datadoghq-browser-agent.com/datadog-rum-v4.js','DD_RUM');</script>
<!-- End Datadog RUM -->
{{ end }}
<script ignore-csp>
window.context = {{.Context }}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@babel/runtime": "^7.14.0",
"@datadog/browser-rum-slim": "^4.7.1",
"@gql2ts/from-schema": "^1.10.1",
"@gql2ts/language-typescript": "^1.9.0",
"@gql2ts/types": "^1.9.0",
Expand Down
Loading