preview
Mastering Log Records (Part 8): Error Records That Translate Themselves

Mastering Log Records (Part 8): Error Records That Translate Themselves

MetaTrader 5Examples | 18 June 2025, 08:29
264 1
joaopedrodev
joaopedrodev

Introduction

At this point in the journey, it's no surprise that logging isn't just about recording events. It's about accurately capturing what your EA is trying to tell you in the midst of the whirlwind of ticks, decisions and uncertainties that define everyday algorithmic trading.

During my day-to-day use of Logify, I noticed something that bothered me: the error handling was still superficial. Even with a robust formatting structure, the logs still only displayed the raw error code, without any indication of what it actually meant. Something like:

logify.Error("Failed to send sell order", "Order Management", "Code: "+IntegerToString(GetLastError()));
// Console:
// 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | Code: 10016

The result? A nebulous message. We know where the error happened, but not why. And who has ever had to investigate dozens of MQL5 codes in the documentation? I used to do it myself quite often: once I had the error code, I had to search the documentation to find out what really happened. It was out of this real friction that I came up with the idea: what if Logify could interpret the error for me? What if, instead of just giving me the code, it also gave me its meaning in a clear, contextualized way, ready to be logged?

Thus a functional evolution was born: it is now possible to pass the error code directly to the library, and it takes care of querying, formatting and displaying the corresponding description automatically. The same previous example, redone with this improvement, becomes:

logify.Error("Failed to send sell order", GetLastError(), "Order Management");
// Console:
// 2025.06.06 10:30:00 [ERROR]: (Order Management) Failed to send sell order | 10016: Invalid stops in the request

Much clearer and more useful, but I decided to go further. A system that sets out to deliver clarity needs to be fluent, not just in code, but in language. That's why, in this eighth stage, Logify also gains multilingual support for error messages, with automatic translation into 11 languages, including English, Portuguese, Spanish, German, French, Italian, Russian, Turkish, Chinese, Japanese and Korean. Now, your logs speak the language of your team or your customer without manual adjustments.

In this step, you will learn how to:

  1. Enrich error logs with precise descriptions, taken directly from the MQL5 documentation.
  2. Display error messages in multiple languages, with dynamic selection of the most appropriate language for each context.
  3. Customize formatting by severity, creating distinct patterns for errors, warnings and information based on their degree of criticality.

At the end of the day, Logify will be smarter, more accessible and much more useful in the real world. Because in algorithmic trading, every detail matters, and every second spent deciphering an error is a second away from the next right decision.


Creating a way to handle errors

As every good system needs a solid foundation, we start by defining a structure to represent errors. This structure, which we'll call MqlError , will be used throughout the system to store the three fundamental pieces of any error:

  • The numeric code (code )
  • The symbolic constant
  • The readable description (description )

We created this structure in the <Include/Logify/Error/Error.mqh> file:

//+------------------------------------------------------------------+
//| Data structure for error handling                                |
//+------------------------------------------------------------------+
struct MqlError
  {
   int      code;          // Cod of error
   string   description;   // Description of error
   string   constant;      // Type error
   
   MqlError::MqlError(void)
     {
      code = 0;
      description = "";
      constant = "";
     }
  };
//+------------------------------------------------------------------+

This structure is simple but sufficient to represent any error returned by the platform, be it an execution error, a negotiation error or even a user-defined error. Now that we have a place to store the data, we need to populate this structure with the actual errors.

MetaQuotes provides hundreds of error codes, each with its symbolic constant and description in English, but we want to go further. We want the library to speak German, Spanish, French, Portuguese, Chinese... and for the logs to automatically adapt to the configured language.

The strategy here is to create an .mqh file for each language, containing a function that initializes an array with all known errors. The file name follows the standard ErrorMessages.xx.mqh , where xx stands for the language abbreviation (e.g. en for English, de for German, pt for Portuguese).

Here's what the file looks like in English:

//+------------------------------------------------------------------+
//|                                             ErrorMessages.en.mqh |
//|                                                     joaopedrodev |
//|                       https://www.mql5.com/en/users/joaopedrodev |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link      "https://www.mql5.com/en/users/joaopedrodev"
//+------------------------------------------------------------------+
//| Import struct                                                    
//+------------------------------------------------------------------+
#include "../Error.mqh"
void InitializeErrorsEnglish(MqlError &errors[])
  {
   //--- Free and resize
   ArrayFree(errors);
   ArrayResize(errors,274);
   
   //+------------------------------------------------------------------+
   //| Unknown error                                                    |
   //+------------------------------------------------------------------+
   errors[0].code = 0;
   errors[0].description = "No error found";
   errors[0].constant = "ERROR_UNKNOWN";
   //+------------------------------------------------------------------+
   //| Server error                                                     |
   //+------------------------------------------------------------------+
   errors[1].code = 10004;
   errors[1].description = "New quote";
   errors[1].constant = "TRADE_RETCODE_REQUOTE";
   //---
   errors[2].code = 10006;
   errors[2].description = "Request rejected";
   errors[2].constant = "TRADE_RETCODE_REJECT";
   //---
   // Remaining error codes...
   //---
   errors[272].code = 5625;
   errors[272].description = "Parameter binding error, wrong index";
   errors[272].constant = "ERR_DATABASE_RANGE";
   //---
   errors[273].code = 5626;
   errors[273].description = "Open file is not a database file";
   errors[273].constant = "ERR_DATABASE_NOTADB";
  }
//+------------------------------------------------------------------+

The InitializeErrorsEnglish() function receives an array of MqlError and fills it with all known errors in the English language. We repeat this pattern for other languages such as German, Spanish, French, Italian, Japanese, Korean, Portuguese, Russian, Turkish and Chinese. All these files are kept in the <Include/Logify/Error/Languages> folder. In total, we have 11 language files and 274 entries per file. And yes, this was painstaking work, but now you can enjoy it with a simple inclusion. Remember that all the code is attached at the end of the article.

Now that we have all the data organized, we need an interface to query it. That's where the CLogifyError class comes in. This class will be responsible for loading the errors in the correct language and returning the complete information for any error code requested.

The class interface is simple:

//+------------------------------------------------------------------+
//| class : CLogifyError                                             |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : LogifyError                                        |
//| Heritage    : No heritage                                        |
//| Description : class to look up the error code and return details |
//|               of each error code.                                |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogifyError
  {
private:
   
   ENUM_LANGUAGE     m_language;
   MqlError          m_errors[];
   
public:
                     CLogifyError(void);
                    ~CLogifyError(void);
   
   //--- Set/Get
   void              SetLanguage(ENUM_LANGUAGE language);
   ENUM_LANGUAGE     GetLanguage(void);
   
   //--- Get error
   MqlError          Error(int code);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CLogifyError::CLogifyError()
  {
   InitializeErrorsEnglish(m_errors);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CLogifyError::~CLogifyError(void)
  {
  }
//+------------------------------------------------------------------+

When it is instantiated, it loads errors in English by default. You can change the language at any time with SetLanguage() , and the library will reload the data in the appropriate language.

The Error(int code) method is the heart of the class. It scours the error array for the code entered and returns the corresponding MqlError . If the code is not found, the function returns a generic error as a fallback. In addition, it automatically detects whether the error is in the range reserved for user-defined errors (ERR_USER_ERROR_FIRST to ERR_USER_ERROR_LAST ), and returns an answer for them too.

//+------------------------------------------------------------------+
//| Returns error information based on the error code received       |
//+------------------------------------------------------------------+
MqlError CLogifyError::Error(int code)
  {
   int size = ArraySize(m_errors);
   for(int i=0;i<size;i++)
     {
      if(m_errors[i].code == code)
        {
         //--- Return
         return(m_errors[i]);
        }
     }
   
   //--- User error
   if(code >= ERR_USER_ERROR_FIRST && code < ERR_USER_ERROR_LAST)
     {
      MqlError error;
      error.code = code;
      error.constant = "User error";
      error.description = "ERR_USER_ERROR";
      
      //--- Return
      return(m_errors[274]);
     }
   
   //--- Return
   return(m_errors[0]);
  }
//+------------------------------------------------------------------+

Finally, let's add this error structure to the MqlLogifyModel log data model, so that we can access the error data within the data structure of a record.

#include "LogifyLevel.mqh"
#include "Error/Error.mqh"
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
struct MqlLogifyModel
  {
   string formated;        // The log message formatted according to the specified format.
   string levelname;       // Textual name of the log level (e.g., "DEBUG", "INFO")
   string msg;             // Main content of the log message
   string args;            // Additional arguments associated with the log message
   ulong timestamp;        // Timestamp of the log event, represented in seconds since the start of the Unix epoch
   datetime date_time;     // Date and time of the log event, in datetime format
   ENUM_LOG_LEVEL level;   // Enumeration representing the severity level of the log
   string origin;          // Source or context of the log message (e.g., class or module)
   string filename;        // Name of the source file where the log message was generated
   string function;        // Name of the function where the log was called
   ulong line;             // Line number in the source file where the log was generated
   MqlError error;         // Error data
   
   void MqlLogifyModel::Reset(void)
     {
      formated = "";
      levelname = "";
      msg = "";
      args = "";
      timestamp = 0;
      date_time = 0;
      level = LOG_LEVEL_DEBUG;
      origin = "";
      filename = "";
      function = "";
      line = 0;
     }
   
   MqlLogifyModel::MqlLogifyModel(void)
     {
      this.Reset();
     }
   MqlLogifyModel::MqlLogifyModel(string _formated,string _levelname,string _msg,string _args,ulong _timestamp,datetime _date_time,ENUM_LOG_LEVEL _level,string _origin,string _filename,string _function,ulong _line,MqlError &_error)
     {
      formated = _formated;
      levelname = _levelname;
      msg = _msg;
      args = _args;
      timestamp = _timestamp;
      date_time = _date_time;
      level = _level;
      origin = _origin;
      filename = _filename;
      function = _function;
      line = _line;
      error = _error;
     }
  };
//+------------------------------------------------------------------+

With these changes, this is the structure of our library:


Integrating errors into the main class

We already have a solid structure for representing errors, multilingual files that carry their descriptions and a class dedicated to retrieving these messages based on language. But so far, all of this is hanging in the air, disconnected from the library's central engine: the CLogify class.

It's time to integrate the error system into the core of Logify, so that each log generated has, if necessary, a clear, multilingual explanation of what went wrong. To do this, we'll follow three steps: instantiate the error class, adjust the method for adding logs (Append ) and, finally, adjust the formatter to recognize new placeholders {err_code} , {err_constant} and {err_description} .

1. Bringing error intelligence to the heart of Logify

The CLogify class is the one that connects formatting, manipulating and sending the log, so the natural first step is to give it direct access to our error system. To do this, simply import the LogifyError.mqh file and instantiate the CLogifyError class as a private member of CLogify . This guarantees that it will always be available internally, without requiring the library user to worry about it. This small addition already opens important doors. Now, whenever an error occurs, we can quickly consult its code, description and constant.

As CLogifyError 's default language is English, it would be a waste not to allow the user to choose the most appropriate language. That's why we've added two simple methods. This is how the code looks at the end:

#include "LogifyModel.mqh"
#include "Handlers/LogifyHandler.mqh"
#include "Handlers/LogifyHandlerComment.mqh"
#include "Handlers/LogifyHandlerConsole.mqh"
#include "Handlers/LogifyHandlerDatabase.mqh"
#include "Handlers/LogifyHandlerFile.mqh"
#include "Error/LogifyError.mqh"
//+------------------------------------------------------------------+
//| class : CLogify                                                  |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : Logify                                             |
//| Heritage    : No heritage                                        |
//| Description : Core class for log management.                     |
//|                                                                  |
//+------------------------------------------------------------------+
class CLogify
  {
private:
   CLogifyError      m_error;
         
public:
                     CLogify();
                    ~CLogify();
   
   //--- Language
   void              SetLanguage(ENUM_LANGUAGE language);
   ENUM_LANGUAGE     GetLanguage(void);
  };
//+------------------------------------------------------------------+

2. Enriching the Append method with error code

Until now, the Append() method received the main message, additional arguments, source, filename, line, function... but not the error code itself. If we want the log to carry context about failures, we need to allow this code to be passed, even optionally. That's why we added the code_error parameter to the end of the method signature:

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

With this, the call to Append() doesn't change for those who don't want to pass the error, but for those who do, just add it at the end. This approach avoids breaking compatibility with existing code.

Now, with the error code in hand, we use the Error() method of the CLogifyError class to fetch the corresponding MqlError structure:

//+------------------------------------------------------------------+
//| Generic method for adding logs                                   |
//+------------------------------------------------------------------+
bool CLogify::Append(ENUM_LOG_LEVEL level,string msg, string origin = "", string args = "",string filename="",string function="",int line=0,int code_error=0)
  {
   //--- Ensures that there is at least one handler
   this.EnsureDefaultHandler();
   
   //--- Textual name of the log level
   string levelStr = "";
   switch(level)
     {
      case LOG_LEVEL_DEBUG: levelStr = "DEBUG"; break;
      case LOG_LEVEL_INFO : levelStr = "INFO"; break;
      case LOG_LEVEL_ALERT: levelStr = "ALERT"; break;
      case LOG_LEVEL_ERROR: levelStr = "ERROR"; break;
      case LOG_LEVEL_FATAL: levelStr = "FATAL"; break;
     }
   
   //--- Creating a log template with detailed information
   datetime time_current = TimeCurrent();
   MqlLogifyModel data("",levelStr,msg,args,time_current,time_current,level,origin,filename,function,line,m_error.Error(code_error));
   
   //--- Call handlers
   int size = this.SizeHandlers();
   for(int i=0;i<size;i++)
     {
      data.formated = m_handlers[i].GetFormatter().Format(data);
      m_handlers[i].Emit(data);
     }
   
   return(true);
  }
//+------------------------------------------------------------------+

The MqlLogifyModel object now carries not only the log information, but also the soul of the error that caused it. Now all you have to do is add a new function overload for the Error() and Fatal() methods, without removing the ones that already exist, as this can generate errors, because the library user doesn't always need to add an error code. This is how the code looks:

class CLogify
  {
public:
   //--- Specific methods for each log level
   bool              Debug(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Info(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Alert(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Error(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Fatal(string msg, string origin = "", string args = "",string filename="",string function="",int line=0);
   bool              Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0);
  };
bool CLogify::Error(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0)
  {
   return(this.Append(LOG_LEVEL_ERROR,msg,origin,args,filename,function,line,code_error));
  }
bool CLogify::Fatal(string msg, int code_error, string origin = "", string args = "",string filename="",string function="",int line=0)
  {
   return(this.Append(LOG_LEVEL_FATAL,msg,origin,args,filename,function,line,code_error));
  }

3. Formatting the message with error placeholders

So far, the error is being loaded, but it still doesn't appear in the formatted log. To do this, we need to expand CLogifyFormatter 's placeholder system to include the error properties. The logic is simple but ingenious: we only show the error data if the log level is ERROR or higher. Informational or debug logs don't need this verbosity, we want clarity, not pollution.

//+------------------------------------------------------------------+
//| Format logs                                                      |
//+------------------------------------------------------------------+
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   
   StringReplace(formated,"{levelname}",data.levelname);
   StringReplace(formated,"{msg}",data.msg);
   StringReplace(formated,"{args}",data.args);
   StringReplace(formated,"{timestamp}",IntegerToString(data.timestamp));
   StringReplace(formated,"{date_time}",this.FormatDate(data.date_time));
   StringReplace(formated,"{level}",IntegerToString(data.level));
   StringReplace(formated,"{origin}",data.origin);
   StringReplace(formated,"{filename}",data.filename);
   StringReplace(formated,"{function}",data.function);
   StringReplace(formated,"{line}",IntegerToString(data.line));
   
   if(data.level >= LOG_LEVEL_ERROR)
     {
      StringReplace(formated,"{err_code}",IntegerToString(data.error.code));
      StringReplace(formated,"{err_constant}",data.error.constant);
      StringReplace(formated,"{err_description}",data.error.description);
     }
   else
     {
      StringReplace(formated,"{err_code}","");
      StringReplace(formated,"{err_constant}","");
      StringReplace(formated,"{err_description}","");
     }
   
   return(formated);
  }
//+------------------------------------------------------------------+

With this, we've created three new customization hooks:

  • {err_code} : Shows the exact error number (e.g. 10006 )
  • {err_constant} : The associated constant (e.g. TRADE_RETCODE_REJECT ).
  • {err_description} : The readable explanation (e.g. Request rejected )

Imagine we defined a global format like this:

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

In your head, this should produce something like:

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

And indeed, for ERROR and FATAL log levels, it works perfectly. But what about when we use this same format for DEBUG , INFO or ALERT logs? Well... the result is something like this:

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

Or worse, depending on the formatting, it can even cause a visual misalignment in the logs. Even with our care to clean up the error placeholders to lower levels, the problem remains: the format was made expecting {err_code} , {err_constant} and {err_description} to have values. When they don't, the log gets... weird and incomplete. What we have here is a break in semantics: the same formatting mask is being applied to messages that have totally different contexts.

The solution doesn't lie in trying to correct the data. It lies in treating each log level as what it is: a different type of message, with different needs and formats.

So instead of CLogifyFormatter with a universal format, we make each level (DEBUG , INFO , ALERT , ERROR , FATAL ) have its own formatter. This allows us to customize, for example:

  • DEBUG : Show function, file and line
  • INFO : Be minimalist, just what happened
  • ALERT : Add context such as origin and args
  • ERROR : Include error data
  • FATAL : Be as complete as possible


Implementing multiple formats

First we adjust the way the format is stored, instead of a simple string, we change it to an array with 5 possibilities (one for each severity level).

Old code New code
class CLogifyFormatter
  {
private:
   
   //--- Stores the formats
   string            m_date_format;
   string            m_log_format;
  };
class CLogifyFormatter
  {
private:
   
   //--- Stores the formats
   string            m_date_format;
   string            m_format[5];
  };

We replaced m_log_format with an array of strings, one position for each log level. We now have five different formats, each of which can be adjusted according to the type of message that will be sent.

With the formats separated by level, we need a convenient way to set them. To do this, we created two SetFormat methods, one that applies the same format to all levels, and another that individually configures a specific level:

class CLogifyFormatter
  {
public:
   void              SetFormat(string format);
   void              SetFormat(ENUM_LOG_LEVEL level, string format);
  };
//+------------------------------------------------------------------+
//| Sets the format for all levels                                   |
//+------------------------------------------------------------------+
void CLogifyFormatter::SetFormat(string format)
  {
   m_format[LOG_LEVEL_DEBUG] = format;
   m_format[LOG_LEVEL_INFO] = format;
   m_format[LOG_LEVEL_ALERT] = format;
   m_format[LOG_LEVEL_ERROR] = format;
   m_format[LOG_LEVEL_FATAL] = format;
  }
//+------------------------------------------------------------------+
//| Sets the format to a specific level                              |
//+------------------------------------------------------------------+
void CLogifyFormatter::SetFormat(ENUM_LOG_LEVEL level, string format)
  {
   m_format[level] = format;
  }
//+------------------------------------------------------------------+

This design is not only convenient, it allows us to be both generic and specific. For those who want simplicity, a single SetFormat(...) will do. For those who want precision, just call SetFormat(level, format) for each desired level.

With the formats separated, the formatting method now needs to reflect this division. Before, we used a generic variable m_log_format to get the message mask. Now, we search for the format directly in the array, indexing by log level:

Old code New code
string CLogifyFormatter::FormatLog(MqlLogifyModel &data)
  {
   string formated = m_log_format;
   ...
  }
string CLogifyFormatter::Format(MqlLogifyModel &data)
  {
   string formated = m_format[data.level];
   ...
  }

This solves the problem in a clean, extensible and predictable way. No extra checks, no workarounds to suppress empty placeholders. Each level knows exactly what it should contain.


Testing

We've come to the part that brings everything we've built to life: practical testing, and seeing how it behaves under different scenarios and log levels. Inside the same file we used for testing in the last article, LogifyTest.mqh , we've prepared the following elements:

  • A visual handler via Comment() with frame, title and line limit.
  • A console handler via Print() to record messages in the platform's internal log.
  • A formatter shared between the handlers.
  • Four log messages with different levels: two DEBUG , one INFO and one ERROR with code.
//+------------------------------------------------------------------+
//| Import CLogify                                                   |
//+------------------------------------------------------------------+
#include <Logify/Logify.mqh>
CLogify logify;
//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Handler config
   MqlLogifyHandleCommentConfig m_config;
   m_config.size = 5;                                      // Max log lines
   m_config.frame_style = LOG_FRAME_STYLE_SINGLE;          // Frame style
   m_config.direction = LOG_DIRECTION_UP;                  // Log direction (up)
   m_config.title = "Expert name";                         // Log panel title
   
   CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}");
   formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]");
   
   //--- Create and configure handler
   CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment();
   handler_comment.SetConfig(m_config);
   handler_comment.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_comment.SetFormatter(formatter);
   
   CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole();
   handler_console.SetLevel(LOG_LEVEL_DEBUG);              // Min log level
   handler_console.SetFormatter(formatter);
   
   //--- Add handler to Logify
   logify.AddHandler(handler_comment);
   logify.AddHandler(handler_console);
   
   //--- Test logs
   logify.Debug("Initializing Expert Advisor...", "Init", "");
   logify.Debug("RSI indicator value calculated: 72.56", "Indicators", "Period: 14");
   logify.Info("Buy order sent successfully", "Order Management", "Symbol: EURUSD, Volume: 0.1");
   logify.Error("Failed to send sell order", 10016,"Order Management");
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

When you run this code, the MetaTrader console displays the following messages, formatted exactly as we expected. The visual result makes it clear that the lower levels (such as DEBUG and INFO ) follow a leaner format, while ERROR invokes the more detailed format with error data:

09/06/2025 14:22:31 [DEBUG]: Initializing Expert Advisor...
09/06/2025 14:22:31 [DEBUG]: RSI indicator value calculated: 72.56
09/06/2025 14:22:31 [INFO]: Buy order sent successfully
09/06/2025 14:22:31 [ERROR]: Failed to send sell order [TRADE_DISABLED | 10016 | Trade operations not allowed]

Notice how the last log carries an additional block in square brackets, with the symbolic constant, the numeric code and the user-friendly description of the error. And all this was possible without adding extra conditional logic in the Expert code. Each log level carries its own format, and the right placeholders appear only where they make sense.


Conclusion

In this article, we've made improvements to the Logify library. In the first article, we started by structuring the data model, then created flexible handlers for different output channels. And today, we've added multilingual support for error messages, allowing each log to bring clear technical context to the developer, regardless of language.

We've moved forward with a dynamic formatter, capable of handling custom placeholders and automatically adapting to the severity level of the log. When we realized that generic formats caused inconsistency, we evolved the architecture to accept a specific format per level. And finally, we validated everything with a practical test that showed Logify working in real time, in a clean, predictable and extensible way.

If you've made it this far, you've learned how to:

  • Design a scalable and decoupled log structure;
  • Integrate multilingual error messages based on MetaTrader code;
  • Work with placeholders and formatting contextualized by severity;
  • Building reusable handlers with your own configurations;

Logify, like any good tool, can continue to evolve. If new features are implemented, I'll bring them to you in a future article. Until then, in the meantime, use, adapt and improve.


File Name Description
Experts/Logify/LogiftTest.mq5
File where we test the library's features, containing a practical example
Include/Logify/Error/Languages/ErrorMessages.XX.mqh Count the error messages in each language, where X represents the language acronym
Include/Logify/Error/Error.mqh
Data structure for storing errors
Include/Logify/Error/LogifyError.mqh
Class for getting detailed error information
Include/Logify/Formatter/LogifyFormatter.mqh
Class responsible for formatting log records, replacing placeholders with specific values
Include/Logify/Handlers/LogifyHandler.mqh
Base class for managing log handlers, including level setting and log sending
Include/Logify/Handlers/LogifyHandlerComment.mqh
Log handler that sends formatted logs directly to the comment on the terminal chart in MetaTrader
Include/Logify/Handlers/LogifyHandlerConsole.mqh
Log handler that sends formatted logs directly to the terminal console in MetaTrader
Include/Logify/Handlers/LogifyHandlerDatabase.mqh
Log handler that sends formatted logs to a database (Currently it only contains a printout, but soon we will save it to a real sqlite database)
Include/Logify/Handlers/LogifyHandlerFile.mqh
Log handler that sends formatted logs to a file
Include/Logify/Utils/IntervalWatcher.mqh
Checks if a time interval has passed, allowing you to create routines within the library
Include/Logify/Logify.mqh Core class for log management, integrating levels, models and formatting
Include/Logify/LogifyLevel.mqh File that defines the log levels of the Logify library, allowing for detailed control
Include/Logify/LogifyModel.mqh Structure that models log records, including details such as level, message, timestamp, and context
Attached files |
Logify.zip (151.36 KB)
Last comments | Go to discussion (1)
hini
hini | 19 Jun 2025 at 02:09
Both libraries you created are excellent, thanks for the code.
Reimagining Classic Strategies (Part 13): Taking Our Crossover Strategy to New Dimensions (Part 2) Reimagining Classic Strategies (Part 13): Taking Our Crossover Strategy to New Dimensions (Part 2)
Join us in our discussion as we look for additional improvements to make to our moving-average cross over strategy to reduce the lag in our trading strategy to more reliable levels by leveraging our skills in data science. It is a well-studied fact that projecting your data to higher dimensions can at times improve the performance of your machine learning models. We will demonstrate what this practically means for you as a trader, and illustrate how you can weaponize this powerful principle using your MetaTrader 5 Terminal.
From Novice to Expert: Animated News Headline Using MQL5 (I) From Novice to Expert: Animated News Headline Using MQL5 (I)
News accessibility is a critical factor when trading on the MetaTrader 5 terminal. While numerous news APIs are available, many traders face challenges in accessing and integrating them effectively into their trading environment. In this discussion, we aim to develop a streamlined solution that brings news directly onto the chart—where it’s most needed. We'll accomplish this by building a News Headline Expert Advisor that monitors and displays real-time news updates from API sources.
Neural Networks in Trading: Directional Diffusion Models (DDM) Neural Networks in Trading: Directional Diffusion Models (DDM)
In this article, we discuss Directional Diffusion Models that exploit data-dependent anisotropic and directed noise in a forward diffusion process to capture meaningful graph representations.
MQL5 Wizard Techniques you should know (Part 70):  Using Patterns of SAR and the RVI with a Exponential Kernel Network MQL5 Wizard Techniques you should know (Part 70): Using Patterns of SAR and the RVI with a Exponential Kernel Network
We follow up our last article, where we introduced the indicator pair of the SAR and the RVI, by considering how this indicator pairing could be extended with Machine Learning. SAR and RVI are a trend and momentum complimentary pairing. Our machine learning approach uses a convolution neural network that engages the Exponential kernel in sizing its kernels and channels, when fine-tuning the forecasts of this indicator pairing. As always, this is done in a custom signal class file that works with the MQL5 wizard to assemble an Expert Advisor.