ちきさんです。
Sentryでエラー収集してますか? してますよね。
さて、とあるWebアプリからSentryにエラーを送りたいが、下記の要件を満たす必要があったという話です。
(Sentry JavaScript SDK => Raven)
- Ravenをバンドルしてはいけない。(極限まで容量を削減する必要があるため)
- Sentryへのエラー送信がページ表示のパフォーマンスに影響を与えてはいけない。
- 同じページ上で発生する関係ないJavaScriptのエラーをSentryに送信してはいけない。
- 同じページに別のアプリのRavenが存在していたらそれを邪魔してはいけない。
なかなか厳しい要件ですね。
これらを満たすためにやったことは、
- Ravenをバンドルせずにエラーを送信する必要が生じてから非同期でCDNから読み込む。
- 同じページに別のアプリのRavenが既に存在していたらCDNから読み込まない。(エラー収集を諦める)
-
window.Raven
を変数に格納し直してそれを使う。 - RavenがSentryにエラーを送信するために
window.onerror
を書き換えるので、エラー送信後に都度それを元に戻す。(uninstall()
) - エラー送信の度に
install()
とuninstall()
をセットで行う。
以上です。
無理やり感があるので公式である程度柔軟にできるように対応していただきたいものです。
サンプルコードは↓
SentryService.ts
export class SentryService {
private isRavenRequested = false
private isRavenLoadedByThisApp = false
private isRavenSetupByThisApp = false
private myRaven: any = null;
private loadRaven(): Promise<void> {
if ((window as any).Raven && !this.myRaven) {
return Promise.reject('Raven is already loaded by other apps.');
} else if (this.myRaven) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
if (this.isRavenRequested) {
resolve();
return;
}
const ravenVersion = '3.22.1'
const s = window.document.createElement('script')
s.src = `https://cdn.ravenjs.com/${ravenVersion}/raven.min.js`
s.async = true
s.crossOrigin = 'anonymous'
s.onload = () => {
this.isRavenLoadedByThisApp = true
if ((window as any).Raven && !(window as any).Raven.isSetup() && (window as any).Raven.VERSION === ravenVersion) {
this.myRaven = (window as any).Raven; // window.Ravenを変数にコピーして、
delete (window as any).Raven; // window.Ravenを削除する。
this.isRavenSetupByThisApp = true;
} else {
reject('Raven is already setup by other apps.')
}
resolve();
}
s.onerror = reject
window.document.body.appendChild(s)
this.isRavenRequested = true
})
}
captureException(e: Error): void {
this.loadRaven()
.then(() => {
if (this.myRaven && this.isRavenLoadedByThisApp && this.isRavenSetupByThisApp) {
console.log('captureException', e)
try {
this.myRaven
.config('https://<key>@sentry.io/<project>')
.clearContext()
.setTagsContext({
type: 'foo',
account: '123',
})
.setExtraContext({
log: ['do something', 'next something', 'finally something'],
state: {
x: 'bar',
y: true
}
})
.install() // window.onerrorハンドラを書き換える。
.captureException(e) // Sentryにエラーを送る。
.uninstall(); // window.onerrorハンドラを元に戻す。
console.log('Sentryにエラーを送りました。')
} catch (_) {
this.myRaven.uninstall()
}
} else {
console.log('SentrySDKがまだ読み込まれていません。')
}
})
.catch(console.log)
}
}