
Meistern der Log-Einträge (Teil 4): Speichern der Protokolle in Dateien
Einführung
Im ersten Artikel dieser Serie, „Meistern der Log-Einträge (Teil 1): Grundlegende Konzepte und erste Schritte in MQL5“, haben wir mit der Erstellung einer nutzerdefinierten Protokollbibliothek für die Entwicklung von Expert Advisors (EA) begonnen. Darin haben wir die Motivation für die Entwicklung eines solchen wichtigen Tools untersucht: die Überwindung der Beschränkungen von MetaTrader 5's nativen Protokollen und die Bereitstellung einer robusten, anpassbaren und leistungsstarken Lösung für das MQL5-Universum.
Um die wichtigsten, behandelten Punkte zusammenzufassen, haben wir die Grundlage für unsere Bibliothek geschaffen, indem wir die folgenden grundlegenden Anforderungen festgelegt haben:
- Robuste Struktur unter Verwendung des Singleton-Musters, das die Konsistenz zwischen den Code-Komponenten gewährleistet.
- Erweiterte Persistenz für die Speicherung von Protokollen in Datenbanken, die eine nachvollziehbare Historie für eingehende Audits und Analysen bieten.
- Flexibilität bei den Ausgaben, sodass die Protokolle bequem gespeichert oder angezeigt werden können, sei es in der Konsole, in Dateien, im Terminal oder in einer Datenbank.
- Klassifizierung nach Protokollebenen, wobei informative Meldungen von kritischen Warnungen und Fehlern unterschieden werden.
- Anpassung des Ausgabeformats an die individuellen Bedürfnisse der einzelnen Entwickler oder Projekte.
Mit dieser gut etablierten Grundlage wurde klar, dass das von uns entwickelte Logging-Framework weit mehr sein wird als ein einfaches Ereignisprotokoll; es wird ein strategisches Tool zum Verstehen, Überwachen und Optimieren des Verhaltens von EAs in Echtzeit sein.
Bisher haben wir uns mit den Grundlagen von Protokollen befasst, gelernt, wie man sie formatiert, und verstanden, wie Handler das Ziel von Nachrichten steuern. Aber wo speichern wir diese Protokolle für spätere Zwecke? In diesem vierten Artikel werden wir uns den Prozess der Speicherung von Protokollen in Dateien genauer ansehen. Fangen wir an!
Warum Protokolle in Dateien speichern?
Die Speicherung von Protokollen in Dateien ist eine wesentliche Praxis für jedes System, das Wert auf Robustheit und effiziente Wartung legt. Stellen Sie sich folgendes Szenario vor: Ihr Expert Advisor läuft schon seit Tagen, und plötzlich tritt ein unerwarteter Fehler auf. Wie können Sie verstehen, was passiert ist? Ohne ständige Aufzeichnungen wäre es so, als würde man versuchen, ein Puzzle zu lösen, ohne alle Teile zu haben.
Protokolldateien sind nicht nur eine Möglichkeit, Nachrichten zu speichern. Sie stellen den Speicher des Systems dar. Hier sind die wichtigsten Gründe für ihre Einführung:
-
Persistenz und Verlauf
In Dateien gespeicherte Protokolle bleiben auch nach der Ausführung des Programms verfügbar. Dies ermöglicht historische Abfragen, um die Leistung zu überprüfen, frühere Verhaltensweisen zu verstehen und Muster im Laufe der Zeit zu erkennen.
-
Prüfung und Transparenz
Bei kritischen Projekten, wie z. B. auf dem Finanzmarkt, ist die Führung einer detaillierten Historie für Audits oder Begründungen von automatisierten Entscheidungen unerlässlich. Ein gut aufbewahrtes Protokoll kann im Falle von Fragen Ihre beste Verteidigung sein.
-
Diagnostik und Fehlersuche
Mit den Protokolldateien können Sie bestimmte Fehler verfolgen, kritische Ereignisse überwachen und jeden Schritt der Systemausführung analysieren.
-
Zugang Flexibilität
Im Gegensatz zu den Protokollen, die auf der Konsole oder dem Terminal angezeigt werden, kann auf die Dateien per Fernzugriff zugegriffen oder sie können mit anderen Teams geteilt werden, sodass eine gemeinsame und detaillierte Sicht auf wichtige Ereignisse entsteht.
-
Automatisierung und Integration
Die Dateien können von automatisierten Tools gelesen und analysiert werden, die bei kritischen Problemen Warnungen senden oder detaillierte Berichte über die Leistung erstellen.
Durch das Speichern von Protokollen in Dateien verwandeln Sie eine einfache Ressource in ein Werkzeug für die Verwaltung, Verfolgung und Verbesserung. Ich brauche hier nicht näher auf die Bedeutung der Speicherung dieser Daten in einer Datei einzugehen; kommen wir im nächsten Thema zum Punkt, wie wir diese Ressource effizient in unsere Bibliothek implementieren können.
Bevor wir uns direkt dem Code zuwenden, ist es wichtig, die Funktionen zu definieren, die der File-Handler bieten soll. Nachfolgend habe ich alle erforderlichen Anforderungen im Einzelnen aufgeführt:
-
Anpassung von Verzeichnis, Name und Dateityp
Erlauben Sie den Nutzern die Konfiguration:
- Das Verzeichnis, in dem die Protokolle gespeichert werden sollen.
- Der Name der Protokolldateien, der eine bessere Kontrolle und Organisation ermöglicht.
- Das Ausgabedateiformat, mit Unterstützung für .log, .txt und .json.
-
Kodierung Konfiguration
Unterstützung verschiedener Kodierungsarten für Protokolldateien, wie z. B.:
- UTF-8 (empfohlener Standard).
- UTF-7, ANSI Code Page (ACP) oder andere, je nach Bedarf.
-
Bibliothek für die Fehlerberichte
Die Bibliothek muss ein System zur Erkennung und Meldung von Fehlern bei ihrer eigenen Ausführung enthalten:
- Fehlermeldungen werden direkt in der Terminalkonsole angezeigt.
- Klare Informationen zur Erleichterung von Diagnose und Problemlösung.
Arbeiten mit Dateien in MQL5
In MQL5 erfordert der Umgang mit Dateien ein grundlegendes Verständnis dafür, wie die Sprache diese Operationen handhabt. Wenn Sie wirklich in die komplizierten Vorgänge des Lesens, Schreibens und Verwendens von Flags eintauchen wollen, kann ich Ihnen nur empfehlen, den Artikel MQL5 Grundlagen der Programmierung: Dateien von Dmitry Fedoseev zu lesen. Er bietet einen vollständigen und detaillierten Überblick über das Thema, und zwar so, dass die Komplexität in etwas Klares umgewandelt wird, ohne dabei an Tiefe zu verlieren.
Aber was wir hier suchen, ist etwas direkter und objektiver. Wir werden uns nicht in winzigen Details verlieren, denn mein Ziel ist es, Ihnen das Wesentliche zu vermitteln: das Öffnen, Bearbeiten und Schließen von Dateien auf einfache und praktische Weise.
-
Verständnis der Dateiverzeichnisse in MQL5: In MQL5 werden alle Dateien, die von Standardfunktionen verarbeitet werden, automatisch im Ordner MQL5/Files gespeichert, der sich im Installationsverzeichnis des Terminals befindet. Das bedeutet, dass Sie bei der Arbeit mit Dateien in MQL5 nur den relativen Pfad von diesem Basisordner aus angeben müssen, ohne dass der vollständige Pfad angegeben werden muss. Wenn Sie zum Beispiel im Ordner logs/expert.log speichern, lautet der vollständige Pfad:
<terminal folder>/MQL5/Files/logs/expert.log
-
Erstellen und Öffnen von Dateien Die Funktion zum Öffnen oder Erstellen von Dateien ist FileOpen. Sie benötigt als obligatorisches Argument den Dateipfad (nach MQL5/Files) und einige Flags, die bestimmen, wie die Datei behandelt wird. Wir werden die folgenden Flags verwenden:
- FILE_READ: Ermöglicht das Öffnen der Datei zum Lesen.
- FILE_WRITE: Ermöglicht das Öffnen der Datei zum Schreiben.
- FILE_ANSI: Gibt an, dass der Inhalt in Form von Zeichenketten im ANSI-Format (jedes Zeichen belegt ein Byte) geschrieben werden soll.
Ein nützliches Merkmal von MQL5 ist, dass es bei der Kombination von FILE_READ und FILE_WRITE die Datei automatisch erstellt, wenn sie nicht existiert. Damit entfällt die Notwendigkeit einer manuellen Existenzprüfung.
-
Schließen der Datei: Wenn Sie die Arbeit mit der Datei beendet haben, verwenden Sie die Funktion FileClose(), um die Verarbeitung der Datei abzuschließen.
Hier ist ein praktisches Beispiel, wie man eine Datei in MQL5 öffnet (oder erstellt) und schließt:
int OnInit() { //--- Open the file and store the handler int handle_file = FileOpen("logs\\expert.log", FILE_READ|FILE_WRITE|FILE_ANSI, '\t', CP_UTF8); //--- If opening fails, display an error in the terminal log if(handle_file == INVALID_HANDLE) { Print("[ERROR] Unable to open log file. Ensure the directory exists and is writable. (Code: "+IntegerToString(GetLastError())+")"); return(INIT_FAILED); } //--- Close file FileClose(handle_file); return(INIT_SUCCEEDED); }
Nun, da wir die Datei geöffnet haben, ist es an der Zeit zu lernen, wie man in die Datei schreibt.
- Positionierung des Zeigers zum Schreiben: Bevor wir schreiben, müssen wir festlegen, wo die Daten eingefügt werden sollen. Wir verwenden die Funktion FileSeek(), um den Zeiger zum Schreiben an das Ende der Datei zu setzen. Dadurch wird das Überschreiben vorhandener Inhalte vermieden.
- Schreiben der Daten: Die Methode FileWrite() schreibt Zeichenketten in die Datei. Es ist nicht notwendig, „\n“ zu verwenden, um die Zeile zu unterbrechen. Wenn Sie diese Methode verwenden, werden die Daten beim nächsten Mal automatisch in eine andere Zeile geschrieben, um eine bessere Organisation zu gewährleisten.
Hier erfahren Sie, wie Sie dies in der Praxis umsetzen können:
int OnInit() { //--- Open the file and store the handler int handle_file = FileOpen("logs\\expert.log", FILE_READ|FILE_WRITE|FILE_ANSI, '\t', CP_UTF8); //--- If opening fails, display an error in the terminal log if(handle_file == INVALID_HANDLE) { Print("[ERROR] Unable to open log file. Ensure the directory exists and is writable. (Code: "+IntegerToString(GetLastError())+")"); return(INIT_FAILED); } //--- Move the writing pointer FileSeek(handle_file, 0, SEEK_END); //--- Writes the content inside the file FileWrite(handle_file, "[2025-01-02 12:35:27] DEBUG (CTradeManager): Order sent successfully, server responded in 32ms"); //--- Close file FileClose(handle_file); return(INIT_SUCCEEDED); }
Nachdem Sie den Code ausgeführt haben, wird eine Datei im Ordner „Files“ erstellt. Der vollständige Pfad lautet etwa wie folgt:
<Terminal folder>/MQL5/Files/logs/expert.log
Wenn Sie die Datei öffnen, werden Sie genau das sehen, was wir geschrieben haben:
[2025-01-02 12:35:27] DEBUG (CTradeManager): Order sent successfully, server responded in 32ms
Jetzt haben wir gelernt, wie man in MQL5 auf sehr einfache Weise mit Dateien umgeht. Fügen wir diese Arbeit der Klasse des Handler hinzu, die für das Speichern von Dateien zuständig ist, CLogifyHandlerFile.
Erstellen der Konfigurationen der Klasse CLogifyHandlerFile
Lassen Sie uns nun im Detail erläutern, wie wir diese Klasse konfigurieren können, um die im Abschnitt Anforderungen erwähnte Dateirotation effizient zu handhaben. Aber was genau bedeutet „Dateirotation“? Lassen Sie mich das genauer erklären. Die Rotation ist eine wesentliche Praxis, um das chaotische Szenario zu vermeiden, in dem eine einzelne Protokolldatei ins Unendliche wächst, und um die übermäßige Anhäufung von Daten in einer einzelnen Protokolldatei zu vermeiden, die eine spätere Analyse erschweren und die Protokolle in einen langsamen, schwer zu handhabenden und fast unmöglich zu entziffernden Alptraum verwandeln kann.
Stellen Sie sich folgendes Szenario vor: Ein Expert Advisor läuft wochen- oder monatelang und zeichnet jedes Ereignis, jeden Fehler oder jede Meldung in derselben Datei auf. Schon bald erreicht das Protokoll beträchtliche Ausmaße, was das Lesen und Interpretieren von Informationen recht komplex macht. Hier kommt die Rotation ins Spiel. Sie ermöglicht es uns, diese Informationen in kleinere und geordnete Teile aufzuteilen, was das Lesen und Analysieren sehr viel einfacher macht.
Die beiden gängigsten Methoden hierfür sind:
- Nach Größe: Sie legen eine Größenbeschränkung für die Protokolldatei fest, normalerweise in Megabyte (MB). Wenn diese Grenze erreicht ist, wird automatisch eine neue Datei angelegt, und der Zyklus beginnt von vorne. Dieser Ansatz ist sehr praktisch, wenn der Schwerpunkt auf der Kontrolle des Stammwachstums liegt, ohne sich an einen Kalender halten zu müssen. Sobald die aktuelle Datei das Größenlimit (in Megabyte) erreicht, kommt es zu folgendem Ablauf: Die aktuelle Protokolldatei wird umbenannt und erhält einen Index, z. B. „log1.log“. Die vorhandenen Dateien im Verzeichnis werden ebenfalls umnummeriert, z. B. wird aus „log1.log“ „log2.log“. Wenn die Anzahl der Dateien die zulässige Höchstzahl erreicht, werden die ältesten Dateien gelöscht. Dieser Ansatz ist nützlich, um sowohl den von den Protokollen belegten Platz als auch die Anzahl der gespeicherten Dateien zu begrenzen.
- Nach Datum: In diesem Fall wird jeden Tag eine neue Protokolldatei erstellt. Jedes dieser Protokolle trägt das Erstellungsdatum in seinem Namen, z. B. log_2025-01-19.log, wodurch die meisten Probleme bei der Organisation der Protokolle gelöst werden. Dieser Ansatz ist ideal, wenn Sie sich einen bestimmten Tag ansehen möchten, ohne sich in einer einzigen riesigen Datei zu verlieren. Dies ist die Methode, die ich am häufigsten verwende, wenn ich die Protokolle meiner Experts Adivisors speichere; alles ist sauberer, direkter und einfacher zu navigieren.
Darüber hinaus können Sie die Anzahl der gespeicherten Protokolldateien begrenzen. Diese Kontrolle ist sehr wichtig, um die unnötige Anhäufung von alten Stämmen zu verhindern. Stellen Sie sich vor, Sie stellen das System so ein, dass es die 30 neuesten Dateien aufbewahrt. Wenn die 31. erscheint, verwirft das System automatisch die älteste Datei, wodurch die Anhäufung von sehr alten Protokollen auf der Festplatte verhindert wird und die neuesten Dateien aufbewahrt werden.
Ein weiteres wichtiges Detail ist die Verwendung eines Caches. Anstatt jede Nachricht direkt in die Datei zu schreiben, sobald sie eintrifft, werden die Nachrichten im Cache zwischengespeichert. Wenn der Cache eine bestimmte Grenze erreicht, wird der gesamte Inhalt der Datei auf einmal geschrieben. Dies führt zu weniger Lese- und Schreibvorgängen auf der Festplatte, einer besseren Leistung und einer längeren Lebensdauer Ihrer Speichergeräte.
Nachdem wir nun das Konzept der Dateirotation verstanden haben, erstellen wir eine Struktur namens MqlLogifyHandleFileConfig, um alle Konfigurationen für die Klasse CLogifyHandlerFile zu speichern. Diese Struktur enthält die Parameter, die festlegen, wie die Protokolle verwaltet werden sollen.
Der erste Teil der Struktur umfasst die Definition von Enums für die zu verwendenden Rotationstypen und Dateierweiterungen:
//+------------------------------------------------------------------+ //| ENUMS for log rotation and file extension | //+------------------------------------------------------------------+ enum ENUM_LOG_ROTATION_MODE { LOG_ROTATION_MODE_NONE = 0, // No rotation LOG_ROTATION_MODE_DATE, // Rotate based on date LOG_ROTATION_MODE_SIZE, // Rotate based on file size }; enum ENUM_LOG_FILE_EXTENSION { LOG_FILE_EXTENSION_TXT = 0, // .txt file LOG_FILE_EXTENSION_LOG, // .log file LOG_FILE_EXTENSION_JSON, // .json file };
Die Struktur MqlLogifyHandleFileConfig selbst enthält die folgenden Parameter:
- directory: das Verzeichnis, in dem die Protokolldateien gespeichert werden sollen,
- base_filename: Basisdateiname, ohne die Erweiterung,
- file_extension: Erweiterungstyp der Protokolldatei (z. B. .txt, .log oder .json).
- Rotation_mode: Modus der Dateirotation.
- messages_per_flush: Anzahl der Protokollmeldungen, die zwischengespeichert werden sollen, bevor sie in die Datei geschrieben werden.
- codepage: Für die Protokolldateien verwendete Kodierung (z. B. UTF-8 oder ANSI).
- max_file_size_mb: Maximale Größe der einzelnen Protokolldateien, wenn die Rotation auf der Größe basiert.
- max_file_count: Maximale Anzahl der Protokolldateien, die aufbewahrt werden sollen, bevor die ältesten gelöscht werden.
Zusätzlich zu den Konstruktoren und Destruktoren werde ich der Struktur Hilfsmethoden hinzufügen, um jeden der Rotationsmodi zu konfigurieren, damit der Konfigurationsprozess praktischer und vor allem zuverlässiger wird. Diese Methoden dienen nicht nur der Eleganz, sondern gewährleisten, dass bei der Konfiguration kein kritisches Detail übersehen wird.
Wenn zum Beispiel der Rotationsmodus nach Zeit (LOG_ROTATION_MODE_DATE) eingestellt ist, macht der Versuch, das Attribut max_file_size_mb zu konfigurieren, überhaupt keinen Sinn, da dieser Parameter nur im Modus nach Größe (LOG_ROTATION_MODE_SIZE) relevant ist. Die Aufgabe dieser Methoden besteht darin, solche Inkonsistenzen zu vermeiden und das System vor ungültigen Konfigurationen zu schützen.
Wenn zufällig ein wesentlicher Parameter nicht angegeben ist, wird das System aktiv. Es kann automatisch einen Standardwert eingeben und den Entwickler warnen, um sicherzustellen, dass der Ablauf stabil ist und keine unangenehmen Überraschungen auftreten.
Die Hilfsmethoden, die wir implementieren werden, sind:
- CreateNoRotationConfig(): Konfiguration für Rotation ohne Dateien (alle Protokolle werden ohne Rotation in dieselbe Datei geschrieben).
- CreateDateRotationConfig(): Konfiguration für datumsabhängige Rotation.
- CreateSizeRotationConfig(): Konfiguration für die Rotation nach der Dateigröße.
- ValidateConfig(): Methode, die prüft, ob alle Konfigurationen korrekt und einsatzbereit sind. (Dies ist eine Methode, die von der Klasse automatisch verwendet wird und nicht vom Entwickler, der die Bibliothek verwenden wird).
Hier ist die vollständige Implementierung der Struktur:
//+------------------------------------------------------------------+ //| Struct: MqlLogifyHandleFileConfig | //+------------------------------------------------------------------+ struct MqlLogifyHandleFileConfig { string directory; // Directory for log files string base_filename; // Base file name ENUM_LOG_FILE_EXTENSION file_extension; // File extension type ENUM_LOG_ROTATION_MODE rotation_mode; // Rotation mode int messages_per_flush; // Messages before flushing uint codepage; // Encoding (e.g., UTF-8, ANSI) ulong max_file_size_mb; // Max file size in MB for rotation int max_file_count; // Max number of files before deletion //--- Default constructor MqlLogifyHandleFileConfig(void) { directory = "logs"; // Default directory base_filename = "expert"; // Default base name file_extension = LOG_FILE_EXTENSION_LOG;// Default to .log extension rotation_mode = LOG_ROTATION_MODE_SIZE;// Default size-based rotation messages_per_flush = 100; // Default flush threshold codepage = CP_UTF8; // Default UTF-8 encoding max_file_size_mb = 5; // Default max file size in MB max_file_count = 10; // Default max file count } //--- Destructor ~MqlLogifyHandleFileConfig(void) { } //--- Create configuration for no rotation void CreateNoRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, int msg_per_flush=100, uint cp=CP_UTF8) { directory = dir; base_filename = base_name; file_extension = extension; rotation_mode = LOG_ROTATION_MODE_NONE; messages_per_flush = msg_per_flush; codepage = cp; } //--- Create configuration for date-based rotation void CreateDateRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, int max_files=10, int msg_per_flush=100, uint cp=CP_UTF8) { directory = dir; base_filename = base_name; file_extension = extension; rotation_mode = LOG_ROTATION_MODE_DATE; messages_per_flush = msg_per_flush; codepage = cp; max_file_count = max_files; } //--- Create configuration for size-based rotation void CreateSizeRotationConfig(string base_name="expert", string dir="logs", ENUM_LOG_FILE_EXTENSION extension=LOG_FILE_EXTENSION_LOG, ulong max_size=5, int max_files=10, int msg_per_flush=100, uint cp=CP_UTF8) { directory = dir; base_filename = base_name; file_extension = extension; rotation_mode = LOG_ROTATION_MODE_SIZE; messages_per_flush = msg_per_flush; codepage = cp; max_file_size_mb = max_size; max_file_count = max_files; } //--- Validate configuration bool ValidateConfig(string &error_message) { //--- Saves the return value bool is_valid = true; //--- Check if the directory is not empty if(directory == "") { directory = "logs"; error_message = "The directory cannot be empty."; is_valid = false; } //--- Check if the base filename is not empty if(base_filename == "") { base_filename = "expert"; error_message = "The base filename cannot be empty."; is_valid = false; } //--- Check if the number of messages per flush is positive if(messages_per_flush <= 0) { messages_per_flush = 100; error_message = "The number of messages per flush must be greater than zero."; is_valid = false; } //--- Check if the codepage is valid (verify against expected values) if(codepage != CP_ACP && codepage != CP_MACCP && codepage != CP_OEMCP && codepage != CP_SYMBOL && codepage != CP_THREAD_ACP && codepage != CP_UTF7 && codepage != CP_UTF8) { codepage = CP_UTF8; error_message = "The specified codepage is invalid."; is_valid = false; } //--- Validate limits for size-based rotation if(rotation_mode == LOG_ROTATION_MODE_SIZE) { if(max_file_size_mb <= 0) { max_file_size_mb = 5; error_message = "The maximum file size (in MB) must be greater than zero."; is_valid = false; } if(max_file_count <= 0) { max_file_count = 10; error_message = "The maximum number of files must be greater than zero."; is_valid = false; } } //--- Validate limits for date-based rotation if(rotation_mode == LOG_ROTATION_MODE_DATE) { if(max_file_count <= 0) { max_file_count = 10; error_message = "The maximum number of files for date-based rotation must be greater than zero."; is_valid = false; } } //--- No errors found error_message = ""; return(is_valid); } }; //+------------------------------------------------------------------+
Ein interessantes Detail, das ich hier hervorheben möchte, ist die Funktionsweise der Funktion ValidateConfig(). Bei der Analyse dieser Funktion fällt etwas Interessantes auf: Wenn sie einen Fehler in einem Konfigurationswert feststellt, ist es nicht so, dass sie sofort false zurückgibt und damit sagt, dass etwas falsch gelaufen ist. Sie handelt zuerst und ergreift automatisch Korrekturmaßnahmen zur Lösung des Problems, bevor sie ein endgültiges Ergebnis liefert.
Zunächst wird der ungültige Wert auf seinen Standardwert zurückgesetzt. Dadurch wird die Konfiguration gewissermaßen vorübergehend „repariert“, ohne dass der Fehler den Programmablauf behindert. Um die Situation nicht unerklärt zu lassen, weist die Funktion dann eine detaillierte Meldung zu, aus der klar hervorgeht, wo der Fehler aufgetreten ist und was korrigiert werden muss. Und schließlich markiert die Funktion die Variable is_valid mit dem Wert false und signalisiert damit, dass etwas schief gelaufen ist. Erst am Ende, nachdem all diese Maßnahmen ergriffen wurden, wird diese Variable mit dem endgültigen Status zurückgegeben, der angibt, ob die Konfiguration bestanden hat und gültig ist oder nicht.
Noch interessanter ist jedoch, wie die Funktion mit Mehrfachfehlern umgeht. Wenn mehr als ein falscher Wert zur gleichen Zeit auftritt, konzentriert sich das Programm nicht auf die Korrektur des ersten Fehlers und lässt die anderen für später stehen. Ganz im Gegenteil, sie geht ihnen allen auf einmal nach und korrigiert alles gleichzeitig. Am Ende gibt die Funktion die Meldung zurück, in der erklärt wird, welcher Fehler zuletzt behoben wurde, wobei sichergestellt wird, dass nichts ausgelassen wird.
Diese Art von Ansatz ist wertvoll und erleichtert die Arbeit des Entwicklers. Während der Entwicklung eines Systems kann es vorkommen, dass einige Werte falsch oder versehentlich definiert werden. Das Schöne daran ist, dass die Funktion eine zusätzliche Sicherheitsebene hat, indem sie Fehler automatisch korrigiert, ohne darauf zu warten, dass der Programmierer sie einzeln bemerkt. Schließlich können kleine Fehler, wenn sie unbehandelt bleiben, zu größeren Ausfällen führen - wie z. B. das Versäumnis, Protokollsätze zu speichern. Diese von mir entwickelte Automatisierung der Fehlerbehandlung verhindert, dass kleine Fehler den Betrieb des Systems unterbrechen, und trägt dazu bei, dass alles läuft.
Implementierung der Klasse CLogifyHandlerFile
Wir verfügen ja bereits über die Klasse, die bereits im letzten Artikel erstellt wurde. Wir werden nur Änderungen vornehmen, um sie funktionsfähig zu machen. Im Folgenden werde ich jede Anpassung detailliert beschreiben, damit Sie verstehen, wie alles funktioniert.
Im privaten Bereich der Klasse fügen wir Variablen und einige wichtige Hilfsmethoden hinzu:
- Konfiguration: Wir erstellen eine Variable m_config vom Typ MqlLogifyHandleFileConfig, um die Einstellungen für das Protokollierungssystem zu speichern.
- Ich habe auch die Methoden SetConfig() und GetConfig() implementiert, um die Klasseneinstellungen zu definieren und darauf zuzugreifen.
Hier ist die anfängliche Struktur der Klasse, mit den grundlegenden Definitionen und Methoden:
//+------------------------------------------------------------------+ //| class : CLogifyHandlerFile | //| | //| [PROPERTY] | //| Name : CLogifyHandlerFile | //| Heritage : CLogifyHandler | //| Description : Log handler, inserts data into file, supports | //| rotation modes. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerFile : public CLogifyHandler { private: //--- Config MqlLogifyHandleFileConfig m_config; public: //--- Configuration management void SetConfig(MqlLogifyHandleFileConfig &config); MqlLogifyHandleFileConfig GetConfig(void); }; //+------------------------------------------------------------------+ //| Set configuration | //+------------------------------------------------------------------+ void CLogifyHandlerFile::SetConfig(MqlLogifyHandleFileConfig &config) { m_config = config; //--- Validade string err_msg = ""; if(!m_config.ValidateConfig(err_msg)) { Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg); } } //+------------------------------------------------------------------+ //| Get configuration | //+------------------------------------------------------------------+ MqlLogifyHandleFileConfig CLogifyHandlerFile::GetConfig(void) { return(m_config); } //+------------------------------------------------------------------+
Ich werde die Hilfsmethoden auflisten und genauer erklären, wie sie funktionieren. Ich habe drei nützliche Methoden implementiert, die bei der Dateiverwaltung zum Einsatz kommen werden:
-
LogFileExtensionToStr(): Konvertiert den Wert der Enumeration ENUM_LOG_FILE_EXTENSION in eine Zeichenkette, die die Dateierweiterung darstellt. Die Enumeration definiert die möglichen Werte für den Dateityp, z. B. .log, .txt und .json.
//+------------------------------------------------------------------+ //| Convert log file extension enum to string | //+------------------------------------------------------------------+ string CLogifyHandlerFile::LogFileExtensionToStr(ENUM_LOG_FILE_EXTENSION file_extension) { switch(file_extension) { case LOG_FILE_EXTENSION_LOG: return(".log"); case LOG_FILE_EXTENSION_TXT: return(".txt"); case LOG_FILE_EXTENSION_JSON: return(".json"); } //--- Default return return(".txt"); } //+------------------------------------------------------------------+
-
LogPath(): Diese Funktion ist dafür verantwortlich, den vollständigen Pfad der Protokolldatei auf der Grundlage der aktuellen Klasseneinstellungen zu generieren. Zunächst wird die konfigurierte Dateierweiterung mit der Funktion LogFileExtensionToStr() umgewandelt. Dann wird der konfigurierte Rotationstyp überprüft. Wenn die Rotation auf der Dateigröße basiert oder es keine Rotation gibt, wird nur der Dateiname im konfigurierten Verzeichnis zurückgegeben. Wenn die Rotation auf dem Datum basiert, wird das aktuelle Datum (Format JJJJ-MM-TT) als Präfix in den Dateinamen aufgenommen.
//+------------------------------------------------------------------+ //| Generate log file path based on config | //+------------------------------------------------------------------+ string CLogifyHandlerFile::LogPath(void) { string file_extension = this.LogFileExtensionToStr(m_config.file_extension); string base_name = m_config.base_filename + file_extension; if(m_config.rotation_mode == LOG_ROTATION_MODE_SIZE || m_config.rotation_mode == LOG_ROTATION_MODE_NONE) { return(m_config.directory + "\\\\" + base_name); } else if(m_config.rotation_mode == LOG_ROTATION_MODE_DATE) { MqlDateTime date; TimeCurrent(date); string date_str = IntegerToString(date.year) + "-" + IntegerToString(date.mon, 2, '0') + "-" + IntegerToString(date.day, 2, '0'); base_name = date_str + (m_config.base_filename != "" ? "-" + m_config.base_filename : "") + file_extension; return(m_config.directory + "\\\\" + base_name); } //--- Default return return(base_name); } //+------------------------------------------------------------------+
Die Methode Emit() ist für die Protokollierung von Meldungen in einer Datei zuständig. Im aktuellen Code werden die Protokolle einfach auf der Terminalkonsole angezeigt. Verbessern wir dies so, dass die Protokolldatei geöffnet, eine neue Zeile mit den formatierten Daten hinzugefügt und die Datei nach dem Schreiben geschlossen wird. Wenn die Datei nicht geöffnet werden kann, wird eine Fehlermeldung in der Konsole angezeigt.
void CLogifyHandlerFile::Emit(MqlLogifyModel &data) { //--- Checks if the configured level allows if(data.level >= this.GetLevel()) { //--- Get the full path of the file string log_path = this.LogPath(); //--- Open file ResetLastError(); int handle_file = FileOpen(log_path, FILE_READ|FILE_WRITE|FILE_ANSI, '\t', m_config.codepage); if(handle_file == INVALID_HANDLE) { Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: Unable to open log file '"+log_path+"'. Ensure the directory exists and is writable. (Code: "+IntegerToString(GetLastError())+")"); return; } //--- Write FileSeek(handle_file, 0, SEEK_END); FileWrite(handle_file, data.formated); //--- Close file FileClose(handle_file); } }
Wir haben also die einfachste Version der Klasse, die die Protokolle in eine Datei schreibt. Nun wollen wir einige einfache Tests durchführen, um zu überprüfen, ob die Grundlagen korrekt funktionieren.
Erster Test mit Dateien
Wir verwenden die Testdatei, die wir bereits in den vorherigen Beispielen verwendet haben, LogifyTest.mqh. Ziel ist es, das Protokollierungssystem so zu konfigurieren, dass es Datensätze in Dateien speichert, wobei die Basisklasse CLogify und der soeben implementierte Dateihandler verwendet werden.
- Wir erstellen eine Variable vom Typ MqlLogifyHandleFileConfig, um die spezifischen Einstellungen des Filehandlers zu speichern.
- Wir konfigurieren den Handler so, dass er das gewünschte Format und die gewünschten Regeln verwendet, z. B. die Rotation der Dateien nach Größe.
- Wir fügen diesen Handler der Basisklasse CLogify hinzu.
- Wir konfigurieren einen Formatierer, um festzulegen, wie die einzelnen Datensätze in der Datei angezeigt werden sollen.
Siehe den vollständigen Code:
//+------------------------------------------------------------------+ //| Import CLogify | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Configs MqlLogifyHandleFileConfig m_config; m_config.CreateSizeRotationConfig("expert","logs",LOG_FILE_EXTENSION_LOG,5,5,10); //--- Handler File CLogifyHandlerFile *handler_file = new CLogifyHandlerFile(); handler_file.SetConfig(m_config); handler_file.SetLevel(LOG_LEVEL_DEBUG); //--- Add handler in base class logify.AddHandler(handler_file); logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","{date_time} [{levelname}] {msg}")); //--- Using logs logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14"); logify.Infor("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1"); logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678"); logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance"); logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Wenn Sie den obigen Code ausführen, wird eine neue Protokolldatei in dem konfigurierten Verzeichnis (logs) erstellt. Sie kann im Dateibrowser angezeigt werden.
Wenn Sie die Datei in Notepad oder einem anderen Texteditor öffnen, sehen Sie den von den Nachrichtentests erzeugten Inhalt:
Bevor ich mich den Verbesserungen widme, werde ich einen Leistungstest durchführen, um zu verstehen, wie sehr die Leistung dadurch verbessert wird, damit wir später eine Referenz zum Vergleich haben. Innerhalb der Funktion OnTick() werde ich einen Datensatz zum Protokoll hinzufügen, sodass bei jedem neuen Tick die Protokolldatei geöffnet, geschrieben und geschlossen wird.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Logs logify.Debug("Debug Message"); } //+------------------------------------------------------------------+
Ich werde den Strategietester verwenden, um diesen Test durchzuführen. Auch im Backtest funktioniert das Dateierstellungssystem normal, aber die Dateien werden in einem anderen Ordner gespeichert. Ich werde später zeigen, wie darauf zugegriffen wird. Der Test wird mit den folgenden Einstellungen durchgeführt:
Bei der Modellierung „OHLC für 1 Minute“ auf dem EURUSD-Symbol mit 7 Testtagen dauerte der Test 5 Minuten und 11 Sekunden, wenn man bedenkt, dass bei jedem Tick ein neuer Log-Eintrag erzeugt und sofort in der Datei gespeichert wird.
Testen mit JSON-Dateien
Schließlich möchte ich die Verwendung von JSON-Protokolldateien in der Praxis zeigen, da sie für einige spezifische Szenarien nützlich sein können. Um als JSON zu speichern, ändern Sie einfach den Dateityp in den Einstellungen und definieren Sie ein gültiges Formatierungsprogramm für das JSON-Format, hier ein Beispiel für die Implementierung:
//+------------------------------------------------------------------+ //| Import CLogify | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Configs MqlLogifyHandleFileConfig m_config; m_config.CreateSizeRotationConfig("expert","logs",LOG_FILE_EXTENSION_JSON,5,5,10); //--- Handler File CLogifyHandlerFile *handler_file = new CLogifyHandlerFile(); handler_file.SetConfig(m_config); handler_file.SetLevel(LOG_LEVEL_DEBUG); //--- Add handler in base class logify.AddHandler(handler_file); logify.SetFormatter(new CLogifyFormatter("hh:mm:ss","{\\"datetime\\":\\"{date_time}\\", \\"level\\":\\"{levelname}\\", \\"msg\\":\\"{msg}\\"}")); //--- Using logs logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14"); logify.Infor("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1"); logify.Alert("Stop Loss adjusted to breakeven level", "Risk Management", "Order ID: 12345678"); logify.Error("Failed to send sell order", "Order Management", "Reason: Insufficient balance"); logify.Fatal("Failed to initialize EA: Invalid settings", "Initialization", "Missing or incorrect parameters"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Mit den gleichen Protokollmeldungen ist dies das Ergebnis der Datei nach der Ausführung des Experten auf dem Chart:
{"datetime":"08:24:10", "level":"DEBUG", "msg":"RSI indicator value calculated: 72.56"} {"datetime":"08:24:10", "level":"INFOR", "msg":"Buy order sent successfully"} {"datetime":"08:24:10", "level":"ALERT", "msg":"Stop Loss adjusted to breakeven level"} {"datetime":"08:24:10", "level":"ERROR", "msg":"Failed to send sell order"} {"datetime":"08:24:10", "level":"FATAL", "msg":"Failed to initialize EA: Invalid settings"}
Schlussfolgerung
In diesem Artikel haben wir eine praktische und ausführliche Anleitung zur Durchführung grundlegender Dateioperationen vorgestellt: Öffnen, Manipulieren des Inhalts und schließlich Schließen der Datei auf einfache Art und Weise. Ich habe auch die Bedeutung der Konfiguration der Struktur des „Handlers“ erörtert. Durch diese Konfiguration ist es möglich, mehrere Merkmale anzupassen, wie z. B. den Typ der zu verwendenden Datei (z. B. Text, Log oder sogar json) und das Verzeichnis, in dem die Datei gespeichert wird, wodurch die Bibliothek sehr flexibel wird.
Außerdem haben wir spezifische Verbesserungen an der Klasse CLogifyHandlerFile vorgenommen. Diese Änderungen ermöglichten es, jede Meldung direkt in einer Protokolldatei aufzuzeichnen. Nach dieser Implementierung habe ich im Rahmen der Studie auch einen Leistungstest durchgeführt, um die Effizienz der Lösung zu messen. Wir haben ein spezifisches Szenario verwendet, bei dem das System die Ausführung einer Handelsstrategie für den EURUSD über einen Zeitraum von einer Woche simuliert hat. Während dieses Tests wurde für jeden neuen „Tick“ des Marktes ein Protokollsatz erstellt. Dieser Vorgang ist sehr aufwendig, da für jede Änderung des Vermögenspreises eine neue Zeile in der Datei gespeichert werden muss.
Das Endergebnis wurde aufgezeichnet: Der gesamte Vorgang dauerte 5 Minuten und 11 Sekunden. Dieses Ergebnis wird als Bezugspunkt für den nächsten Artikel dienen, in dem wir ein Cache-System (temporärer Speicher) implementieren werden. Um nicht ständig auf die Datei zugreifen zu müssen, werden im Cache die Datensätze zwischengespeichert, was die Gesamtleistung verbessert.
Bleiben Sie dran für den nächsten Artikel, in dem wir noch mehr fortgeschrittene Techniken zur Steigerung der Systemeffizienz und -leistung erforschen werden. Wir sehen uns!
Datei Name | Beschreibung |
---|---|
Experts/Logify/LogiftTest.mq5 | die Datei, in der wir die Funktionen der Bibliothek testen, mit einem praktischen Beispiel |
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/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/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/16986





- 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.
Sehen Sie sich den neuen Artikel an: Beherrschen von Protokolldatensätzen (Teil 4): Speichern von Protokollen in Dateien.
Autor: joaopedrodev