![画像](https://habrastorage.org/files/7a4/8f2/3ac/7a48f23ac12f4d859602e82eb8d0c630.jpg)
問題を解決するための時間を短縮するには、エラーをすばやく見つけて、原因をできるだけ正確に把握し、できればすべてをまとめて収集する必要があります。
カットの下での私の決定についてお話しします。
1.目的
議論の後、クライアントとサーバーからエラー情報を収集し、後続の応答のためにデータを転送または処理できるメカニズムを作成することにしました。 このメカニズムにより、将来、コードを不必要に書き換えることなくデータを操作する方法を追加し、構成から作業方法、順序などを変更できるようにする必要があります。
キーポイント:
- フロントエンドとバックエンドの両方でバグをキャッチ
- 以下を含む複数のエラーハンドラを追加する機能 将来的に
- 大量のデバッグ情報
- 各プロジェクトの柔軟な構成
- 高い信頼性
2.決定
サーバーの起動時に、特別なエラーハンドラーを読み込むことが決定されました。ドライバーは、構成から順番と優先順位が読み込まれます。 フロントエンドのエラーはサーバーに送信され、そこで残りと一緒に処理されます。
主なアイデアは、エラーが発生し、スタックがグローバルエリアに巻き戻されると、分散コレクターを使用してエラークラスにデバッグ情報が追加されるということです。 グローバルエリアに該当する場合、エラーはエラードライバーを使用してインターセプトおよび処理されます。
2.1エラークラス
カスタムエラークラスが標準から継承されました。 コンストラクターがエラーを受け入れ、「アラームレベル」を指定してデバッグデータを追加する機能。 このクラスは、フロントエンドファイルとバックエンドファイル用の単一のツールにあります。
以降、コードはライブラリco、socket.io、sugar.jsを使用しました
フルクラスコード
app.Error = function Error(error,lastFn){ if(error && error.name && error.message && error.stack){ , this.name=error.name; this.message=error.message; this.stack=error.stack; this.clueData=error.clueData||[]; this._alarmLvl=error._alarmLvl||'trivial'; this._side=error._side || (module ? "backend" : "frontend");// return; } if(!app.isString(error)) error='unknown error'; this.name='Error'; this.message=error; this._alarmLvl='trivial'; this._side=module ? "backend" : "frontend"; this.clueData=[]; if (Error.captureStackTrace) { Error.captureStackTrace(this, app.isFunction(lastFn)? lastFn : this.constructor); } else { this.stack = (new Error()).stack.split('\n').removeAt(1).join();// } }; app.Error.prototype = Object.create(Error.prototype); app.Error.prototype.constructor = app.Error; app.Error.prototype.setFatal = function () {//getter/setters this._alarmLvl='fatal'; return this; }; app.Error.prototype.setTrivial = function () { this._alarmLvl='trivial'; return this; }; app.Error.prototype.setWarning = function () { this._alarmLvl='warning'; return this; }; app.Error.prototype.getAlarmLevel = function () { return this._alarmLvl; }; app.Error.prototype.addClueData = function(name,data){// var dataObj={}; dataObj[name]=data; this.clueData.push(dataObj); return this; };
Promiseの使用例:
socket.on(fullName, function (values) { <...> method(values)// api .then(<...>) .catch(function (error) {// throw new app.Error(error)// .setFatal()// " " .addClueData('api', {// fullName, values, handshake: socket.handshake }) }); });
try-catchについても同じことを行います。
2.2フロントエンド
フロントエンドの場合、問題は、トランスポートライブラリ(この場合はsocket.io)が読み込まれる前でもエラーが発生する可能性があることです。
この問題を回避するには、一時変数にエラーを収集します。 グローバルスコープからエラーをインターセプトするには、window.onerrorを使用します。
app.errorForSending=[]; app.sendError = function (error) {// app.io.emit('server error send', new app.Error(error)); }; window.onerror = function (message, source, lineno, colno, error) {// app.errorForSending.push(// . new app.Error(error) .setFatal());// , }; app.events.on('socket.io ready', ()=> {// window.onerror = function (message, source, lineno, colno, error) {// app.sendError(new app.Error(error).setFatal()); }; app.errorForSending.forEach((error)=> {// , app.sendError(error); }); delete app.errorForSending; }); app.events.on('client ready', ()=> {// window.onerror = function (message, source, lineno, colno, error) { app.sendError(error); }; });
一部のライブラリでは、エラーをスローせず、コンソールに印刷するのが
function wrapConsole(name, action) { console['$' + name] = console[name];// console[name] = function () { console['$' + name](...arguments);// app.sendError( new app.Error(`From console.${name}: ` + [].join.call(arguments, '' ),// console[name])// ( v8) .addClueData('console', {// consoleMethod: name, arg : Array.create(arguments) })[action]());// }; } wrapConsole('error', 'setTrivial'); wrapConsole('warn', 'setWarning'); wrapConsole('info', 'setWarning');
2.3サーバー
ここまで読んで疲労で死んでいないすべての人にとって、私たちは最も興味深いものを残しました。 結局、エラーを受け取るドライバーの初期化と実行だけでなく、
- エラーを初期化/処理するプロセスの各ドライバーが別のサーバーと「真剣に話し合う」必要がある場合や、生命の宇宙の主要な質問に対する答えを計算する必要がある場合でも、すべてができるだけ早く機能する必要があります。
- スペアおよび冗長ドライバーの柔軟なシステム。
- 前のドライバーに障害が発生した場合にスペアドライバーを動的に実行します。
- ドライバーの操作中に発生した例外は、動作しているドライバーに送信する必要があります。
- フロントエンドと、グローバルドメインnode.jsに表示されるエラーをキャッチして処理します。
すべてのコードはgithub(下のリンク)で表示できます。次に、主なタスクを見てみましょう。
- 速度の並列スタート
これらの目的のために、yield [...](またはPromise.all(...))を使用します。配列の各関数がエラーをスローすべきではないという事実を考慮し、エラーのある関数が複数ある場合、それらすべてを処理することはできません - 柔軟な構成
すべてのドライバーは「ドライバーパッケージ」に含まれており、優先順位によってアレイに配置されます。 エラーはドライバーパッケージ全体に直ちに送信されます。パッケージ全体が機能しない場合、システムは次のパッケージに進みます。 - ダイナミックスタート
初期化するときに、すべてのドライバーを「開始されていない」としてマークします。
最初のドライバーパッケージを開始するとき、「開始済み」または「不良」としてマークします。
現在のパッケージで送信する場合、「bad」をスキップして「started」に送信し、「not started」を実行します。 エラーをスローしたドライバーは不良としてマークされ、先に進みます。 現在のパッケージ内のすべてのドライバーが不良としてマークされている場合は、次のパッケージに進みます。 - まだ動作中のドライバーでドライバーエラーを送信する
エラードライバー自体でエラーが発生した場合(ちょっとしたトートロジー)、特別な配列に書き込みます。 最初のライブドライバーを見つけた後、それを通じてドライバーエラーを送信し、エラー自体(エラー送信時にドライバーがクラッシュした場合)とドライバーエラーを送信します。 - フロント/バックエンドでバグをキャッチ
フロントエンド用の特別なAPIを作成し、process.on( 'uncaughtException'、fn)およびprocess.on( 'unhandledRejection'、fn)を介してnode.js例外をキャッチします
3.結論
説明したエラーメッセージの収集と送信のメカニズムにより、エンドユーザーの前であっても、エラーに即座に対応でき、最後に押されたボタンをエンドユーザーに問い合わせることなく実行できます。
開発について考える場合、将来的にいくつかの便利な機能を追加できます。
- 壊れたドライバーを無効にするポリシーを変更する
たとえば、しばらくしてからドライバの機能を再確認する機能を追加します。 - フロントエンドにドライバーコードを挿入する機能
追加情報を収集するために使用できます。 - ロギングプリセット
繰り返しの一般的な情報収集機能のDRY (最近読み込まれたページ、最近使用されたapi)
実例はgithubで見ることができます。 アーキテクチャについては、oldらないでください。この例は、delete-from-project-all-unnecessaryメソッドを使用して行われました。
コメントさせていただきます。