マイクロゴーサービスの明確なアーキテクチャ(クリーン・アーキテクチャ):ログ管理

グッドロギングが大幅に符号化効率を向上させること、デバッグ時に簡単な問題を発見するために、ログデータの富を提供することができます。より良い記録が提供する自動化された情報は、ログ情報は、また、重要なデータを見つけることは容易で、簡潔であることを道のニーズに提示されています。

ログイン要求:
  1. 他のビジネスジャーナルライブラリにスイッチにコードを変更する必要はありません

  2. 直接、ロギングライブラリに頼ることなく、

  3. アプリケーション全体のロギングライブラリの唯一のグローバルインスタンスは、あなたが、ログの設定を変更して、一つの場所にプログラム全体に適用することができます。

  4. あなたは簡単に、例えば、コードを変更せずにログレベルをログオプションを変更することができます

  5. 実行時に動的にログレベルを変更する機能

リソースハンドル:なぜ別のログデータベース

アプリケーションは、データベース、ファイルシステム、ネットワーク接続、SMTPサーバなどの外部リソースを処理する必要がある場合、それは通常、リソースハンドル(リソースハンドラ)が必要です。依存性注入、それは根本的なリソースへのアクセスを処理するためのリソースを使用できるように、コンテナにリソースハンドルを作成しているが、各業務機能の中に注入されます。このアプリケーションでは、リソースハンドルインタフェースは、サービスレイヤは、リソースハンドルの任意の特定の実装に直接依存しません。GRPCデータベースとのリンクは、この方法で処理されます。

しかし、ほぼすべての機能がそれを必要とするのでロガーは、多少異なりますが、データベースではありません。Javaでは、Javaのクラス初期化子レコーダーのインスタンスごとに、私たち(ロガー)。Javaのロギングフレームワークが使用するロガーの異なる階層を管理するために、彼らは親ロガーから同じログ設定を継承します。あなたはどちらかのレコーダーを作成したり、多くの異なったレコーダーでお互いに関連しないことができるように行くには、ロガー階層の間に何ら変わりはありません。一貫したロギング構成を達成するために、グローバルと各レコーダ機能に注入を作成することが好ましいです。しかし、多くの作業を行う必要がありますので、私は中央位置にグローバルなレコードを作成することを決定し、各関数は、直接それを参照することができます。

密接レコーダーの特定のアプリケーションに縛られないようにするために、私は、一般的なロガー・インターフェース、透明特定のロガーのためのアプリケーションを作成しました。以下に、レコーダ(ロガー)インタフェースです。

// Log is a package level variable, every program should access logging function through "Log"
var Log Logger

// Logger represent common interface for logging function
type Logger interface {
    Errorf(format string, args ...interface{})
    Fatalf(format string, args ...interface{})
    Fatal(args ...interface{})
    Infof(format string, args ...interface{})
    Info( args ...interface{})
    Warnf(format string, args ...interface{})
    Debugf(format string, args ...interface{})
    Debug(args ...interface{})
}

各ファイルは、ログに依存しているため、私はこの問題を回避するために別のサブパッケージ「ロガー」を作成した「コンテナ」のパッケージにしていて、それは、循環依存関係を生成することは容易です。それだけで「ログ」変数と「ロガー」のインタフェースです。アクセス変数とログには、このインタフェースを介して各ファイル。

レコーダーパッケージ

ログデータベースは、標準的な方法(例えば、サポートZAP ¹又はLogrusをパッケージレコード既に作成インタフェースを作成するために達成される²)。それは、次のコードで、非常に簡単です。

type loggerWrapper struct {
    lw *zap.SugaredLogger
}
func (logger *loggerWrapper) Errorf(format string, args ...interface{}) {
    logger.lw.Errorf(format, args)
}
func (logger *loggerWrapper) Fatalf(format string, args ...interface{}) {
    logger.lw.Fatalf(format, args)
}
func (logger *loggerWrapper) Fatal(args ...interface{}) {
    logger.lw.Fatal(args)
}
func (logger *loggerWrapper) Infof(format string, args ...interface{}) {
    logger.lw.Infof(format, args)
}
func (logger *loggerWrapper) Warnf(format string, args ...interface{}) {
    logger.lw.Warnf(format, args)
}
func (logger *loggerWrapper) Debugf(format string, args ...interface{}) {
    logger.lw.Debugf(format, args)
}
func (logger *loggerWrapper) Printf(format string, args ...interface{}) {
    logger.lw.Infof(format, args)
}
func (logger *loggerWrapper) Println(args ...interface{}) {
    logger.lw.Info(args, "\n")
}

しかし、問題のログがありました。ロギング機能はログメッセージに名前が記録し印刷することです。インタフェースパッケージした後、発信者がログを印刷する方法が、ラッパーではありません。この問題を解決するには、直接ログインソースコードリポジトリを変更していますが、アップグレードした場合、ライブラリの原因の互換性の問題をログに記録することができます。究極の解決策は、パッケージかどうか、発信者に応じて適切な方法を返すことができる新しい機能を作成するには、ロギングライブラリを必要とすることです。

コードは現在正常に動作させるために、私は短いカットを取りました。私は2つのログ・データベースは、既にこれらの機能を持っているので、それらは自動的にこれらのインタフェースを実装し、通常の署名を抽出し、共有インターフェースを作成してZAPとLogrus間の関数シグネチャのほとんどは、同様であるため。ゴーインタフェースデザインの優位性は、特定の実装を作成し、インターフェイスを作成し、関数のシグネチャが一致する場合は、自動的にインターフェイスを実装することができるということです。それは一種不正行為のだが、非常に効果的。レコードは共通のインターフェースの使用をサポートしていない場合、または呼び出し元の機能を犠牲にしたり、ソースコードを変更するので、一時的にしか、それをカプセル化します。

ライブラリの比較ロギング:

別のログライブラリは、デバッグのための重要な特徴であるそのうちのいくつかは異なる機能を提供します。

重要情報のニーズ(以下のデータを必要とする)レコード:

  1. ファイル名と行番号

  2. メソッド名やファイル名の呼び出し

  3. メッセージのログレベル

  4. タイムスタンプ

  5. エラースタックトレース

  6. 自動的にパラメータと結果を含む各関数呼び出しを記録

私は、ログデータベースは自動的に、そのような実装するために明示的に書き込み、コードなしで、メソッド名を呼ぶと、このデータを提供したいです。上記の6つの機能のために、ログ・ライブラリ#6が存在しないが、彼らは、一部または全部で1-5を提供しています。私は2つの非常に人気のロギングライブラリLogrusとZAPを試してみました。Logrusは、すべての機能を提供しますが、私のコンソール上のフォーマットが間違っている(それは示し「N T」というよりも、私のWindowsコンソール上で新しい行)クリーンとしてZAPなどの出力フォーマット。ZAPは#2に提供されていないが、私はそれを使用することにしましたので、すべてがそうでない、かなり良いに見えます。

驚くべきことに、この手順を使用すると、出力を比較するためにさまざまなログデータベースに切り替えることができるので、テスト異なるログデータベースに非常に優れたツールであることが判明したが、唯一のコンフィギュレーション・ファイルの行を変更する必要があります。これは、このプログラムの機能が、良い副作用ではありません。

実際には、I最も必要機能が自動的に記録されている各関数呼び出しは、パラメータと結果(#6)を含むが、ログ・ライブラリが提供する機能を提供していません。私はそれを得ることができると思います。

エラー(エラー)の取り扱い:

私もここで議論するように、エラー処理は直接、伐採に関連しています。以下は、エラーを処理するとき、私は従うルールです。

1.使用して、エラーのスタックトレースの作成
エラーメッセージ自体は、スタックトレース情報が含まれている必要があります。あなたのプログラムからのエラーの場合は、エラー・スタックトレースを格納するためのライブラリを作成するには、「github.com/pkg/errors」をインポートすることができます。それが別のライブラリから生成され、ライブラリが「PKG /エラー」を使用していない場合でも、あなたはエラーがスタックトレース情報を取得することを「errors.Wrap(ERR、メッセージ)」文パッケージを使用する必要があります。私たちは、サードパーティのライブラリを制御することはできませんので、その最善の解決策は、私たちのプログラム内のエラーのためにパッケージ化されています。詳細については、を参照してくださいここ ³。

2.印刷エラーのスタックトレース
あなたが使用する必要がある"logger.Log.Errorfを(" %+ V \ n "は、ERR)" または"fmt.Printf(" %+ V \ n "は、ERR)" スタックトレース情報を印刷するためにキーは、「+ V」オプション(もちろん、あなたが#1を持っている必要)です。

ハンドルエラー3.のみトップレベル関数
「プロセス」とは、記録エラーを示し、エラーがコール元に戻されます。唯一の最上位機能処理エラーので、そうエラーのみプログラムに一度記録されています。トップ発信者は通常、ユーザー指向のプログラミングされている、それは、ユーザ・プログラム・インターフェース(UI)または他のマイクロサービスです。あなたは(あなたがプログラムの記録を持っているので)エラーメッセージを記録したい、彼らが再試行するか、エラー特定の操作を行うことができますので、そのメッセージは、UIや他のマイクロサービスに返されます。

4.他のすべての機能単にエラー伝搬のより高いレベルにレベルべき
層又は記録の中間層または処理エラーが機能しない、また誤廃棄。あなたは、エラーにデータを追加し、それを広めることができます。エラーが発生すると、アプリケーション全体を停止する必要はありません。

パニック(パニック):

「main.go」ローカルに加えて、私はパニック(パニック)を使用したことがありません。それはより多くのバグではなく、機能のようなものです。ログについてましょう話デイブチェイニー副大統領が書いた、で⁴、「広く、アプリケーションがライブラリパニックを使用してはならないと考えられています。」もう一つのミスが、それはパニックと同じ効果があり、log.Fatalあり、また、禁止されるべき。それは、単一責任のルールに違反している、「パニック」の後に「Log.Fatalは」さらに悪いことに、それはログのように見えますが、出力ログ。

パニックには2つの問題があります。まず第一に、それはさまざまな方法でエラー処理であるが、それは実際には間違い、間違ったサブタイプです。さて、エラー処理コードのニーズはエラーを処理するには、そのように、パニックトランザクション処理コード ⁵のエラー処理コード。第二に、それは非常に悪いですこれは、アプリケーションを停止します。唯一のトップレベルのマスター制御プログラムがエラーを処理する方法を決定するために、他のすべての機能は、上部のみにエラー伝播と呼ばれるべきです。特に今、サービスグリッド層(サービスメッシュ)は、より複雑な再試行、パニックメイクなどの機能を提供することができます。

あなたは、サードパーティのライブラリを呼び出しているし、それがコード内でパニックを作成した場合は、停止するためのコードを防ぐために、あなたがインターセプトする必要があり、パニックから回復します。ここでのコード例があり、あなたは(各機能に配置された「catchPanic(延期)」)が発生する可能性があり、各トップレベル機能のパニックのためにこれを行う必要があります。次のコードでは、キャプチャし、パニックから回復する機能「catchPanic」を持っています。「catchPanic延期()」呼び出し元のコードの最初の行で機能「のregisterUser」。詳細な議論パニックについては、こちらを ⁶。

func catchPanic() {
    if p := recover(); p != nil {
        logger.Log.Errorf("%+v\n", p)
    }
}

func (uss *UserService) RegisterUser(ctx context.Context, req *uspb.RegisterUserReq)
    (*uspb.RegisterUserResp, error) {
    
    defer catchPanic()
    ruci, err := getRegistrationUseCase(uss.container)
    if err != nil {
        logger.Log.Errorf("%+v\n", err)
        return nil, errors.Wrap(err, "")
    }
    mu, err := userclient.GrpcToUser(req.User)
...
}
結論:

グッドログは、プログラマがより効率的に行うことができます。あなたは、スタックトレースのエラーを使用します。ハンドルエラーにのみ、トップレベルの機能は、他のすべてのレベルは、上位レベルにのみエラー伝搬機能すべきです。パニックにならないでください。

出典:

完全なソースコードへのリンクGitHubのhttps://github.com/jfeng45/servicetmpl

インデックス:

[1] ZAP

[2] Logrus

[3] [スタックトレースとエラー・パッケージ(https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package

[4] [ロギングに関するましょう話](https://dave.cheney.net/2015/11/05/lets-talk-about-logging

[5] [データベース/ SQLのTx -コミットまたはロールバック検出](https://stackoverflow.com/questions/16184238/database-sql-tx-detecting-commit-or-rollback/23502629#23502629

[6] [移動における使用およびパニックの誤用について](https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/

おすすめ

転載: www.cnblogs.com/code-craftsman/p/12145415.html