
Mastering Log Records (Part 9): Implementing the builder pattern and adding default configurations
Introduction
Since I started using Logify in various personal and professional projects, I quickly realized that the biggest challenge was not in the robustness or functionality of the library itself, but in its configuration. Logify is a powerful tool for managing logs in Expert Advisors, offering multiple handlers, log levels, customized formats, language support and much more. All of this, however, required the user to configure each handler, formatter and parameter manually, which may work well for small projects, but which quickly becomes a repetitive, tiresome and error-prone task as the number of EAs and projects grows.
Imagine having to replicate, for each EA, the same complex set of configurations: creating specific handlers for tab commenting, console, files, databases; setting minimum levels for each handler; defining specific formats for error messages, debugging, alerts, and so on. Each of these steps, although necessary, generates long, detailed and unintuitive code, breaking the flow of development. This initial complexity becomes a barrier, a friction that can discourage the adoption of Logify, even if it offers impeccable log management at runtime.
This insight motivated me to think: how can I make this configuration easier, simplifying the user's life, without giving up flexibility and customization? It was at this point that the idea of creating a builder for Logify was born, a class that allows you to assemble the entire configuration in a fluid, chained way, with intuitive methods that create handlers with sensible patterns and allow quick, localized adjustments. The aim is to transform dozens of lines of configuration into a few method calls, almost as if we were writing a clear summary of what we want, rather than all the manual assembly.
In this article, I'll show you how I've implemented these improvements. I'll introduce the builder, explaining its design and use. Then I'll demonstrate how Logify can be configured with practical examples.
Understanding the Builder pattern: simplifying the construction of complex objects
Before we dive into the implementation of our CLogifyBuilder, it's important to understand the idea behind the pattern we're using: the Builder.
In practice, Builder is a design pattern that has a single objective: to make it easier to create complex objects, especially when these objects require multiple configuration steps or have many possible options. The proposal is to separate the construction process from the final representation of the object, allowing the same assembly structure to create different "flavors" of ready-to-use objects.
Let's take a simple example: imagine you're going to assemble a car. You can choose the model, the color, the type of engine, the transmission, the options, the size of the wheels, the interior trim, among dozens of other decisions. Doing all this directly in the code, passing hundreds of arguments to a constructor, is neither practical nor easy to maintain.
It's exactly for this kind of situation that Builder shines. It breaks this construction into chained methods (also called fluent interfaces), each responsible for configuring a specific part of the object, until at the end you call .Build() or something similar, and receive the final object, 100% ready for use.
This approach brings three major benefits:
- Clear and linear reading: the construction of the object looks like a logical "script", almost as if you were describing what you want.
- Error reduction: as each step has an isolated purpose, it is much easier to detect and correct incorrect configurations.
- Flexibility and reuse: the same builder can be reused to create variations of the object with minor changes.
Applying the Builder to Logify
In the case of Logify, building a logger instance (CLogify) involves creating multiple handlers (such as console, comment, file), configuring minimum log levels, defining specific formatters for each handler, and even parameters such as panel size or frame style. Doing all this manually, line by line, over and over again, has become a polluted process.
Using the Builder pattern here solves this problem. Instead of exposing the user to the responsibility of manually assembling each piece, we offer a more intuitive interface, like this one:
CLogify *logify = logify .Create() .AddHandlerComment() .SetTitle("My Logger") .SetSize(5) .Done() .AddHandlerConsole() .Done() .Build();
Notice how straightforward and expressive it reads. The .Create() starts the construction, each .AddHandlerXXX() opens the configuration of a handler, the SetX() methods adjust its parameters, the .Done() ends the configuration of a handler, and the .Build() delivers the ready-made instance of CLogify.
This is the power of Builder: it allows the developer to describe what they want, without worrying about the details of how it will be implemented under the hood.
Now that we've understood why we use this pattern and how it helps us make Logify more practical and scalable, let's take a practical look at how this class was built and how we can use it in the real world.
Specialized builders for each handler
We've created a new file <Include/Logify/LogifyBuilder.mqh>. Inside it is the CLogifyBuilder class, which already contains, as a private field, an instance of CLogify, this instance will be manipulated and, at the end, returned to the user.
//+------------------------------------------------------------------+ //| LogifyBuilder.mqh | //| joaopedrodev | //| https://www.mql5.com/en/users/joaopedrodev | //+------------------------------------------------------------------+ #property copyright "joaopedrodev" #property link "https://www.mql5.com/en/users/joaopedrodev" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "Logify.mqh" //+------------------------------------------------------------------+ //| class : CLogifyBuilder | //| | //| [PROPERTY] | //| Name : LogifyBuilder | //| Heritage : No heritage | //| Description : Build CLogify objects, following the Builder design| //| pattern. | //| | //+------------------------------------------------------------------+ class CLogifyBuilder { private: CLogify *m_logify; public: CLogifyBuilder(void) ~CLogifyBuilder(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyBuilder::CLogifyBuilder(void) { m_logify = new CLogify(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyBuilder::~CLogifyBuilder(void) { } //+------------------------------------------------------------------+
The CLogifyBuilder class is the central point of the construction of a CLogify object, but it delegates the configuration of each type of handler to specialized builders: CLogifyHandlerCommentBuilder, CLogifyHandlerConsoleBuilder, CLogifyHandlerDatabaseBuilder and CLogifyHandlerFileBuilder.
Each builder encapsulates the details of a single type of log output. This avoids mixing responsibilities and keeps the core of the build clean and modular. Let's take a look at their structure, taking ConsoleBuilder as an example.
CLogifyHandlerConsoleBuilder
The class starts by defining the minimum structure to support the fluent API. It receives a pointer to the main builder (CLogifyBuilder*) in the constructor, keeping a reference to the build context. This allows you to return to this context with Done() after setting up the handler:
//+------------------------------------------------------------------+ //| class : CLogifyHandlerConsoleBuilder | //| | //| [PROPERTY] | //| Name : LogifyHandlerConsoleBuilder | //| Heritage : No heritage | //| Description : Console handler constructor. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerConsoleBuilder { private: CLogifyBuilder *m_parent; public: CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify); ~CLogifyHandlerConsoleBuilder(void); CLogifyBuilder *Done(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify) { m_parent = logify; }; //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void) { } //+------------------------------------------------------------------+ //| Finalizes the handler configuration. | //+------------------------------------------------------------------+ CLogifyBuilder *CLogifyHandlerConsoleBuilder::Done(void) { m_parent.AddHandler(GetPointer(m_handler)); delete GetPointer(this); return(m_parent); } //+------------------------------------------------------------------+
Done() is the return point to the main builder, adds the handler to CLogify and destroys the intermediate builder. This keeps the cycle flowing and avoids unnecessary memory retention.
All specialized builders follow the same anatomy:
- CLogifyBuilder *m_parent: reference to the parent builder, used to return via Done().
- CLogifyFormatter *m_formatter: formatter instance that will be associated with the handler.
- CLogifyHandlerX *m_handler: the handler itself, being configured.
More complex builders (such as File or Database) also use an internal configuration struct (MqlLogifyHandleXConfig), which stores the values temporarily until the handler is ready to be registered.
This separation between configuration data and application to the handler makes it possible to apply validations, use presets and combine options without inflating the logic of the handler itself.
Complete Console Builder
Next, the builder with the configuration methods already implemented:
//+------------------------------------------------------------------+ //| class : CLogifyHandlerConsoleBuilder | //| | //| [PROPERTY] | //| Name : LogifyHandlerConsoleBuilder | //| Heritage : No heritage | //| Description : Console handler constructor. | //| | //+------------------------------------------------------------------+ class CLogifyHandlerConsoleBuilder { private: CLogifyBuilder *m_parent; CLogifyFormatter *m_formatter; CLogifyHandlerConsole *m_handler; public: CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify); ~CLogifyHandlerConsoleBuilder(void); CLogifyHandlerConsoleBuilder *SetLevel(ENUM_LOG_LEVEL level); CLogifyHandlerConsoleBuilder *SetFormatter(string format); CLogifyHandlerConsoleBuilder *SetFormatter(ENUM_LOG_LEVEL level, string format); CLogifyBuilder *Done(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::CLogifyHandlerConsoleBuilder(CLogifyBuilder *logify) { m_parent = logify; m_formatter = new CLogifyFormatter(); m_handler = new CLogifyHandlerConsole(); m_handler.SetFormatter(GetPointer(m_formatter)); }; //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder::~CLogifyHandlerConsoleBuilder(void) { } //+------------------------------------------------------------------+ //| Sets the log level for the handler. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetLevel(ENUM_LOG_LEVEL level) { m_handler.SetLevel(level); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Sets the default format string for the formatter. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(string format) { m_formatter.SetFormat(format); m_handler.SetFormatter(GetPointer(m_formatter)); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Sets a log-level-specific format for the formatter. | //+------------------------------------------------------------------+ CLogifyHandlerConsoleBuilder *CLogifyHandlerConsoleBuilder::SetFormatter(ENUM_LOG_LEVEL level, string format) { m_formatter.SetFormat(level,format); m_handler.SetFormatter(GetPointer(m_formatter)); return(GetPointer(this)); } //+------------------------------------------------------------------+ //| Finalizes the handler configuration. | //+------------------------------------------------------------------+ CLogifyBuilder *CLogifyHandlerConsoleBuilder::Done(void) { m_parent.AddHandler(GetPointer(m_handler)); delete GetPointer(this); return(m_parent); } //+------------------------------------------------------------------+
With this ready, we move on to the rest of the specialized builders
Other specialized builders
CLogifyHandlerCommentBuilder
Responsible for configuring the handler that writes messages directly to the graph (Comment() ), using the MqlLogifyHandleCommentConfig structure.
Allows you to define:
- SetSize(int): number of messages displayed.
- SetFrameStyle(ENUM_LOG_FRAME_STYLE): frame around the log area.
- SetDirection(ENUM_LOG_DIRECTION): vertical or horizontal orientation.
- SetTitle(string): fixed title at the top of the log.
Also accepts SetLevel() and SetFormatter(), global or by level. The configuration is finalized with Done().
CLogifyHandlerDatabaseBuilder
Configures the database persistence handler (binary structure, for now). Uses MqlLogifyHandleDatabaseConfig. Offers:
- SetDirectory(string)
- SetBaseFileName(string)
- SetMessagesPerFlush(int)
The structure is identical to the other builders, maintaining consistency.
CLogifyHandlerFileBuilder
The most complete of all. Configures writing to .log, .txt, etc. files, via MqlLogifyHandleFileConfig.
Available options:
- SetDirectory(), SetFilename(), SetFileExtension()
- SetRotationMode(), by date, size or manual
- SetMessagesPerFlush()
- SetCodepage(), such as CP_UTF8
- SetFileSizeMB(), SetMaxFileCount()
And also three utility methods with ready-made presets:
- ConfigNoRotation()
- ConfigDateRotation()
- ConfigSizeRotation()
These shortcuts encapsulate the complete configuration with single calls, useful for recurring patterns. The builders for the other handlers follow the same structure, with specific configuration variations. You can consult the complete codes in the attached files.
The core class: CLogifyBuilder
Now that we've explored how the specialized builders work, it's time to look at the piece that coordinates them: the CLogifyBuilder class.
This is responsible for creating and maintaining the main CLogify instance, to which all the handlers will be added. But, instead of configuring everything directly, it delegates this responsibility to specialized builders, each one taking care of a specific type of handler. Thus, CLogifyBuilder acts as a kind of conductor, leading the modular construction of the logger.
Below is the complete implementation of the class:
//+------------------------------------------------------------------+ //| class : CLogifyBuilder | //| | //| [PROPERTY] | //| Name : LogifyBuilder | //| Heritage : No heritage | //| Description : Build CLogify objects, following the Builder design| //| pattern. | //| | //+------------------------------------------------------------------+ class CLogifyBuilder { private: CLogify *m_logify; public: CLogifyBuilder(void); ~CLogifyBuilder(void); CLogifyBuilder *UseLanguage(ENUM_LANGUAGE language); //--- Starts configuration handlers CLogifyHandlerCommentBuilder *AddHandlerComment(void); CLogifyHandlerConsoleBuilder *AddHandlerConsole(void); CLogifyHandlerDatabaseBuilder *AddHandlerDatabase(void); CLogifyHandlerFileBuilder *AddHandlerFile(void); void AddHandler(CLogifyHandler *handler); CLogify *Build(void); }; //+------------------------------------------------------------------+
This class concentrates some important functions:
- UseLanguage(ENUM_LANGUAGE language): Allows the main language of the logging system to be set. This affects internal error messages (via CLogifyError) and formatting that depends on localization.
- AddHandlerX(): These are the entry points for configuring the handlers. Each method (AddHandlerConsole(), AddHandlerFile() etc.) instantiates a specialized builder, passing itself as a pointer (this) so that it can return via Done() once configured.
- AddHandler(CLogifyHandler *handler): This method is called internally by specialized builders at the end of configuration (Done() ). It registers the ready-made handler in the CLogify instance being built.
- Build(): Finalizes the build process, releases the builder from memory with delete GetPointer(this) and returns the ready logger. This reinforces the idea that the builder instance only exists during the assembly process.
With this structure, the Builder pattern is complete: modular, clear and extensible. You can see the complete code in the attached files.
An elegant entry point
Although we already have the CLogifyBuilder constructor, directly exposing this constructor for the user to instantiate with new CLogifyBuilder() is not the most expressive or intuitive way to start building the logger.
That's why we've added a static method called Create() to the CLogify class:
//+------------------------------------------------------------------+ //| Returns an instance of the builder | //+------------------------------------------------------------------+ #include "LogifyBuilder.mqh" CLogifyBuilder *CLogify::Create(void) { return(new CLogifyBuilder()); } //+------------------------------------------------------------------+
And the method is declared like this in the CLogify class:
class CLogify { public: static CLogifyBuilder *Create(void); };
The Create() method is static because:
- It belongs to the class, not the instance — you don't have an instance of CLogify yet when you want to start building it.
- It doesn't depend on any internal state — all it does is create and return a builder.
- Avoids direct coupling to the builder — if the builder implementation changes tomorrow, you can keep the same static interface in CLogify and preserve compatibility with existing code.
Default settings
As the Logify library takes shape, we need to think about a common scenario: the user who wants to log quickly, without configuring anything. They don't care about handlers, languages, formats or directories, they just need a visible output for messages during development or testing. To cater for this, we introduced the EnsureDefaultHandler() method.
This method acts as an automatic fallback: if no handler has been explicitly configured, it adds two basic, functional handlers, one for the Console, the other for Comment(). Both are native to MQL5 and guarantee immediate visibility of the message.
void CLogify::EnsureDefaultHandler() { //--- Check if there is no handler if(this.SizeHandlers() == 0) { this.AddHandler(new CLogifyHandlerConsole()); this.AddHandler(new CLogifyHandlerComment()); } }
The call takes place inside the Append() method, not in the constructor:
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(); // (continues...) }
This decision has a strategic purpose: if we added default handlers in the constructor, they would be added to any configuration made afterwards, meaning that the log would end up with duplicate or unintended handlers. This is particularly problematic when the user wants to direct all output to a single destination, such as a file, database or remote server.
By moving this logic to Append(), we keep control in the hands of the developer. It works like this:
- If no handler is configured, EnsureDefaultHandler() activates both defaults on the first call to Append().
- If at least one handler is added manually, the method does nothing.
- The default behavior is safe and visible, but does not interfere when there is explicit configuration.
This approach balances convenience and predictability. For those who want something fast and functional, the system "runs itself". For those who need fine control, the library strictly respects the developer's choices.
With this, Logify becomes plug-and-play without sacrificing customization, an important step in facilitating its adoption by both beginners and teams that demand stricter logging standards.
Testing
Let's compare what it was like to use the library before and after the improvements implemented in this article.
Before, setting up a log required a series of manual steps: instantiating objects, defining levels, creating formatters, filling in configuration structures and assembling handlers one by one. Now, with the introduction of default settings via EnsureDefaultHandler(), the developer can start using the library with a single line.
Below, see the two scenarios side by side:
Old code | New code |
---|---|
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { MqlLogifyHandleCommentConfig m_config; m_config.size = 5; m_config.frame_style = LOG_FRAME_STYLE_SINGLE; m_config.direction = LOG_DIRECTION_UP; m_config.title = "Expert name"; CLogifyFormatter *formatter = new CLogifyFormatter("{date_time} [{levelname}]: {msg}"); formatter.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}]: {msg} [{err_constant} | {err_code} | {err_description}]"); CLogifyHandlerComment *handler_comment = new CLogifyHandlerComment(); handler_comment.SetConfig(m_config); handler_comment.SetLevel(LOG_LEVEL_DEBUG); handler_comment.SetFormatter(formatter); CLogifyHandlerConsole *handler_console = new CLogifyHandlerConsole(); handler_console.SetLevel(LOG_LEVEL_DEBUG); handler_console.SetFormatter(formatter); logify = new CLogify(); logify.AddHandler(handler_comment); logify.AddHandler(handler_console); 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+ | //+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { logify = new CLogify(); 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+ |
This minimalist approach covers most cases with zero manual configuration, ideal for rapid prototyping and testing.
For cases where we need more control over the log's behavior, the new Builder comes into play. It brings a fluent interface, most importantly, 100% typed, meaning that the code editor itself suggests the available methods in real time, reducing errors and eliminating the need to memorize function signatures.
When you type logify.Create().AddHandler, the editor already suggests all the available handlers:
And when you continue with .AddHandlerComment(), only the valid settings for that specific type of handler appear:
This is how the code looks at the end, configuring the comment handler, with a specific format for errors.
//+------------------------------------------------------------------+ //| Import | //+------------------------------------------------------------------+ #include <Logify/Logify.mqh> CLogify *logify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { logify = logify.Create().AddHandlerComment().SetLevel(LOG_LEVEL_DEBUG).SetFormatter(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})").SetTitle("My expert").SetSize(5).Done().Build(); //--- 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); } void OnDeinit(const int reason) { delete logify; } //+------------------------------------------------------------------+
This guided experience eliminates doubts and reduces friction in development. The code is clean, straightforward and error-proof.
Fix for MetaTrader 5 build 5100 or higher
With the release of build 5100 of MetaTrader 5, some internal changes to the compiler now require more clarity in the handling of types in calls such as DatabaseColumnLong() and DatabaseColumnInteger().
In practice, this means that it is no longer safe to directly pass references to struct fields (such as data[size].timestamp) in these functions. To avoid compilation errors, the ideal is to first store the value in a temporary variable of the appropriate type, and only then pass it by reference to the function.
Old code | New code |
---|---|
//+------------------------------------------------------------------+ //| Get data by sql command | //+------------------------------------------------------------------+ bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[]) { //--- The rest of the method code remains the same //--- Reads query results line by line for(int i=0;DatabaseRead(request);i++) { int size = ArraySize(data); ArrayResize(data,size+1,size); //--- Maps database data to the MqlLogifyModel model DatabaseColumnText(request,1,data[size].formated); DatabaseColumnText(request,2,data[size].levelname); DatabaseColumnText(request,3,data[size].msg); DatabaseColumnText(request,4,data[size].args); DatabaseColumnLong(request,5,data[size].timestamp); string value; DatabaseColumnText(request,6,value); data[size].date_time = StringToTime(value); DatabaseColumnInteger(request,7,data[size].level); DatabaseColumnText(request,8,data[size].origin); DatabaseColumnText(request,9,data[size].filename); DatabaseColumnText(request,10,data[size].function); DatabaseColumnLong(request,11,data[size].line); } //--- The rest of the method code remains the same } //+------------------------------------------------------------------+ | //+------------------------------------------------------------------+ //| Get data by sql command | //+------------------------------------------------------------------+ bool CLogifyHandlerDatabase::Query(string query, MqlLogifyModel &data[]) { //--- The rest of the method code remains the same //--- Reads query results line by line for(int i=0;DatabaseRead(request);i++) { int size = ArraySize(data); ArrayResize(data,size+1,size); //--- Maps database data to the MqlLogifyModel model DatabaseColumnText(request,1,data[size].formated); DatabaseColumnText(request,2,data[size].levelname); DatabaseColumnText(request,3,data[size].msg); DatabaseColumnText(request,4,data[size].args); long timestamp = (long)data[size].timestamp; DatabaseColumnLong(request,5,timestamp); string value; DatabaseColumnText(request,6,value); data[size].date_time = StringToTime(value); int level = data[size].level; DatabaseColumnInteger(request,7,level); DatabaseColumnText(request,8,data[size].origin); DatabaseColumnText(request,9,data[size].filename); DatabaseColumnText(request,10,data[size].function); long line = (long)data[size].line; DatabaseColumnLong(request,11,line); } //--- The rest of the method code remains the same } //+------------------------------------------------------------------+ |
The rest of the code remains the same. This is a one-off adjustment, but necessary to maintain compatibility with recent versions of the terminal.
It's worth remembering that this type of adjustment is common when the compiler becomes more demanding with types and this usually comes to avoid subtle problems at runtime. So, although it may seem like a simple change, it's an important update to ensure that Logify continues to work with stability in newer versions of MetaTrader 5.
Conclusion
Until now, configuring the Logify library was powerful, but a little bureaucratic. You had to create objects, set up configurations manually, remember the order of calls... in short, it worked, but it was a bit of a pain.
In this part of the article, we've solved that. We've created a new way of working with the log: simple, clear and fast. The builder came into play to make everything more natural: you type logify.Create() and the editor itself shows you the next options. Want a comment handler? Type AddHandlerComment(). Want to change the title? SetTitle() appears. You don't need to memorize anything, you don't need to go back through the documentation. Just follow the flow.
We've also made it even more user-friendly with default settings. If you just want to record messages and aren't worried about customizing the log, you don't need to do anything. Just create the object and start using it. Logify itself turns around to display the messages in the console and graph.
Finally, we've adjusted an important technical detail: with the arrival of MetaTrader 5 build 5100, the compiler has become stricter about passing references in functions such as DatabaseColumnLong() and DatabaseColumnInteger(). To ensure compatibility, we've added small corrections to CLogifyHandlerDatabase, using intermediate variables before passing data to these functions. Nothing changes for those who use the library, but behind the scenes it remains stable, even with terminal updates.
In the end, we achieved what every developer likes: less code, fewer errors and more clarity. The library now talks better to those who use it, without forcing any rigidity and without complicating what should be simple. As Logify evolves, becoming even more flexible and gaining new features that I think will be useful to most users, I'll bring you new articles showing the improvements, making our day-to-day lives easier. The idea is for the library to grow along with those who use it, without magic, just well thought-out code.
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/LogifyBuilder.mqh | Class responsible for creating a CLockify object, simplifying configuration |
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 |





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use