
プロのプログラマーからのヒント(第III部): ロギングSeqログ収集および分析システムへの接続
目次
はじめに
ロギングとは、アプリケーションの動作を分析するためのメッセージの出力です。MQL5のPrint関数とPrintFormat関数は、出力メッセージをエキスパート操作ログに保存します。エキスパート操作ログはUnicode形式のテキストファイルです。ログが上書きされるのを回避するために、新しいMQL5/Logs/yyyymmdd.logファイルが毎日作成されます。
開いているすべてのチャートのすべてのスクリプトとエキスパートアドバイザーは、1つのファイルに「ログを書き込みます」。ログの一部はディスクキャッシュに残ります。つまり、エクスプローラーからログファイルを開くと、キャッシュがあるため、最新の情報は表示されません。キャッシュをファイルに保存させるには、ターミナルを閉じるか、[エキスパート]タブのコンテキストメニューを使用して、その中の[開く]を選択します。これにより、ログファイルを含むディレクトリが開きます。
特にターミナル内では、これらのログを分析するのは簡単ではありませんが、そのような分析は非常に重要です。連載第I部では、ターミナルログの情報の検索、選択、表示を簡素化する方法の1つを示しましたが、この記事では、次の方法を紹介します。
- ログ出力を統合する(Loggerクラス)
- ログをSeqログ収集および分析システムに接続する
- Seqでオンラインでメッセージ(イベント)を表示する
- 通常のMetaTrader 5ログをSeqにインポートする(Pythonパッケージ)
Seq: ログを収集および分析するためのシステム
Seqは、アプリケーションログをリアルタイムで検索および分析するためのサーバーで、適切に設計されたユーザーインターフェイス、JSON形式のイベントストレージ、SQLクエリ言語により、複雑なアプリケーションやマイクロサービスの問題を識別および診断するための効果的なプラットフォームになっています。
Seqにメッセージを送信するには、次のことを行う必要があります。
- コンピュータにSeqをインストールします。
インストール後、SeqUIは次の場所で利用できるようになります。
http://localhost:5341/#/events - 次の行をファイルc:/windows/system32/drivers/etc/hostsに追加します。
127.0.0.1 seqlocal.net
MetaTrader 5ターミナル設定にURLを追加できるようになります。 - Seqでタイムゾーンの使用を無効にして、メッセージ時間を「現状のまま」表示します。
- UIシーケンスに移動します。
- admin /Preferences/Preferencesに移動します。
- [タイムスタンプをCoordinated Universal Time(UTC)で表示]を有効にします。 - 次のアドレスをMT5/Tools/Options/Expert Advisorsに追加します。
http://seqlocal.net
WebRequest関数がこのURLを使用できるようになります。
Loggerクラス
考え方は次のように単純です。統一かつ構造化された情報を取得するには、同じ方法で形成して表示する必要があります。 この目的のために、完全に自律的なLoggerクラスを使用します。#includeファイルのような追加の依存関係はないので、クラスは「そのまま」使用できます。
// Message levels #define LEV_DEBUG "DBG" // debugging (for service use) #define LEV_INFO "INF" // information (to track the functions) #define LEV_WARNING "WRN" // warning (attention) #define LEV_ERROR "ERR" // a non-critical error (check the log, work can be continued) #define LEV_FATAL "FTL" // fatal error (work cannot be continued)
メッセージレベルは、メッセージの重大度と緊急性の大まかな目安を示します。エキスパート操作ログでレベルを読みやすくして、レベルを強調表示して配置するために、3文字のプレフィックス(DBG、INF、WRN、ERR、 FTL)を使用します。
- DEB(UG) はプログラマーを対象としており、多くのロギングシステムでは、コンソールに出力されず、ファイルに保存されます。DEBUGメッセージは他のメッセージよりも頻繁に表示され、通常、パラメータ付きの関数の名前やその呼び出し結果が含まれています。
- INF(O)はユーザーを対象としています。これらのメッセージは、DEBUGメッセージほど頻繁に表示されません。これらには、アプリケーションの操作に関する情報が含まれており、メニュー項目のクリック、トランザクション結果などのユーザーアクション、つまりユーザーが理解できるすべてのものである可能性があります。
- WAR(NING)は、この情報に注意を払う必要があることを示します。例は、取引の開始または終了、未決注文のトリガーなどです。
- ERR(OR)は、重大ではないエラーが発生したが、アプリケーションが引き続き機能することを意味します。例ば、注文が拒否されたか実行されなかったために、注文の価格またはストップレベルが無効になった場合です。
- FAT(AL)は重大なエラーを示し、その後、通常モードでのアプリケーションの動作は保証されません。緊急にアプリケーションを停止してエラーを修正する必要があります。
読みやすさとコード削減のために、メッセージは次のマクロ置換によって出力されます。
// Message output macros #define LOG_SENDER gLog.SetSender(__FILE__, __FUNCTION__) #define LOG_INFO(message) LOG_SENDER; gLog.Info(message) #define LOG_DEBUG(message) LOG_SENDER; gLog.Debug(message) #define LOG_WARNING(message) LOG_SENDER; gLog.Warning(message) #define LOG_ERROR(message) LOG_SENDER; gLog.Error(message) #define LOG_FATAL(message) LOG_SENDER; gLog.Fatal(message)
したがって、各メッセージには、ファイルまたはモジュールの名前、関数の名前、メッセージ自体が表示されます。メッセージを作成するには、PrintFormat関数を使用することをお勧めします。各値は「/」で区切ることが望ましいです。この手法により、すべてのメッセージが統一され、構造化されます。
演算子の例
LOG_INFO(m_result); LOG_INFO(StringFormat("%s / %s / %s", StringSubstr(EnumToString(m_type), 3), TimeToString(m_time0Bar), m_result));
エキスパートログに出力されるオペレーターデータ
Time Source Message --------------------------------------------------------------------------------------------------------------------- 2022.02.16 13:00:06.079 Cayman (GBPUSD,H1) INF: AnalyserRollback::Run Rollback, H1, 12:00, R1, D1, RO, order 275667165 2022.02.16 13:00:06.080 Cayman (GBPUSD,H1) INF: Analyser::SaveParameters Rollback / 2022.02.16 12:00 / Rollback, H1, 12:00, R1, D1, RO, order 275667165
MetaTrader 5で出力されるメッセージの特定の特徴は、Time列ではTimeLocalが指定され、情報が実際にはサーバー時間TimeCurrentに属するということです。したがって、時間を強調する必要がある場合は、メッセージ自体で時間を指定する必要があります。これは2番目のメッセージに示されています。ここで、13:00は現地時間、12: 00はサーバー時間(実際のバーの営業時間)です。
Loggerクラスの構造は次のとおりです。
class Logger { private: string m_module; // module or file name string m_sender; // function name string m_level; // message level string m_message; // message text string m_urlSeq; // url of the Seq message service string m_appName; // application name for Seq // private methods void Log(string level, string message); string TimeToStr(datetime value); string PeriodToStr(ENUM_TIMEFRAMES value); string Quote(string value); string Level(); void SendToSeq(); public: Logger(string appName, string urlSeq); void SetSender(string module, string sender); void Debug(string message) { Log(LEV_DEBUG, message); }; void Info(string message) { Log(LEV_INFO, message); }; void Warning(string message) { Log(LEV_WARNING, message); }; void Error(string message) { Log(LEV_ERROR, message); }; void Fatal(string message) { Log(LEV_FATAL, message); }; }; extern Logger *gLog; // logger instance
すべてが簡潔で読みやすく、不要な詳細が含まれていません。gLogロガーインスタンスの宣言に注意してください。同じタイプと識別子を持つ「extern」として宣言された変数は、同じプロジェクトの異なるソースファイルに存在する可能性があります。外部変数は初期化できますが、一度だけです。したがって、プロジェクトファイルにロガーを作成した後、gLog変数は同じオブジェクトを指します。
// ----------------------------------------------------------------------------- // Constructor // ----------------------------------------------------------------------------- Logger::Logger(string appName, string urlSeq = "") { m_appName = appName; m_urlSeq = urlSeq; }
ロガーコンストラクタは、次の2つのパラメータを受け取ります。
- appName - Seqのアプリケーション名。Seqシステムは、オンラインモードでさまざまなアプリケーションからログを受信できるので、メッセージをフィルタリングするためにappNameが使用されます。
- urlSeq - SeqサービスのURL。特定のポート(http://localhost:5341/#/events)でリッスンしているローカルサイトにすることができます。
urlSeqパラメータはオプションです。指定しない場合、メッセージはエキスパートログにのみ出力されます。urlSeqが定義されている場合、イベントはWebRequestを介してSeqサービスに追加で送信されます。
// ----------------------------------------------------------------------------- // Set the message sender // ----------------------------------------------------------------------------- void Logger::SetSender(string module, string sender) { m_module = module; // module or file name m_sender = sender; // function name StringReplace(m_module, ".mq5", ""); }
SetSender関数は、2つの必須パラメータを取得し、メッセージの送信者を設定します。「.mq5」ファイル拡張子がモジュール名から削除されます。ロギング演算子LOG_LEVELがクラスメソッドで使用されている場合、クラス名が関数名に追加されます(例: TestClass::TestFunc)。
// ----------------------------------------------------------------------------- // Convert time to the ISO8601 format for Seq // ----------------------------------------------------------------------------- string Logger::TimeToStr(datetime value) { MqlDateTime mdt; TimeToStruct(value, mdt); ulong msec = GetTickCount64() % 1000; // for comparison return StringFormat("%4i-%02i-%02iT%02i:%02i:%02i.%03iZ", mdt.year, mdt.mon, mdt.day, mdt.hour, mdt.min, mdt.sec, msec); }
Seqの時間タイプは、ISO8601形式(YYYY-MM-DDThh:mm:ss[.SSS])である必要があります。MQL5の日時タイプは、最大1秒まで計算されます。Seqの時間は、最大1ミリ秒で表されます。したがって、システムの起動(GetTickCount64)から経過したミリ秒数は、指定された時間に強制的に追加されます。このメソッドでは、メッセージの時間を相互に比較できます。
// ----------------------------------------------------------------------------- // Convert period to string // ----------------------------------------------------------------------------- string Logger::PeriodToStr(ENUM_TIMEFRAMES value) { return StringSubstr(EnumToString(value), 7); }
期間はシンボリック形式でSeqに渡されます。任意の期間のシンボリック表現には、「PERIOD_」というプレフィックスが付いています。したがって、期間を文字列に変換する場合、プレフィックスは単純に切り捨てられます。たとえば、PERIOD_H1は「H1」に変換されます。
SendToSeq関数は、メッセージ (イベントを登録するため)をSeqに送信するために使用されます。
// ----------------------------------------------------------------------------- // Send message to Seq via http // ----------------------------------------------------------------------------- void Logger::SendToSeq() { // replace illegal characters StringReplace(m_message, "\n", " "); StringReplace(m_message, "\t", " "); // prepare a string in the CLEF (Compact Logging Event Format) format string speriod = PeriodToStr(_Period); string extended_message = StringFormat("%s, %s / %s / %s / %s", _Symbol, speriod, m_module, m_sender, m_message); string clef = "{" + "\"@t\":" + Quote(TimeToStr(TimeCurrent())) + // event time ",\"AppName\":" + Quote(m_appName) + // application name (Cayman) ",\"Symbol\":" + Quote(_Symbol) + // symbol (EURUSD) ",\"Period\":" + Quote(speriod) + // period (H4) ",\"Module\":" + Quote(m_module) + // module name (__FILE__) ",\"Sender\":" + Quote(m_sender) + // sender name (__FUNCTION__) ",\"Level\":" + Quote(m_level) + // level abbreviation (INF) ",\"@l\":" + Quote(Level()) + // level details (Information) ",\"Message\":" + Quote(m_message) + // message without additional info ",\"@m\":" + Quote(extended_message) + // message with additional info "}"; // prepare data for POST request char data[]; // HTTP message body data array char result[]; // Web service response data array string answer; // Web service response headers string headers = "Content-Type: application/vnd.serilog.clef\r\n"; ArrayResize(data, StringToCharArray(clef, data, 0, WHOLE_ARRAY, CP_UTF8) - 1); // send message to Seq via http ResetLastError(); int rcode = WebRequest("POST", m_urlSeq, headers, 3000, data, result, answer); if (rcode > 201) { PrintFormat("%s / rcode=%i / url=%s / answer=%s / %s", __FUNCTION__, rcode, m_urlSeq, answer, CharArrayToString(result)); } }
まず、新しい行とタブがスペースに置き換えられます。次に、メッセージパラメータを"key": "value"のペアとして持つJSONレコードが形成されます。@プレフィックスが付いたパラメータは必須(サービス)です。残りはユーザー定義で、名前とその番号はプログラマーが決定します。パラメータとその値は、SQLクエリで使用できます。
メッセージ時間@t = TimeCurrent()に注意してください。ターミナルとは対照的に、サーバー時間を修正しますが、ローカルは修正しません(TimeLocal())。次に、リクエスト本文が作成され、WebRequestを介してSeqサービスに送信されます。
// ----------------------------------------------------------------------------- // Write a message to log // ----------------------------------------------------------------------------- void Logger::Log(string level, string message) { m_level = level; m_message = message; // output a message to the expert log (Toolbox/Experts) PrintFormat("%s: %s %s", m_level, m_sender, m_message); // if a URL is defined, then send a message to Seq via http if (m_urlSeq != "") SendToSeq(); }
この関数には、メッセージの重大度レベルとメッセージ文字列の2つの必須パラメータがあります。メッセージはエキスパート操作ログに出力されます。レベルの後にはコロン文字が続きます。これは、Notepad ++が線を強調表示するために特別に行われました(WRN: -黄色に黒、ERR: -赤に黄色)。
Loggerクラスのテスト
TestLogger.mq5スクリプトは、クラスをテストするために使用されます。ロギングマクロはさまざまな機能で使用されます。
#include <Cayman/Logger.mqh> class TestClass { int m_id; public: TestClass(int id) { m_id = id; LOG_DEBUG(StringFormat("create object with id = %i", id)); }; }; void TestFunc() { LOG_INFO("info message from inner function"); } void OnStart() { string urlSeq = "http://seqlocal.net:5341/api/events/raw?clef"; gLog = new Logger("TestLogger", urlSeq); LOG_DEBUG("debug message"); LOG_INFO("info message"); LOG_WARNING("warning message"); LOG_ERROR("error message"); LOG_FATAL("fatal message"); // call function TestFunc(); // create object TestClass *testObj = new TestClass(101); // free memory delete testObj; delete gLog; }
エキスパートログのメッセージの表示。メッセージには、レベルとメッセージ送信者(所有者)が明確に示されます。
2022.02.16 20:17:21.048 TestLogger (USDJPY,H1) DBG: OnStart debug message 2022.02.16 20:17:21.291 TestLogger (USDJPY,H1) INF: OnStart info message 2022.02.16 20:17:21.299 TestLogger (USDJPY,H1) WRN: OnStart warning message 2022.02.16 20:17:21.303 TestLogger (USDJPY,H1) ERR: OnStart error message 2022.02.16 20:17:21.323 TestLogger (USDJPY,H1) FTL: OnStart fatal message 2022.02.16 20:17:21.328 TestLogger (USDJPY,H1) INF: TestFunc info message from inner function 2022.02.16 20:17:21.332 TestLogger (USDJPY,H1) DBG: TestClass::TestClass create object with id = 101
Notepad++エディターでのメッセージの表示
Seqでのメッセージの表示
MetaTrader5ログをSeqにインポートする
ログをSeqにインポートするために、Pythonでseq2logパッケージを作成しました。この記事では説明しません。パッケージには、README.mdファイルが含まれています。コードには詳細なコメントが含まれています。seq2logパッケージは、エキスパート操作ログMQL5/Logs/yyyymmdd.logから任意のログをインポートします。重要度レベルのないメッセージには、INFレベルが割り当てられます。
seq2logはどこで使用できるのでしょうか。たとえば、フリーランスの開発者は、クライアントにエキスパートログを送信するように依頼できます。テキストエディターでログを分析することは可能ですが、SeqではSQLクエリを使用する方が便利です。最も頻繁に使用されるクエリまたは複雑なクエリをSeqに保存し、クエリ名を1回クリックするだけで実行できます。
Run: py log2seq appName pathLog where log2seq - package name appName - application name to identify events in Seq pathLog - MetaTrader 5 log path Example: py log2seq Cayman d:/Project/MQL5/Logs/20211028.log
終わりに
この記事では、Loggerクラスとその使用方法について説明します。
- 重大度レベルの構造化メッセージをログに記録する
- Seqログ収集および分析システムにメッセージ(イベント)を登録する
Loggerクラスのソースコードとそのテストが添付されています。さらに、添付ファイルには、既存のMetaTrader 5ログをSeqにインポートするために使用されるlog2seqパッケージのソースPythonコードが含まれています。
Seqサービスを使用すると、専門家レベルでログを分析できます。Seqサービスでは優れたデータサンプリングおよび視覚化機能が提供されてます。さらに、Loggerクラスのソースコードを使用すると、Seqで図を描画するために、視覚化のために特別に設計されたデータをログメッセージに追加できます。これにより、アプリケーションログのデバッグ情報を確認することをお勧めします。実際に適用してみてください。ご健闘をお祈りします。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10475





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索