feat: cache report error

This commit is contained in:
EnixCoda 2022-07-02 23:09:49 +08:00
parent bcc34949ab
commit 919ce07b20
6 changed files with 76 additions and 14 deletions

View file

@ -2,6 +2,8 @@ import * as Sentry from '@sentry/browser'
import { Middleware } from 'driver/connect.js'
import { IN_PRODUCTION_MODE, VERSION } from 'env'
import { platform } from 'platforms'
import { atomicAsyncFunction } from 'utils/general'
import { storageHelper, storageKeys } from 'utils/storageHelper'
const PUBLIC_KEY = 'd22ec5c9cc874539a51c78388c12e3b0'
const PROJECT_ID = '1406497'
@ -69,12 +71,43 @@ export const withErrorLog: Middleware = function withErrorLog(method, args) {
]
}
export function raiseError(
// 1. Only cache errors for current version, so that future errors can still be exposed
// - Run migration to clean on every update
// 2. Only cache the top 2 levels of stack, e.g.
// ```
// Error: cannot get current branch
// at Module.getCurrentBranch (chrome-extension://______id______/index.js:1:1)"
// ```
// So that different initial callees would not result in multiple records
const MAX_STACK_LEVEL = 2
const hasTheErrorBeenReported = atomicAsyncFunction(async function hasTheErrorBeenReported(
error: Error,
) {
const message = error.stack?.split('\n').slice(0, MAX_STACK_LEVEL).join('\n')
if (!message) return true // ignore errors that has no stack
type ErrorCache = string
const cache: ErrorCache[] =
(await storageHelper.get(storageKeys.raiseErrorCache))?.[storageKeys.raiseErrorCache] || []
const has = cache.includes(message)
if (!has) {
cache.push(message)
await storageHelper.set({ [storageKeys.raiseErrorCache]: cache })
}
return has
})
export async function raiseError(
error: Error,
extra?: {
[key: string]: any
},
) {
if (await hasTheErrorBeenReported(error)) return
if (!IN_PRODUCTION_MODE || platform.isEnterprise()) {
// ignore errors from enterprise to get less noise on Sentry
console.error(error)

View file

@ -1,6 +1,6 @@
import { SearchMode } from 'components/searchModes'
import { platformName } from 'platforms'
import { storageHelper } from 'utils/storageHelper'
import { Storage, storageHelper } from 'utils/storageHelper'
import { migrateConfig } from './migrations'
export type Config = {
@ -83,7 +83,7 @@ function applyDefaultConfigs(configs: Partial<Config>) {
}, {} as Config)
}
export type VersionedConfig<SiteConfig> = Record<string, SiteConfig> & { configVersion: string }
export type VersionedConfig<SiteConfig> = Record<string, SiteConfig> & Storage
export const configRef: Partial<Config> = {}
const updateConfigRef = async (config: Partial<Config>) => {

View file

@ -1,12 +1,11 @@
import { storageHelper } from 'utils/storageHelper'
import { Migration } from '.'
import { Storage } from '../../storageHelper'
import { Config, VersionedConfig } from '../helper'
export const migration: Migration = {
version: '1.3.4',
async migrate(version) {
const config: any | void = await storageHelper.get<VersionedConfig<Config> & Storage>([
const config: any | void = await storageHelper.get<VersionedConfig<Config>>([
'configVersion',
'platform_undefined',
'platform_GitHub',

View file

@ -0,0 +1,13 @@
import { storageHelper } from 'utils/storageHelper'
import { Migration, onConfigOutdated } from '.'
import { version } from '../../../../package.json'
// Run every time a new version is released.
export const migration: Migration = {
version,
async migrate(version) {
await onConfigOutdated(version, async () => {
await storageHelper.set({ raiseErrorCache: [] })
})
},
}

View file

@ -1,10 +1,12 @@
import { storageHelper } from 'utils/storageHelper'
import { storageHelper, storageKeys } from 'utils/storageHelper'
import { version } from '../../../../package.json'
import { Storage } from '../../storageHelper'
import { migration as v1v0v1 } from './1.0.1'
import { migration as v1v3v4 } from './1.3.4'
import { migration as v2v6v0 } from './2.6.0'
import { migration as v3v0v0 } from './3.0.0'
import { migration as v3v5v0 } from './3.5.0'
import { migration as clearRaiseErrorCache } from './clearRaiseErrorCache'
export type Migration = {
version: string
@ -13,21 +15,29 @@ export type Migration = {
export async function migrateConfig() {
const migrations: Migration[] = [v1v0v1, v1v3v4, v2v6v0, v3v0v0, v3v5v0]
migrations.push(clearRaiseErrorCache) // Make sure this is run after other version-specific migrations
for (const { version, migrate } of migrations) {
await migrate(version)
}
await storageHelper.set({ [storageKeys.configVersion]: version })
}
export async function onConfigOutdated<T extends { [key: string]: any }>(
configVersion: string,
migrationConfigVersion: string,
runIfOutdated: (config: T) => Async<void>,
) {
const config = await storageHelper.get<Storage>()
if (config && config.configVersion < configVersion) {
const { configVersion: $configVersion, ...restConfig } = config
await runIfOutdated(restConfig as T)
await storageHelper.set({ configVersion })
if (config) {
const {
[storageKeys.configVersion]: savedConfigVersion,
[storageKeys.raiseErrorCache]: __,
...restConfig
} = config
if (savedConfigVersion < migrationConfigVersion) {
await runIfOutdated(restConfig as T)
}
}
}

View file

@ -1,8 +1,15 @@
const localStorage = browser.storage.local
const keys = {
configVersion: 'configVersion',
raiseErrorCache: 'raiseErrorCache',
} as const
export const storageKeys = keys
export type Storage = {
// save root level `configVersion` for easier future migrating
[key in EnumString<'configVersion'>]: string
// save root level keys for easier future migrating
[key in EnumString<keyof typeof keys>]: string
// separate different platform configs to simplify interactions with browser storage API
// e.g.
@ -12,7 +19,7 @@ export type Storage = {
async function get<
T extends {
[key: string]: any
}
},
>(mapping: string | string[] | null = null): Promise<T | undefined> {
try {
return (await localStorage.get(mapping || undefined)) as T