English Deutsch
preview
ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加

ログレコードをマスターする(第9回):ビルダーパターンの実装とデフォルト設定の追加

MetaTrader 5 |
103 5
joaopedrodev
joaopedrodev

はじめに

私が個人および業務の様々なプロジェクトでLogifyを使い始めてすぐに気づいたのは、ライブラリそのものの堅牢性や機能性が課題なのではなく、その設定の複雑さこそが最大のハードルであるということでした。Logifyはエキスパートアドバイザー(EA)におけるログ管理のための強力なツールであり、複数のハンドラ、ログレベル、カスタムフォーマット、多言語対応など数多くの機能を提供しています。しかしその一方で、ユーザーは各ハンドラやフォーマッタ、各種パラメータを手作業で設定しなければならず、小規模なプロジェクトであれば問題ないものの、EAやプロジェクトの数が増えるにつれて、繰り返し、煩雑、エラーの温床となる作業に変わってしまいます。

たとえば、各EAごとに、タブコメント、コンソール、ファイル、データベースといった特定のハンドラを作成する、各ハンドラに対して最小ログレベルを設定する、エラーメッセージ、デバッグ、アラートごとにフォーマットを定義するといった複雑な設定を毎回繰り返すことを想像してください。これらはすべて必要な手順ではあるものの、結果としてコードは冗長で直感的でなくなり、開発の流れを妨げます。最初の複雑さがが障壁となり、Logifyが実行時に優れたログ管理を提供するにもかかわらず、その採用を躊躇させる要因になりかねません。

そこで、ユーザーの負担を減らし、柔軟性やカスタマイズ性を損なわずに、設定をもっと簡単にできないかと考えたのです。この発想から誕生したのがLogify用のビルダーです。これは、直感的なメソッドを連鎖的に呼び出すことで設定を組み立てられる仕組みで、ハンドラを適切なデフォルト値付きで生成し、必要に応じて局所的に調整できるようにします。目指したのは、数十行に及ぶ複雑な設定コードを、ほんの数回のメソッド呼び出しに置き換えることです。つまり、何をしたいのかを明快に要約して書けるようにすることでした。

本記事では、この改善をどのように実装したかをご紹介します。まずビルダーの設計と使い方を解説し、続いて実際の例を用いてLogifyをどのように設定できるかをお見せします。


ビルダーパターンを理解する:複雑なオブジェクトの構築をシンプルに

CLogifyBuilderの実装に入る前に、今回用いるビルダーパターンについて整理しておきましょう。

ビルダーパターンの目的はただ一つ、複雑なオブジェクトの生成を容易にすることです。特に、生成に多くのステップや多様なオプションが関わる場合に有効です。ビルダーでは、構築のプロセスと最終的なオブジェクトの表現を切り離すことで、同じ構築手順から異なる「バリエーション」のオブジェクトを作り出すことができます。

例を挙げましょう。車を組み立てるとします。モデル、色、エンジンの種類、トランスミッション、オプション、タイヤサイズ、内装など、数多くの選択肢があります。これらをすべてコード上でコンストラクタに渡すのは現実的ではありません。

ビルダーはこれを解決します。構築を連鎖的なメソッド(Fluent Interface、流暢なインターフェース)⁠に分け、各ステップで特定の部分を設定していきます。そして最後に「.Build()」を呼び出せば、完成したオブジェクトが手に入るのです。

このアプローチには、3つの大きな利点があります。

  1. 明快で直線的な記述:構築プロセスが「欲しいもの」を順序立てて説明するスクリプトのように見えます。
  2. エラーの削減:各ステップが独立しているため、誤設定を特定しやすく修正も容易です。
  3. 柔軟性と再利用性:同じビルダーを使って、少しの変更で異なるバリエーションを生成できます。


Logifyへのビルダーの適用

Logifyでロガーインスタンス(CLogify)を構築する場合、複数のハンドラ(コンソール、コメント、ファイルなど)を用意し、最小ログレベルを設定し、各ハンドラごとにフォーマッタを指定し、さらにパネルサイズやフレームスタイルといったパラメータも設定しなければなりません。これを毎回手作業でおこなえば、コードは煩雑になりがちです。

ビルダーを使うことで、この問題は解決できます。ユーザーは細部を気にせず、次のように直感的に記述できるのです。

CLogify *logify = logify
   .Create()
   .AddHandlerComment()
      .SetTitle("My Logger")
      .SetSize(5)
   .Done()
   .AddHandlerConsole()
   .Done()
   .Build();

このコードの読みやすさをご覧ください。「.Create()」で構築を開始し、「.AddHandlerXXX()」でハンドラを追加します。各ハンドラのパラメータはSetX()メソッドで調整でき、「.Done()」でそのハンドラの設定を終了します。最後に「.Build()」を呼び出すことで、完成したCLogifyインスタンスを受け取ることができます。

ビルダーの強みはここにあります。開発者は「どのように実装されるか」ではなく、「何を望むのか」を簡潔に記述できるのです。

これで、なぜこのパターンを使うのか、そしてそれがどのようにしてLogifyをより実用的かつスケーラブルにするのかが理解できました。次に、このクラスがどのように構築されているのか、そして実際の現場でどのように活用できるのかを具体的に見ていきましょう。


各ハンドラ専用のビルダー

新たに<Include/Logify/LogifyBuilder.mqh>というファイルを作成しました。この中にはCLogifyBuilderがあり、その内部にはCLogifyのインスタンスがprivateフィールドとして保持されています。このインスタンスをビルダーが操作し、最終的に完成した状態でユーザーに返す仕組みです。

//+------------------------------------------------------------------+
//|                                                LogifyBuilder.mqh |
//|                                                     joaopedrodev |
//|                       https://www.mql5.com/ja/users/joaopedrodev |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/ja/users/joaopedrodev"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#include "Logify.mqh"
//+------------------------------------------------------------------+
//| class : CLogifyBuilder                                           |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyBuilder                                      |
//| Heritage    : No heritage                                        |
//| Description : Build CLogify objects, following the Builder design|
//|               pattern.                                           |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyBuilder
  {
private:
   CLogify           *m_logify;
   
public:
                     CLogifyBuilder(void)
                    ~CLogifyBuilder(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyBuilder::CLogifyBuilder(void)
  {
   m_logify = new CLogify();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyBuilder::~CLogifyBuilder(void)
  {
  }
//+------------------------------------------------------------------+

CLogifyBuilderクラスはCLogifyオブジェクト構築の中心となる存在ですが、各ハンドラの設定は専用のビルダーに委譲されています。具体的には、CLogifyHandlerCommentBuilder、CLogifyHandlerConsoleBuilder、CLogifyHandlerDatabaseBuilder、CLogifyHandlerFileBuilderです。

それぞれのビルダーは、特定のログ出力タイプに関する詳細をカプセル化しています。これにより、責務が混在することを避け、ビルドの核となる部分をクリーンかつモジュール化された状態に保つことができます。ConsoleBuilderを例に、その構造を見てみましょう。


CLogifyHandlerConsoleBuilder

このクラスは、まずFluent APIをサポートするための最小限の構造を定義することから始まります。コンストラクタではメインのビルダーへのポインタ(CLogifyBuilder*)を受け取り、ビルドのコンテキストへの参照を保持します。これにより、ハンドラの設定が完了した後、Done()を呼び出すことでこのコンテキストに戻ることができるようになっています。

//+------------------------------------------------------------------+
//| class : CLogifyHandlerConsoleBuilder                             |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyHandlerConsoleBuilder                        |
//| Heritage    : No heritage                                        |
//| Description : Console handler constructor.                       |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyHandlerConsoleBuilder
  {
private:

   CLogifyBuilder    *m_parent;

public:
                     CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify);
                    ~CLogifyHandlerConsoleBuilder(void);

   CLogifyBuilder    *Done(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify)
  {
   m_parent = logify;
  };
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void)
  {
  }
//+------------------------------------------------------------------+
//| Finalizes the handler configuration.                             |
//+------------------------------------------------------------------+
CLogifyBuilder    *CLogifyHandlerConsoleBuilder::Done(void)
  {
   m_parent.AddHandler(GetPointer(m_handler));
   delete GetPointer(this);
   return(m_parent);
  }
//+------------------------------------------------------------------+

Done()はメインビルダーに戻るためのポイントであり、CLogifyにハンドラを追加し、中間的なビルダーを破棄します。これにより処理のサイクルがスムーズに流れ、不要なメモリ保持を避けることができます。

すべての専門ビルダーは基本的に同じ構造を持っています。

  • CLogifyBuilder *m_parent:親ビルダーへの参照で、Done() を通じて戻る際に使用します。
  • CLogifyFormatter *m_formatter:ハンドラに関連付けられるフォーマッタのインスタンスです。
  • CLogifyHandlerX *m_handler:実際に設定されるハンドラ本体です。

より複雑なビルダー(たとえばFileやDatabaseなど)では、内部に設定用の構造体(MqlLogifyHandleXConfig)を持ちます。これは、ハンドラが登録されるまでの間、値を一時的に保持するために使われます。

このように、設定データとハンドラへの適用を分離することで、検証の適用、プリセットの利用、オプションの組み合わせをおこないやすくなり、ハンドラ自体のロジックを肥大化させずに済むのです。


完全なコンソールビルダー

次に、設定用のメソッドがすでに実装されているビルダーを見てみましょう。

//+------------------------------------------------------------------+
//| class : CLogifyHandlerConsoleBuilder                             |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyHandlerConsoleBuilder                        |
//| Heritage    : No heritage                                        |
//| Description : Console handler constructor.                       |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyHandlerConsoleBuilder
  {
private:

   CLogifyBuilder    *m_parent;
   CLogifyFormatter  *m_formatter;
   CLogifyHandlerConsole *m_handler;

public:
                     CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify);
                    ~CLogifyHandlerConsoleBuilder(void);

   CLogifyHandlerConsoleBuilder *SetLevel(ENUM_LOG_LEVEL level);
   CLogifyHandlerConsoleBuilder *SetFormatter(string format);
   CLogifyHandlerConsoleBuilder *SetFormatter(ENUM_LOG_LEVEL level, string format);
   CLogifyBuilder    *Done(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify)
  {
   m_parent = logify;
   m_formatter = new CLogifyFormatter();
   m_handler = new CLogifyHandlerConsole();

   m_handler.SetFormatter(GetPointer(m_formatter));
  };
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void)
  {
  }
//+------------------------------------------------------------------+
//| Sets the log level for the handler.                              |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetLevel(ENUM_LOG_LEVEL level)
  {
   m_handler.SetLevel(level);
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//| Sets the default format string for the formatter.                |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(string format)
  {
   m_formatter.SetFormat(format);
   m_handler.SetFormatter(GetPointer(m_formatter));
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//| Sets a log-level-specific format for the formatter.              |
//+------------------------------------------------------------------+
CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(ENUM_LOG_LEVEL level, string format)
  {
   m_formatter.SetFormat(level,format);
   m_handler.SetFormatter(GetPointer(m_formatter));
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//| Finalizes the handler configuration.                             |
//+------------------------------------------------------------------+
CLogifyBuilder    *CLogifyHandlerConsoleBuilder::Done(void)
  {
   m_parent.AddHandler(GetPointer(m_handler));
   delete GetPointer(this);
   return(m_parent);
  }
//+------------------------------------------------------------------+

これで準備が整ったので、残りの専門ビルダーに移ります。


その他の専門ビルダー

CLogifyHandlerCommentBuilder

グラフ上にメッセージを直接出力するハンドラ(Comment())の設定を担当し、MqlLogifyHandleCommentConfig構造体を使用します。

以下の設定が可能です。

  • SetSize(int):表示するメッセージの数
  • SetFrameStyle(ENUM_LOG_FRAME_STYLE):ログ領域の枠のスタイル
  • SetDirection(ENUM_LOG_DIRECTION):縦方向または横方向の表示
  • SetTitle(string):ログ上部に固定表示するタイトル

また、SetLevel()やSetFormatter()による設定も可能で、グローバルまたはレベルごとに指定できます。設定はDone()で確定されます。

CLogifyHandlerDatabaseBuilder

データベース永続化ハンドラ(現時点ではバイナリ構造向け)の設定をおこないます。MqlLogifyHandleDatabaseConfigを使用します。提供される設定は以下の通りです。

  • SetDirectory(string)
  • SetBaseFileName(string)
  • SetMessagesPerFlush(int)

構造は他のビルダーと同様で、一貫性が保たれています。

CLogifyHandlerFileBuilder

すべての中で最も充実しているのが、.logや.txtなどのファイルへの書き込みを設定するハンドラで、MqlLogifyHandleFileConfigを使用します。

利用可能なオプションは以下の通りです。

  • SetDirectory()、SetFilename()、SetFileExtension()
  • SetRotationMode():日付、サイズ、手動
  • SetMessagesPerFlush()
  • SetCodepage():CP_UTF8など
  • SetFileSizeMB()、SetMaxFileCount()

さらに、あらかじめ用意されたプリセットを使えるユーティリティメソッドが3つあります。

  • ConfigNoRotation()
  • ConfigDateRotation()
  • ConfigSizeRotation()

これらのショートカットを使うことで、単一の呼び出しで完全な設定をおこなうことができ、繰り返し利用するパターンに便利です。他のハンドラ用のビルダーも基本的に同じ構造を持ち、個別の設定バリエーションがあるのみです。完全なコードは添付ファイルで参照できます。


コアクラス:CLogifyBuilder

これまで専用ビルダーの仕組みを見てきたので、次はそれらを統括する役割を持つCLogifyBuilderクラスを見ていきましょう。

このクラスは、すべてのハンドラが追加されるメインのCLogifyインスタンスを生成・管理する責任を持ちます。しかし、すべてを直接設定するのではなく、その役割を専用ビルダーに委譲しています。各専用ビルダーは特定の種類のハンドラの設定を担当します。こうして、CLogifyBuilderはログ構築のモジュール化されたプロセスを導く統括役のようになります。

以下はクラスの完全な実装です。

//+------------------------------------------------------------------+
//| class : CLogifyBuilder                                           |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyBuilder                                      |
//| Heritage    : No heritage                                        |
//| Description : Build CLogify objects, following the Builder design|
//|               pattern.                                           |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyBuilder
  {
private:

   CLogify           *m_logify;

public:
                     CLogifyBuilder(void);
                    ~CLogifyBuilder(void);

   CLogifyBuilder    *UseLanguage(ENUM_LANGUAGE language);

   //--- Starts configuration handlers
   CLogifyHandlerCommentBuilder *AddHandlerComment(void);
   CLogifyHandlerConsoleBuilder *AddHandlerConsole(void);
   CLogifyHandlerDatabaseBuilder *AddHandlerDatabase(void);
   CLogifyHandlerFileBuilder *AddHandlerFile(void);

   void              AddHandler(CLogifyHandler *handler);
   CLogify           *Build(void);
  };
//+------------------------------------------------------------------+

このクラスには、いくつか重要な機能が集中しています。

  • UseLanguage(ENUM_LANGUAGE language):ログシステムの主要言語を設定できるメソッドです。これにより、内部エラーメッセージ(CLogifyError経由)や、ローカライズに依存するフォーマットが影響を受けます。
  • AddHandlerX():各ハンドラを設定するためのエントリーポイントです。AddHandlerConsole()やAddHandlerFile()などの各メソッドは、専用ビルダーを生成し、自身(this)をポインタとして渡します。これにより、設定完了後にDone()を通じて元のコンテキストに戻ることができます。
  • AddHandler(CLogifyHandler *handler):専用ビルダーが設定終了時(Done())に内部から呼び出すメソッドです。完成したハンドラを、構築中のCLogifyインスタンスに登録します。
  • Build():構築プロセスを完了させ、「delete GetPointer(this)」によってビルダーをメモリから解放し、完成したログインスタンスを返します。これにより、ビルダーのインスタンスはあくまで組み立て中のみ存在するという設計思想が強調されます。

この構造により、ビルダーパターンはモジュール化され、明快で拡張性のある形で完成しています。完全なコードは添付ファイルで確認できます。


洗練されたエントリーポイント

すでにCLogifyBuilderのコンストラクタは存在しますが、ユーザーが「new CLogifyBuilder()」と直接呼び出してインスタンス化する方法は、ログ構築の開始として最も表現力があり直感的とは言えません。

そこで、CLogifyクラスにはCreate()という静的メソッドを追加しました。

//+------------------------------------------------------------------+
//| Returns an instance of the builder                               |
//+------------------------------------------------------------------+
#include "LogifyBuilder.mqh"
CLogifyBuilder *CLogify::Create(void)
  {
   return(new CLogifyBuilder());
  }
//+------------------------------------------------------------------+

そして、このメソッドはCLogifyクラス内で次のように宣言されています。

class CLogify
  {
public:
   static CLogifyBuilder *Create(void);
  };

Create()メソッドが静的である理由は以下の通りです。

  1. クラスに属するため:インスタンスではなくクラスに属するメソッドです。まだCLogifyのインスタンスを持たない状態でビルダーを開始したい場合に利用します。
  2. 内部状態に依存しないため:このメソッドがおこなうのは、ビルダーを生成して返すことだけです。
  3. ビルダーへの直接的な依存を避けるため:もし将来ビルダーの実装が変更されても、CLogifyの静的インターフェースを維持することで、既存コードとの互換性を保つことができます。


デフォルト設定

Logifyライブラリが形になってくると、一般的なシナリオについても考える必要があります。それは、「何も設定せずに、すぐにログを取りたいユーザー」です。こうしたユーザーはハンドラや言語、フォーマット、ディレクトリには関心がなく、開発やテスト中にメッセージをすぐに確認できる出力が必要なだけです。こうしたニーズに応えるために、EnsureDefaultHandler()メソッドを導入しました。

このメソッドは自動的なフォールバックとして機能します。明示的にハンドラが設定されていない場合、Console用とComment()用の2つの基本的で機能的なハンドラを追加します。どちらもMQL5にネイティブに備わっており、メッセージを即座に確認できることを保証します。

void CLogify::EnsureDefaultHandler()
  {
   //--- Check if there is no handler
   if(this.SizeHandlers() == 0)
     {
      this.AddHandler(new CLogifyHandlerConsole());
      this.AddHandler(new CLogifyHandlerComment());
     }
  }

呼び出しはコンストラクタ内ではなくAppend()メソッド内でおこなわれます。

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();
   
   // (continues...)
  }

この設計には戦略的な意図があります。もしデフォルトハンドラをコンストラクタで追加してしまうと、その後におこなわれるどんな設定にもデフォルトが追加されてしまい、ログに重複したり意図しないハンドラが含まれる可能性があります。これは特に、ユーザーが出力をファイルやデータベース、リモートサーバーなど単一の宛先にまとめたい場合に問題となります。

このため、このロジックをAppend()に移すことで、開発者の手で制御できるようにしています。動作は次の通りです。

  • ハンドラが一つも設定されていない場合、Append()の最初の呼び出し時にEnsureDefaultHandler()が両方のデフォルトハンドラを有効にします。
  • 少なくとも1つのハンドラが手動で追加されている場合、このメソッドは何もおこないません。
  • デフォルトの挙動は安全で可視性が高く、明示的な設定がある場合には干渉しません

このアプローチにより、利便性と予測可能性のバランスが取れています。手軽で機能的なログを求めるユーザーにとってはシステムが自動で動作し、細かい制御が必要な場合には、ライブラリは開発者の選択を厳密に尊重します。

これにより、Logifyはすぐに使える一方、カスタマイズ性も損なわれません。これは、初心者や厳格なログ基準を求めるチーム双方にとって、採用を容易にする重要なステップです。


テスト

この記事で紹介した改善を適用する前と後で、ライブラリの使い勝手がどのように変わったかを比較してみましょう。

以前は、ログを設定するために一連の手作業が必要でした。オブジェクトを生成し、ログレベルを定義し、フォーマッタを作成し、設定構造体に値を入れ、ハンドラを一つずつ組み立てる、といった作業です。しかし、EnsureDefaultHandler()によるデフォルト設定が導入されたことで、開発者はライブラリをわずか1行で利用開始できるようになりました。

以下に、改善前と改善後の2つのシナリオを並べて示します。

古いコード 新しいコード
//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify *logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   MqlLogifyHandleCommentConfig m_config;
   m_config.size = 5;
   m_config.frame_style = LOG_FRAME_STYLE_SINGLE;
   m_config.direction = LOG_DIRECTION_UP;
   m_config.title = "Expert name";

   CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}");
   formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]");

   CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment();
   handler_comment.SetConfig(m_config);
   handler_comment.SetLevel(LOG_LEVEL_DEBUG);
   handler_comment.SetFormatter(formatter);

   CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole();
   handler_console.SetLevel(LOG_LEVEL_DEBUG);
   handler_console.SetFormatter(formatter);

   logify = new CLogify();
   logify.AddHandler(handler_comment);
   logify.AddHandler(handler_console);

   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);
  }
void OnDeinit(const int reason)
  {
   delete logify;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify *logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   logify = new CLogify();
   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);
  }
void OnDeinit(const int reason)
  {
   delete logify;
  }
//+------------------------------------------------------------------+

この最小限のアプローチは、手動設定なしでほとんどのケースをカバーできるため、プロトタイピングやテストに非常に適しています。

一方で、ログの挙動をより細かく制御したい場合には、新しいビルダーが活躍します。ビルダーはFluent Interfaceを提供し、さらに重要なのは完全に型安全であるということです。つまり、コードエディタ自体がリアルタイムで利用可能なメソッドを提案してくれるため、エラーが減り、関数シグネチャを暗記する必要がなくなります。

たとえば「logify.Create().AddHandler」と入力すると、エディタは利用可能なすべてのハンドラを提案してくれます。

さらに「.AddHandlerComment()」と続けると、その特定のハンドラに有効な設定だけが表示されます。

最終的に、コメントハンドラを設定し、エラー用の特定フォーマットを適用したコードはこのようになります。

//+------------------------------------------------------------------+
//| Import                                                           |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify *logify;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   logify = logify.Create().AddHandlerComment().SetLevel(LOG_LEVEL_DEBUG).SetFormatter(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})").SetTitle("My expert").SetSize(5).Done().Build();
//---
   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);
  }
void OnDeinit(const int reason)
  {
   delete logify;
  }
//+------------------------------------------------------------------+

このガイド付きの仕組みによって、迷いがなくなり、開発がスムーズになります。コードはクリーンで、直感的、かつエラーに強いものになります。


MetaTrader 5ビルド5100以降の修正

MetaTrader 5のビルド5100がリリースされ、コンパイラの内部仕様が一部変更されたことにより、DatabaseColumnLong()やDatabaseColumnInteger()などの呼び出しにおける型の取り扱いをより明確にする必要が出てきました。

実務上の影響としては、構造体のフィールド(たとえばdata[size].timestamp)をこれらの関数に直接参照渡しすることは安全ではなくなった、という点です。コンパイルエラを避けるためには、まず適切な型の一時変数に値を格納し、その後でその変数を参照として関数に渡すのが理想的です。

古いコード 新しいコード
//+------------------------------------------------------------------+
//| Get data by sql command                                          |
//+------------------------------------------------------------------+
bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[])
  {
   //--- The rest of the method code remains the same

   //--- Reads query results line by line
   for(int i=0;DatabaseRead(request);i++)
     {
      int size = ArraySize(data);
      ArrayResize(data,size+1,size);
      
      //--- Maps database data to the MqlLogifyModel model
      DatabaseColumnText(request,1,data[size].formated);
      DatabaseColumnText(request,2,data[size].levelname);
      DatabaseColumnText(request,3,data[size].msg);
      DatabaseColumnText(request,4,data[size].args);
      DatabaseColumnLong(request,5,data[size].timestamp);
      string value;
      DatabaseColumnText(request,6,value);
      data[size].date_time = StringToTime(value);
      DatabaseColumnInteger(request,7,data[size].level);
      DatabaseColumnText(request,8,data[size].origin);
      DatabaseColumnText(request,9,data[size].filename);
      DatabaseColumnText(request,10,data[size].function);
      DatabaseColumnLong(request,11,data[size].line);
     }
   
   //--- The rest of the method code remains the same
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get data by sql command                                          |
//+------------------------------------------------------------------+
bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[])
  {
   //--- The rest of the method code remains the same

   //--- Reads query results line by line
   for(int i=0;DatabaseRead(request);i++)
     {
      int size = ArraySize(data);
      ArrayResize(data,size+1,size);
      
      //--- Maps database data to the MqlLogifyModel model
      DatabaseColumnText(request,1,data[size].formated);
      DatabaseColumnText(request,2,data[size].levelname);
      DatabaseColumnText(request,3,data[size].msg);
      DatabaseColumnText(request,4,data[size].args);
      long timestamp = (long)data[size].timestamp;
      DatabaseColumnLong(request,5,timestamp);
      string value;
      DatabaseColumnText(request,6,value);
      data[size].date_time = StringToTime(value);
      int level = data[size].level;
      DatabaseColumnInteger(request,7,level);
      DatabaseColumnText(request,8,data[size].origin);
      DatabaseColumnText(request,9,data[size].filename);
      DatabaseColumnText(request,10,data[size].function);
      long line = (long)data[size].line;
      DatabaseColumnLong(request,11,line);
     }
   
   //--- The rest of the method code remains the same
  }
//+------------------------------------------------------------------+

残りのコードは同じままです。この調整は一度きりの対応ですが、最新バージョンのターミナルとの互換性を維持するためには必要です。

なお、コンパイラが型に対してより厳格になる場合、この種の調整はよくあることです。これは通常、実行時の微妙な問題を未然に防ぐためにおこなわれます。したがって、一見単純な変更に見えても、LogifyがMetaTrader 5の新しいバージョンでも安定して動作し続けるための重要な更新と言えます。


結論

これまで、Logifyライブラリの設定は強力ではありましたが、少し手間がかかるものでした。オブジェクトを作成し、設定を手動でおこない、呼び出しの順序を覚え…要するに「動くけれど面倒」という状況です。

この記事では、その課題を解決しました。ログの扱いを、シンプルで明快、かつ高速にしました。ビルダーを導入することで、すべてが自然に操作できるようになっています。「logify.Create()」と入力すれば、エディタが次の選択肢を表示します。コメントハンドラが欲しい場合は「AddHandlerComment()」と入力します。タイトルを変更したければSetTitle()が提示されます。何も暗記する必要はなく、ドキュメントを何度も参照する必要もありません。ただ流れに沿って入力すればよいのです。

さらに、デフォルト設定により、より使いやすくなりました。メッセージを記録するだけで、ログのカスタマイズにこだわらない場合は、何も設定せずにオブジェクトを作成して使い始めるだけで十分です。Logify自身がコンソールやグラフにメッセージを表示してくれます。

最後に重要な技術的調整もおこないました。MetaTrader 5ビルド5100の登場により、DatabaseColumnLong()やDatabaseColumnInteger()といった関数への参照渡しに対してコンパイラがより厳格になりました。この互換性を確保するため、CLogifyHandlerDatabaseに小さな修正を加え、データを渡す前に一時変数を使用するようにしました。ライブラリを利用する側には変更はなく、ターミナルアップデートがあっても裏側で安定性が保たれています。

結果として、開発者が喜ぶことを実現できました。コードは短く、エラーは減り、可読性は向上しています。ライブラリは使う側に対してより自然に応答し、無理な制約を課すことも、シンプルであるべき部分を複雑にすることもありません。Logifyは今後も進化し、さらに柔軟になり、多くのユーザーに役立つ新機能を備えていく予定です。その際には、改善点を紹介する新しい記事を通じて、日々の開発がより快適になるようにお届けします。ライブラリは、魔法ではなく、しっかりと考え抜かれたコードとして、使う人と共に成長していくのです。

ファイル名 詳細
Experts/Logify/LogiftTest.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/LogifyBuilder.mqh CLockifyオブジェクトを作成し、構成を簡素化するクラス
Include/Logify/LogifyLevel.mqh Logifyライブラリのログレベルを定義するファイル。詳細な制御が可能
Include/Logify/LogifyModel.mqh レベル、メッセージ、タイムスタンプ、コンテキストなどの詳細を含むログレコードをモデル化する構造

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

添付されたファイル |
Logify.zip (154.39 KB)
最後のコメント | ディスカッションに移動 (5)
hini
hini | 28 6月 2025 において 03:29

ランタイム停止ロギングの追加を検討したことはありますか?もちろん、開発者がEAでこれを処理するのは普通のことです。

hini
hini | 28 6月 2025 において 05:34
TerminalInfoString(TERMINAL_LANGUAGE);

ログのデフォルトのエラー出力言語は、以下のコードに従って、ユーザーの端末言語に戻すことができる。

Spoxus Spoxus
Spoxus Spoxus | 2 7月 2025 において 21:16

デバッグメッセージとエラーメッセージだけを出力したいのですが。情報、アラートなどはすべてEAに組み込んでいます。enum ENUM_LOG_LEVEL'の各タイプにbool値を設定することで、必要なものを表示できるのでは?

本番 コードでは、ログのいくつかをオフにすると、最終的なex5ファイルにはコンパイルされないはずです。

joaopedrodev
joaopedrodev | 31 7月 2025 において 16:58
Spoxus Spoxus 本番 コードでは、ログのいくつかをオフにすると、最終的なex5ファイルにはコンパイルされないはずです。

これを行うには、エキスパートに変数またはIPアドレスを使用して、希望のレベル値を格納し、それをハンドラに渡すだけです。以下はその例です。

//+------------------------------------------------------------------+
//| インポート|
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify Logify;
//+------------------------------------------------------------------+
//| 入力|
//+------------------------------------------------------------------+
input ENUM_LOG_LEVEL InpLogLevel = LOG_LEVEL_INFO; // ログレベル
//+------------------------------------------------------------------+
//| エキスパート初期化関数|
//+------------------------------------------------------------------+
int OnInit()
  {
   Logify.EnsureDefaultHandler();
   Logify.GetHandler(0).SetLevel(InpLogLevel);
   
   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);
  }
//+------------------------------------------------------------------+

これにより、ハンドラで定義された重大度レベル以上のメッセージのみが表示されます。

joaopedrodev
joaopedrodev | 31 7月 2025 において 16:59
hini #:

ログのデフォルトのエラー出力言語は、以下のコードに従って、ユーザーの端末言語に戻すことができる。

この記事のパート10は公開待ちです。この記事では、同一のログを抑制する方法と、端末のデフォルト言語を設定する方法について説明しています。ご提案ありがとうございました!

初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(III)-インジケーターインサイト 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(III)-インジケーターインサイト
本記事では、News Headline EAをさらに進化させるために、専用の「インジケーターインサイトレーン」を導入します。これは、RSI、MACD、ストキャスティクス、CCIなどの主要インジケーターから生成されるテクニカルシグナルを、チャート上にコンパクトにまとめて表示する仕組みです。この方法により、MetaTrader 5ターミナルで複数のインジケーターウィンドウを開く必要がなくなり、作業スペースをすっきりと保つことができます。さらに、MQL5のAPIを活用してインジケーターデータをバックグラウンドで取得することで、カスタムロジックを使ったリアルタイムの市場分析や可視化が可能になります。本記事では、MQL5でインジケーターデータを操作し、チャート上の単一水平レーンに、知的で省スペースなスクロール式インサイトシステムを作成する方法を詳しく解説します。
MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略 MQL5での取引戦略の自動化(第20回):CCIとAOを使用した多銘柄戦略
この記事では、CCI (Commodity Channel Index)とAO (Awesome Oscillator)を用いてトレンド反転を検出する多銘柄取引戦略を作成します。戦略の設計、MQL5での実装、バックテストのプロセスについて解説します。記事の最後には、パフォーマンス改善のためのヒントも紹介します。
データサイエンスとML(第45回):FacebookのPROPHETモデルを用いた外国為替時系列予測 データサイエンスとML(第45回):FacebookのPROPHETモデルを用いた外国為替時系列予測
Prophetモデルは、Meta(旧Facebook)によって開発された強力な時系列予測ツールであり、トレンドや季節性、イベント効果(holiday effects)を最小限の手作業で捉えることができます。このモデルは、需要予測やビジネスプランニングにおいて広く活用されてきました。本記事では、ProphetモデルをFXのボラティリティ予測に応用する効果について探り、従来のビジネス用途を超えた利用例を紹介します。
MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2) MQL5で自己最適化エキスパートアドバイザーを構築する(第8回):複数戦略分析(2)
次のフォローアップディスカッションにぜひご参加ください。今回は、これまでの2つの取引戦略を統合し、アンサンブル取引戦略(複合戦略)を作成する方法を解説します。複数の戦略を組み合わせる際のさまざまな手法を紹介するとともに、パラメータ空間の制御方法についても説明します。これにより、パラメータの数が増えても、効果的な最適化が可能な状態を保つことができます。