mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
web: initial Datadog RUM integration (#34063)
This commit is contained in:
parent
251459a29e
commit
b553e2dd9d
@ -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'
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import '../sentry/init'
|
||||
import '../monitoring/initMonitoring'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@ -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). */
|
||||
readonly datadog?: {
|
||||
clientToken: string
|
||||
applicationId: string
|
||||
}
|
||||
|
||||
/** Externally accessible URL for Sourcegraph (e.g., https://sourcegraph.com or http://localhost:3080). */
|
||||
externalURL: string
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import './sentry/init'
|
||||
import './monitoring/initMonitoring'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
|
||||
25
client/web/src/monitoring/datadog/datadogClient.ts
Normal file
25
client/web/src/monitoring/datadog/datadogClient.ts
Normal file
@ -0,0 +1,25 @@
|
||||
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
|
||||
*/
|
||||
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
client/web/src/monitoring/datadog/initDatadog.ts
Normal file
75
client/web/src/monitoring/datadog/initDatadog.ts
Normal file
@ -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:
|
||||
* 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
client/web/src/monitoring/index.ts
Normal file
2
client/web/src/monitoring/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { DatadogClient } from './datadog/datadogClient'
|
||||
export { isWebpackChunkError } from './shouldErrorBeReported'
|
||||
21
client/web/src/monitoring/initMonitoring.ts
Normal file
21
client/web/src/monitoring/initMonitoring.ts
Normal file
@ -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'
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && isResizeObserverLoopError) {
|
||||
error.stopImmediatePropagation()
|
||||
}
|
||||
})
|
||||
|
||||
initSentry()
|
||||
initDatadog()
|
||||
51
client/web/src/monitoring/sentry/initSentry.ts
Normal file
51
client/web/src/monitoring/sentry/initSentry.ts
Normal file
@ -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 })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
// Import only types to avoid including @sentry/browser into the main chunk.
|
||||
import type { Hub, init, onLoad } from '@sentry/browser'
|
||||
|
||||
import { authenticatedUser } from '../auth'
|
||||
|
||||
import { shouldErrorBeReported } from './shouldErrorBeReported'
|
||||
|
||||
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'
|
||||
|
||||
if (process.env.NODE_ENV === 'production' && isResizeObserverLoopError) {
|
||||
error.stopImmediatePropagation()
|
||||
}
|
||||
})
|
||||
|
||||
export type SentrySDK = Hub & {
|
||||
init: typeof init
|
||||
onLoad: typeof onLoad
|
||||
}
|
||||
|
||||
declare global {
|
||||
const Sentry: SentrySDK
|
||||
}
|
||||
|
||||
if (typeof Sentry !== 'undefined') {
|
||||
// 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(() => {
|
||||
// This check is required to please the Typescript compiler 🙂.
|
||||
if (window.context.sentryDSN) {
|
||||
Sentry.init({
|
||||
dsn: window.context.sentryDSN,
|
||||
release: 'frontend@' + window.context.version,
|
||||
beforeSend(event, hint) {
|
||||
// Report errors only in production environment.
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null
|
||||
}
|
||||
|
||||
// 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 })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -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"`
|
||||
@ -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 {
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
<!-- 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 }}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -444,6 +444,12 @@ type CustomGitFetchMapping struct {
|
||||
Fetch string `json:"fetch"`
|
||||
}
|
||||
|
||||
// Datadog description: Datadog configuration
|
||||
type Datadog struct {
|
||||
// RUM description: Datadog RUM configuration
|
||||
RUM *RUM `json:"RUM,omitempty"`
|
||||
}
|
||||
|
||||
// DebugLog description: Turns on debug logging for specific debugging scenarios.
|
||||
type DebugLog struct {
|
||||
// ExtsvcGitlab description: Log GitLab API requests.
|
||||
@ -1228,6 +1234,12 @@ type ObservabilityAlerts struct {
|
||||
Owners []string `json:"owners,omitempty"`
|
||||
}
|
||||
|
||||
// ObservabilityLogging description: Configuration for logging to external services.
|
||||
type ObservabilityLogging struct {
|
||||
// Datadog description: Datadog configuration
|
||||
Datadog *Datadog `json:"datadog,omitempty"`
|
||||
}
|
||||
|
||||
// ObservabilityTracing description: Controls the settings for distributed tracing.
|
||||
type ObservabilityTracing struct {
|
||||
// Debug description: Turns on debug logging of opentracing client requests. This can be useful for debugging connectivity issues between the tracing client and the Jaeger agent, the performance overhead of tracing, and other issues related to the use of distributed tracing.
|
||||
@ -1436,6 +1448,14 @@ type QuickLink struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// RUM description: Datadog RUM configuration
|
||||
type RUM struct {
|
||||
// ApplicationId description: Datadog application ID required RUM (https://docs.datadoghq.com/real_user_monitoring/browser/#setup).
|
||||
ApplicationId string `json:"applicationId"`
|
||||
// ClientToken description: Datadog client token required RUM (https://docs.datadoghq.com/real_user_monitoring/browser/#setup).
|
||||
ClientToken string `json:"clientToken"`
|
||||
}
|
||||
|
||||
// Ranking description: Experimental search result ranking options.
|
||||
type Ranking struct {
|
||||
// MaxReorderQueueSize description: The maximum number of search results that can be buffered to sort results. -1 is unbounded. The default is 24. Set this to small integers to limit latency increases from slow backends.
|
||||
@ -1863,6 +1883,8 @@ type SiteConfiguration struct {
|
||||
ObservabilityLogSlowGraphQLRequests int `json:"observability.logSlowGraphQLRequests,omitempty"`
|
||||
// ObservabilityLogSlowSearches description: (debug) logs all search queries (issued by users, code intelligence, or API requests) slower than the specified number of milliseconds.
|
||||
ObservabilityLogSlowSearches int `json:"observability.logSlowSearches,omitempty"`
|
||||
// ObservabilityLogging description: Configuration for logging to external services.
|
||||
ObservabilityLogging *ObservabilityLogging `json:"observability.logging,omitempty"`
|
||||
// ObservabilitySilenceAlerts description: Silence individual Sourcegraph alerts by identifier.
|
||||
ObservabilitySilenceAlerts []string `json:"observability.silenceAlerts,omitempty"`
|
||||
// ObservabilityTracing description: Controls the settings for distributed tracing.
|
||||
|
||||
@ -1063,6 +1063,47 @@
|
||||
"type": "string",
|
||||
"examples": ["https://sourcegraph.example.com"]
|
||||
},
|
||||
"observability.logging": {
|
||||
"description": "Configuration for logging to external services.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"datadog": {
|
||||
"description": "Datadog configuration",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"RUM": {
|
||||
"description": "Datadog RUM configuration",
|
||||
"type": "object",
|
||||
"required": ["clientToken", "applicationId"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"clientToken": {
|
||||
"description": "Datadog client token required RUM (https://docs.datadoghq.com/real_user_monitoring/browser/#setup).",
|
||||
"type": "string"
|
||||
},
|
||||
"applicationId": {
|
||||
"description": "Datadog application ID required RUM (https://docs.datadoghq.com/real_user_monitoring/browser/#setup).",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"datadog": {
|
||||
"RUM": {
|
||||
"clientToken": "aaa1111a1a11a1aa11a1aa1a111aaa111aa",
|
||||
"applicationId": "a11aa111-a1aa-1a11-aaa1-a11a111a111a"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"group": "Debug"
|
||||
},
|
||||
"observability.tracing": {
|
||||
"description": "Controls the settings for distributed tracing.",
|
||||
"type": "object",
|
||||
|
||||
20
yarn.lock
20
yarn.lock
@ -1722,6 +1722,26 @@
|
||||
dependencies:
|
||||
"@cspotcode/source-map-consumer" "0.8.0"
|
||||
|
||||
"@datadog/browser-core@4.7.1":
|
||||
version "4.7.1"
|
||||
resolved "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.7.1.tgz#5c78028217df455ad2fc981b51035144c25ac7ab"
|
||||
integrity sha512-PmHrTFYGvegheaub4sdbHmcFxwoCy2Ot/PBblSIJjfRddpbElBiSvBAOfFi3Wx8AK50a2yq0/YX9Nd/oL3Q0Dw==
|
||||
|
||||
"@datadog/browser-rum-core@4.7.1":
|
||||
version "4.7.1"
|
||||
resolved "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.7.1.tgz#252dfc7c72a8aa5157d27c0427c22bd28d7bc281"
|
||||
integrity sha512-USlAHIvMpNczCbtpwLkn6D41GnewuP6X2D+Rn34F3PzQ16CeCXEvXrC+8cJvjy/pcsYUeDK06ATRfK0gInd0tA==
|
||||
dependencies:
|
||||
"@datadog/browser-core" "4.7.1"
|
||||
|
||||
"@datadog/browser-rum-slim@^4.7.1":
|
||||
version "4.7.1"
|
||||
resolved "https://registry.npmjs.org/@datadog/browser-rum-slim/-/browser-rum-slim-4.7.1.tgz#d8ff6e103085615d69a0124d585b0723dc645654"
|
||||
integrity sha512-EmRIEu0LW698hnX707lwlORRgH1nsI3wtyuo/zqBDLR6rUrntIb4rNE9SL5fC6pZTg9eCb9V21sndDgIRxXn9w==
|
||||
dependencies:
|
||||
"@datadog/browser-core" "4.7.1"
|
||||
"@datadog/browser-rum-core" "4.7.1"
|
||||
|
||||
"@devicefarmer/adbkit-logcat@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/@devicefarmer/adbkit-logcat/-/adbkit-logcat-1.1.0.tgz#866d3406dc9f3791446adfe3ae622ffc48607db4"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user