1733880115
2024-12-10 11:27:00
アプリケーションでログを書き込む場合、おそらく、これらのログの書き込みを管理し、コード内のあらゆる場所で使用する専用のクラス (またはシングルトン インスタンス) が存在します。このメカニズムは一般にシンプルで簡単です。しかし、PandApache でロガーをどのように使用するかを見てみると、次の行に少し驚かれるかもしれません。
ExecutionContext.Current.Logger.LogInfo($"Admin server listening on {ServerConfiguration.Instance.ServerIP}:{ServerConfiguration.Instance.AdminPort}");
出撃:
12/09/2024 12:26:48 - Module: Admin - Thread ID: 1 - INFO - Admin server listening on 0.0.0.0:4040
この行を説明する前に、ここに至る経緯を理解するために一歩下がってみましょう。
パンダパッチはどのように機能しますか?
PandApache の実行中は、いくつかのことが行われます。
- サービス : これはメインプログラムそのものです。
- モジュール : 各モジュールは同時に実行されるため、新しいタスクが作成されます。
- サブタスク : 一部のモジュールは、実行するアクションに応じて独自のサブタスクを開始できます。
目的は、各モジュールを可能な限り独立させ、構成可能にすることです。したがって、各モジュールには以下が必要です。
- あなた自身のロガー : これにより、特定のログ ルール (異なるレベル、異なるファイルなど) を適用しながら、どのログがどのモジュールによって書き込まれたかを知ることができます。
- 息子専用タスクスケジューラー : すべてのリソースを独占せず、他の人に十分な量を残すため
各モジュールで正しいロガーを管理するにはどうすればよいですか?
各モジュールが適切なロガー (および適切なタスク スケジューラ) を確実に使用する方法はいくつかありますが、それは今は脇に置きます。解決策は、適切なロガーをパラメータとして各メソッドに渡すか、依存関係の注入を使用することです。次のようになります。
Logger loggerAdmin = new Logger(configAdmin);
Logger loggerWeb = new Logger(configWeb);
Module moduleAdmin = new Module(loggerAdmin);
Module moduleWeb = new Module(loggerWeb);
このアプローチは理想的ではありません。何のために ?各サブ関数は、パラメータとして明示的に渡すか、依存性注入によって適切なロガーにアクセスできる必要があるため、これはすぐに面倒で冗長になる可能性があります。
モジュール内の例を見てみましょう。
Module{
public Logger LoggerAdmin;
Foo() {
LoggerAdmin.LogInfo("Here");
RandomObject.Bar(LoggerAdmin);
}
}
RandomObject{
Bar(Logger logger) {
logger.LogInfo("There");
}
}
正しいロガーを返す静的オブジェクトを使用したソリューションを選択することもできます。これにより、次のようになります。
Foo() {
LoggerAdmin.LogInfo("Here");
}
Bar() {
LoggerAdmin.LogInfo("There");
}
しかし、この解決策には 2 つの大きな問題があります。
-
適切なロガーを手動で選択する : 関数またはクラスごとに、どのロガーを使用するかを決定する必要があります。また、関数がコンテキストを変更した場合 (たとえば、Web モジュールから Admin モジュールに)、この選択を常に適応させる必要があります。これでは柔軟性に欠け、バグのリスクが高まります。
-
複数のインスタンスの管理 : Web モジュールと管理モジュールは実際には同じモジュールの 2 つのインスタンスですが、同じコードを実行します。呼び出しごとにロガーを指定せずに、各インスタンスに独自のロガーがあることを確認するにはどうすればよいでしょうか?
本当の課題は、毎回指定したり、特定のパラメータを経由したりすることなく、適切なロガー (またはその他のモジュール固有のオブジェクト) を自動的に取得する方法を見つけることです。この正しいオブジェクトは、関数呼び出しごとに手動で決定するのではなく、現在実行中のモジュールに基づいて決定する必要があります。
これを念頭に置いて、この問題をよりエレガントな方法で解決するにはどうすればよいでしょうか?コメントを残してアプローチを共有し、特にロガーのようなモジュール固有のオブジェクトの場合に、このコンテキストで依存関係の挿入をどのように処理するかを説明してください。
仮想ロガー
最終的なソリューションを明確に想像するには、目的のログを念頭に置いておくことが重要です。 PandaPache のログは次のとおりです。
12/09/2024 12:33:25 - Module: Server - Thread ID: 1 - WARNING - Module Telemetry disabled
12/09/2024 12:33:25 - Module: Server - Thread ID: 1 - INFO - PandApache3 is starting
12/09/2024 12:33:25 - Module: Web - Thread ID: 1 - INFO - Starting Connection manager module
12/09/2024 12:33:25 - Module: Web - Thread ID: 1 - INFO - Web server listening on 0.0.0.0:8080
12/09/2024 12:33:25 - Module: Admin - Thread ID: 1 - INFO - Starting Connection manager module
12/09/2024 12:33:25 - Module: Admin - Thread ID: 1 - INFO - Admin server listening on 0.0.0.0:4040
12/09/2024 12:33:25 - Module: default - Thread ID: 1 - INFO - PandApache3 process id:3738
12/09/2024 12:33:25 - Module: default - Thread ID: 1 - INFO - PandApache3 process name:dotnet
12/09/2024 12:33:25 - Module: Server - Thread ID: 1 - INFO - PandApache3 is up and running!
12/09/2024 12:33:25 - Module: Web - Thread ID: 6 - INFO - Running Connection manager module
12/09/2024 12:33:28 - Module: Web - Thread ID: 6 - INFO - Client connected
12/09/2024 12:33:28 - Module: Web - Thread ID: 12 - INFO - Reading query string parameter
12/09/2024 12:33:28 - Module: Web - Thread ID: 13 - INFO - LoggerMiddleware invoked
12/09/2024 12:33:28 - Module: Web - Thread ID: 13 - INFO - Log Request
12/09/2024 12:33:28 - Module: Web - Thread ID: 13 - INFO - [12/09/2024 10:33:28] GET /
12/09/2024 12:33:28 - Module: Web - Thread ID: 14 - INFO - Log Response
12/09/2024 12:33:28 - Module: Web - Thread ID: 14 - INFO - [12/09/2024 10:33:28] Response status code: 200
12/09/2024 12:33:28 - Module: Web - Thread ID: 14 - INFO - client Closed
12/09/2024 12:33:28 - Module: Web - Thread ID: 6 - INFO - Client connected
12/09/2024 12:33:28 - Module: Web - Thread ID: 13 - INFO - Reading query string parameter
そこには古典的な情報が表示されますが、モジュールとスレッド ID という 2 つの要素は定期的に変更され、非常に重要です。
これらのログにどのように到達するかを理解するには、間違いなく、PandApache モジュールが何で構成されているか、特にそのプロパティを調べることから始めるのが最も論理的です。モジュールのプロパティは次のとおりです ConnectionManagerこれは、Web および管理モジュールのインスタンス化されたクラスです。
public TcpListener Listener { get; set; }
public TaskFactory TaskFactory { get; }
public ModuleConfiguration ModuleInfo { get; set; }
public ModuleType ModuleType { get; set; }
private static AsyncLocalModuleConfiguration> _current = new AsyncLocalModuleConfiguration>();
public CancellationTokenSource _cancellationTokenSource { get; } = new CancellationTokenSource();
private ConcurrentDictionaryGuid, ISocketWrapper> _clients { get; } = new ConcurrentDictionaryGuid, ISocketWrapper>();
private ConcurrentDictionaryGuid, ISocketWrapper> _clientsRejected = new ConcurrentDictionaryGuid, ISocketWrapper>();
private FuncHttpContext, Task> _pipeline;
private TaskScheduler _taskScheduler;
特に興味のあるプロパティは次のとおりです。
public ModuleConfiguration ModuleInfo { get; set; }private TaskScheduler _taskScheduler;
の TaskScheduler 独自のものを再実装したにもかかわらず、かなり単純です TaskScheduler いくつかの変更のために。これはデフォルト クラスからの継承であるため、通常は同じように動作します。
ModuleConfigurationは、次のようなオリジナルのクラスです。
public class ModuleConfiguration
{
private TaskScheduler _taskScheduler;
public ModuleType Type;
public string Name;
public bool isEnable;
public TaskFactory TaskFactory { get; }
public VirtualLogger Logger;
public ModuleConfiguration(string name)
{
Name = name;
Type = moduleType;
Logger = new VirtualLogger(name);
}
}
私たちは私たちの TaskScheduler、しかし、私たちも持っています VirtualLogger これはパラメータとして名前を受け取ります。
そして VirtualLogger 通常の PandApache ロガーとまったく同じです。しかもクラスは VirtualLogger など Logger どちらもインターフェイスを実装しています ILogger。
違いは、 VirtualLogger にログを送信します Logger、後者はログをシステム (コンソールまたはファイル) に送信します。
したがって、それぞれが独自のタスクで実行され、非常に特殊な実行コンテキストで使用したいオブジェクトの 2 つのインスタンスを含むモジュールがあります。やりたいことを実現するための要素はすべて揃っています。あとはそれがどのように機能するかを一緒に確認するだけです。
私たちの間に
言えることは、
VirtualLoggerここでは不必要な抽象化です。クラスを直接使用することもできますLoggerいくつかの異なるインスタンスを含むオリジナル。VirtualLogger。確かにその通りですが、他にもメリットはありますVirtualLoggerここでは説明されていません。覚えておかなければならないのは、VirtualLogger独立したロガーを用意するためだけではなく、何よりも情報をログに記録するアクションと、それをコンソールまたはファイルに配布するアクションを分離するために存在します。これについては、今後のブログ投稿で詳しく説明します。
実行コンテキスト
ロガーを最初から実行する行に戻りましょう。
ExecutionContext.Current.Logger.LogInfo($"Admin server listening on {ServerConfiguration.Instance.ServerIP}:{ServerConfiguration.Instance.AdminPort}");
オブジェクトについて話す時が来ました ExecutionContext。これは、どこからでも簡単に呼び出せる静的フィールドのみを含む単純なクラスです。ここにあります:
public static class ExecutionContext
{
private static AsyncLocalModuleConfiguration> _current = new AsyncLocalModuleConfiguration>();
public static ModuleConfiguration Current
{
get => _current.Value;
set => _current.Value = value;
}
}
このクラスの特徴は、 _current、これは AsyncLocal。これにより、次のような明確な値を保証することができます。 _current 異なる実行コンテキスト、つまりタスク内で。
各モジュールは起動時に変数に代入します。 _current その実行コンテキストのオブジェクト ModuleInfo :
ExecutionContext.Current = ModuleInfo;
モジュール内で次の行があった場合:
ExecutionContext.Current.Logger.LogInfo("Starting Connection manager module");
出撃:
12/09/2024 12:26:48 - Module: Web - Thread ID: 1 - INFO - Starting Connection manager module
が使用されている場合、それは使用される正しいロガー、つまりモジュールのロガーです。
さらに、2 つのモジュール (Web と管理) によって生成された同じ行からの 2 つのログ出力を次に示します。
出撃:
12/09/2024 12:26:48 - Module: Web - Thread ID: 1 - INFO - Starting Connection manager module
12/09/2024 12:26:48 - Module: Web - Thread ID: 1 - INFO - Web server listening on 0.0.0.0:8080
12/09/2024 12:26:48 - Module: Admin - Thread ID: 1 - INFO - Starting Connection manager module
12/09/2024 12:26:48 - Module: Admin - Thread ID: 1 - INFO - Admin server listening on 0.0.0.0:4040
リクエストを処理するために接続を受け入れる Web モジュールなど、モジュールが別のタスクを起動するとき:
await AcceptConnectionsAsync(client);
理論的にはコンテキストを変更し、メインタスクのサブタスクに入ります。ただし、不動産の価値は、 AsyncLocal は自動的に継承されます。つまり、関数内で AcceptConnectionsAsync、経由でロガーを使用する場合 ExecutionContext :
ExecutionContext.Current.Logger.LogInfo($"Client connected");
出撃:
12/09/2024 12:33:45 - Module: Admin - Thread ID: 9 - INFO - Client connected
常に適切なものをご用意しております ModuleInfo 設定されています。
最終的に
これらすべてにより、PandApache で多くのことが可能になります。ロガーの場合、たとえそれぞれ VirtualLogger ほとんど違いはありません(ロガーに直接保存されているモジュールの名前のみが異なります) VirtualLogger 他のものに)、使用時の透明性と論理的分離、および作成時の一貫性と標準化という利点がまだあります。
の TaskSchedulerこれはあまり視覚的ではないためほとんど説明しませんでしたが、ロガーとまったく同じように機能し、特にパーソナライズされたスレッドの数に関するルールを設けることで、非常に透過的な方法でモジュール内で新しいタスクを起動できるようになります。 。こうすることで、重要なモジュールではないテレメトリ モジュールが大量のリソースを使用しすぎないようにすることができます (たとえば、キャプチャするメトリクスの数によっては、テレメトリ用の 1 つのスレッドで十分です)。
逆に、Web モジュールに新しいリクエストを処理するのに十分なリソースがなくなった場合は、非常に動揺するでしょう。したがって、この共有実行コンテキストのおかげで、最終的にはより詳細なリソース管理を実行できるようになります。
この記事が C# での AsyncLocal の使用をより深く理解するのに役立つことを願っています。この言語に興味がある場合は、PandApache3 コードが次の場所で入手できることに注意してください。 GitHub そして生き続ける けいれん。迷わず冒険を続けましょう!
#クラス間で情報を共有するための #AsyncLocal