Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
configValueOrDefault,
remoteConfig
} from '@/platform/remoteConfig/remoteConfig'
import { reportAssertFailureToRum } from '@/platform/telemetry/assertRumReporter'
import { syncHostUserIdWithFirebaseAuth } from '@/platform/telemetry/hostUserIdSync'
import '@/lib/litegraph/public/css/litegraph.css'
import router from '@/router'
Expand Down Expand Up @@ -99,6 +100,9 @@ setAssertReporter((message) => {
if (isDesktop) {
Sentry.captureMessage(message, { level: 'warning' })
}
if (isCloud) {
reportAssertFailureToRum(message)
}
if (isNightly) {
useToastStore(pinia).add({
severity: 'warn',
Expand Down
64 changes: 64 additions & 0 deletions src/platform/telemetry/assertRumReporter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

const { reportedErrors } = vi.hoisted(() => ({
reportedErrors: [] as { error: Error; context: unknown }[]
}))

vi.mock('@datadog/browser-rum', () => ({
datadogRum: {
addError: (error: Error, context: unknown) => {
reportedErrors.push({ error, context })
}
}
}))

async function loadReporter() {
vi.resetModules()
return import('./assertRumReporter')
}

describe('reportAssertFailureToRum', () => {
beforeEach(() => {
reportedErrors.length = 0
})

it('reports each distinct message to RUM exactly once', async () => {
const { reportAssertFailureToRum } = await loadReporter()

reportAssertFailureToRum('[Assertion failed]: node missing')
reportAssertFailureToRum('[Assertion failed]: node missing')
reportAssertFailureToRum('[Assertion failed]: widget missing')

await vi.waitFor(() => expect(reportedErrors).toHaveLength(2))
expect(reportedErrors.map(({ error }) => error.message)).toEqual([
'[Assertion failed]: node missing',
'[Assertion failed]: widget missing'
])
expect(reportedErrors[0].context).toEqual({ source: 'invariant-assert' })
})

it('stops reporting once the per-session cap is reached', async () => {
const { reportAssertFailureToRum } = await loadReporter()

for (let i = 0; i < 25; i++) {
reportAssertFailureToRum(`[Assertion failed]: failure ${i}`)
}

await vi.waitFor(() => expect(reportedErrors).toHaveLength(20))
expect(reportedErrors.at(-1)?.error.message).toBe(
'[Assertion failed]: failure 19'
)
})

it('builds the error synchronously so the stack holds the call site', async () => {
const { reportAssertFailureToRum } = await loadReporter()

function assertingCallSite() {
reportAssertFailureToRum('[Assertion failed]: stack check')
}
assertingCallSite()

await vi.waitFor(() => expect(reportedErrors).toHaveLength(1))
expect(reportedErrors[0].error.stack).toContain('assertingCallSite')
})
})
30 changes: 30 additions & 0 deletions src/platform/telemetry/assertRumReporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type * as DatadogRumModule from '@datadog/browser-rum'

const MAX_REPORTS_PER_SESSION = 20

const reportedMessages = new Set<string>()

let rumModule: Promise<typeof DatadogRumModule> | undefined

/**
* Send an assertion failure to Datadog RUM Error Tracking (cloud builds only).
*
* Invariants can fire from render loops, so reports are deduplicated by exact
* message and capped per session. The RUM SDK is imported lazily because it is
* only ever loaded on cloud, where `bootstrap.ts` has already resolved it.
*/
export function reportAssertFailureToRum(message: string): void {
if (reportedMessages.has(message)) return
if (reportedMessages.size >= MAX_REPORTS_PER_SESSION) return
Comment thread
mattmillerai marked this conversation as resolved.
reportedMessages.add(message)

// Built here, before awaiting the SDK, so the stack holds the assert call site.
const error = new Error(message)

rumModule ??= import('@datadog/browser-rum')
Comment thread
mattmillerai marked this conversation as resolved.
Outdated
void rumModule
.then(({ datadogRum }) => {
datadogRum.addError(error, { source: 'invariant-assert' })
Comment thread
mattmillerai marked this conversation as resolved.
})
.catch(() => {})
}
Loading