English Deutsch
preview
ログレコードをマスターする(第8回):自己翻訳するエラーレコード

ログレコードをマスターする(第8回):自己翻訳するエラーレコード

MetaTrader 5 |
123 6
joaopedrodev
joaopedrodev

はじめに

ログ記録が単なるイベントの記録にとどまらないことは、もはや驚くことではありません。ログは、EAが日々のティック、意思決定、そして不確実性の中で何を伝えようとしているかを正確に捉えるための手段です。

日常的にLogifyを使っていて気になったのは、エラーハンドリングがまだ表層的である点です。堅牢なフォーマットがあっても、ログにはエラーコードだけが表示され、それが何を意味するのかは分かりませんでした。たとえば以下のようになります。

logify.Error("Failed to send sell order", "Order Management", "Code: "+IntegerToString(GetLastError()));
// Console:
// 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | Code: 10016

その結果、曖昧なメッセージしか得られないのです。どこでエラーが発生したかは分かりますが、なぜ発生したのかはわかりません。MQL5のドキュメントで数十ものコードを調べなければならなかった経験は、誰にでもあるでしょう。私自身もよくそうしていました。エラーコードが分かったとしても、実際に何が起きたのかを知るためにはドキュメントを検索する必要がありました。この実際のフラストレーションからひとつのアイデアが生まれました。「もしLogifyがエラーを解釈してくれたらどうだろう?」「コードだけでなく、その意味を文脈付きで明確に表示してくれたら?」

こうしてこうして機能的な改善がおこなわれました。今では、エラーコードを直接ライブラリに渡すことができ、ライブラリが自動で説明を取得し、フォーマットして表示してくれるようになりました。先ほどの例をこの改善を加えて書き直すと、以下のようになります。

logify.Error("Failed to send sell order", GetLastError(), "Order Management");
// Console:
// 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | 10016: Invalid stops in the request

ずっと明確で役に立つメッセージになりました。しかし、私はさらに一歩進めることにしました。明確さを実現するシステムは、コードだけでなく多言語にも対応できるべきです。そのため、この第8回では、Logifyにエラーメッセージの多言語対応を実装しました。英語、ポルトガル語、スペイン語、ドイツ語、フランス語、イタリア語、ロシア語、トルコ語、中国語、日本語、韓国語の計11言語に自動的に対応します。これにより、チームや顧客が使用する言語でログを言語でログを出力できるようになります。

今回のステップで学べることは次の通りです。

  1. MQL5ドキュメントから取得した正確な説明でエラーログを充実させること。
  2. コンテキストに応じて最適な言語を動的に選択し、多言語でメッセージを表示すること。
  3. 重大度に応じてフォーマットをカスタマイズし、エラー、警告、情報のそれぞれに異なるパターンを作成すること。

最終的に、Logifyはより賢く、アクセスしやすく、現場で実用的に役立つツールとなります。アルゴリズム取引では、あらゆる細部が重要です。エラーの意味を解読するために一秒費やすごとに、次の正しい判断から一秒遠ざかることになります。


エラー処理の設計

優れたシステムには堅実な基盤が不可欠です。そこでまず、エラーを表現する構造体を定義します。この構造体をMqlErrorと呼び、システム全体で使用して、エラーの基本的な3つの要素を格納します。

  • 数値コード(code)
  • 記号定数
  • 読みやすい説明(description)

この構造体は<Include/Logify/Error/Error.mqh>ファイルで作成しました。

//+------------------------------------------------------------------+
//| Data structure for error handling                                |
//+------------------------------------------------------------------+
struct MqlError
  {
   int      code;          // Cod of error
   string   description;   // Description of error
   string   constant;      // Type error
   
   MqlError::MqlError(void)
     {
      code = 0;
      description = "";
      constant = "";
     }
  };
//+------------------------------------------------------------------+

この構造体はシンプルですが、プラットフォームが返すあらゆるエラー(実行時エラー、通信エラー、さらにはユーザー定義のエラー)を表現するのに十分です。データを格納する場所ができたので、次にこの構造体に実際のエラー情報を格納する必要があります。

MetaQuotesは数百ものエラーコードを提供しており、それぞれにシンボリック定数と英語の説明があります。しかし私たちは、さらに一歩進めたいと考えています。ライブラリがドイツ語、スペイン語、フランス語、ポルトガル語、中国語など多言語に対応し、ログが設定された言語に自動的に切り替わるようにしたいのです。

そのための戦略として、言語ごとに.mqhファイルを作成し、既知のすべてのエラーを配列に初期化する関数を含めます。ファイル名は規則に従い「ErrorMessages.xx.mqh」とし、xxは言語の略称(例:英語ならen、ドイツ語ならde、ポルトガル語ならpt)にします。

英語の場合のファイルは、以下のようになります。

//+------------------------------------------------------------------+
//|                                             ErrorMessages.en.mqh |
//|                                                     joaopedrodev |
//|                       https://www.mql5.com/ja/users/joaopedrodev |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/ja/users/joaopedrodev"
//+------------------------------------------------------------------+
//| Import struct                                                    
//+------------------------------------------------------------------+
#include "../Error.mqh"
void InitializeErrorsEnglish(MqlError &errors[])
  {
   //--- Free and resize
   ArrayFree(errors);
   ArrayResize(errors,274);
   
   //+------------------------------------------------------------------+
   //| Unknown error                                                    |
   //+------------------------------------------------------------------+
   errors[0].code = 0;
   errors[0].description = "No error found";
   errors[0].constant = "ERROR_UNKNOWN";
   //+------------------------------------------------------------------+
   //| Server error                                                     |
   //+------------------------------------------------------------------+
   errors[1].code = 10004;
   errors[1].description = "New quote";
   errors[1].constant = "TRADE_RETCODE_REQUOTE";
   //---
   errors[2].code = 10006;
   errors[2].description = "Request rejected";
   errors[2].constant = "TRADE_RETCODE_REJECT";
   //---
   // Remaining error codes...
   //---
   errors[272].code = 5625;
   errors[272].description = "Parameter binding error, wrong index";
   errors[272].constant = "ERR_DATABASE_RANGE";
   //---
   errors[273].code = 5626;
   errors[273].description = "Open file is not a database file";
   errors[273].constant = "ERR_DATABASE_NOTADB";
  }
//+------------------------------------------------------------------+

InitializeErrorsEnglish()関数はMqlErrorの配列を受け取り、英語の既知エラー情報で配列を初期化します。このパターンは、ドイツ語、スペイン語、フランス語、イタリア語、日本語、韓国語、ポルトガル語、ロシア語、トルコ語、中国語など、他の言語にも繰り返して適用します。これらのファイルはすべて<Include/Logify/Error/Languages>フォルダに保管されています。合計で11言語分のファイルがあり、1ファイルにつき274件のエントリーが含まれます。大変な作業でしたが、現在はインクルードするだけで利用可能です。なお、すべてのコードは記事の最後に添付されています。

すべてのデータが整理できたので、次に必要なのはそれを照会するためのインターフェースです。ここで登場するのがCLogifyErrorクラスです。このクラスは、指定された言語でエラー情報を読み込み、要求されたエラーコードに対して完全な情報を返す役割を担います。

クラスのインターフェースはシンプルです。

//+------------------------------------------------------------------+
//| class : CLogifyError                                             |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyError                                        |
//| Heritage    : No heritage                                        |
//| Description : class to look up the error code and return details |
//|               of each error code.                                |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyError
  {
private:
   
   ENUM_LANGUAGE     m_language;
   MqlError          m_errors[];
   
public:
                     CLogifyError(void);
                    ~CLogifyError(void);
   
   //--- Set/Get
   void              SetLanguage(ENUM_LANGUAGE language);
   ENUM_LANGUAGE     GetLanguage(void);
   
   //--- Get error
   MqlError          Error(int code);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyError::CLogifyError()
  {
   InitializeErrorsEnglish(m_errors);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyError::~CLogifyError(void)
  {
  }
//+------------------------------------------------------------------+

インスタンス化されると、デフォルトでは英語のエラー情報を読み込みます。SetLanguage()を使えば、いつでも言語を変更でき、ライブラリは適切な言語でデータを再読み込みします。

Error(int code)メソッドは、このクラスの中核となる機能です。入力されたコードをエラー配列から探し、対応するMqlErrorを返します。もしコードが見つからない場合は、代替として一般的なエラーを返します。さらに、このメソッドは自動的に、エラーがユーザー定義エラー用の範囲(ERR_USER_ERROR_FIRSTからERR_USER_ERROR_LAST)にあるかどうかを判定し、該当する場合はそれに応じた結果も返します。

//+------------------------------------------------------------------+
//| Returns error information based on the error code received       |
//+------------------------------------------------------------------+
MqlError CLogifyError::Error(int code)
  {
   int size = ArraySize(m_errors);
   for(int i=0;i<size;i++)
     {
      if(m_errors[i].code == code)
        {
         //--- Return
         return(m_errors[i]);
        }
     }
   
   //--- User error
   if(code >= ERR_USER_ERROR_FIRST && code < ERR_USER_ERROR_LAST)
     {
      MqlError error;
      error.code = code;
      error.constant = "User error";
      error.description = "ERR_USER_ERROR";
      
      //--- Return
      return(m_errors[274]);
     }
   
   //--- Return
   return(m_errors[0]);
  }
//+------------------------------------------------------------------+

最後に、このエラー構造体をMqlLogifyModelのログデータモデルに追加しましょう。これにより、レコードのデータ構造内からエラー情報にアクセスできるようになります。

#include "LogifyLevel.mqh"
#include "Error/Error.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct MqlLogifyModel
  {
   string formated;        // The log message formatted according to the specified format.
   string levelname;       // Textual name of the log level (e.g., "DEBUG", "INFO")
   string msg;             // Main content of the log message
   string args;            // Additional arguments associated with the log message
   ulong timestamp;        // Timestamp of the log event, represented in seconds since the start of the Unix epoch
   datetime date_time;     // Date and time of the log event, in datetime format
   ENUM_LOG_LEVEL level;   // Enumeration representing the severity level of the log
   string origin;          // Source or context of the log message (e.g., class or module)
   string filename;        // Name of the source file where the log message was generated
   string function;        // Name of the function where the log was called
   ulong line;             // Line number in the source file where the log was generated
   MqlError error;         // Error data
   
   void MqlLogifyModel::Reset(void)
     {
      formated = "";
      levelname = "";
      msg = "";
      args = "";
      timestamp = 0;
      date_time = 0;
      level = LOG_LEVEL_DEBUG;
      origin = "";
      filename = "";
      function = "";
      line = 0;
     }
   
   MqlLogifyModel::MqlLogifyModel(void)
     {
      this.Reset();
     }
   MqlLogifyModel::MqlLogifyModel(string _formated,string _levelname,string _msg,string _args,ulong _timestamp,datetime _date_time,ENUM_LOG_LEVEL _level,string _origin,string _filename,string _function,ulong _line,MqlError &_error)
     {
      formated = _formated;
      levelname = _levelname;
      msg = _msg;
      args = _args;
      timestamp = _timestamp;
      date_time = _date_time;
      level = _level;
      origin = _origin;
      filename = _filename;
      function = _function;
      line = _line;
      error = _error;
     }
  };
//+------------------------------------------------------------------+

これらの変更により、ライブラリの構造は以下のようになります。


エラーをメインクラスに統合する

これまでに、エラーを表現するための堅固な構造体、多言語の説明を持つファイル、言語に応じてメッセージを取得する専用クラスが揃いました。しかし、これらはまだライブラリの中核エンジンであるCLogifyクラスとはつながっていません。

ここで、エラーシステムをLogifyのコアに統合し、必要に応じて、生成されるログごとに明確で多言語対応のエラー説明を付与できるようにします。これを実行するには、エラークラスのインスタンス化、ログ追加メソッド(Append)の調整、そして、新しいプレースホルダー{err_code}、{err_constant}、{err_description}を認識するようフォーマッターの調整をおこないます。

1. Logifyの中核にエラー機能を組み込む

CLogifyクラスは、ログのフォーマット、操作、送信をつなぐ役割を持つため、最初の自然なステップは、このクラスにエラーシステムへの直接アクセス権を与えることです。そのためには、LogifyError.mqhファイルをインポートし、CLogifyErrorクラスをCLogifyのprivateメンバーとしてインスタンス化します。これにより、ライブラリ利用者が意識することなく、常に内部で利用できる状態が保証されます。この小さな追加によっても大きな可能性が開かれます。エラーが発生した場合、コード、説明、定数をすぐに参照できるようになります。

さらに、CLogifyErrorのデフォルト言語は英語ですが、ユーザーが最適な言語を選択できるように、2つのシンプルなメソッドを追加しました。最終的なコードは以下のようになります.

#include "LogifyModel.mqh"
#include "Handlers/LogifyHandler.mqh"
#include "Handlers/LogifyHandlerComment.mqh"
#include "Handlers/LogifyHandlerConsole.mqh"
#include "Handlers/LogifyHandlerDatabase.mqh"
#include "Handlers/LogifyHandlerFile.mqh"
#include "Error/LogifyError.mqh"
//+------------------------------------------------------------------+
//| class : CLogify                                                  |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : Logify                                             |
//| Heritage    : No heritage                                        |
//| Description : Core class for log management.                     |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogify
  {
private:
   CLogifyError      m_error;
         
public:
                     CLogify();
                    ~CLogify();
   
   //--- Language
   void              SetLanguage(ENUM_LANGUAGE language);
   ENUM_LANGUAGE     GetLanguage(void);
  };
//+------------------------------------------------------------------+

2. Appendメソッドにエラーコードを組み込む

これまで、Append()メソッドはメインメッセージ、追加の引数、発生元、ファイル名、行番号、関数名などを受け取っていましたが、エラーコード自体は受け取っていませんでした。ログに障害の状況を反映させたい場合、このコードをオプションとして渡せるようにする必要があります。そのため、メソッドのシグネチャの最後にcode_errorパラメータを追加しました。

bool Append(ENUM_LOG_LEVEL level, string msg, string origin = "", string args = "", string filename = "", string function = "", int line = 0, int code_error = 0);

これにより、エラーコードを渡す必要がない場合はAppend()の呼び出し方は従来通りで問題ありません。エラーコードを渡す場合は、引数の最後に追加するだけでよい設計です。この方法により、既存のコードとの互換性を壊すことを避けられます。

そして、エラーコードを受け取ったら、CLogifyErrorクラスのError()メソッドを使って、対応するMqlError構造体を取得します。

//+------------------------------------------------------------------+
//| Generic method for adding logs                                   |
//+------------------------------------------------------------------+
bool CLogify::Append(ENUM_LOG_LEVEL level,string msg, string origin = "", string args = "",string filename="",string function="",int line=0,int code_error=0)
  {
   //--- Ensures that there is at least one handler
   this.EnsureDefaultHandler();
   
   //--- Textual name of the log level
   string levelStr = "";
   switch(level)
     {
      case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break;
      case LOG_LEVEL_INFO : levelStr = "INFO"; break;
      case LOG_LEVEL_ALERT: levelStr = "ALERT"; break;
      case LOG_LEVEL_ERROR: levelStr = "ERROR"; break;
      case LOG_LEVEL_FATAL: levelStr = "FATAL"; break;
     }
   
   //--- Creating a log template with detailed information
   datetime time_current = TimeCurrent();
   MqlLogifyModel data("",levelStr,msg,args,time_current,time_current,level,origin,filename,function,line,m_error.Error(code_error));
   
   //--- Call handlers
   int size = this.SizeHandlers();
   for(int i=0;i<size;i++)
     {
      data.formated = m_handlers[i].GetFormatter().Format(data);
      m_handlers[i].Emit(data);
     }
   
   return(true);
  }
//+------------------------------------------------------------------+

MqlLogifyModelオブジェクトは、これまでのログ情報に加えて、それを引き起こしたエラーの本質も保持するようになりました。あとは、Error()およびFatal()メソッドに新しい関数オーバーロードを追加するだけです。既存のメソッドは削除せずに残しておく必要があります。削除してしまうと、ライブラリ利用者が必ずしもエラーコードを渡すわけではないため、エラーが発生する可能性があります。コードは以下のようになります。

class CLogify
  {
public:
   //--- Specific methods for each log level
   bool              Debug(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Info(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Alert(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Error(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Fatal(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0);
  };
bool CLogify::Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0)
  {
   return(this.Append(LOG_LEVEL_ERROR,msg,origin,args,filename,function,line,code_error));
  }
bool CLogify::Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0)
  {
   return(this.Append(LOG_LEVEL_FATAL,msg,origin,args,filename,function,line,code_error));
  }

3. エラープレースホルダーを使ったメッセージのフォーマット

現時点ではエラー情報は読み込まれていますが、フォーマット済みログにはまだ反映されていません。これを実現するために、CLogifyFormatterのプレースホルダーシステムを拡張し、エラーのプロパティを含める必要があります。ロジックはシンプルですが、とても巧妙です。ログレベルがERROR以上の場合にのみエラー情報を表示します。情報ログやデバッグログにはこの冗長さは不要で、求められるのは「明確さ」であり「雑多さ」ではありません。

//+------------------------------------------------------------------+
//| Format logs                                                      |
//+------------------------------------------------------------------+
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   
   StringReplace(formated,"{levelname}",data.levelname);
   StringReplace(formated,"{msg}",data.msg);
   StringReplace(formated,"{args}",data.args);
   StringReplace(formated,"{timestamp}",IntegerToString(data.timestamp));
   StringReplace(formated,"{date_time}",this.FormatDate(data.date_time));
   StringReplace(formated,"{level}",IntegerToString(data.level));
   StringReplace(formated,"{origin}",data.origin);
   StringReplace(formated,"{filename}",data.filename);
   StringReplace(formated,"{function}",data.function);
   StringReplace(formated,"{line}",IntegerToString(data.line));
   
   if(data.level >= LOG_LEVEL_ERROR)
     {
      StringReplace(formated,"{err_code}",IntegerToString(data.error.code));
      StringReplace(formated,"{err_constant}",data.error.constant);
      StringReplace(formated,"{err_description}",data.error.description);
     }
   else
     {
      StringReplace(formated,"{err_code}","");
      StringReplace(formated,"{err_constant}","");
      StringReplace(formated,"{err_description}","");
     }
   
   return(formated);
  }
//+------------------------------------------------------------------+

これにより、3つの新しいカスタマイズ用フックを作成しました。

  • {err_code}:正確なエラー番号を表示(例:10006)
  • {err_constant}:関連する定数を表示(例:TRADE_RETCODE_REJECT)
  • {err_description}:読みやすい説明を表示(例:「Request rejected」)

たとえば、次のようなグローバルフォーマットを定義したとします。

"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})"

この場合、頭の中では次のような出力が期待されます。

2025.06.09 14:22:15 [ERROR] Failed to send order (10016 TRADE_RETCODE_REJECT: Request rejected)

実際、ERRORやFATALログレベルではこのフォーマットは完璧に機能します。しかし、同じフォーマットをDEBUG、INFO、ALERTで使うとどうなるでしょうか。結果は以下のようになります。

2025.06.09 14:22:15 [INFO] Operation completed successfully (  : )

あるいは、フォーマットによってはログの表示がずれてしまうこともあります。エラープレースホルダーを下位レベルではクリアする工夫をしても、問題は残ります。つまり、このフォーマットは{err_code}、{err_constant}、{err_description}に値があることを前提としているため、値がない場合はログが不自然で不完全になります。同じフォーマットマスクを、全く異なる文脈のメッセージに適用していることが、意味論上のズレを生んでいます。

解決策はデータを無理に補正することではなく、各ログレベルをその性質に応じた「別種のメッセージ」として扱うことです。必要性やフォーマットが異なる前提で設計します。

そのため、CLogifyFormatterに汎用フォーマットを持たせるのではなく、ログレベル(DEBUG、INFO、ALERT、ERROR、FATAL)ごとに専用のフォーマッターを用意します。これにより、次のようにカスタマイズが可能になります。

  • DEBUG:関数、ファイル、行を表示
  • INFO:最小限の情報、何が起きたかだけ
  • ALERT:発生元や引数などのコンテキストを追加
  • ERROR:エラー情報を含める
  • FATAL:可能な限り詳細に表示


複数フォーマットの実装

まず、フォーマットの保持方法を調整します。単なる文字列ではなく、5つの選択肢(重大度レベルごとに1つずつ)を持つ配列に変更します。

古いコード 新しいコード
class CLogifyFormatter
  {
private:
   
   //--- Stores the formats
   string            m_date_format;
   string            m_log_format;
  };
class CLogifyFormatter
  {
private:
   
   //--- Stores the formats
   string            m_date_format;
   string            m_format[5];
  };

m_log_formatは文字列の配列に置き換えられ、ログレベルごとに1つの位置を持つようになりました。これにより、5種類のフォーマットを用意でき、それぞれのメッセージ種別に応じて調整可能になります。

レベルごとにフォーマットを分けたため、それらを設定する便利な方法が必要になります。そこで、2つのSetFormatメソッドを作成しました。全レベルに同じフォーマットを適用するメソッドと、特定のレベルにだけ個別設定するメソッドです。

class CLogifyFormatter
  {
public:
   void              SetFormat(string format);
   void              SetFormat(ENUM_LOG_LEVEL level, string format);
  };
//+------------------------------------------------------------------+
//| Sets the format for all levels                                   |
//+------------------------------------------------------------------+
void CLogifyFormatter::SetFormat(string format)
  {
   m_format[LOG_LEVEL_DEBUG] = format;
   m_format[LOG_LEVEL_INFO] = format;
   m_format[LOG_LEVEL_ALERT] = format;
   m_format[LOG_LEVEL_ERROR] = format;
   m_format[LOG_LEVEL_FATAL] = format;
  }
//+------------------------------------------------------------------+
//| Sets the format to a specific level                              |
//+------------------------------------------------------------------+
void CLogifyFormatter::SetFormat(ENUM_LOG_LEVEL level, string format)
  {
   m_format[level] = format;
  }
//+------------------------------------------------------------------+

この設計は便利なだけでなく、汎用性と特異性の両方を兼ね備えています。シンプルさを求める場合は、単一のSetFormat(...)を呼ぶだけで十分です。より精密な設定をおこないたい場合は、希望する各レベルに対してSetFormat(level, format)を呼び出せばよいのです。

フォーマットをレベルごとに分けたことで、フォーマット処理メソッドもこの区分を反映させる必要があります。以前は、汎用の変数m_log_formatからメッセージマスクを取得していましたが、現在は配列から直接、ログレベルをインデックスとしてフォーマットを取得するようになっています。

古いコード 新しいコード
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   ...
  }
string CLogifyFormatter::Format(MqlLogifyModel &data)
  {
   string formated = m_format[data.level];
   ...
  }

これにより、問題をクリーンで拡張性があり、予測可能な方法で解決できます。余計なチェックや、空のプレースホルダーを抑制するための回避策は不要です。各ログレベルは、何を含むべきかを正確に把握しています。


テスト

構築した機能を実際に動かし、さまざまなシナリオやログレベルでの動作を確認します。前回の記事で使用したテスト用ファイル「LogifyTest.mqh」内に、次の要素を用意しています。

  • Comment()を使ったビジュアルハンドラ(フレーム、タイトル、行数制限付き)
  • Print()を使ったコンソールハンドラ(プラットフォーム内部ログにメッセージを記録)
  • ハンドラ間で共有されるフォーマッター
  • 4件のログメッセージ(DEBUG 2件、INFO 1件、エラーコード付きのERROR 1件)
//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Handler config
   MqlLogifyHandleCommentConfig m_config;
   m_config.size = 5;                                      // Max log lines
   m_config.frame_style = LOG_FRAME_STYLE_SINGLE;          // Frame style
   m_config.direction = LOG_DIRECTION_UP;                  // Log direction (up)
   m_config.title = "Expert name";                         // Log panel title
   
   CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}");
   formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]");
   
   //--- Create and configure handler
   CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment();
   handler_comment.SetConfig(m_config);
   handler_comment.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_comment.SetFormatter(formatter);
   
   CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole();
   handler_console.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_console.SetFormatter(formatter);
   
   //--- Add handler to Logify
   logify.AddHandler(handler_comment);
   logify.AddHandler(handler_console);
   
   //--- Test logs
   logify.Debug("Initializing Expert Advisor...", "Init", "");
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   logify.Error("Failed to send sell order", 10016,"Order Management");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

このコードを実行すると、MetaTraderのコンソールには期待どおりにフォーマットされたメッセージが表示されます。視覚的な結果からも、DEBUGやINFOのような下位レベルは簡潔なフォーマットに従い、ERRORはエラー情報を含むより詳細なフォーマットが適用されていることが一目でわかります。

09/06/2025 14:22:31 [DEBUG]: Initializing Expert Advisor...
09/06/2025 14:22:31 [DEBUG]: RSI indicator value calculated: 72.56
09/06/2025 14:22:31 [INFO]: Buy order sent successfully
09/06/2025 14:22:31 [ERROR]: Failed to send sell order [TRADE_DISABLED | 10016 | Trade operations not allowed]

最後のログには、角括弧で囲まれた追加ブロックがあり、エラーのシンボリック定数、数値コード、ユーザー向けの説明が表示されていることに注目してください。しかも、これはエキスパートアドバイザー(EA)のコードに余分な条件分岐を追加することなく実現されています。各ログレベルは独自のフォーマットを持ち、適切なプレースホルダーは、意味のある場所にだけ表示される仕組みです。


結論

この記事では、Logifyライブラリの改善点について紹介しました。最初の記事では、まずデータモデルを構築し、次に異なる出力チャネル向けの柔軟なハンドラを作成しました。そして今回は、エラーメッセージの多言語対応を追加し、どの言語環境でも、各ログが開発者に明確な技術的コンテキストを提供できるようにしました。

さらに、動的フォーマッターを導入し、カスタムプレースホルダーを扱えるだけでなく、ログの重大度に応じて自動で適応する仕組みを作りました。汎用フォーマットでは一貫性が保てないことに気づいたため、レベルごとに特定のフォーマットを受け入れるアーキテクチャに進化させました。そして最後に、実践的なテストでLogifyがリアルタイムで、クリーンかつ予測可能で拡張性のある形で動作することを検証しました。

ここまで読んでいただいた方は、以下のことを学んだことになります。

  • 拡張性があり、結合度の低いログ構造を設計する方法
  • MetaTraderのコードに基づく多言語エラーメッセージを統合する方法
  • プレースホルダーと、重大度に応じた文脈に基づくフォーマットの扱い方
  • 自分の設定で再利用可能なハンドラを構築する方法

Logifyは、他の優れたツールと同様に、今後も進化し続ける可能性があります。新機能が実装され次第、次回の記事でご紹介します。それまでは、ぜひLogifyを使い、適応させ、改善してみてください。


ファイル名 詳細
Experts/Logify/LogifyTest.mq5
ライブラリの機能をテストするファイル。実用的な例が含まれています。
Include/Logify/Error/Languages/ErrorMessages.XX.mqh 各言語のエラーメッセージを格納します。Xは言語の頭字語を表します。
Include/Logify/Error/Error.mqh
エラー情報を格納するためのデータ構造体
Include/Logify/Error/LogifyError.mqh
詳細なエラー情報を取得するためのクラス
Include/Logify/Formatter/LogifyFormatter.mqh
ログレコードをフォーマットし、プレースホルダーを特定の値に置き換えるクラス
Include/Logify/Handlers/LogifyHandler.mqh
レベル設定やログ送信を含むログハンドラを管理するための基本クラス
Include/Logify/Handlers/LogifyHandlerComment.mqh
MetaTraderのターミナルチャートのコメントにフォーマットされたログを直接送信するログハンドラ
Include/Logify/Handlers/LogifyHandlerConsole.mqh
フォーマットされたログをMetaTraderの端末コンソールに直接送信するログハンドラ
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
フォーマットされたログをデータベースに送信するログハンドラ(現在は出力のみが含まれているが、すぐに実際のSQLiteデータベースに保存する予定)
Include/Logify/Handlers/LogifyHandlerFile.mqh
フォーマットされたログをファイルに送るログハンドラ
Include/Logify/Utils/IntervalWatcher.mqh
時間間隔が経過したかどうかをチェックし、ライブラリ内でルーチンを作成できるようにする
Include/Logify/Logify.mqh ログ管理、レベル、モデル、フォーマットの統合のためのコアクラス
Include/Logify/LogifyLevel.mqh Logifyライブラリのログレベルを定義するファイル。詳細な制御が可能
Include/Logify/LogifyModel.mqh レベル、メッセージ、タイムスタンプ、コンテキストなどの詳細を含むログレコードをモデル化する構造

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/18467

添付されたファイル |
Logify.zip (151.36 KB)
最後のコメント | ディスカッションに移動 (6)
joaopedrodev
joaopedrodev | 20 6月 2025 において 12:44
ありがとうございます。改善の提案があれば、ご連絡ください!
hini
hini | 20 6月 2025 において 17:42
joaopedrodev #:
ありがとうございます。改善提案があれば、ご連絡ください!
ロギングの頻度が非常に高いが、繰り返しのプリントを避けたい場合はどうすればよいですか?例えば、取引開始シグナルが検出されたものの、スプレッドが大きすぎて何十秒も持続している場合、ロギングを使用すると、少なくとも何十回、あるいは何百回も印刷されてしまいます。この状況を解決するにはどうすればよいでしょうか?変数を使用して、ログが一度しか表示されないようにするのが一つの解決策であることは知っていますが、ロギング・ライブラリ自体がこれを処理できればより良いでしょう。何かご提案いただけますか?
hini
hini | 20 6月 2025 において 17:54

ロギングライブラリ Logify をコンパイルしたところ、MT5 ビルド 5100 以降、CLogifyHandlerDatabase::Query 型に関連するコンパイルエラーがいくつか発生しました。この問題はすでに解決されているはずです。
joaopedrodev
joaopedrodev | 23 6月 2025 において 13:24
ご指摘ありがとうございます。今後の記事で取り上げさせていただきます。
Carl Schreiber
Carl Schreiber | 26 9月 2025 において 20:01
joaopedrodev #:
ご提案ありがとうございます。今後の記事で取り上げさせていただきます。

言語についてのちょっとしたアイデア。

オリジナルの英語のメッセージを、翻訳機(deepl.comやtranslate.google.com)に簡単にコピー&ペーストできるように並べ、その結果をプログラムに戻す。これにより、誰でも簡単に自分の言語でプログラムをセットアップすることができる。

Deepleは36言語、Googleは約130言語を認識する。

初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(II) 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(II)
本日は、外部ニュースAPIを統合し、News Headline EAの見出し取得元として活用する新たなステップに進みます。このフェーズでは、既存の大手ニュースソースから新興の情報源まで幅広く取り上げ、それぞれのAPIに効果的にアクセスする方法を学びます。さらに、取得したデータをパースし、エキスパートアドバイザー(EA)内での表示に最適化された形式へ変換する手法についても解説します。ニュース見出しや経済指標カレンダーをチャート上に直接表示できることには、大きなメリットがあります。コンパクトで邪魔にならないインターフェースを通じて、取引中でも効率的に情報を確認できるようになるのです。
知っておくべきMQL5ウィザードのテクニック(第71回):MACDとOBVのパターンの使用 知っておくべきMQL5ウィザードのテクニック(第71回):MACDとOBVのパターンの使用
移動平均収束拡散法(MACD)オシレーターとオンバランスボリューム(OBV)オシレーターは、MQL5のエキスパートアドバイザー(EA)内で併用できるもう一つの指標ペアです。本連載における慣例どおり、この組み合わせも補完関係にあり、MACDがトレンドを確認し、OBVが出来高を検証します。MQL5ウィザードを用いて、この2つが持つ潜在力を構築、検証します。
データサイエンスとML(第43回):潜在ガウス混合モデル(LGMM)を用いた指標データにおける隠れパターン検出 データサイエンスとML(第43回):潜在ガウス混合モデル(LGMM)を用いた指標データにおける隠れパターン検出
チャートを見ていて、奇妙な感覚を覚えたことはありませんか。表面のすぐ下にパターンが隠されている気がして、もし解読できれば価格がどこに向かうか分かるかもしれない、そんな秘密のコードが存在するかもしれないという感覚です。ここで紹介するのがLGMM、マーケットの隠れたパターンを検出するモデルです。これは機械学習モデルで、隠れた市場のパターンを識別する手助けをします。
MQL5 Algo Forgeへの移行(第3回):外部コードを自分のプロジェクトに統合する MQL5 Algo Forgeへの移行(第3回):外部コードを自分のプロジェクトに統合する
MQL5 Algo Forgeストレージにある任意のリポジトリから外部コードを自分のプロジェクトへ統合する方法を見ていきましょう。本記事ではいよいよ、有望でありながらもより複雑な課題に踏み込みます。すなわち、MQL5 Algo Forge内のサードパーティ製リポジトリからライブラリを実際に接続し、活用する方法についてです。