Logify/Formatter/LogifyFormatter.mqh
2025-08-21 11:59:58 -03:00

299 lines
26 KiB
MQL5

//+------------------------------------------------------------------+
//| LogifyFormatter.mqh |
//| joaopedrodev |
//| https://www.mql5.com/en/users/joaopedrodev |
//+------------------------------------------------------------------+
#property copyright "joaopedrodev"
#property link "https://www.mql5.com/en/users/joaopedrodev"
//+------------------------------------------------------------------+
//| Include files |
//+------------------------------------------------------------------+
#include "../LogifyModel.mqh"
//+------------------------------------------------------------------+
//| class : CLogifyFormatter |
//| |
//| [PROPERTY] |
//| Name : CLogifyFormatter |
//| Heritage : No heritage |
//| Description : Class responsible for formatting the log into a |
//| string, replacing placeholders with their respective values. |
//| |
//+------------------------------------------------------------------+
class CLogifyFormatter
{
private:
//--- Stores the formats
string m_date_format;
string m_format[5];
//--- Validate format
bool CheckLogFormat(string format);
string FormatDate(datetime date);
public:
CLogifyFormatter(string format);
CLogifyFormatter(string format, string date_format);
CLogifyFormatter(void);
~CLogifyFormatter(void);
void SetDateFormat(string date_format);
void SetFormat(string format);
void SetFormat(ENUM_LOG_LEVEL level, string format);
//--- Format query methods
string GetDateFormat(void);
string GetFormat(ENUM_LOG_LEVEL level=LOG_LEVEL_DEBUG);
//--- Date and log formatting methods
virtual string Format(MqlLogifyModel &data);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CLogifyFormatter::CLogifyFormatter(string format)
{
this.SetDateFormat("yyyy.MM.dd hh:mm:ss");
this.SetFormat(format);
this.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
this.SetFormat(LOG_LEVEL_FATAL,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
}
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CLogifyFormatter::CLogifyFormatter(string format, string date_format)
{
this.SetDateFormat(date_format);
this.SetFormat(format);
this.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
this.SetFormat(LOG_LEVEL_FATAL,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
}
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CLogifyFormatter::CLogifyFormatter(void)
{
this.SetDateFormat("yyyy.MM.dd hh:mm:ss");
this.SetFormat("{date_time} [{levelname}]: {msg}");
this.SetFormat(LOG_LEVEL_ERROR,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
this.SetFormat(LOG_LEVEL_FATAL,"{date_time} [{levelname}] {msg} ({err_constant} {err_code}: {err_description})");
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLogifyFormatter::~CLogifyFormatter(void)
{
}
//+------------------------------------------------------------------+
//| Sets the date and time format |
//+------------------------------------------------------------------+
void CLogifyFormatter::SetDateFormat(string date_format)
{
m_date_format = date_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;
}
//+------------------------------------------------------------------+
//| Get date format |
//+------------------------------------------------------------------+
string CLogifyFormatter::GetDateFormat(void)
{
return(m_date_format);
}
//+------------------------------------------------------------------+
//| Get the log format |
//+------------------------------------------------------------------+
string CLogifyFormatter::GetFormat(ENUM_LOG_LEVEL level=LOG_LEVEL_DEBUG)
{
if(level >= 0 && level < 5)
{
return(m_format[level]);
}
return("Invalid level");
}
//+------------------------------------------------------------------+
//| Validate format |
//+------------------------------------------------------------------+
bool CLogifyFormatter::CheckLogFormat(string format)
{
//--- Variables to track the most recent '{' opening index and the number of '{' brace openings
int openIndex = -1; // Index of last '{' found
int openBraces = 0; // '{' counter
int len = StringLen(format); // String length
//--- Checks if string is empty
if(len == 0)
{
//--- Prints error message and returns false
Print("Formatting error: Unexpected string found. Check the placeholder pattern used.");
return(false);
}
//--- Iterate through each character of the string
for(int i=0;i<len;i++)
{
//--- Gets the current character
ushort character = StringGetCharacter(format,i);
//--- Checks if the character is an opening '{'
if(character == '{')
{
openBraces++; // Increments the opening counter '{'
openIndex = i; // Updates the index of the last opening
}
//--- Checks if the character is a closing '}'
else if(character == '}')
{
//--- If there is no matching '{'
if(openBraces == 0)
{
//--- Prints error message and returns false
Print("Erro de formatação: o caractere '}' na posição ",i," não possui um '{' correspondente.");
return(false);
}
//--- Decreases the open count because a matching '{' was found
openBraces--;
//--- Extracts the contents of the placeholder (between '{' and '}')
string placeHolder = StringSubstr(format, openIndex + 1, i - openIndex - 1);
//--- Checks if placeholder is empty
if(StringLen(placeHolder) == 0)
{
//--- Prints error message and returns false
Print("Erro de formatação: placeholder vazio detectado na posição ",i,". Um nome é esperado dentro de '{...}'.");
return(false);
}
}
}
//--- After traversing the entire string, check if there are still any unmatched '{'}'
if(openBraces > 0)
{
//--- Prints error message indicating the index of the last opened '{' and returns false
Print("Erro de formatação: o placeholder '{' na posição ",openIndex," não possui um '}' correspondente.");
return(false);
}
//--- Format is correct
return(true);
}
//+------------------------------------------------------------------+
//| Formats dates |
//+------------------------------------------------------------------+
string CLogifyFormatter::FormatDate(datetime date)
{
string formated = m_date_format;
//--- Date and time structure
MqlDateTime time;
TimeToStruct(date, time);
//--- Array with months and days of the week in string
string months_abbr[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
string months_full[12] = {"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
string day_of_week_abbr[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
string day_of_week_full[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
//--- Replace year
StringReplace(formated, "yyyy", IntegerToString(time.year));
StringReplace(formated, "yy", IntegerToString(time.year % 100, 2, '0'));
//--- Replace month
if(StringFind(formated,"MMM") < 0 && StringFind(formated,"MMMM") < 0)
{
StringReplace(formated, "MM", IntegerToString(time.mon, 2, '0'));
StringReplace(formated, "M", IntegerToString(time.mon));
}
//--- Replace day
StringReplace(formated, "dd", IntegerToString(time.day, 2, '0'));
StringReplace(formated, "d", IntegerToString(time.day));
//--- Replace day of year
StringReplace(formated, "DDD", IntegerToString(time.day_of_year, 3, '0'));
StringReplace(formated, "D", IntegerToString(time.day_of_year));
//--- Replace Replace hours (24h and 12h)
StringReplace(formated, "HH", IntegerToString(time.hour, 2, '0'));
StringReplace(formated, "H", IntegerToString(time.hour));
int hour_12 = time.hour % 12;
if (hour_12 == 0) hour_12 = 12;
StringReplace(formated, "hh", IntegerToString(hour_12, 2, '0'));
StringReplace(formated, "h", IntegerToString(hour_12));
//--- Replace minutes and seconds
StringReplace(formated, "mm", IntegerToString(time.min, 2, '0'));
StringReplace(formated, "m", IntegerToString(time.min));
StringReplace(formated, "ss", IntegerToString(time.sec, 2, '0'));
StringReplace(formated, "s", IntegerToString(time.sec));
//--- Replace AM/PM
bool is_am = (time.hour < 12);
StringReplace(formated, "tt", is_am? "am" : "pm");
StringReplace(formated, "TT", is_am? "AM" : "PM");
//--- Replace month
StringReplace(formated, "MMMM", months_full[time.mon - 1]);
StringReplace(formated, "MMM", months_abbr[time.mon - 1]);
//--- Replace day of week
StringReplace(formated, "EEEE", day_of_week_full[time.day_of_week]);
StringReplace(formated, "E", day_of_week_abbr[time.day_of_week]);
return(formated);
}
//+------------------------------------------------------------------+
//| Format logs |
//+------------------------------------------------------------------+
string CLogifyFormatter::Format(MqlLogifyModel &data)
{
string formated = m_format[data.level];
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);
}
//+------------------------------------------------------------------+