サイトのエラー...どうすればいいですか?

コードが実稼働に入ると、プログラマは便利な機能とともにエラーを外部にリリースします。 たとえば、特定のサイトで、小さな障害が発生する可能性があります。これは、さまざまな理由に起因するものであり、その原因にはなりません。 自分の仕事を知っている開発者にとって、自分の過ちに対処し、耐えなければならない冒険についての彼らの話を聞いて、結果としてそれらを修正できるメカニズムを提供することは素晴らしいことです。







今日、プログラマーのデビッド・ギルバートソンによる記事の翻訳を共有したいと思います。彼は、Reactで書かれたWebプロジェクトのエラーを追跡して再現できる実験システムについて話しています。 このアプローチは他の環境に移すことができると信じていますが、すべてが順調です。



エラー報告のアプローチ



おそらく、Webプロジェクトのエラーに関する情報を収集するために、このようなシンプルなシステムを使用しているのでしょう(次の例では、私に石を投げないでください):



window.onerror = err => fetch(`/errors/${err}`);
      
      





エラーレポートを確認するには、フレンドリーなIT専門家に依頼して、 /errors



で始まる404ページに関するすべての記録を含むファイルを提供するだけで十分です。



ただし、このアプローチで得られる「コード」は、エラーが発生した正確な場所を見つけるのに役立ちません。 おそらく、ここで何かを改善し、ファイルと行番号に関する情報を含むエラーメッセージを生成する必要があります。



 window.addEventListener('error', e => { fetch('/errors', {   method: 'POST',   body: `${e.message} (in ${e.filename} ${e.lineno}:${e.colno})`, }); });
      
      





このコードは良識のどこかでバランスを取っていますが、これまでのところ、それはもっと深刻な何かの骨格にすぎません。 エラーが特定のデータに関連している場合、行番号に関する情報は特に有益ではありません。



エラーが発生した時点でユーザーのアクティビティに関する完全なレポートがあれば便利です。これにより、障害が発生した状況を再現できます。 たとえば、次のようなもの:





ユーザーアクティビティレポート



最も興味深いのは、ユーザーが製品に関する詳細情報のページに移動し(ステップ4)、購入ボタン(最後の5番目のステップ)をクリックしたことです。



特定の製品のデータに何か疑わしいことが起こっているとすぐに推測できるため、同じリンクにアクセスして、「これを$で購入」という購入ボタンをクリックします。



もちろん、これを行った後、私は同じ間違いを見ます。 この特定のアイテムには価格がないため、 toLocaleString



を呼び出すと失敗します。 私たちの前に、あまり経験のない開発者の典型的な監督があります。



しかし、ユーザーのサイトとのやり取りがはるかに複雑な場合はどうでしょうか? ユーザーが作業をURLに反映していない多くのタブの1つであるか、フォームのデータをチェックしているときにエラーが発生した可能性があります。 リンクをたどってボタンをクリックしても、このようなエラーは明らかになりません。



この状況では、エラーが発生するまですべてのユーザーアクションを再現できるようにしたいと考えています。 理想的には、再生中に「次のステップ」というボタンをクリックするだけです。



想像力を失っていないなら、これを自分自身に想像してみてください。





DOMを観察してユーザーアクションを再現する



画面に表示されるエラー情報とエディターで開くファイルは、Reactアプリの作成のメリットです。



実験の形で、「モデレートされていないユーザビリティテスト」のようなものを実行できるシステムを実際に構築したことに注意してください。 ユーザーのアクションを追跡して再生するコードを作成し、知識のある1人のジョンに、彼がこのすべてについてどう思うかを尋ねました。 彼はこれは馬鹿げたアイデアだと言ったが、私のコードはエラーを再現するのに役立つだろうと付け加えた。



実際、ここでこれについてお話したいと思います。 ありがとう、ジョン。



システムコア



問題のコードはここにあります 。 あなたは私の話よりもそれを読むことに興味があるかもしれません。 以下に関数の簡易バージョンを示し、全文へのリンクを提供します。



さまざまなユーザーアクションをインターセプトするいくつかの関数を含むモジュールrecord.jsがあります。 これはすべてjourney



オブジェクトに分類され、エラーが発生したときにサーバーに転送できます。



アプリケーションの入力ポイントで、次のようなstartRecording()



関数を呼び出して情報の収集を開始します。



 const journey = { meta: {}, steps: [], }; export const startRecording = () => { journey.meta.startTime = Date.now(); journey.meta.startUrl = document.location.href; journey.meta.screenWidth = window.innerWidth; journey.meta.screenHeight = window.innerHeight; journey.meta.userAgent = navigator.userAgent; };
      
      





エラーが発生した場合、 journey



オブジェクトは、たとえば、分析のためにサーバーに送信できます。 これを行うには、対応するイベントハンドラーが接続されます。



 window.addEventListener('error', sendErrorReport);
      
      





sendErrorReport



関数sendErrorReport



journey



オブジェクトと同じモジュールで宣言されます。



 export const sendErrorReport = (err) => { journey.meta.endTime = Date.now(); journey.meta.error = `${err.message} (in ${err.filename} ${err.lineno}:${err.colno})`; fetch('/error', {   method: 'POST',   body: JSON.stringify(journey) }) .catch(console.error); };
      
      





ところで、誰かがJSON.stringify(err)



コマンドがエラーの本文を提供しない理由を説明できれば、非常にクールになります。



これまでのところ、これらすべてに特別な利点はありません。 しかし、今では他のすべてを構築するためのフレームワークがあります。



アプリケーションが状態ベースの場合(つまり、DOMがいくつかの主要な状態に基づいてのみ表示される場合)、より簡単に生きることができます(エラーが発生する可能性が低くなると想定してリスクがあります)。 エラーを再現しようとすると、単純に状態を再作成できます。これにより、このエラーが発生する可能性が高くなります。



アプリケーションが最新のテクノロジーに基づいていない場合、バインディングを使用し、ユーザーがページと対話する方法に直接基づいて何かを表示すると、問題はもう少し複雑になります。 エラーを再現するには、マウスクリック、要素の損失とフォーカスに関連するイベント、およびキーボードキーを押すことを再現する必要があります。 確かに、ユーザーがクリップボードからフィールドに何かを貼り付けた場合の対処方法を言うのは難しいと思います。 ここで私は実験で幸運を願うことができます。



私は認めたいです-私は怠け者で利己的な人ですので、私が話すことは私が扱う技術、すなわちReactとReduxで構築されたプロジェクトに焦点を当てます。



これがまさに私が傍受したいものです:





Reduxアクションインターセプト



以下は、 journey



オブジェクトのReduxアクションをインターセプトして保存するために使用されるコードです。



 export const captureActionMiddleware = () => next => action => { journey.steps.push({   time: Date.now(),   type: INTERACTION_TYPES.REDUX_ACTION,   data: action, }); return next(action); };
      
      





最初に、構造= () => next => action => {



を見ることができます。これは、一見理解できないことはまったく不可能です。 それでも理解できない場合は、読んでください 。 確かに、これを掘り下げるのではなく、もっと重要なことをするほうがいいでしょう。たとえば、幸せな笑顔を描く練習をします。これは、誕生日を祝福するときに役立ちます。



このコードで理解する最も重要なことは、プロジェクトで果たす役割です。 つまり、彼はReduxの「アクション」を実行中にjourney



オブジェクトに配置するのに忙しくしています。



次に、Reduxリポジトリを作成するときに上記の関数を適用し、リンクをこのapplyMiddleware()



フレームワークの関数に渡します。



 const store = createStore( reducers, applyMiddleware(captureActionMiddleware), ); ReactDOM.render( <Provider store={store}>   <App /> </Provider>, document.getElementById('root') );
      
      





URLの変更を記録する



URL変更キャプチャが実行される場所は、アプリケーションのルーティング方法によって異なります。



ReactルーターはURLの変更を検出するのにあまり役に立たないので、 このアプローチ、または多分これに頼らなければなりません。 Reactルーターを使用して、 onRouteChange



ハンドラーを設定したいだけonRouteChange



。 これが私だけでなく必要であることは注目に値します。 たとえば、多くは、仮想ページビュー情報をGoogleアナリティクスに送信する必要に直面しています。



それはともかく、私はほとんどのサイトのために私自身のルーティングシステムを書くことを好む、それは約17分しかかからず、最終的に、起こることは非常に高速であることが判明したからだ。



URLの変更を傍受するために、URLが変更されるたびに呼び出される次の関数を準備しました。



 export const captureCurrentUrl = () => { journey.steps.push({   time: Date.now(),   type: INTERACTION_TYPES.URL_CHANGE,   data: document.location.href, }); };
      
      





私は彼女を2か所で呼びます history.push()



コマンドを実行してURLを更新するのと同じ場所で、またユーザーがブラウザの[



]ボタンをクリックすると呼び出されるpopstate



イベントでも:



 window.addEventListener('popstate', () => { //  - ,     captureCurrentUrl(); });
      
      





ユーザーアクションの記録



おそらく、これは文字通りどこにでも埋め込む必要があるため、サイトでの作業に関する情報を傍受するための最も「侵入的な」メカニズムです。 それが私の欲望だけに依存していれば、私はこれを気にしません。 しかし、私が思ったように、ユーザーがどこをクリックしたかを正確に知らなければ再現できないエラーに遭遇しました。



いずれにせよ、タスクは面白かったので、ここでその解決策について説明します。 Reactで開発するとき、私は常に<Link>



および<Button>



コンポーネントを使用します;その結果、集中クリック代行受信システムの開発は非常に簡単です。 <Link>



見てください:



 const Link = props => ( <a   href={props.to}   data-interaction-id={props.interactionId} //     onClick={(e) => {     e.preventDefault();         captureInteraction(e); //           historyManager.push(props.to);   }} >   {props.children} </a> );
      
      





ここで話しているのは、 data-interaction-id={props.interactionId}



およびcaptureInteraction(e);







セッションを再現するときが来たら、ユーザーがクリックしたものを強調したいと思います。 これを行うには、何らかのセレクターが必要です。 クリックした要素には識別子( id



)があると自信を持って述べることができますが、何らかの理由で覚えていないので、監視システム用に特別に設計されたものの方が良いと判断しましたユーザーアクティビティ用。



captureInteraction()



関数は次のとおりです。



 export const captureInteraction = (e) => { journey.steps.push({   time: Date.now(),   type: INTERACTION_TYPES.ELEMENT_INTERACTION,   data: {     interactionId: e.target.dataset.interactionId,     textContent: e.target.textContent,   }, }); };
      
      





ここで完全なコードを見つけることができます。このコードでは、セッションを再生した後、要素が再び見つかるようにチェックされます。



他の情報と同様に、必要なものを収集して、 journey.steps.push



コマンドを実行します。



スクロール



私が残しているのは、ユーザーが表示しているページのどの部分を正確に知るために、スクロールデータを記録するかだけを伝えることです。 たとえば、ページが一番下に巻き戻されてフォームに入力し始めた場合、スクロールせずにこれを再現してもあまりメリットはありません。



多くの小さなイベントを記録する際にシステムリソースを無駄にせず、 Lodash



を使用するために、連続するすべてのスクロールイベントを1つのイベントに収集します。



 const startScrollCapturing = () => { function handleScroll() {   journey.steps.push({     type: INTERACTION_TYPES.SCROLL,     data: window.scrollY,   }); } window.addEventListener('scroll', debounce(handleScroll, 200)); };
      
      





このコードの作業バージョンでは 、連続スクロールに関連するイベントは除外されています。



startScrollCapturing()



関数は、アプリケーションが初めて起動されたときに呼び出されます。



追加のアイデア



これは私のプロジェクトで使用されていないアイデアの短いリストです。 おそらく、それらを実装する価値があると思うでしょう。





ここでは、記事の元のバージョンを公開した後に追加しました。 いくつかのコメントで述べられていることとは対照的に、この資料で説明されている方法は、個人データのセキュリティまたは保護に関する追加の懸念を引き起こさないことに注意できます。 この場合、すでにユーザーの機密データを使用している場合、そのようなデータの収集と保存に適用される要件は、エラーレポートを準備および送信するときにも適用される必要があります。 たとえば、ユーザーに対応する質問をせずにフォームデータを自動的に保存しない場合、ユーザーに質問せずにエラーレポートを自動的に送信しないでください。 ユーザーの個人データを送信する前に、特別なフィールドに設定されたチェックマークの形でユーザーから同意を得る義務がある場合は、エラーレポートを送信する前に同じことを行う必要があります。 システムに登録するときにアドレス/signup



にユーザーデータを送信する場合、またはエラーが発生する場合にアドレス/error



にユーザーデータを送信する場合、特別な違いはありません。 あちこちで最も重要なことは、データを正しく合法的に扱うことです。



おそらく、既に会話を終了していると思われますが、この時点では、ユーザーがサイトで行っていることのみを記録しました。 次に、最も興味深い部分、つまり録音の再生を見てみましょう。



ユーザーアクションの再生



サイトでの作業中にユーザーが実行したアクションの再現については、次の2つの問題について説明します。





ユーザーアクションを再現するためのインターフェイス



このページでは、ユーザーのアクションを繰り返すために、 iFrame



使用されますiFrame



では、Webサイトが開き、その上でjourney



オブジェクトに以前に記録されたステップが再生されます。



このページは、エラーが発生したセッションに関する情報をダウンロードし、記録された各ステップをサイトに送信します。サイトは状態を変更し、同じエラーが発生します。



このページを開くと、見苦しいシンプルなインターフェイスが表示されます。その後、iPadで表示されているかのようにサイトが読み込まれます(ここでは、タブレットの通常の写真を使用しました。

これは、記事の冒頭で示したのと同じアニメーション画像です。 ここでそのコードを見つけることができます。





ユーザーセッションを再生するプロセス



[ Next step



]ボタンをクリックすると、 iFrame



iFrame.contentWindow.postMessage(nextStep, '*')



構造を使用してメッセージiFrame



送信されます。 URLの変更に関連する例外が1つあります。 つまり、同様の状況では、 iFrame



src



プロパティは単に変更されます。 アプリケーションの場合、これは実際にはページ全体の更新であるため、これが機能するかどうかは、ページ間でアプリケーションの状態を転送する方法によって異なります。



わからない場合、 postMessage —



、異なるウィンドウ間の対話を提供するために作成されたWindowオブジェクトのメソッドです (この場合、これはページのメインウィンドウとiFrame



開かれたウィンドウです)。



実際、ユーザーアクションを再現するページについて言えることはこれだけです。



外部からサイトを管理するためのメカニズム



サイトでの作業中にユーザーアクションを再現するメカニズムは、 playback.jsファイルに実装されています。



アプリケーションの起動時に、リポジトリに分類され、後で呼び出すことができるメッセージを予期する関数を呼び出します。 これは開発モードでのみ行われます。



 const store = createStore( //     ); if (process.env.NODE_ENV === 'development') { startListeningForPlayback(store); }
      
      





これは 、このコードが使用される場所です。



興味のある関数は次のようになります。



 export const startListeningForPlayback = (store) => { window.addEventListener('message', (message) => {   switch (message.data.type) {     case INTERACTION_TYPES.REDUX_ACTION:       store.dispatch(message.data.data);       break;     case INTERACTION_TYPES.SCROLL:       window.scrollTo(0, message.data.data);       break;     case INTERACTION_TYPES.ELEMENT_INTERACTION:       highlightElement(message.data.data.interactionId);       break;     default:       //  -   ,          return;   } }); };
      
      





ここでは、そのフルバージョンを見つけることができます。



Reduxアクションを使用する場合、それらはリポジトリにディスパッチされ、それ以上はディスパッチされません。



スクロールが再生されるとき、まさにあなたが期待できることが行われます。 この状況では、ページの幅が正しいことが重要です。 プロジェクトリポジトリを見ると、ユーザーがウィンドウのサイズを変更したり、たとえばサイトが表示されているモバイルデバイスを回転させたりすると、すべてが正しく動作することがscrollIntoView() —



scrollIntoView() —



を呼び出すことscrollIntoView() —



、いずれにしても合理的な解決策だと思います。



highlightElement()



関数は、単純に要素の周囲に境界線を追加します。 彼女のコードは次のようになります。



 function highlightElement(interactionId) { const el = document.querySelector(`[data-interaction-id="${interactionId}"]`); el.style.outline = '5px solid rgba(255, 0, 0, 0.67)'; setTimeout(() => {   el.style.outline = ''; }, 2000); }
      
      





いつものように、これはこの関数の完全なコードです。



まとめ



React / Reduxアプリケーションでの簡単なエラー報告システムを見ました。 実際に役立ちますか? これは、プロジェクトに表示されるエラーの数と、それらを見つけるのがどれほど難しいかによると思います。



エラーが発生した場合は、URLを書き留めてその情報を保存しておけば、問題の原因を特定するのに役立ちます。 または、ユーザーアクションを記録するシステムは成功しているように見えますが、サイトとのセッションを再生するページは成功していません。 たとえば、iOSのSafari 9でのみ発生するエラーが発生した場合、セッション再生ページは役に立たなくなります。エラーを繰り返すことはできないからです。



いろいろな研究について話しますが、その中の1つについて話しましたが、実際のプロジェクトの1つで実験の結果として作成されたものを埋め込む準備ができているかどうかを自問するとき、私にとって真実の瞬間が来ます。 この場合、この質問に対する答えはノーです。



いずれにせよ、ユーザーアクションを傍受して再現するシステムで作業することは、私が何か新しいことを学ぶことを可能にする興味深い体験です。 さらに、あるサイトに監視システムを迅速に実装する必要がある場合、いつかこれらすべてが役に立つと信じています。



親愛なる読者! バグはどのように処理しますか? .



All Articles