Beherrschung von Protokollaufzeichnungen (Teil 10): Vermeidung von Log Replay durch Implementierung einer Unterdrückung
Einführung
Dieser Artikel geht auf eine direkte Anfrage eines Nutzers der Logify-Bibliothek zurück. Er wies auf ein Problem hin, mit dem viele Menschen in der Praxis konfrontiert sind: Wenn die Menge der Protokolle zu groß wird, verschmutzen wiederholte oder irrelevante Nachrichten den Verlauf, sodass es schwierig wird, das wirklich Wichtige zu finden. Wenn Sie weitere Ideen, Fragen oder Herausforderungen haben, auf die ich eingehen soll, können Sie am Ende einen Kommentar hinterlassen. Dies ist unser Raum, und die Bibliothek entwickelt sich, indem sie auf Sie hört.
Bevor wir weitermachen, ist es wichtig zu verstehen, was „Protokollunterdrückung“ bedeutet. Kurz gesagt ist die Unterdrückung ein Prozess, bei dem kontrolliert wird, welche Protokollmeldungen aufgezeichnet werden, um ein Übermaß, eine Redundanz oder eine Verschmutzung von Informationen zu vermeiden. Anstatt einfach alles zu löschen, was das System produziert, filtern und begrenzen Sie, was erscheint, und stellen so sicher, dass das Protokoll nur die Meldungen enthält, die nützlich und relevant sind und zum richtigen Zeitpunkt anfallen.
In diesem Artikel stellen wir eine praktische Implementierung eines Protokollunterdrückungssystems für Logify vor, das flexibel und effizient ist. Sie werden sehen, wie Sie verschiedene Formen der Kontrolle kombinieren können, z. B. die Vermeidung identischer Meldungen, die in Folge wiederholt werden, die Begrenzung der Häufigkeit, mit der dasselbe Protokoll erscheint, die Kontrolle der maximalen Anzahl von Wiederholungen und sogar die Filterung nach der Quelle oder Datei, aus der das Protokoll stammt. All dies mit einem intelligenten System, das auf bitweisen Modi basiert, die es Ihnen ermöglichen, ohne Komplikationen mehrere Regeln gleichzeitig zu aktivieren.
Nach der Lektüre dieses Artikels werden Sie die Erstellung einer robusten Lösung beherrschen, mit der Sie Ihr Protokoll schlank und effizient halten und Überschreitungen automatisch unterdrücken können. Sie werden verstehen, wie man klare Regeln anwendet, die die Analyse erleichtern und das Rauschen reduzieren, was Ressourcen und Zeit spart. Diese Verbesserung ist besonders für Produktionsumgebungen nützlich, in denen übermäßige Protokolle die Leistung beeinträchtigen und die Wartung erschweren können. Denken Sie daran, dass die endgültige Version der Bibliothek am Ende des Artikels zum Herunterladen zur Verfügung steht.
Organisation von Dateien
Erstellen Sie in Ihrem Logify-Bibliotheksprojekt einen neuen Ordner mit dem Namen Suppression innerhalb des Hauptordners von Logify. Dies hilft, den Code zu organisieren und macht deutlich, dass alles, was die „Unterdrückung“ von Protokollen betrifft, dort konzentriert ist. Erstellen Sie im Ordner Suppression eine neue Datei namens LogifySuppression.mqh. Diese Datei wird der Ausgangspunkt für unsere neue Unterdrückungsklasse sein, die steuern wird, welche Meldungen tatsächlich im Protokoll erscheinen sollen, um Wiederholungen und Exzesse zu vermeiden.
Auf den ersten Blick mag die Klasse einfach aussehen, mit nur einem leeren Konstruktor und Destruktor, etwa so:
//+------------------------------------------------------------------+ //| LogifySuppression.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CLogifySuppression { private: public: CLogifySuppression(void); ~CLogifySuppression(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLogifySuppression::CLogifySuppression(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CLogifySuppression::~CLogifySuppression(void) { } //+------------------------------------------------------------------+
Es handelt sich um einen sauberen Ausgangspunkt, bei dem noch nichts implementiert ist, nur um die Aufnahme der Datei zu strukturieren und zu testen.
Erweitern: Importe und Definitionen
Um diese Klasse mit Funktionen auszustatten, müssen wir das Protokolldatenmodell (LogifyModel.mqh ) importieren, das das Format der Nachrichten definiert, die wir zur Verarbeitung erhalten. Wir werden auch Konstanten definieren, um die Unterdrückungsparameter zu validieren und Mindest- und Höchstgrenzen zu garantieren, um ungültige Konfigurationen zu vermeiden.
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "../LogifyModel.mqh" //+------------------------------------------------------------------+ //| Validation constants | //+------------------------------------------------------------------+ #define MAX_SUPPRESSION_MODE 255 // Maximum valid mode combination #define MIN_THROTTLE_SECONDS 1 // Minimum interval between messages #define MIN_REPEAT_COUNT 1 // Minimum number of repetitions
Unterdrückungsmodi mit einer bitweiser Enumeration
Um zu verstehen, wie man verschiedene Möglichkeiten zur Vermeidung von Wiederholungen und überflüssigen Protokollen kontrollieren kann, müssen wir über ein grundlegendes Konzept sprechen: die Verwendung einer bitweisen Enumeration.
Aber was genau ist das? In der Programmierung ist ein Enum eine organisierte Art und Weise, eine Reihe von benannten Konstanten zu definieren. Sie könnten zum Beispiel eine Enumeration haben, um Protokollstufen wie DEBUG, INFO, ERROR usw. darzustellen. Bitweiser Operator wirken direkt auf die Bits ein, aus denen eine ganze Zahl besteht. Diese Bits sind wie Schalter, die eingeschaltet (1) oder ausgeschaltet (0) sein können. Wenn wir enum und bitwise kombinieren, erzeugen wir eindeutige Werte, die Potenzen von 2 darstellen, d. h. 1, 2, 4, 8, 16 und so weiter. Jeder dieser Werte entspricht einem anderen Bit in der Binärzahl.
Warum bitweise Enums verwenden? Stellen Sie sich vor, Sie möchten mehr als einen Unterdrückungsmodus gleichzeitig anwenden. Zum Beispiel die Begrenzung von aufeinanderfolgenden, wiederholten Nachrichten und auch die Begrenzung von Nachrichten, die zu oft hintereinander erscheinen. Wenn wir nur einfache Werte in der Enumeration erstellen würden, könnten wir immer nur einen Modus auswählen, oder? Das würde das System zu sehr einschränken. Mit bitweisen Enums können wir mehrere Modi kombinieren, indem wir die entsprechenden Bits aktivieren. Die Kombination erfolgt mit dem bitweisen ODER-Operator (|), der die Bits der gewünschten Modi zu einer einzigen Zahl „verknüpft“.
Schauen wir uns die von uns verwendete Definition einmal praktisch an:
enum ENUM_LOG_SUPRESSION_MODE { LOG_SUPRESSION_MODE_NONE = 0, // No suppression LOG_SUPRESSION_MODE_CONSECUTIVE = 1 << 0, // 00001 = 1: Identical consecutive messages LOG_SUPRESSION_MODE_THROTTLE_TIME = 1 << 1, // 00010 = 2: Same message within X seconds LOG_SUPRESSION_MODE_BY_REPEAT_COUNT = 1 << 2, // 00100 = 4: After N repetitions LOG_SUPRESSION_MODE_BY_ORIGIN = 1 << 3, // 01000 = 8: Based on message origin LOG_SUPRESSION_MODE_BY_FILENAME = 1 << 4, // 10000 = 16: Based on source filename };
1 << N bedeutet hier „1 N-mal nach links verschoben“. Jede Verschiebung erzeugt ein einzelnes Bit:
- 1 << 0 = 1 (binär 00001)
- 1 << 1 = 2 (binär 00010)
- 1 << 2 = 4 (binär 00100)
- 1 << 3 = 8 (binär 01000)
- 1 << 4 = 16 (binär 10000)
Wenn Sie mehr als einen Modus gleichzeitig aktivieren möchten, kombinieren Sie die Werte einfach mit dem Operator OR (|):
int mode = LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_THROTTLE_TIME; // 1 | 2 = 3 (00011)
Die Zahl 3, binär 00011, bedeutet, dass die ersten beiden Modi gleichzeitig aktiv sind. Warum hilft dies bei der Protokollunterdrückung?
- Flexibilität: Das System akzeptiert mehrere Unterdrückungsregeln gleichzeitig, ohne dass für jede mögliche Kombination separate Enums erstellt werden müssen.
- Effizienz: Die Prüfung, ob ein Modus aktiv ist, ist einfach und schnell: Verwenden Sie einfach den bitweisen Operator AND (&), um zu prüfen, ob das Bit dieses Modus eingeschaltet ist.
- Erweiterbarkeit: Wenn wir in Zukunft neue Unterdrückungsmodi hinzufügen wollen, können wir einfach neue Bits zu der Enumeration hinzufügen, ohne etwas zu zerstören, was bereits funktioniert.
Um zum Beispiel herauszufinden, ob der Modus „Unterdrückung nach Herkunft“ aktiv ist, tun wir das:
if((mode & LOG_SUPRESSION_MODE_BY_ORIGIN) == LOG_SUPRESSION_MODE_BY_ORIGIN) { // Apply filter by source }
Wenn das entsprechende Bit eingeschaltet ist, ist die Bedingung erfüllt.
Konfiguration mit struct
Nachdem die möglichen Unterdrückungsmodi definiert wurden, ist es an der Zeit, die gesamte Konfigurationslogik an einem Ort zu kapseln, und hier kommt die Struktur MqlLogifySuppressionConfig ins Spiel. Diese Struktur funktioniert wie ein „Kontrollfeld“, in dem Sie festlegen, wie und wann Protokollmeldungen unterdrückt werden sollen. Die Idee ist einfach: die Parameter zu speichern, die das Unterdrückungsverhalten steuern und eine saubere und wiederverwendbare Konfiguration ermöglichen, sei es in EAs, Indikatoren oder Hilfsbibliotheken.
Schauen wir uns an, was da drin ist:
-
Modus: Kombination von Unterdrückungsmodi
Dies ist das Herzstück der Konfiguration. Hier wird eine Kombination von Unterdrückungsmodi unter Verwendung eines int mit bitweisen Operationen gespeichert. Dies ermöglicht es dem Entwickler, mehrere Unterdrückungsmodi gleichzeitig zu aktivieren, z. B:
- Unterdrückung von identischen aufeinanderfolgenden Protokollen
- Ignorieren wiederholter Meldungen in einem Zeitintervall
- Anhalten nach X Wiederholungen
-
throttle_seconds: Begrenzung nach Zeit
Dieses Feld definiert ein Intervall (in Sekunden), das zwischen wiederholten Meldungen liegen muss, damit sie erneut angezeigt werden. Sie ist nützlich, wenn eine Funktion mehrmals pro Sekunde dasselbe Protokoll auslöst, was die Konsole schnell verschmutzt und alles unlesbar macht.
-
max_repeat_count: Begrenzung nach Menge
Hier wird festgelegt, wie oft die gleiche Meldung maximal erscheinen darf, bevor sie unterdrückt wird. Dies ist z. B. nützlich, um einen Fehler abzufangen, der einige Male aufgetreten ist, ohne dass er jedoch unendlich lange angezeigt wird.
-
Whitelist/Blacklist nach Quelle und Datei
Oft möchte der Entwickler die Unterdrückung nur auf Nachrichten anwenden, die von bestimmten Stellen im Code stammen, oder bestimmte Ursprünge ganz ausschließen.
Aus diesem Grund enthält struct vier Felder:
- allowed_origins[]: Wenn ausgefüllt, werden nur diese Ursprünge zugelassen.
- blocked_origins[]: Jede hier aufgeführte Herkunft wird immer blockiert.
- allowed_filenames[]: Dasselbe Konzept, aber angewendet auf den Namen der Quelldatei.
- blocked_filenames []: Dateien werden immer gesperrt.
Diese Felder ermöglichen eine extrem granulare Kontrolle. Sie könnten zum Beispiel Protokolle von Ihrem Haupt-EA zulassen, aber alle Protokolle unterdrücken, die von einer lauten Drittanbieter-Bibliothek stammen.
Definieren wir einen Konstruktor mit Standardwerten. Für eine Klasse zur Unterdrückung von Protokollen in Handelssystemen ist es wichtig, eine ausgewogene Standardeinstellung zu haben, die Protokollspam verhindert, aber keine wichtigen Informationen verbirgt. Ich werde eine Standardkonfiguration vorschlagen und begründen:
- mode = LOG_SUPRESSION_MODE_THROTTLE_TIME | LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_BY_REPEAT_COUNT: Kombiniert die drei grundlegenden Unterdrückungsmodi, verhindert Protokollspam, ohne dass wichtige Informationen verloren gehen, und bewahrt eine nachvollziehbare Historie
- throttle_seconds = 5 : 5 Sekunden sind in den meisten Fällen ein guter Kompromiss, lang genug, um keine wichtigen Änderungen zu verpassen, aber kurz genug, um die Rückverfolgbarkeit zu gewährleisten.
- max_repeat_count = 15 : 15 Wiederholungen ermöglichen es Ihnen, Muster zu erkennen, die ausreichen, um bei Bedarf Fehler zu beheben und eine Überflutung bei Problemen zu vermeiden.
//+------------------------------------------------------------------+ //| Struct: MqlLogifySuppressionConfig | //+------------------------------------------------------------------+ struct MqlLogifySuppressionConfig { // Basic configuration int mode; // Combination of suppression modes int throttle_seconds; // Seconds between messages int max_repeat_count; // Max repetitions before suppression // Origin whitelist/blacklist string allowed_origins[]; // If not empty, only these are allowed string blocked_origins[]; // Always blocked // Filename whitelist/blacklist string allowed_filenames[]; // If not empty, only these are allowed string blocked_filenames[]; // Always blocked //--- Default constructor MqlLogifySuppressionConfig(void) { mode = LOG_SUPRESSION_MODE_THROTTLE_TIME | LOG_SUPRESSION_MODE_CONSECUTIVE | LOG_SUPRESSION_MODE_BY_REPEAT_COUNT; throttle_seconds = 5; max_repeat_count = 15; ArrayResize(allowed_origins, 0); ArrayResize(blocked_origins, 0); ArrayResize(allowed_filenames, 0); ArrayResize(blocked_filenames, 0); } //--- Destructor ~MqlLogifySuppressionConfig(void) { } }; //+------------------------------------------------------------------+
Wir werden auch zwei Hilfsmethoden definieren, um den Nutzer daran zu hindern, die Arrays manuell mit ArrayResize() und Indizes zu manipulieren. Es wurden praktische Methoden wie AddAllowedOrigin(), AddBlockedFilename() usw. erstellt. Dadurch wird die Konfiguration übersichtlich, lesbar und weniger fehleranfällig:
//+------------------------------------------------------------------+ //| Struct: MqlLogifySuppressionConfig | //+------------------------------------------------------------------+ struct MqlLogifySuppressionConfig { //--- Helper methods for array configuration void AddAllowedOrigin(string origin) { int size = ArraySize(allowed_origins); ArrayResize(allowed_origins, size + 1); allowed_origins[size] = origin; } void AddBlockedOrigin(string origin) { int size = ArraySize(blocked_origins); ArrayResize(blocked_origins, size + 1); blocked_origins[size] = origin; } void AddAllowedFilename(string filename) { int size = ArraySize(allowed_filenames); ArrayResize(allowed_filenames, size + 1); allowed_filenames[size] = filename; } void AddBlockedFilename(string filename) { int size = ArraySize(blocked_filenames); ArrayResize(blocked_filenames, size + 1); blocked_filenames[size] = filename; } }; //+------------------------------------------------------------------+
Schließlich haben wir die Methode ValidateConfig() eingefügt. Dabei wird geprüft, ob die angegebenen Werte sinnvoll sind, um Fehler zu vermeiden. Zu den Validierungen:
- throttle_seconds kann nicht kleiner als ein Mindestwert sein (Vermeidung von Null oder negativen Werten).
- max_repeat_count muss größer als ein symbolischer Wert sein.
- Der Moduswert muss eine gültige Kombination sein.
Diese Methode gibt false zurück, wenn etwas nicht stimmt, und füllt die error_message mit einer Beschreibung des Problems. Dies ist sowohl für die Fehlersuche als auch für Anwendungen, die nutzerfreundliche Fehlermeldungen anzeigen wollen, nützlich.
//+------------------------------------------------------------------+ //| Struct: MqlLogifySuppressionConfig | //+------------------------------------------------------------------+ struct MqlLogifySuppressionConfig { //--- Validates configuration parameters bool ValidateConfig(string &error_message) { if(throttle_seconds < MIN_THROTTLE_SECONDS) { error_message = "throttle_seconds must be greater than or equal to " + (string)MIN_THROTTLE_SECONDS; return false; } if(max_repeat_count < MIN_REPEAT_COUNT) { error_message = "max_repeat_count must be greater than or equal to " + (string)MIN_REPEAT_COUNT; return false; } if(mode < LOG_SUPRESSION_MODE_NONE || mode > MAX_SUPPRESSION_MODE) { error_message = "invalid suppression mode"; return false; } return true; } }; //+------------------------------------------------------------------+
Mit dieser Struktur können Sie die Unterdrückung von Protokollen mit wenigen Befehlen konfigurieren, das Verhalten genau steuern und dennoch sicherstellen, dass sich alles innerhalb akzeptabler Grenzen bewegt. Und das alles, ohne sich auf die im Code verstreute Logik zu verlassen, die alles auf eine saubere und erweiterbare Weise zentralisiert.
Weiterentwicklung von CLogifySuppression
Nun, da wir unsere Konfigurationsstruktur gut definiert haben, wollen wir die Klasse erstellen, die für die Anwendung dieser Konfiguration in Echtzeit verantwortlich ist: CLogifySuppression. Er entscheidet bei jedem ausgegebenen Protokoll, ob die Meldung auf der Konsole erscheinen soll oder nicht, und zwar auf der Grundlage der aktiven Regeln.
Bevor wir eine echte Unterdrückungslogik implementieren, ist es wichtig, dass die Klasse Anweisungen von außen erhält, wie sie sich verhalten soll. Das bedeutet, dass die Konfiguration mit Regeln, Grenzwerten und Ausnahmelisten außerhalb der Klasse erfolgen muss, um die Ausführungslogik von der Art und Weise ihrer Parametrisierung zu entkoppeln. Diese Trennung zwischen Logik und Konfiguration macht das System flexibel und wiederverwendbar. Zu diesem Zweck haben wir der Unterdrückungsklasse zwei Methoden hinzugefügt:
class CLogifySuppression { public: //--- Configuration management void SetConfig(MqlLogifySuppressionConfig &config); MqlLogifySuppressionConfig GetConfig(void) const; }; //+------------------------------------------------------------------+ //| Updates suppression configuration | //+------------------------------------------------------------------+ void CLogifySuppression::SetConfig(MqlLogifySuppressionConfig &config) { m_config = config; string err_msg = ""; if(!m_config.ValidateConfig(err_msg)) { Print("[ERROR] ["+TimeToString(TimeCurrent())+"] Log system error: "+err_msg); } } //+------------------------------------------------------------------+ //| Returns current configuration | //+------------------------------------------------------------------+ MqlLogifySuppressionConfig CLogifySuppression::GetConfig(void) const { return m_config; } //+------------------------------------------------------------------+
Die Methode SetConfig() empfängt als Referenz ein Objekt aus der Struktur MqlLogifySuppressionConfig und speichert es intern. Es führt dann eine automatische Validierung durch, indem es die Methode ValidateConfig() der Struktur selbst aufruft. Liegt eine Konfiguration außerhalb der zulässigen Grenzen, wie z. B. throttle_seconds unter dem zulässigen Minimum oder ein ungültiger Modus, wird sofort ein Fehler ausgegeben, der das Problem signalisiert. Dies vermeidet stille Fehler mitten in der Ausführung, spart Zeit bei der Fehlersuche und hält das System intakt, selbst wenn es zur Laufzeit dynamisch konfiguriert wird.
Mit der Methode GetConfig() können Sie den aktuellen Stand der gespeicherten Konfiguration abfragen. Dies kann für die Diagnose, die Fehlersuche oder den Aufbau von Schnittstellen zur Anzeige von Unterdrückungsregeln in größeren Systemen nützlich sein. Damit ist die Unterdrückungskonfiguration nun etwas Formales, Validiertes und Zentralisiertes.
Deklaration der privaten Variablen
Nachdem Sie die Konfiguration vorgenommen haben, müssen Sie im nächsten Schritt Daten speichern, die es Ihnen ermöglichen, diese Konfiguration auf der Grundlage des Anrufverlaufs anzuwenden. Da die Unterdrückungslogik sich „merken“ muss, was vorher passiert ist, z. B. wie die letzte aufgezeichnete Nachricht lautete oder wie oft sie wiederholt wurde, müssen wir diese Informationen zwischen den Anrufen zugänglich halten. Wir haben der Klasse die folgenden privaten Felder hinzugefügt:
class CLogifySuppression { private: //--- Configuration MqlLogifySuppressionConfig m_config; //--- State tracking string m_last_message; ENUM_LOG_LEVEL m_last_level; int m_repeat_count; datetime m_last_time; };
Lassen Sie uns die Rolle jedes einzelnen verstehen:
- m_config: ist die aktuelle Instanz der Konfigurationsstruktur. Jedes Mal, wenn eine Nachricht ausgewertet wird, wird sie mit den hier definierten Regeln verglichen, sei es das Mindestintervall (throttle_seconds ), die Anzahl der tolerierten Wiederholungen (max_repeat_count ) oder die Quell- und Dateilisten.
- m_last_message: speichert den Inhalt der letzten Nachricht, die den Unterdrückungsfilter durchlaufen hat. Sie dient als Referenz dafür, ob die neue Nachricht mit der vorherigen identisch ist, was eines der Hauptkriterien für die Erkennung von Wiederholungen ist.
- m_last_level: speichert die Stufe der zuletzt verarbeiteten Meldung (Info, Warnung, Fehler usw.). Dies ist wichtig, da ein und derselbe Nachrichtenstring auf verschiedenen Ebenen unterschiedliche Bedeutungen haben kann. So sollte z. B. die Meldung Info: connection lost nicht mit der Meldung Error: connection lost gleichgesetzt werden.
- m_repeat_count: Zählt, wie oft hintereinander die gleiche Nachricht gefunden wurde. Sie wird immer dann erhöht, wenn die Nachricht und der Pegel mit dem vorherigen Aufruf identisch sind. Wenn diese Zahl den konfigurierten Grenzwert überschreitet, kann die Unterdrückung aktiviert werden.
- m_last_time: speichert den Zeitstempel des letzten akzeptierten Protokolls. Sie ist die Grundlage für die Berechnung der seit der letzten Meldung verstrichenen Zeit und die korrekte Anwendung des Drosselmodus, bei dem die in sehr kurzen Abständen erfolgten Meldungen unterdrückt werden.
Diese Variablen stellen zusammen den internen Zustand der Unterdrückung dar. Sie ermöglichen es dem System, Regeln mit Gedächtnis anzuwenden, d. h. mit dem Wissen, was zuvor geschehen ist, was für eine zuverlässige und leistungsfähige Entscheidung darüber, ob eine neue Nachricht angezeigt werden soll oder nicht, unerlässlich ist.
Erstellen der Methode ShouldSuppress()
Nachdem die vorangegangenen Blöcke - externe Konfiguration, interner Zustand und Definition der Modi - bereits festgelegt wurden, können wir nun das Herzstück des Unterdrückungssystems aufbauen: die Methode ShouldSuppress(). Diese Methode wird immer dann aufgerufen, wenn ein Protokoll ausgegeben wird. Sie erhält als Argument ein MqlLogifyModel, das alle Daten über das betreffende Protokoll enthält: Meldung, Stufe, Quelle, Datum und Dateiname. Die Aufgabe von ShouldSuppress() ist es, auf der Grundlage der aktiven Konfiguration zu entscheiden, ob dieses Protokoll angezeigt oder unterdrückt werden soll.
Wir beginnen mit der logischen Grundlage der Methode und befassen uns mit den einfachsten Modi:
class CLogifySuppression { private: //--- Main suppression logic bool ShouldSuppress(MqlLogifyModel &data); }; //+------------------------------------------------------------------+ //| Checks if a message should be suppressed based on active modes | //+------------------------------------------------------------------+ bool CLogifySuppression::ShouldSuppress(MqlLogifyModel &data) { datetime now = data.date_time; //--- Reset counters if message or level changed if(data.msg != m_last_message || data.level != m_last_level) { m_repeat_count = 0; m_last_message = data.msg; m_last_level = data.level; m_last_time = now; return false; } //--- Increment counter once per check m_repeat_count++; //--- Check suppression modes if(((m_config.mode & LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) == LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) && m_repeat_count >= m_config.max_repeat_count) { return true; } if(((m_config.mode & LOG_SUPRESSION_MODE_THROTTLE_TIME) == LOG_SUPRESSION_MODE_THROTTLE_TIME) && (now - m_last_time) < m_config.throttle_seconds) { return true; } if((m_config.mode & LOG_SUPRESSION_MODE_CONSECUTIVE) == LOG_SUPRESSION_MODE_CONSECUTIVE) { return true; } m_last_time = now; return false; } //+------------------------------------------------------------------+
Wir haben es hier mit drei Modi zu tun:
- Nach Aufeinanderfolge: Wenn dieselbe Meldung mehrmals hintereinander protokolliert wird, wird nur die erste Meldung angezeigt.
- Nach Anzahl der Wiederholungen: Anstatt alle gleichen Meldungen hintereinander zu unterdrücken, können Sie eine Toleranz festlegen, d. h. wie oft dieselbe Meldung angezeigt werden kann, bevor sie unterdrückt wird.
- Nach Zeitintervall: Selbst wenn die Meldungen identisch sind, werden sie nur dann unterdrückt, wenn sie in einem kürzeren Zeitabstand als dem konfigurierten Wert ausgegeben werden.
Das Verhalten ist in den meisten Fällen bereits funktionsfähig. Es fehlt jedoch noch eine verfeinerte Ebene: die Möglichkeit, Protokolle in Abhängigkeit von der Herkunft (Herkunftsfeld) oder der Datei (Dateiname) zu ignorieren. Diese Funktion ist nützlich, wenn der Entwickler Meldungen ausblenden möchte, die von einer bestimmten Systemkomponente stammen, wie z. B. interne Protokolle von einer externen Bibliothek oder sehr ausführliche Debug-Meldungen von einer einzelnen .mq5- oder .mqh-Datei.
Hinzufügen der Unterdrückung nach Quelle und Dateiname (mit intelligenter Suche)
In komplexeren Umgebungen, in denen mehrere Systemkomponenten oder Module gleichzeitig Protokolle erzeugen, ist es üblich, dass der Entwickler nur Protokolle von bestimmten Teilen des Codes unterdrücken möchte, z. B. Meldungen von einem automatischen Handelssystem oder Protokolle, die von sekundären Indikatoren erzeugt zwerden. Zu diesem Zweck haben wir die Möglichkeit hinzugefügt, Protokolle auf der Grundlage des Herkunftsfeldes (der logische Ursprung des Protokolls) und des Dateinamensfeldes (der Name der MQL-Datei, die es erzeugt hat) zu unterdrücken.
Die erste Version dieser Logik kann direkte Vergleiche mit exakten Zeichenfolgen verwenden. In der Praxis erweist sich dies jedoch als einschränkend. Stellen Sie sich zum Beispiel vor, Ihre Quelle ist „Trade.Signal“ und Sie setzen die Zeichenfolge „signal“ als blockiert. Bei genau diesem Ansatz würde dies nicht funktionieren, da „Trade.Signal“ und „signal“ nicht identisch sind. Aus diesem Grund haben wir eine Hilfsmethode namens StringContainsIgnoreCase() erstellt. Diese Methode führt eine Prüfung auf Groß- und Kleinschreibung durch, was Vergleiche viel flexibler und toleranter macht.
Hier ist seine Umsetzung:
class CLogifySuppression { private: //--- Helper methods for string comparison bool StringContainsIgnoreCase(string text, string search_term); }; //+------------------------------------------------------------------+ //| Checks if a string contains another string (case insensitive) | //+------------------------------------------------------------------+ bool CLogifySuppression::StringContainsIgnoreCase(string text, string search_term) { string text_lower = text; string term_lower = search_term; StringToLower(text_lower); StringToLower(term_lower); return StringFind(term_lower, text_lower) >= 0; } //+------------------------------------------------------------------+
Diese Funktion wandelt beide Texte in Kleinbuchstaben um, bevor sie nach dem Vorkommen der Teilzeichenkette sucht. Das bedeutet, dass „Trade.Signal“ als mit „signal“ oder „trade“ verwandt identifiziert werden kann, ohne dass Sie den vollständigen Namen der Quelle genau aufschreiben müssen. So können Sie eine kleine semantische Hierarchie zwischen den Quellen erstellen. Wenn Sie zum Beispiel „signal“ blockieren, unterdrücken Sie automatisch Protokolle, die von „Trade.Signal“, „Risk.Signal“ oder sogar „Execution.Signal“ stammen. Mit dieser Strategie wird der Aufwand für die Einrichtung nützlicher Filter drastisch reduziert, während die Logik klar und effizient bleibt.
Wir wenden dies auf unsere Unterdrückungslogik an:
//+------------------------------------------------------------------+ //| Checks if a message should be suppressed based on active modes | //+------------------------------------------------------------------+ bool CLogifySuppression::ShouldSuppress(MqlLogifyModel &data) { datetime now = data.date_time; //--- Check origin-based suppression if((m_config.mode & LOG_SUPRESSION_MODE_BY_ORIGIN) == LOG_SUPRESSION_MODE_BY_ORIGIN) { //--- Check blacklist first if(ArraySize(m_config.blocked_origins) > 0) { for(int i = 0; i < ArraySize(m_config.blocked_origins); i++) { if(StringContainsIgnoreCase(data.origin, m_config.blocked_origins[i])) { return true; } } } //--- Then check whitelist if(ArraySize(m_config.allowed_origins) > 0) { bool origin_allowed = false; for(int i = 0; i < ArraySize(m_config.allowed_origins); i++) { if(StringContainsIgnoreCase(data.origin, m_config.allowed_origins[i])) { origin_allowed = true; break; } } if(!origin_allowed) { return true; } } } //--- Check filename-based suppression if((m_config.mode & LOG_SUPRESSION_MODE_BY_FILENAME) == LOG_SUPRESSION_MODE_BY_FILENAME) { //--- Check blacklist first if(ArraySize(m_config.blocked_filenames) > 0) { for(int i = 0; i < ArraySize(m_config.blocked_filenames); i++) { if(StringContainsIgnoreCase(data.filename, m_config.blocked_filenames[i])) { return true; } } } //--- Then check whitelist if(ArraySize(m_config.allowed_filenames) > 0) { bool filename_allowed = false; for(int i = 0; i < ArraySize(m_config.allowed_filenames); i++) { if(StringContainsIgnoreCase(data.filename, m_config.allowed_filenames[i])) { filename_allowed = true; break; } } if(!filename_allowed) { return true; } } } //--- Reset counters if message or level changed if(data.msg != m_last_message || data.level != m_last_level) { m_repeat_count = 0; m_last_message = data.msg; m_last_level = data.level; m_last_time = now; return false; } //--- Increment counter once per check m_repeat_count++; //--- Check suppression modes if(((m_config.mode & LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) == LOG_SUPRESSION_MODE_BY_REPEAT_COUNT) && m_repeat_count >= m_config.max_repeat_count) { return true; } if(((m_config.mode & LOG_SUPRESSION_MODE_THROTTLE_TIME) == LOG_SUPRESSION_MODE_THROTTLE_TIME) && (now - m_last_time) < m_config.throttle_seconds) { return true; } if((m_config.mode & LOG_SUPRESSION_MODE_CONSECUTIVE) == LOG_SUPRESSION_MODE_CONSECUTIVE) { return true; } m_last_time = now; return false; } //+------------------------------------------------------------------+
Das Gleiche gilt für die Felder allowed_origins und allowed_filenames, sodass auch eine Whitelist erstellt werden kann, d. h. ein Filter, der nur bestimmte Protokolle durchlässt und alles andere blockiert, was das Gegenteil der traditionellen Blacklist ist.
Diese Kombination von Filtern nach Herkunft, Dateinamen und groß- und kleingeschriebenen Textmustern ergibt ein unglaublich leistungsfähiges System zur selektiven Unterdrückung. Sie kann so konfiguriert werden, dass sie freizügig oder extrem streng ist, je nachdem, was der Entwickler in dem jeweiligen Kontext benötigt, sei es bei der Entwicklung eines Roboters, beim Backtesting einer Strategie oder bei der Analyse eines Echtzeitsystems in der Produktion.
Erstellen der Methode Reset()
Das System zur Unterdrückung von Protokollen sammelt bei seiner Nutzung interne Informationen, wie z. B. die zuletzt protokollierte Meldung, wie oft sie wiederholt wurde und wann sie zuletzt erschienen ist. Dieser interne Zustand ist für die korrekte Anwendung der Unterdrückungsregeln unerlässlich. Zu bestimmten Zeiten ist es jedoch sinnvoll, diesen Zustand „zurückzusetzen“.
Ein klassisches Beispiel ist die Änderung des Ausführungskontextes, z. B. beim Wechsel von Charts, Symbolen oder Zeitrahmen. In diesen Fällen kann die Beibehaltung des bisherigen Verlaufs zu falschen Unterdrückungsentscheidungen führen, da relevante Nachrichten in einem neuen Kontext verborgen werden.
Um dieses Problem zu lösen, haben wir die Methode Reset() entwickelt. Sie löscht die internen Daten, die die Klasse bisher gespeichert hat, als ob wir bei Null anfangen würden:
//+------------------------------------------------------------------+ //| Resets all internal state tracking | //+------------------------------------------------------------------+ void CLogifySuppression::Reset(void) { m_last_message = ""; m_repeat_count = 0; m_last_time = 0; m_last_level = LOG_LEVEL_INFO; } //+------------------------------------------------------------------+
Diese Methode ist sehr einfach, aber sehr wichtig, um sicherzustellen, dass die Unterdrückungslogik korrekt ist. Sobald der Klassenkonstruktor aufgerufen wird, rufen wir intern auch Reset() auf. Dadurch wird sichergestellt, dass jede neue Instanz der Klasse „sauber“ beginnt, ohne dass sie eine Vorgeschichte von Nachrichten, Wiederholungen oder Zeitstempeln enthält.
Später können Sie Reset() auch manuell aufrufen, wenn Sie eine fortgeschrittenere Steuerung implementieren, z. B. die Unterdrückung zwischen den Ausführungen oder nach bestimmten Ereignissen in Ihrem Code neu starten wollen.
Erstellen der zusätzlichen Getter
Trotz aller Automatisierung der Unterdrückung muss der Entwickler in vielen Fällen wissen, was hinter den Kulissen vor sich geht. Wenn das System Protokolle ausblendet, die Sie eigentlich sehen sollten, ist es nützlich, den internen Zustand der Klasse zu untersuchen und zu verstehen, warum.
Zu diesem Zweck haben wir einige einfache öffentliche Methoden, so genannte Getter, Funktionen zum Datenabruf, hinzugefügt, mit denen Sie auf die wichtigsten Kontrollvariablen der Unterdrückung zugreifen können. Sie ändern den Zustand der Klasse nicht, sondern geben nur Werte zurück, die für Diagnosen, Protokolle oder Debugging-Tools nützlich sind:
class CLogifySuppression { public: //--- Monitoring getters int GetRepeatCount(void) const { return m_repeat_count; } datetime GetLastMessageTime(void) const { return m_last_time; } string GetLastMessage(void) const { return m_last_message; } ENUM_LOG_LEVEL GetLastLevel(void) const { return m_last_level; } };
- GetRepeatCount() gibt zurück, wie oft hintereinander dieselbe Meldung erschienen ist. Dies kann helfen zu verstehen, warum eine Nachricht unterdrückt wurde oder nicht.
- GetLastMessageTime() gibt an, wann eine Nachricht den Filter zuletzt durchlaufen hat. Dies ist wichtig, um zu überprüfen, ob die Unterdrückung nach Zeit wie erwartet funktioniert.
- GetLastMessage() zeigt den wörtlichen Inhalt der letzten verarbeiteten Nachricht an.
- GetLastLevel() gibt Ihnen die Stufe (Info, Warnung, Fehler usw.) der letzten Meldung an, sodass Sie diese mit der Schweregradkontrolle Ihres Systems abgleichen können.
Diese Methoden sind bei der allgemeinen Verwendung der Klasse optional, aber sie werden äußerst wertvoll, wenn ein unerwartetes Verhalten auftritt, denn das automatische Unterdrücken von Meldungen ist ein zweischneidiges Schwert: Es vermindert das Rauschen, aber es kann Probleme verbergen. Die Möglichkeit, die Logik von innen zu überprüfen, verkürzt die Untersuchungszeit in solchen Fällen drastisch.
Integration der Protokollunterdrückung in die Hauptklasse von CLogify
Nachdem CLogifySuppression nun läuft, ist es an der Zeit, sie in den Kern unserer Bibliothek zu integrieren: die Klasse CLogify. Hier findet alles statt, von der Nachrichtenweiterleitung bis zur Steuerung der Handler. Und jetzt wird es auch entscheiden, wann wiederholte oder irrelevante Protokolle stummgeschaltet werden sollen. Der erste Schritt besteht darin, die Unterdrückungsdatei zu importieren und eine Instanz der Klasse als privates Mitglied von CLogify zu deklarieren:
//+------------------------------------------------------------------+ //| Imports | //+------------------------------------------------------------------+ #include "LogifyModel.mqh" #include "Suppression/LogifySuppression.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: CLogifySuppression*m_suppression; }; //+------------------------------------------------------------------+
Diese Instanz wird intern verwendet, um zu entscheiden, ob eine bestimmte Nachricht protokolliert werden soll oder nicht. Im Konstruktor von CLogify wird diese Instanz initialisiert:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogify::CLogify() { m_suppression = new CLogifySuppression(); } //+------------------------------------------------------------------+
Und natürlich sorgen wir im Destruktor dafür, dass der Speicher freigegeben wird:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogify::~CLogify() { //--- Delete handlers int size_handlers = ArraySize(m_handlers); for(int i=0;i<size_handlers;i++) { if(CheckPointer(m_handlers[i]) != POINTER_INVALID) { m_handlers[i].Close(); delete m_handlers[i]; } } delete m_suppression; } //+------------------------------------------------------------------+
In der Methode Append(), die für die Protokollierung zuständig ist, prüfen wir nun, ob die Unterdrückung direkt nach der Erstellung der Protokollvorlage erfolgt. Wird die Nachricht als unnötig erachtet, wird sie an dieser Stelle ignoriert:
//+------------------------------------------------------------------+ //| 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)); //--- Supression if(m_suppression.ShouldSuppress(data)) { return(true); } //--- 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); } //+------------------------------------------------------------------+
Durch diese einfache Prüfung wird verhindert, dass unnötige Nachrichten verarbeitet werden. Wenn der Mechanismus feststellt, dass das aktuelle Protokoll redundant ist, entweder weil es zu oft hintereinander aufgetreten ist, weil es von der gleichen Stelle im Code stammt oder aufgrund eines anderen konfigurierten Kriteriums, wird es dort einfach gestoppt. Alles erledigt!
Tests
Jetzt, da wir wissen, wie das Unterdrückungssystem intern funktioniert, ist es an der Zeit, es mit einigen praktischen Tests auf die Probe zu stellen. Hier geht es darum, in der Praxis zu überprüfen, ob sich jeder der Unterdrückungsmodi wie erwartet verhält, d. h. ob doppelte oder unerwünschte Protokolle entsprechend der gewählten Konfiguration unterdrückt werden. Lassen Sie uns Schritt für Schritt vorgehen.
Test 1. Unterdrückung durch aufeinanderfolgende Meldungen (LOG_SUPPRESSION_MODE_CONSECUTIVE)Dies ist die einfachste Form der Unterdrückung. Die Logik ist einfach: Wenn dieselbe Meldung mehr als einmal hintereinander protokolliert wird, wird nur die erste angezeigt. Dies ist nützlich, um zu vermeiden, dass die Konsole überfüllt wird, wenn z. B. dasselbe Protokoll innerhalb einer Schleife wiederholt wird. Testen wir dieses Verhalten mit einem Code, der 11 exakt gleiche Protokolle sendet, alle mit dem gleichen Inhalt und der gleichen Herkunft.
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify Logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifySuppressionConfig config; config.mode = LOG_SUPRESSION_MODE_CONSECUTIVE; Logify.Suppression().SetConfig(config); for(int i=0;i<11;i++) { Logify.Info("Check signal buy", "Signal"); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Wenn Sie den obigen Code ausführen, wird das Ergebnis auf der Konsole lauten:
2025.07.31 04:34:26 [INFO]: Check signal buy
Das war's. Keine Wiederholungen. Selbst wenn die Meldung 11 Mal protokolliert wurde, da sie alle identisch und aufeinanderfolgend waren, verstand das System, dass es ausreichte, sie einmal anzuzeigen. Dies zeigt, dass der Modus korrekt funktioniert.
Test 2. Unterdrückung nach Anzahl der Wiederholungen (LOG_SUPPRESSION_MODE_BY_REPEAT_COUNT)Dieser Modus bietet ein wenig mehr Flexibilität als der vorherige Modus. Anstatt alle gleichen Meldungen hintereinander zu unterdrücken, können Sie eine Toleranz festlegen, d. h. wie oft dieselbe Meldung angezeigt werden kann, bevor sie unterdrückt wird. Diese Option ist nützlich, wenn Sie nur eine begrenzte Anzahl von Wiederholungen sehen möchten, bevor das System den Rest zum Schweigen bringt.
Konfigurieren wir das System so, dass maximal 2 Wiederholungen der gleichen Nachricht möglich sind.
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify Logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifySuppressionConfig config; config.mode = LOG_SUPRESSION_MODE_BY_REPEAT_COUNT; config.max_repeat_count = 2; Logify.Suppression().SetConfig(config); for(int i=0;i<11;i++) { Logify.Info("Check signal buy", "Signal"); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Erwartetes Ergebnis auf der Konsole:
2025.07.31 04:40:49 [INFO]: Check signal buy 2025.07.31 04:40:49 [INFO]: Check signal buy
Genau so, wie wir es konfiguriert haben: Es erscheinen nur die ersten beiden Meldungen. Die übrigen wurden automatisch verworfen, da sie die Wiederholungsgrenze überschritten. Diese Kontrolle ist besonders nützlich in Umgebungen, die sehr ausführliche Protokolle erzeugen, in denen wir aber dennoch die ersten Warnsignale erfassen wollen.
Test 3. Unterdrückung nach Zeitintervall (LOG_SUPPRESSION_MODE_THROTTLE_TIME)Hier basiert die Unterdrückung auf der Zeit zwischen den Meldungen. Selbst wenn die Nachrichten identisch sind, werden sie nur dann unterdrückt, wenn sie in einem kürzeren als dem konfigurierten Zeitintervall gesendet werden.
Konfigurieren wir das System so, dass es alle 1 Sekunde die gleiche Nachricht zulässt. Um dies zu simulieren, drucken wir dieselbe Nachricht 11 Mal mit einem Sleep(200) dazwischen (d. h. 200 Millisekunden zwischen jedem Protokoll). Pro Sekunde werden also 5 Meldungen eingehen - und das System sollte nur eine pro Sekunde anzeigen und den Rest verwerfen.
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify Logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifySuppressionConfig config; config.mode = LOG_SUPRESSION_MODE_THROTTLE_TIME; config.throttle_seconds = 1; Logify.Suppression().SetConfig(config); for(int i=0;i<11;i++) { Logify.Info("Check signal buy", "Signal"); Sleep(200); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Wenn es ausgeführt wird, sollte die Konsole etwas wie folgt anzeigen:
2025.07.31 04:45:26 [INFO]: Check signal buy 2025.07.31 04:45:27 [INFO]: Check signal buy 2025.07.31 04:45:28 [INFO]: Check signal buy
Drei Protokolle erscheinen - eines pro Sekunde – während die übrigen verworfen wurden, weil sie außerhalb des zulässigen Bereichs lagen. Dieser Modus ist besonders interessant für Ereignisse, die in ihrer Häufigkeit schwanken können, wie z. B. Preisaktualisierungen oder Prüfungen der Marktbedingungen.
Test 4. Unterdrückung nach Herkunft (LOG_SUPPRESSION_MODE_BY_ORIGIN)In diesem Modus sperrt das System Meldungen auf der Grundlage des im Protokoll angegebenen Ursprungs. Wenn ein bestimmter Absender auf der schwarzen Liste steht, wird jede Nachricht, die von diesem Absender kommt, ignoriert, unabhängig von ihrem Inhalt oder der Zeit, die zwischen ihnen liegt. Im folgenden Beispiel haben wir die Quelle „Signal“ blockiert und nur die Quelle „Trade“ durchgelassen:
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify Logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifySuppressionConfig config; config.mode = LOG_SUPRESSION_MODE_BY_ORIGIN; config.AddBlockedOrigin("signal"); Logify.Suppression().SetConfig(config); for(int i=0;i<11;i++) { Logify.Info("Check signal buy", "Signal"); } Logify.Info("Purchase order sent successfully", "Trade"); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Ergebnis in der Konsole:
2025.07.31 04:48:36 [INFO]: Purchase order sent successfully
Es erschien nur die Nachricht mit der Herkunft „Trade“. Alle anderen wurden unterdrückt, weil sie zu einer ausdrücklich gesperrten Quelle gehören.
Der dateibasierte Unterdrückungsmodus funktioniert fast identisch, aber das Sperrkriterium ist der Name der Datei, aus der das Protokoll ausgelöst wurde, und nicht der im Protokoll definierte logische Ursprung. Aus diesem Grund haben die Unterdrückungstests nach Herkunft und nach Datei dieselbe Codestruktur. Der Unterschied liegt im Aufruf in der Prüfung, die eine prüft den Ursprung und die andere den Dateinamen. Wir sind daher der Ansicht, dass dieser Test auch die Unterdrückung durch die Akten bestätigt.
Automatisches Erkennen und Einstellen der Sprache
Bisher startete die Klasse CLogifyError immer mit den Fehlermeldungen in Englisch. Dies funktionierte zunächst gut, hatte aber ein großes Problem: Die Fehlermeldungen wurden immer in der gleichen Sprache angezeigt, auch wenn das Terminal des Nutzers in einer anderen Sprache konfiguriert war, z. B. Spanisch, Französisch oder Portugiesisch. Mit dem Ausbau der mehrsprachigen Unterstützung in Logify war es sinnvoll, einen Schritt weiter zu gehen: die Standardsprache der Fehlermeldungen automatisch an die im MetaTrader-Terminal konfigurierte Sprache anzupassen. Um dies zu erreichen, haben wir eine kleine, aber wirkungsvolle Änderung am Klassenkonstruktor vorgenommen. Anstatt den Fehlersatz direkt in Englisch zu starten, lassen wir uns nun vom Terminal selbst sagen, welche Sprache wir verwenden sollen:
CLogifyError::CLogifyError()
{
SetLanguage(GetLanguageFromTerminal());
} Die Methode GetLanguageFromTerminal() verwendet die systemeigene Funktion TerminalInfoString(TERMINAL_LANGUAGE), um die aktuell im MetaTrader konfigurierte Sprache zu erfassen. Dieser Wert ist eine Zeichenkette mit dem Namen der Sprache, z. B. „Französisch“, „Koreanisch“ oder „Portugiesisch (Brasilien)“. Wir ordnen es dann unserem ENUM_LOG_LANGUAGE zu, das die vom Logify-Fehlersystem unterstützten Sprachen darstellt:
ENUM_LOG_LANGUAGE CLogifyError::GetLanguageFromTerminal(void) { string lang = TerminalInfoString(TERMINAL_LANGUAGE); if(lang == "German") return LOG_LANGUAGE_DE; if(lang == "Spanish") return LOG_LANGUAGE_ES; if(lang == "French") return LOG_LANGUAGE_FR; if(lang == "Italian") return LOG_LANGUAGE_IT; if(lang == "Japanese") return LOG_LANGUAGE_JA; if(lang == "Korean") return LOG_LANGUAGE_KO; if(lang == "Portuguese (Brazil)" || lang == "Portuguese (Portugal)") return LOG_LANGUAGE_PT; if(lang == "Russian") return LOG_LANGUAGE_RU; if(lang == "Turkish") return LOG_LANGUAGE_TR; if(lang == "Chinese (Simplified)" || lang == "Chinese (Traditional)") return LOG_LANGUAGE_ZH; //--- Default language: English return LOG_LANGUAGE_EN; }
Diese automatische Anpassung ist besonders nützlich für Händler und Unternehmen, die mit einem internationalen Publikum arbeiten. Die Bibliothek spricht jetzt „die Sprache des Terminals“, ohne dass eine manuelle Konfiguration erforderlich ist. Dies verringert die Reibungsverluste und erspart es technisch weniger versierten Nutzern, herauszufinden, wie sie die richtige Sprache für Fehler einstellen können. Was ist, wenn die Terminalsprache aus irgendeinem Grund nicht erkannt oder zugeordnet wird? Kein Problem, das System schaltet automatisch wieder auf Englisch um, sodass auch in Ausnahmefällen ein funktionierendes Erlebnis garantiert ist.
Diese Änderung ist zwar einfach zu implementieren, verbessert aber die Nutzerfreundlichkeit der Bibliothek drastisch und bringt das Verhalten von Logify in Einklang mit dem Prinzip der automatischen Bequemlichkeit: Das System passt sich dem Nutzer an, nicht umgekehrt.
Schlussfolgerung
Mit allem, was wir bis jetzt gesehen haben, ist Logify noch intelligenter geworden. Es ist jetzt in der Lage zu erkennen, wenn es sich in den Protokollen zu sehr wiederholt, und es spricht auch automatisch in der Sprache Ihres Terminals, ohne dass Sie etwas konfigurieren müssen.
Es wurden mehrere Möglichkeiten zur Unterdrückung wiederholter Meldungen geschaffen, die Sie einzeln oder zusammen verwenden können, je nachdem, was für Ihr Projekt am sinnvollsten ist:
- Wiederholte Meldungen in einer Reihe: verringert die Flut identischer Protokolle nacheinander.
- Mindestzeit zwischen den Protokollen: verhindert, dass dieselbe Meldung innerhalb weniger Sekunden mehrmals erscheint.
- Wiederholung erst nach einer bestimmten Anzahl: wird erst wieder angezeigt, wenn die Wiederholung eine bestimmte Grenze überschreitet.
- Dasselbe Codefragment: blockiert doppelte Protokolle, die vom selben Ort stammen.
- Gleicher Inhalt aus verschiedenen Dateien: blockiert identische Wiederholungen, auch wenn sie aus einer anderen Datei stammen.
All dies kann einfach und direkt über die Konfiguration aktiviert werden, ohne dass Ihr Code kompliziert wird. Darüber hinaus wählt die Bibliothek mit der neuen automatischen Spracherkennung bereits die ideale Sprache auf der Grundlage Ihrer Terminalkonfiguration aus, was bei der Arbeit in internationalen Umgebungen oder mit anderen Teams sehr hilfreich ist.
Wenn Sie neue Ideen haben, einen neuen Unterdrückungsmodus vorschlagen möchten oder etwas gefunden haben, das verbessert werden könnte, hinterlassen Sie es einfach in den Kommentaren. Logify ist immer offen für Änderungen und Verbesserungen. Je nach Entwicklung werden wir Sie mit neuen Artikeln auf dem Laufenden halten.
| 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ählt die Fehlermeldungen in jeder Sprache, wobei X für das Akronym der Sprache steht. |
| Include/Logify/Error/Error.mqh | die Datenstruktur zur Speicherung von Fehlern |
| Include/Logify/Error/LogifyError.mqh | die 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 | der 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/Suppression/LogifySuppression.mqh | ist verantwortlich für die Anwendung intelligenter Regeln zur Unterdrückung von Protokollmeldungen, die unnötige Wiederholungen herausfiltern |
| 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/LogifyBuilder.mqh | die Klasse, die für die Erstellung eines CLockify-Objekts verantwortlich ist und die Konfiguration vereinfacht |
| 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/19014
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.
MetaTrader 5 Machine Learning Blueprint (Teil 2): Kennzeichnung von Finanzdaten für maschinelles Lernen
Statistische Arbitrage durch kointegrierte Aktien (Teil 2): Expert Advisor, Backtests und Optimierung
Statistische Arbitrage durch kointegrierte Aktien (Teil 3): Datenbank-Einrichtung
Vom Neuling zum Experten: Animierte Nachrichtenüberschrift mit MQL5 (IX) – Verwaltung mehrerer Symbole in einem einzigen Chart für den Nachrichtenhandel
- 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.
Vielen Dank für Ihre Verbesserungsvorschläge!
Hallo Autor, ich habe eine Anregung. Erstellen Sie einige Makros. Sie müssen nur eine Datei einbinden. Es ist keine Konfiguration erforderlich. Sie können die Bibliothek mit der Standardkonfiguration verwenden. Wenn die Protokollierung deaktiviert ist, erzeugt das Makro keinen tatsächlichen Code in der endgültigen kompilierten ex5.