324 lines
14 KiB
MQL5
324 lines
14 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Logs.mqh |
|
|
//| Copyright 2015, Vasiliy Sokolov. |
|
|
//| http://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2015, Vasiliy Sokolov."
|
|
#property link "http://www.mql5.com"
|
|
#include "Message.mqh"
|
|
#include <Arrays/ArrayObj.mqh>
|
|
#include <Object.mqh>
|
|
//+------------------------------------------------------------------+
|
|
//| The class implements logging of messages as Singleton |
|
|
//+------------------------------------------------------------------+
|
|
/**================================================================================================
|
|
* * Description
|
|
* * This class implements a singleton logger for handling messages and notifications in the trading system.
|
|
* * As a singleton pattern, it ensures a single logging instance is shared across all parts of the platform,
|
|
* * providing centralized logging and message handling for the entire trading system.
|
|
* *
|
|
* * Key features:
|
|
* * - Maintains a list of messages using CArrayObj
|
|
* * - Supports both terminal messages and push notifications
|
|
* * - Configurable message priority levels for terminal and push notifications
|
|
* * - File-based logging with automatic cleanup of old logs
|
|
* * - Thread-safe singleton implementation
|
|
* *
|
|
* * The logger can be configured to:
|
|
* * - Enable/disable terminal messages
|
|
* * - Enable/disable push notifications
|
|
* * - Set priority thresholds for different message types
|
|
* * - Save messages to CSV files
|
|
* * - Automatically manage log file retention
|
|
* *
|
|
* * Usage across platform:
|
|
* * - Used by all expert advisors for consistent logging
|
|
* * - Handles error reporting from all trading modules
|
|
* * - Provides unified message formatting and storage
|
|
* * - Ensures thread-safe logging in multi-threaded environments
|
|
*================================================================================================**/
|
|
|
|
class CLog
|
|
{
|
|
private:
|
|
static CLog *m_log; // A pointer to the global static sample
|
|
CArrayObj m_messages; // The list of saved messages
|
|
bool m_terminal_enable; // True if you need to print the received message to the trading terminal
|
|
bool m_push_enable; // True if a Push notification is sent
|
|
ENUM_MESSAGE_TYPE m_push_priority; // Contains the specified priority of message display in the terminal window.
|
|
ENUM_MESSAGE_TYPE m_terminal_priority; // Contains the specified priority of sending pushes to mobile devices.
|
|
bool m_recursive; // A flag indicating the recursive call of the destructor.
|
|
bool SendPush(CMessage *msg);
|
|
void CheckMessage(CMessage *msg);
|
|
CLog(void); // Private constructor
|
|
string GetName(void);
|
|
void DeleteOldLogs(int day_history = 30);
|
|
void DeleteOldLog(string file_name, int day_history);
|
|
~CLog(void) { ; }
|
|
|
|
public:
|
|
static CLog *GetLog(void); // The method to receive a static object
|
|
bool AddMessage(CMessage *msg);
|
|
void Clear(void);
|
|
bool Save(string path);
|
|
CMessage *MessageAt(int index) const;
|
|
int Total(void);
|
|
void TerminalEnable(bool enable);
|
|
bool TerminalEnable(void);
|
|
void PushEnable(bool enable);
|
|
bool PushEnable(void);
|
|
void PushPriority(ENUM_MESSAGE_TYPE type);
|
|
ENUM_MESSAGE_TYPE PushPriority(void);
|
|
void TerminalPriority(ENUM_MESSAGE_TYPE type);
|
|
ENUM_MESSAGE_TYPE TerminalPriority(void);
|
|
bool SaveToFile(void);
|
|
static bool DeleteLog(void);
|
|
};
|
|
|
|
CLog *CLog::m_log;
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor of the global object |
|
|
//+------------------------------------------------------------------+
|
|
CLog::CLog(void) : m_terminal_enable(true),
|
|
m_push_enable(false),
|
|
m_recursive(false)
|
|
{
|
|
DeleteOldLogs();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the logger object |
|
|
//+------------------------------------------------------------------+
|
|
static CLog *CLog::GetLog()
|
|
{
|
|
if (CheckPointer(m_log) == POINTER_INVALID)
|
|
m_log = new CLog();
|
|
return m_log;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Deletes the logger object |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::DeleteLog(void)
|
|
{
|
|
bool res = CheckPointer(m_log) != POINTER_INVALID;
|
|
if (res)
|
|
delete m_log;
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns a message with the specified index 'index' |
|
|
//+------------------------------------------------------------------+
|
|
CMessage *CLog::MessageAt(int index) const
|
|
{
|
|
CMessage *msg = m_messages.At(index);
|
|
return msg;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the total number of logs. |
|
|
//+------------------------------------------------------------------+
|
|
int CLog::Total(void)
|
|
{
|
|
return m_messages.Total();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Saves a message to a CSV file. |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::Save(string path)
|
|
{
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Cleans the logger. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::Clear(void)
|
|
{
|
|
m_messages.Clear();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Adds a new message to the list. |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::AddMessage(CMessage *msg)
|
|
{
|
|
CheckMessage(msg);
|
|
if (m_terminal_enable)
|
|
printf(msg.ToConsoleType());
|
|
if (m_push_enable)
|
|
SendPush(msg);
|
|
return m_messages.Add(msg);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets a flag indicating whether it's necessary to display |
|
|
//| the passed message to platform window. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::TerminalEnable(bool enable)
|
|
{
|
|
m_terminal_enable = enable;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the flag indicating whether the received message is |
|
|
//| displayed in the trading platform window (Experts tab) |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::TerminalEnable(void)
|
|
{
|
|
return m_terminal_enable;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets a flag indicating whether it's necessary to send |
|
|
//| the passed message to receivers' mobile devices. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::PushEnable(bool enable)
|
|
{
|
|
m_push_enable = enable;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the flag indicating whether the received message is |
|
|
//| sent as pushes to mobile devices. |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::PushEnable(void)
|
|
{
|
|
return m_push_enable;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sends a passed message as a pushe to mobile devices to specified |
|
|
//| recipients. Details: |
|
|
//| https://www.mql5.com/en/docs/common/sendnotification |
|
|
//| RETURN: |
|
|
//| True if successfully sent, false if otherwise |
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::SendPush(CMessage *msg)
|
|
{
|
|
string d = "\t";
|
|
string stype = EnumToString(msg.Type());
|
|
string date = TimeToString(msg.TimeServer(), TIME_DATE | TIME_MINUTES | TIME_SECONDS);
|
|
string line = stype + d + date + d + msg.Source() + d + msg.Text();
|
|
if (StringLen(line) > 255)
|
|
line = StringSubstr(line, 0, 255);
|
|
bool res = SendNotification(line);
|
|
return res;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the priority of sending push notifications to mobiles of |
|
|
//| recipients For example, if priority = MESSAGE_ERROR, all messages|
|
|
//| of type MESSAGE_INFO и MESSAGE_WARNING will not be sent. If |
|
|
//| priority = MESSAGE_WARNING, it will send messages of types |
|
|
//| MESSAGE_WARNING and MESSAGE_ERROR. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::PushPriority(ENUM_MESSAGE_TYPE priority)
|
|
{
|
|
m_push_priority = priority;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns priority of push-notification sending |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_MESSAGE_TYPE CLog::PushPriority(void)
|
|
{
|
|
return m_push_priority;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets the priority of sending messages to the terminal |
|
|
//| For example, if priority = MESSAGE_ERROR, all messages of type |
|
|
//| MESSAGE_INFO and MESSAGE_WARNING aren't sent to terminal. If |
|
|
//| priority = MESSAGE_WARNING, it will send messages |
|
|
//| MESSAGE_WARNING and MESSAGE_ERROR. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::TerminalPriority(ENUM_MESSAGE_TYPE priority)
|
|
{
|
|
m_terminal_priority = priority;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the priority of terminal message sending. |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_MESSAGE_TYPE CLog::TerminalPriority(void)
|
|
{
|
|
return m_terminal_priority;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Checks if the sent message contains source and the text of the |
|
|
//| message. If it doesn't, creates a new warning |
|
|
//| message. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::CheckMessage(CMessage *msg)
|
|
{
|
|
if (msg.Source() == UNKNOW_SOURCE || msg.Source() == NULL || msg.Source() == "")
|
|
{
|
|
string text = "The passed message does not contain its source of origin.";
|
|
CMessage *msg_info = new CMessage(MESSAGE_INFO, "CLog::AddMessage", text);
|
|
AddMessage(msg_info);
|
|
}
|
|
if (msg.Text() == NULL || msg.Text() == "")
|
|
{
|
|
string text = "The passed message does not contain message text.";
|
|
CMessage *msg_info = new CMessage(MESSAGE_INFO, "CLog::AddMessage", text);
|
|
AddMessage(msg_info);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Saves messages to a file, writing them to the end. |
|
|
//+------------------------------------------------------------------+
|
|
bool CLog::SaveToFile(void)
|
|
{
|
|
int h = INVALID_HANDLE;
|
|
// Make 3 attempts to open the file with a delay of 200 ms.
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
h = FileOpen(GetName(), FILE_TXT | FILE_READ | FILE_WRITE | FILE_COMMON);
|
|
if (h != INVALID_HANDLE)
|
|
break;
|
|
Sleep(200);
|
|
}
|
|
if (h == INVALID_HANDLE)
|
|
{
|
|
string text = "Unable to open file " + GetName() + " Reason: " + (string)GetLastError();
|
|
CMessage *msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
|
|
m_log.AddMessage(msg);
|
|
return false;
|
|
}
|
|
FileSeek(h, 0, SEEK_END);
|
|
for (int i = 0; i < m_messages.Total(); i++)
|
|
{
|
|
CMessage *msg = m_messages.At(i);
|
|
FileWriteString(h, msg.ToCSVType() + "\n");
|
|
}
|
|
FileClose(h);
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generates a log name to record based on the current date. |
|
|
//+------------------------------------------------------------------+
|
|
string CLog::GetName(void)
|
|
{
|
|
string date = TimeToString(TimeCurrent(), TIME_DATE);
|
|
return "Logs\\log_" + date + ".txt";
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Removes logs with the creation date older than day_history |
|
|
//| days (default 30 days). |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::DeleteOldLogs(int day_history = 30)
|
|
{
|
|
string file_name = "";
|
|
string filter = "Logs\\log_*.txt";
|
|
long h = FileFindFirst(filter, file_name, FILE_COMMON);
|
|
DeleteOldLog("Logs\\" + file_name, day_history);
|
|
while (FileFindNext(h, file_name))
|
|
DeleteOldLog("Logs\\" + file_name, day_history);
|
|
int b = 2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Deletes a passed file if it is date is older than |
|
|
//| day_history days. |
|
|
//+------------------------------------------------------------------+
|
|
void CLog::DeleteOldLog(string file_name, int day_history)
|
|
{
|
|
int h = FileOpen(file_name, FILE_READ | FILE_BIN | FILE_COMMON);
|
|
if (h == INVALID_HANDLE)
|
|
return;
|
|
MqlDateTime dt;
|
|
TimeToStruct((datetime)FileGetInteger(h, FILE_ACCESS_DATE), dt);
|
|
int seconds = (int)(TimeCurrent() - FileGetInteger(h, FILE_CREATE_DATE));
|
|
FileClose(h);
|
|
int days = seconds / 3600 / 24;
|
|
if (days >= day_history)
|
|
FileDelete(file_name, FILE_COMMON);
|
|
}
|
|
//+------------------------------------------------------------------+
|