English
preview
Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen

Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen

MetaTrader 5Beispiele |
9 5
joaopedrodev
joaopedrodev

Einführung

An diesem Punkt der Reise ist es keine Überraschung, dass es bei der Protokollierung nicht nur um die Aufzeichnung von Ereignissen geht. Es geht darum, genau zu erfassen, was Ihr EA Ihnen inmitten des Wirbels von Ticks, Entscheidungen und Unwägbarkeiten, die den täglichen algorithmischen Handel bestimmen, mitzuteilen versucht.

Bei der täglichen Arbeit mit Logify ist mir etwas aufgefallen, das mich gestört hat: Die Fehlerbehandlung war noch sehr oberflächlich. Selbst mit einer robusten Formatierungsstruktur zeigten die Protokolle immer noch nur den rohen Fehlercode an, ohne irgendeinen Hinweis darauf, was er eigentlich bedeutete. Etwa so:

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

Das Ergebnis? Eine nebulöse Botschaft. Wir wissen, wo der Fehler aufgetreten ist, aber nicht warum. Und wer hat schon einmal Dutzende von MQL5-Codes in der Dokumentation untersuchen müssen? Das habe ich früher selbst oft gemacht: Sobald ich den Fehlercode hatte, musste ich in der Dokumentation nachsehen, um herauszufinden, was wirklich passiert war. Aus dieser echten Spannung heraus entstand die Idee: Was wäre, wenn Logify den Fehler für mich interpretieren könnte? Wie wäre es, wenn ich nicht nur den Code bekäme, sondern auch seine Bedeutung in klarer, kontextualisierter Form, bereit zur Protokollierung?

So entstand eine funktionale Weiterentwicklung: Es ist nun möglich, den Fehlercode direkt an die Bibliothek zu übergeben, und diese kümmert sich automatisch um die Abfrage, Formatierung und Anzeige der entsprechenden Beschreibung. Dasselbe vorherige Beispiel wird mit dieser Verbesserung zu einer neuen Version:

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

Viel klarer und nützlicher, aber ich habe beschlossen, noch weiter zu gehen. Ein System, das Klarheit schaffen soll, muss fließend sein, nicht nur im Code, sondern auch in der Sprache. Aus diesem Grund bietet Logify in dieser achten Stufe auch mehrsprachige Unterstützung für Fehlermeldungen mit automatischer Übersetzung in 11 Sprachen, darunter Englisch, Portugiesisch, Spanisch, Deutsch, Französisch, Italienisch, Russisch, Türkisch, Chinesisch, Japanisch und Koreanisch. Jetzt sprechen Ihre Protokolle die Sprache Ihres Teams oder Ihrer Kunden, ohne dass manuelle Anpassungen erforderlich sind.

In diesem Schritt werden Sie lernen wie:

  1. Fehlerprotokolle mit präzisen Beschreibungen angereichert werden, die direkt aus der MQL5-Dokumentation stammen.
  2. Fehlermeldungen in mehreren Sprachen mit dynamischer Auswahl der am besten geeigneten Sprache für jeden Kontext angezeigt werden.
  3. Formatierung nach Schweregrad angepasst werden und unterschiedliche Muster für Fehler, Warnungen und Informationen auf der Grundlage ihres Kritikalitätsgrads erstellt werden.

Am Ende des Tages wird Logify intelligenter, zugänglicher und in der realen Welt viel nützlicher sein. Denn beim algorithmischen Handel kommt es auf jedes Detail an, und jede Sekunde, die damit verbracht wird, einen Fehler zu entschlüsseln, ist eine Sekunde von der nächsten richtigen Entscheidung entfernt.


Schaffung einer Möglichkeit zur Fehlerbehandlung

Da jedes gute System eine solide Grundlage braucht, beginnen wir mit der Definition einer Struktur zur Darstellung von Fehlern. Diese Struktur, die wir MqlError nennen werden, wird im gesamten System verwendet, um die drei grundlegenden Teile eines Fehlers zu speichern:

  • Der numerische Code (Code )
  • Die symbolische Konstante
  • Die lesbare Beschreibung (description )

Wir haben diese Struktur in der Datei <Include/Logify/Error/Error.mqh> angelegt:

//+------------------------------------------------------------------+
//| 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 = "";
     }
  };
//+------------------------------------------------------------------+

Diese Struktur ist einfach, aber ausreichend, um jeden von der Plattform zurückgegebenen Fehler darzustellen, sei es ein Ausführungsfehler, ein Verhandlungsfehler oder sogar ein nutzerdefinierter Fehler. Da wir nun einen Ort haben, an dem wir die Daten speichern können, müssen wir diese Struktur mit den tatsächlichen Fehlern auffüllen.

MetaQuotes stellt Hunderte von Fehlercodes zur Verfügung, jeder mit seiner symbolischen Konstante und einer Beschreibung auf Englisch, aber wir wollen noch weiter gehen. Wir möchten, dass die Bibliothek deutsch, spanisch, französisch, portugiesisch, chinesisch ... spricht und dass sich die Protokolle automatisch an die eingestellte Sprache anpassen.

Die Strategie besteht hier darin, für jede Sprache eine .mqh-Datei zu erstellen, die eine Funktion enthält, die ein Array mit allen bekannten Fehlern initialisiert. Der Dateiname entspricht dem Standard ErrorMessages.xx.mqh, wobei xx für das Sprachkürzel steht (z. B. en für Englisch, de für Deutsch, pt für Portugiesisch).

Hier sehen Sie, wie die Datei auf Englisch aussieht:

//+------------------------------------------------------------------+
//|                                             ErrorMessages.en.mqh |
//|                                                     joaopedrodev |
//|                       https://www.mql5.com/en/users/joaopedrodev |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/en/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";
  }
//+------------------------------------------------------------------+

Die Funktion InitializeErrorsEnglish() erhält ein Array von MqlError und füllt es mit allen bekannten Fehlern in englischer Sprache. Wir wiederholen dieses Muster für andere Sprachen wie Deutsch, Spanisch, Französisch, Italienisch, Japanisch, Koreanisch, Portugiesisch, Russisch, Türkisch und Chinesisch. Alle diese Dateien werden im Ordner <Include/Logify/Error/Languages> gespeichert. Insgesamt haben wir 11 Sprachdateien und 274 Einträge pro Datei. Und ja, das war eine mühsame Arbeit, aber jetzt können Sie sie mit einer einfachen Aufnahme genießen. Denken Sie daran, dass der gesamte Code am Ende des Artikels beigefügt ist.

Nachdem wir nun alle Daten organisiert haben, brauchen wir eine Schnittstelle, um sie abzufragen. Hier kommt die Klasse CLogifyError ins Spiel. Diese Klasse ist für das Laden der Fehler in der richtigen Sprache und die Rückgabe der vollständigen Informationen für jeden angeforderten Fehlercode verantwortlich.

Die Schnittstelle der Klasse ist einfach:

//+------------------------------------------------------------------+
//| 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)
  {
  }
//+------------------------------------------------------------------+

Wenn es instanziiert wird, lädt es standardmäßig Fehler in Englisch. Sie können die Sprache jederzeit mit SetLanguage() ändern, und die Bibliothek wird die Daten in der entsprechenden Sprache neu laden.

Die Methode Error(int code) ist das Herzstück der Klasse. Es durchsucht das Fehlerarray nach dem eingegebenen Code und gibt den entsprechenden MqlError zurück. Wird der Code nicht gefunden, gibt die Funktion einen allgemeinen Fehler als Rückfall zurück. Außerdem erkennt es automatisch, ob der Fehler in den für nutzerdefinierte Fehler reservierten Bereich (ERR_USER_ERROR_FIRST bis ERR_USER_ERROR_LAST ) fällt, und gibt auch für diese Fehler eine Antwort.

//+------------------------------------------------------------------+
//| 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]);
  }
//+------------------------------------------------------------------+

Schließlich fügen wir diese Fehlerstruktur dem Protokolldatenmodell MqlLogifyModel hinzu, sodass wir auf die Fehlerdaten innerhalb der Datenstruktur eines Datensatzes zugreifen können.

#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;
     }
  };
//+------------------------------------------------------------------+

Mit diesen Änderungen sieht die Struktur unserer Bibliothek folgendermaßen aus:


Integration von Fehlern in die Hauptklasse

Wir haben bereits eine solide Struktur für die Darstellung von Fehlern, mehrsprachige Dateien, die deren Beschreibungen enthalten, und eine Klasse, die diese Meldungen auf der Grundlage der Sprache abruft. Doch bisher hängt all dies in der Luft, losgelöst vom zentralen Motor der Bibliothek: der Klasse CLogify.

Es ist an der Zeit, das Fehlersystem in den Kern von Logify zu integrieren, sodass jedes erzeugte Protokoll, falls erforderlich, eine klare, mehrsprachige Erklärung enthält, was schief gelaufen ist. Dazu folgen wir drei Schritten: Instanziierung der Fehlerklasse, Anpassung der Methode zum Hinzufügen von Protokollen (Append) und schließlich Anpassung des Formatierers, um neue Platzhalter {err_code}, {err_constant} und {err_description} zu erkennen.

1. Das Herzstück von Logify ist die Fehlererkennung

Die Klasse CLogify ist diejenige, die für die Formatierung, die Bearbeitung und das Senden des Protokolls zuständig ist, sodass der erste Schritt darin besteht, ihr direkten Zugriff auf unser Fehlersystem zu gewähren. Dazu importieren Sie einfach die Datei LogifyError.mqh und instanziieren die Klasse CLogifyError als privates Mitglied von CLogify. Damit ist gewährleistet, dass sie intern immer verfügbar ist, ohne dass sich der Bibliotheksnutzer darum kümmern muss. Dieser kleine Zusatz öffnet bereits wichtige Türen. Wenn nun ein Fehler auftritt, können wir den Code, die Beschreibung und die Konstante schnell abrufen.

Da die Standardsprache von CLogifyError Englisch ist, wäre es eine Verschwendung, dem Nutzer nicht die Möglichkeit zu geben, die am besten geeignete Sprache zu wählen. Aus diesem Grund haben wir zwei einfache Methoden hinzugefügt. So sieht der Code am Ende aus:

#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. Anreicherung der Append-Methode mit Fehlercode

Bisher erhielt die Append()-Methode die Hauptmeldung, zusätzliche Argumente, Quelle, Dateiname, Zeile, Funktion... aber nicht den Fehlercode selbst. Wenn wir wollen, dass das Protokoll Kontext über Fehler enthält, müssen wir die Übergabe dieses Codes zulassen, auch optional. Deshalb haben wir den Parameter code_error an das Ende der Methodensignatur angefügt:

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

Damit ändert sich der Aufruf von Append() für diejenigen, die den Fehler nicht übergeben wollen, nicht, aber für diejenigen, die es tun, fügen Sie es einfach am Ende hinzu. Auf diese Weise wird vermieden, dass die Kompatibilität mit bestehendem Code unterbrochen wird.

Jetzt, mit dem Fehlercode in der Hand, verwenden wir die Methode Error() der Klasse CLogifyError, um die entsprechende MqlError-Struktur zu holen:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Das MqlLogifyModel-Objekt enthält jetzt nicht nur die Protokollinformationen, sondern auch die Seele des Fehlers, der ihn verursacht hat. Jetzt müssen Sie nur noch eine neue Funktionsüberladung für die Methoden Error() und Fatal() hinzufügen, ohne die bereits vorhandenen zu entfernen, da dies zu Fehlern führen kann, da der Nutzer der Bibliothek nicht immer einen Fehlercode hinzufügen muss. So sieht der Code aus:

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. Formatierung der Meldung mit Fehlerplatzhaltern

Bisher wird der Fehler zwar geladen, aber er erscheint noch nicht im formatierten Protokoll. Zu diesem Zweck müssen wir das Platzhaltersystem von CLogifyFormatter um die Fehlereigenschaften erweitern. Die Logik ist einfach, aber genial: Wir zeigen die Fehlerdaten nur an, wenn die Protokollstufe ERROR oder höher ist. Informations- oder Debug-Protokolle brauchen diese Ausführlichkeit nicht, wir wollen Klarheit, keine Verschmutzung.

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Damit haben wir drei neue Anpassungshaken geschaffen:

  • {err_code} : Zeigt die genaue Fehlernummer an (z.B. 10006 )
  • {err_constant} : Die zugehörige Konstante (z. B. TRADE_RETCODE_REJECT ).
  • {err_description} : Die lesbare Erklärung (z.B. Antrag abgelehnt)

Stellen Sie sich vor, wir hätten ein globales Format wie dieses definiert:

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

In Ihrem Kopf sollte dies in etwa Folgendes ergeben:

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

Und in der Tat, für die Protokollebenen ERROR und FATAL funktioniert es perfekt. Aber was ist, wenn wir das gleiche Format für DEBUG-, INFO- oder ALERT-Protokolle verwenden? Nun... das Ergebnis sieht in etwa so aus:

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

Oder schlimmer noch, je nach Formatierung kann es sogar zu einem optischen Versatz in den Protokollen kommen. Trotz unserer Bemühungen, die Fehlerplatzhalter auf niedrigere Ebenen zu verschieben, bleibt das Problem bestehen: Das Format wurde in der Erwartung erstellt, dass {err_code}, {err_constant} und {err_description} Werte haben. Wenn sie das nicht tun, wird das Protokoll... seltsam und unvollständig. Wir haben es hier mit einem Bruch in der Semantik zu tun: dieselbe Formatierungsmaske wird auf Nachrichten angewandt, die völlig unterschiedliche Kontexte haben.

Die Lösung liegt nicht darin, zu versuchen, die Daten zu korrigieren. Sie besteht darin, jede Protokollebene als das zu behandeln, was sie ist: eine andere Art von Nachricht, mit unterschiedlichen Anforderungen und Formaten.

Anstelle von CLogifyFormatter mit einem universellen Format haben wir also für jede Stufe (DEBUG, INFO, ALERT, ERROR, FATAL ) einen eigenen Formatierer. Dadurch können wir beispielsweise Folgendes anpassen:

  • DEBUG : Funktion, Datei und Zeile anzeigen
  • INFO : Minimalistisch sein, genau wie geschehen
  • ALERT : Kontext wie Ursprung und Args hinzufügen
  • ERROR : Fehlerdaten einbeziehen
  • FATAL : So vollständig wie möglich


Implementierung mehrerer Formate

Zuerst passen wir die Art und Weise an, wie das Format gespeichert wird. Statt einer einfachen Zeichenkette wird es in ein Array mit 5 Möglichkeiten (eine für jeden Schweregrad) geändert.

Alter Code Neuer Code
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];
  };

Wir haben m_log_format durch ein Array von Zeichenketten ersetzt, eine Position für jede Protokollstufe. Wir haben jetzt fünf verschiedene Formate, von denen jedes an die Art der zu versendenden Nachricht angepasst werden kann.

Da die Formate nach Ebenen getrennt sind, brauchen wir eine bequeme Möglichkeit, sie einzustellen. Zu diesem Zweck haben wir zwei SetFormat-Methoden entwickelt, eine, die für alle Ebenen das gleiche Format verwendet, und eine andere, die eine bestimmte Ebene individuell konfiguriert:

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;
  }
//+------------------------------------------------------------------+

Dieses Design ist nicht nur praktisch, sondern ermöglicht es uns, sowohl allgemein als auch spezifisch zu sein. Für diejenigen, die es einfach haben wollen, genügt ein einziges SetFormat(...). Wer es genau haben will, ruft einfach SetFormat(level, format) für jede gewünschte Ebene auf.

Da die Formate getrennt sind, muss die Formatierungsmethode nun diese Trennung widerspiegeln. Zuvor haben wir eine generische Variable m_log_format verwendet, um die Nachrichtenmaske zu erhalten. Jetzt suchen wir das Format direkt im Array und indizieren es nach Protokollstufe:

Alter Code Neuer Code
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   ...
  }
string CLogifyFormatter::Format(MqlLogifyModel &data)
  {
   string formated = m_format[data.level];
   ...
  }

Dies löst das Problem auf eine saubere, erweiterbare und vorhersehbare Weise. Keine zusätzlichen Prüfungen, keine Umgehungen zur Unterdrückung leerer Platzhalter. Jede Ebene weiß genau, was sie enthalten soll.


Tests

Wir kommen nun zu dem Teil, der alles, was wir aufgebaut haben, zum Leben erweckt: das praktische Testen, um zu sehen, wie es sich unter verschiedenen Szenarien und Protokollierungsstufen verhält. In der gleichen Datei, die wir im letzten Artikel zum Testen verwendet haben, LogifyTest.mqh, haben wir die folgenden Elemente vorbereitet:

  • Ein visueller Handler über Comment() mit Rahmen, Titel und Zeilenbegrenzung.
  • Ein Konsolen-Handler über Print() zur Aufzeichnung von Meldungen im plattforminternen Protokoll.
  • Ein Formatierer, der von den Handlern gemeinsam genutzt wird.
  • Vier Protokollnachrichten mit verschiedenen Stufen: zwei DEBUG, eine INFO und eine ERROR mit Code.
//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Wenn Sie diesen Code ausführen, zeigt die MetaTrader-Konsole die folgenden Meldungen an, die genau wie erwartet formatiert sind. Das visuelle Ergebnis macht deutlich, dass die unteren Ebenen (wie DEBUG und INFO) einem schlankeren Format folgen, während ERROR das ausführlichere Format mit Fehlerdaten aufruft:

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]

Beachten Sie, dass das letzte Protokoll einen zusätzlichen Block in eckigen Klammern enthält, der die symbolische Konstante, den numerischen Code und die nutzerfreundliche Beschreibung des Fehlers enthält. Und all dies war möglich, ohne zusätzliche bedingte Logik in den Expertencode einzufügen. Jede Protokollebene hat ihr eigenes Format, und die richtigen Platzhalter erscheinen nur dort, wo sie sinnvoll sind.


Schlussfolgerung

In diesem Artikel haben wir Verbesserungen an der Logify-Bibliothek vorgenommen. Im ersten Artikel haben wir mit der Strukturierung des Datenmodells begonnen und dann flexible Handler für verschiedene Ausgabekanäle erstellt. Und heute haben wir mehrsprachige Unterstützung für Fehlermeldungen hinzugefügt, sodass jedes Protokoll dem Entwickler unabhängig von der Sprache einen klaren technischen Kontext liefert.

Wir haben einen dynamischen Formatierer entwickelt, der nutzerdefinierte Platzhalter verarbeiten kann und sich automatisch an den Schweregrad des Protokolls anpasst. Als wir feststellten, dass generische Formate zu Inkonsistenzen führten, entwickelten wir die Architektur weiter, um ein spezifisches Format pro Ebene zu akzeptieren. Und schließlich haben wir alles mit einem Praxistest validiert, der zeigte, dass Logify in Echtzeit, sauber, vorhersehbar und erweiterbar funktioniert.

Wenn Sie es bis hierher geschafft haben, haben Sie gelernt, wie es geht:

  • Entwurf einer skalierbaren und entkoppelten Protokollstruktur;
  • Integrieren Sie mehrsprachige Fehlermeldungen auf Basis des MetaTrader-Codes;
  • Arbeiten Sie mit Platzhaltern und Formatierungen, die nach Schweregrad kontextualisiert sind;
  • Erstellung wiederverwendbarer Handler mit eigenen Konfigurationen;

Logify kann sich, wie jedes gute Tool, weiterentwickeln. Wenn neue Funktionen implementiert werden, werde ich sie in einem zukünftigen Artikel vorstellen. Bis dahin gilt: nutzen, anpassen und verbessern.


Dateiname Beschreibung
Experts/Logify/LogiftTest.mq5
die Datei, in der wir die Funktionen der Bibliothek testen, mit einem praktischen Beispiel.
Include/Logify/Error/Languages/ErrorMessages.XX.mqh Zählen Sie die Fehlermeldungen in jeder Sprache, wobei X für das Akronym der Sprache steht.
Include/Logify/Error/Error.mqh
Datenstruktur zur Speicherung von Fehlern.
Include/Logify/Error/LogifyError.mqh
Klasse zum Abrufen detaillierter Fehlerinformationen.
Include/Logify/Formatter/LogifyFormatter.mqh
die Klasse, die für die Formatierung von Protokolldatensätzen zuständig ist, indem sie Platzhalter durch bestimmte Werte ersetzt.
Include/Logify/Handlers/LogifyHandler.mqh
die Basisklasse für die Verwaltung von Log-Handlern, einschließlich der Einstellung des Levels und des Versands von Logs.
Include/Logify/Handlers/LogifyHandlerComment.mqh
Log-Handler, der formatierte Logs direkt an den Kommentar auf dem Terminal-Chart im MetaTrader sendet.
Include/Logify/Handlers/LogifyHandlerConsole.mqh
der Handler der Logs, der formatierte Logs direkt an die Terminal-Konsole im MetaTrader sendet.
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
der Log-Handler, der formatierte Logs an eine Datenbank sendet (derzeit enthält er nur einen Ausdruck, aber bald werden wir ihn in einer echten Sqlite-Datenbank speichern).
Include/Logify/Handlers/LogifyHandlerFile.mqh
der Log-Handler, der formatierte Logs in eine Datei sendet.
Include/Logify/Utils/IntervalWatcher.mqh
prüft, ob ein Zeitintervall verstrichen ist, sodass Sie Routinen in der Bibliothek erstellen können.
Include/Logify/Logify.mqh die Kernklasse für die Protokollverwaltung, die Ebenen, Modelle und Formatierung integriert.
Include/Logify/LogifyLevel.mqh die Datei, die die Log-Ebenen der Logify-Bibliothek definiert und eine detaillierte Kontrolle ermöglicht.
Include/Logify/LogifyModel.mqh die Struktur, die die Protokolleinträge modelliert, einschließlich Details wie Ebene, Nachricht, Zeitstempel und Kontext.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18467

Beigefügte Dateien |
Logify.zip (151.36 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (5)
hini
hini | 19 Juni 2025 in 02:09
Beide von Ihnen erstellten Bibliotheken sind hervorragend, danke für den Code.
joaopedrodev
joaopedrodev | 20 Juni 2025 in 12:44
Danke, ich helfe gerne. Wenn Sie Verbesserungsvorschläge haben, kontaktieren Sie mich bitte!
hini
hini | 20 Juni 2025 in 17:42
joaopedrodev #:
Danke, ich helfe gerne. Wenn Sie Verbesserungsvorschläge haben, kontaktieren Sie mich bitte!
Was soll ich tun, wenn die Protokollierung sehr häufig erfolgt, ich aber wiederholte Ausdrucke vermeiden möchte? Wenn z. B. ein Handelseröffnungssignal erkannt wird, aber der Spread zu groß ist und Dutzende von Sekunden anhält, würde die Protokollierung mindestens Dutzende oder sogar Hunderte von Malen erfolgen. Wie kann diese Situation behoben werden? Ich weiß, dass eine Lösung darin besteht, eine Variable zu verwenden, um sicherzustellen, dass das Protokoll nur einmal angezeigt wird, aber es wäre besser, wenn die Protokollierungsbibliothek dies selbst erledigen könnte. Könnten Sie einige Vorschläge machen?
hini
hini | 20 Juni 2025 in 17:54

Ich habe die Logging-Bibliothek Logify kompiliert, und nach MT5 Build 5100 gibt es mehrere Kompilierungsfehler im Zusammenhang mit Typen in CLogifyHandlerDatabase::Query . Ich glaube, Sie sollten dieses Problem bereits behoben haben.
joaopedrodev
joaopedrodev | 23 Juni 2025 in 13:24
Danke für die Anregungen, ich werde sie in künftigen Artikeln aufgreifen.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien Erstellen von selbstoptimierenden Expert Advisor in MQL5 (Teil 8): Analyse mehrerer Strategien
Wie können wir mehrere Strategien am besten kombinieren, um eine leistungsfähige Gesamtstrategie zu schaffen? Nehmen Sie an dieser Diskussion teil, in der wir drei verschiedene Strategien in unsere Handelsanwendung einbauen wollen. Händler verwenden oft spezielle Strategien für die Eröffnung und Schließung von Positionen, und wir wollen wissen, ob unsere Maschinen diese Aufgabe besser erfüllen können. In unserer einleitenden Diskussion machen wir uns mit den Fähigkeiten des Strategietesters und den Prinzipien der OOP vertraut, die wir für diese Aufgabe benötigen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Entwicklung des Price Action Analysis Toolkit (Teil 30): Commodity Channel Index (CCI), Zero Line EA Entwicklung des Price Action Analysis Toolkit (Teil 30): Commodity Channel Index (CCI), Zero Line EA
Die Automatisierung der Preisaktionsanalyse ist der Weg in die Zukunft. In diesem Artikel verwenden wir den Dual CCI-Indikator, die Nulllinien-Kreuzungsstrategie, den EMA und die Kursentwicklung, um ein Tool zu entwickeln, das Handelssignale generiert und Stop-Loss- (SL) und Take-Profit-Levels (TP) unter Verwendung der ATR festlegt. Bitte lesen Sie diesen Artikel, um zu erfahren, wie wir bei der Entwicklung des „CCI Zero Line EA“ vorgehen.