
Meistern der Log-Einträge (Teil 8): Fehlereinträge, die sich selbst übersetzen
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:
- Fehlerprotokolle mit präzisen Beschreibungen angereichert werden, die direkt aus der MQL5-Dokumentation stammen.
- Fehlermeldungen in mehreren Sprachen mit dynamischer Auswahl der am besten geeigneten Sprache für jeden Kontext angezeigt werden.
- 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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Danke, ich helfe gerne. Wenn Sie Verbesserungsvorschläge haben, kontaktieren Sie mich bitte!
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.