MtApi5: Added logger in RpcClient

This commit is contained in:
Viacheslav Demydiuk
2024-02-25 17:48:47 +02:00
parent 64c10415a7
commit 35812084d5
2 changed files with 183 additions and 111 deletions
+129 -81
View File
@@ -41,7 +41,7 @@ namespace MtApi5
#endregion #endregion
#region Public Methods #region Public Methods
private IMtLogger? Log { get; } private IMtLogger Log { get; }
public MtApi5Client(IMtLogger? log = null) public MtApi5Client(IMtLogger? log = null)
{ {
@@ -51,7 +51,7 @@ namespace MtApi5
_mtEventHandlers[Mt5EventTypes.OnLastTimeBar] = ReceivedOnLastTimeBarEvent; _mtEventHandlers[Mt5EventTypes.OnLastTimeBar] = ReceivedOnLastTimeBarEvent;
_mtEventHandlers[Mt5EventTypes.OnLockTicks] = ReceivedOnLockTicksEvent; _mtEventHandlers[Mt5EventTypes.OnLockTicks] = ReceivedOnLockTicksEvent;
Log = log; Log = log ?? new StubMtLogger();
} }
///<summary> ///<summary>
@@ -61,7 +61,7 @@ namespace MtApi5
///<param name="port">Port of host connection (default 8222) </param> ///<param name="port">Port of host connection (default 8222) </param>
public void BeginConnect(string host, int port) public void BeginConnect(string host, int port)
{ {
Log?.Info($"BeginConnect: host = {host}, port = {port}"); Log.Info($"BeginConnect: host = {host}, port = {port}");
Task.Factory.StartNew(() => Connect(host, port)); Task.Factory.StartNew(() => Connect(host, port));
} }
@@ -71,16 +71,69 @@ namespace MtApi5
///<param name="port">Port of host connection (default 8222) </param> ///<param name="port">Port of host connection (default 8222) </param>
public void BeginConnect(int port) public void BeginConnect(int port)
{ {
Log?.Info($"BeginConnect: port = localhost:{port}"); Log.Info($"BeginConnect: port = localhost:{port}");
Task.Factory.StartNew(() => Connect("localhost", port)); Task.Factory.StartNew(() => Connect("localhost", port));
} }
public async void Connect(string host, int port)
{
lock (_locker)
{
if (_connectionState == Mt5ConnectionState.Connected
|| _connectionState == Mt5ConnectionState.Connecting)
{
return;
}
_connectionState = Mt5ConnectionState.Connecting;
}
string message = $"Connect: connecting to {host}:{port}";
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connecting, message));
var client = new MtRpcClient(host, port, new RpcClientLogger(Log));
client.ExpertList += Client_ExpertList;
client.ExpertAdded += Client_ExpertAdded;
client.ExpertRemoved += Client_ExpertRemoved;
client.MtEventReceived += Client_MtEventReceived;
client.ConnectionFailed += Client_OnConnectionFailed;
client.Disconnected += Client_Disconnected;
var state = Mt5ConnectionState.Failed;
try
{
await client.Connect();
state = Mt5ConnectionState.Connected;
}
catch (Exception e)
{
Log.Warn($"Connect: Failed connection to {host}:{port}. {e.Message}");
}
Log.Info($"Connect: connection to {host}:{port} is {state}");
lock (_locker)
{
if (state == Mt5ConnectionState.Connected)
{
_client = client;
}
_connectionState = state;
}
if (state == Mt5ConnectionState.Connected)
OnConnected();
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(state, message));
}
///<summary> ///<summary>
///Disconnect from MetaTrader API. Async method. ///Disconnect from MetaTrader API. Async method.
///</summary> ///</summary>
public void BeginDisconnect() public void BeginDisconnect()
{ {
Log?.Info("BeginDisconnect called."); Log.Info("BeginDisconnect called.");
Task.Factory.StartNew(() => Disconnect(false)); Task.Factory.StartNew(() => Disconnect(false));
} }
@@ -121,11 +174,11 @@ namespace MtApi5
/// </returns> /// </returns>
public bool OrderSend(MqlTradeRequest request, out MqlTradeResult? result) public bool OrderSend(MqlTradeRequest request, out MqlTradeResult? result)
{ {
Log?.Debug($"OrderSend: request = {request}"); Log.Debug($"OrderSend: request = {request}");
if (request == null) if (request == null)
{ {
Log?.Warn("OrderSend: request is not defined!"); Log.Warn("OrderSend: request is not defined!");
result = null; result = null;
return false; return false;
} }
@@ -150,11 +203,11 @@ namespace MtApi5
/// </returns> /// </returns>
public bool OrderSendAsync(MqlTradeRequest request, out MqlTradeResult? result) public bool OrderSendAsync(MqlTradeRequest request, out MqlTradeResult? result)
{ {
Log?.Debug($"OrderSend: request = {request}"); Log.Debug($"OrderSend: request = {request}");
if (request == null) if (request == null)
{ {
Log?.Warn("OrderSend: request is not defined!"); Log.Warn("OrderSend: request is not defined!");
result = null; result = null;
return false; return false;
} }
@@ -230,11 +283,11 @@ namespace MtApi5
/// </returns> /// </returns>
public bool OrderCheck(MqlTradeRequest request, out MqlTradeCheckResult? result) public bool OrderCheck(MqlTradeRequest request, out MqlTradeCheckResult? result)
{ {
Log?.Debug($"OrderCheck: request = {request}"); Log.Debug($"OrderCheck: request = {request}");
if (request == null) if (request == null)
{ {
Log?.Warn("OrderCheck: request is not defined!"); Log.Warn("OrderCheck: request is not defined!");
result = null; result = null;
return false; return false;
} }
@@ -3272,59 +3325,6 @@ namespace MtApi5
} }
} }
public async void Connect(string host, int port)
{
lock (_locker)
{
if (_connectionState == Mt5ConnectionState.Connected
|| _connectionState == Mt5ConnectionState.Connecting)
{
return;
}
_connectionState = Mt5ConnectionState.Connecting;
}
string message = $"Connect: connecting to {host}:{port}";
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(Mt5ConnectionState.Connecting, message));
var client = new MtRpcClient(host, port);
client.ExpertList += Client_ExpertList;
client.ExpertAdded += Client_ExpertAdded;
client.ExpertRemoved += Client_ExpertRemoved;
client.MtEventReceived += Client_MtEventReceived;
client.ConnectionFailed += Client_OnConnectionFailed;
client.Disconnected += Client_Disconnected;
var state = Mt5ConnectionState.Failed;
try
{
await client.Connect();
state = Mt5ConnectionState.Connected;
}
catch (Exception e)
{
Log?.Warn($"Connect: Failed connection to {host}:{port}. {e.Message}");
}
Log?.Info($"Connect: connection to {host}:{port} is {state}");
lock (_locker)
{
if (state == Mt5ConnectionState.Connected)
{
_client = client;
}
_connectionState = state;
}
if (state == Mt5ConnectionState.Connected)
OnConnected();
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(state, message));
}
private void Client_MtEventReceived(object? sender, MtEventArgs e) private void Client_MtEventReceived(object? sender, MtEventArgs e)
{ {
Task.Run(() => _mtEventHandlers[(Mt5EventTypes)e.EventType](e.ExpertHandle, e.Payload)); Task.Run(() => _mtEventHandlers[(Mt5EventTypes)e.EventType](e.ExpertHandle, e.Payload));
@@ -3349,7 +3349,7 @@ namespace MtApi5
{ {
if (experts == null || experts.Count == 0) if (experts == null || experts.Count == 0)
{ {
Log?.Warn("ProcessExpertList: expert list invalid or empty"); Log.Warn("ProcessExpertList: expert list invalid or empty");
return; return;
} }
@@ -3382,7 +3382,7 @@ namespace MtApi5
private void ProcessExpertAdded(int handle) private void ProcessExpertAdded(int handle)
{ {
Log?.Debug($"ProcessExpertAdded: {handle}"); Log.Debug($"ProcessExpertAdded: {handle}");
bool added; bool added;
lock (_locker) lock (_locker)
@@ -3405,16 +3405,16 @@ namespace MtApi5
QuoteAdded?.Invoke(this, new Mt5QuoteEventArgs(quote)); QuoteAdded?.Invoke(this, new Mt5QuoteEventArgs(quote));
} }
else else
Log?.Warn($"ProcessExpertAdded: failed to get quote for expert {handle}"); Log.Warn($"ProcessExpertAdded: failed to get quote for expert {handle}");
} }
else else
Log?.Warn($"ProcessExpertAdded: expert handle {handle} is already exist"); Log.Warn($"ProcessExpertAdded: expert handle {handle} is already exist");
} }
private void ProcessExpertRemoved(int handle) private void ProcessExpertRemoved(int handle)
{ {
Log?.Debug($"ProcessExpertRemoved: {handle}"); Log.Debug($"ProcessExpertRemoved: {handle}");
Mt5Quote? quote = null; Mt5Quote? quote = null;
lock (_locker) lock (_locker)
@@ -3433,7 +3433,7 @@ namespace MtApi5
private Mt5Quote? GetQuote(int expertHandle) private Mt5Quote? GetQuote(int expertHandle)
{ {
Log?.Debug($"GetQuote: expertHandle = {expertHandle}"); Log.Debug($"GetQuote: expertHandle = {expertHandle}");
var e = SendCommand<MtQuote>(expertHandle, Mt5CommandType.GetQuote); var e = SendCommand<MtQuote>(expertHandle, Mt5CommandType.GetQuote);
if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Tick == null) if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Tick == null)
@@ -3455,13 +3455,13 @@ namespace MtApi5
private void Client_OnConnectionFailed(object? sender, EventArgs e) private void Client_OnConnectionFailed(object? sender, EventArgs e)
{ {
Log?.Info("Received connection failed"); Log.Info("Received connection failed");
Disconnect(true); Disconnect(true);
} }
private void Client_Disconnected(object? sender, EventArgs e) private void Client_Disconnected(object? sender, EventArgs e)
{ {
Log?.Info("Received normal disconnection"); Log.Info("Received normal disconnection");
Disconnect(false); Disconnect(false);
} }
@@ -3552,7 +3552,7 @@ namespace MtApi5
client?.Disconnect(); client?.Disconnect();
Log?.Info(message); Log.Info(message);
ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(state, message)); ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(state, message));
} }
@@ -3562,33 +3562,33 @@ namespace MtApi5
var client = Client; var client = Client;
if (client == null) if (client == null)
{ {
Log?.Warn("SendCommand: No connection"); Log.Warn("SendCommand: No connection");
throw new Exception("No connection"); throw new Exception("No connection");
} }
var payloadJson = payload == null ? string.Empty : JsonConvert.SerializeObject(payload); var payloadJson = payload == null ? string.Empty : JsonConvert.SerializeObject(payload);
Log?.Debug($"SendCommand: sending '{payloadJson}' ..."); Log.Debug($"SendCommand: sending '{payloadJson}' ...");
var responseJson = client.SendCommand(expertHandle, (int)commandType, payloadJson); var responseJson = client.SendCommand(expertHandle, (int)commandType, payloadJson);
Log?.Debug($"SendCommand: received response JSON [{responseJson}]"); Log.Debug($"SendCommand: received response JSON [{responseJson}]");
if (string.IsNullOrEmpty(responseJson)) if (string.IsNullOrEmpty(responseJson))
{ {
Log?.Warn("SendCommand: Response JSON from MetaTrader is null or empty"); Log.Warn("SendCommand: Response JSON from MetaTrader is null or empty");
throw new ExecutionException(ErrorCode.ErrCustom, "Response from MetaTrader is null"); throw new ExecutionException(ErrorCode.ErrCustom, "Response from MetaTrader is null");
} }
var response = JsonConvert.DeserializeObject<Response<T>>(responseJson); var response = JsonConvert.DeserializeObject<Response<T>>(responseJson);
if (response == null) if (response == null)
{ {
Log?.Warn("SendCommand: Failed to deserialize response from JSON"); Log.Warn("SendCommand: Failed to deserialize response from JSON");
throw new ExecutionException(ErrorCode.ErrCustom, "Response from MetaTrader is null"); throw new ExecutionException(ErrorCode.ErrCustom, "Response from MetaTrader is null");
} }
if (response.ErrorCode != 0) if (response.ErrorCode != 0)
{ {
Log?.Warn($"SendCommand: ErrorCode = {response.ErrorCode}. {response.ErrorMessage}"); Log.Warn($"SendCommand: ErrorCode = {response.ErrorCode}. {response.ErrorMessage}");
throw new ExecutionException((ErrorCode)response.ErrorCode, response.ErrorMessage); throw new ExecutionException((ErrorCode)response.ErrorCode, response.ErrorMessage);
} }
@@ -3597,11 +3597,11 @@ namespace MtApi5
private void OnConnected() private void OnConnected()
{ {
Log?.Debug("OnConnected: begin"); Log.Debug("OnConnected: begin");
Client?.NotifyClientReady(); Client?.NotifyClientReady();
Log?.Debug("OnConnected: finished"); Log.Debug("OnConnected: finished");
} }
private void BacktestingReady() private void BacktestingReady()
@@ -3610,4 +3610,52 @@ namespace MtApi5
} }
#endregion #endregion
} }
internal class RpcClientLogger(IMtLogger logger) : IRpcLogger
{
public void Debug(string message)
{
logger_.Debug(message);
}
public void Error(string message)
{
logger_.Debug(message);
}
public void Info(string message)
{
logger_.Debug(message);
}
public void Warn(string message)
{
logger_.Debug(message);
}
private readonly IMtLogger logger_ = logger;
}
internal class StubMtLogger : IMtLogger
{
public void Debug(object message)
{
}
public void Error(object message)
{
}
public void Fatal(object message)
{
}
public void Info(object message)
{
}
public void Warn(object message)
{
}
}
} }
+54 -30
View File
@@ -3,12 +3,21 @@ using System.Text;
namespace MtClient namespace MtClient
{ {
public interface IRpcLogger
{
public void Debug(string message);
public void Info(string message);
public void Warn(string message);
public void Error(string message);
}
public class MtRpcClient public class MtRpcClient
{ {
public MtRpcClient(string host, int port) public MtRpcClient(string host, int port, IRpcLogger? logger = null)
{ {
host_ = host; host_ = host;
port_ = port; port_ = port;
logger_ = logger ?? new StubLogger();
receiveThread_ = new Thread(new ThreadStart(DoReceive)); receiveThread_ = new Thread(new ThreadStart(DoReceive));
sendThread_ = new Thread(new ThreadStart(DoWrite)); sendThread_ = new Thread(new ThreadStart(DoWrite));
@@ -16,7 +25,7 @@ namespace MtClient
public async Task Connect() public async Task Connect()
{ {
Log($"Connect: started to {host_}:{port_}"); logger_.Debug($"MtRpcClient.Connect: started to {host_}:{port_}");
try try
{ {
@@ -24,19 +33,19 @@ namespace MtClient
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"Connect failed: {ex.Message}"); logger_.Error($"MtRpcClient.Connect failed: {ex.Message}");
throw new Exception($"Failed connection to {host_}:{port_}"); throw new Exception($"Failed connection to {host_}:{port_}");
} }
receiveThread_.Start(); receiveThread_.Start();
sendThread_.Start(); sendThread_.Start();
Log($"Connect: success."); logger_.Debug($"MtRpcClient.Connect: success.");
} }
public async void Disconnect() public async void Disconnect()
{ {
Log($"Disconnect: {host_}:{port_}"); logger_.Debug($"MtRpcClient.Disconnect: {host_}:{port_}");
try try
{ {
@@ -46,33 +55,33 @@ namespace MtClient
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"Disconnect: {ex.Message}"); logger_.Warn($"MtRpcClient.Disconnect: {ex.Message}");
} }
sendWaiter_.Set(); sendWaiter_.Set();
sendThread_.Join(); sendThread_.Join();
receiveThread_.Join(); receiveThread_.Join();
Log($"Disconnect: success"); logger_.Debug($"MtRpcClient.Disconnect: success");
} }
public string? SendCommand(int expertHandle, int commandType, string payload) public string? SendCommand(int expertHandle, int commandType, string payload)
{ {
CommandTask commandTask = new(); CommandTask commandTask = new();
int commandId; int commandId;
lock (_tasks) lock (tasks_)
{ {
commandId = nextCommandId++; commandId = nextCommandId++;
_tasks[commandId] = commandTask; tasks_[commandId] = commandTask;
} }
MtCommand command = new(expertHandle, commandType, commandId, payload); MtCommand command = new(expertHandle, commandType, commandId, payload);
Send(command); Send(command);
var response = commandTask.WaitResponse(10000); // 10 sec var response = commandTask.WaitResponse(10000); // 10 sec
lock (_tasks) lock (tasks_)
{ {
_tasks.Remove(commandId); tasks_.Remove(commandId);
} }
return response; return response;
@@ -113,13 +122,13 @@ namespace MtClient
try try
{ {
string msgStr = message.Serialize(); string msgStr = message.Serialize();
Log($"DoWrite: sending message: {msgStr}"); logger_.Debug($"MtRpcClient.DoWrite: sending message: {msgStr}");
byte[] bytes = Encoding.ASCII.GetBytes(msgStr); byte[] bytes = Encoding.ASCII.GetBytes(msgStr);
await ws_.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None); await ws_.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
} }
catch (Exception e) catch (Exception e)
{ {
Log($"DoWrite: {e.Message}"); logger_.Error($"MtRpcClient.DoWrite: {e.Message}");
} }
} }
} }
@@ -134,8 +143,7 @@ namespace MtClient
var result = await ws_.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None); var result = await ws_.ReceiveAsync(new ArraySegment<byte>(recvBuffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close) if (result.MessageType == WebSocketMessageType.Close)
{ {
Log($"DoReceive: close signal {result.CloseStatusDescription}"); logger_.Info($"MtRpcClient.DoReceive: close signal {result.CloseStatusDescription}");
await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
Disconnected?.Invoke(this, EventArgs.Empty); Disconnected?.Invoke(this, EventArgs.Empty);
break; break;
} }
@@ -148,18 +156,18 @@ namespace MtClient
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"Exception in receive - {ex.Message}"); logger_.Warn($"MtRpcClient.DoReceive: Exception in receive - {ex.Message}");
ConnectionFailed?.Invoke(this, EventArgs.Empty); ConnectionFailed?.Invoke(this, EventArgs.Empty);
} }
} }
private void OnReceive(string msg) private void OnReceive(string msg)
{ {
Log($"OnReceive: {msg}"); logger_.Debug($"MtRpcClient.OnReceive: {msg}");
if (string.IsNullOrEmpty(msg)) if (string.IsNullOrEmpty(msg))
{ {
Log("OnReceive: Invalid message (null or empty)"); logger_.Warn("MtRpcClient.OnReceive: Invalid message (null or empty)");
return; return;
} }
@@ -169,7 +177,7 @@ namespace MtClient
|| string.IsNullOrEmpty(pieces[0]) || string.IsNullOrEmpty(pieces[0])
|| string.IsNullOrEmpty(pieces[1])) || string.IsNullOrEmpty(pieces[1]))
{ {
Log("OnReceive: Invalid message format."); logger_.Warn("MtRpcClient.OnReceive: Invalid message format.");
return; return;
} }
@@ -182,14 +190,14 @@ namespace MtClient
} }
catch (Exception e) catch (Exception e)
{ {
Log($"OnReceive: Parse MessageType failed. {e.Message}"); logger_.Error($"MtRpcClient.OnReceive: Parse MessageType failed. {e.Message}");
return; return;
} }
var message = MtMessageParser.Parse(msgType, (pieces[1])); var message = MtMessageParser.Parse(msgType, (pieces[1]));
if (message == null) if (message == null)
{ {
Log("OnReceive: Failed parse message payload"); logger_.Error("MtRpcClient.OnReceive: Failed parse message payload");
return; return;
} }
@@ -224,20 +232,15 @@ namespace MtClient
var commandId = msg.CommandId; var commandId = msg.CommandId;
var payload = msg.Payload; var payload = msg.Payload;
Log($"ProcessResponse: {handle}, {commandId}, [{payload}]"); logger_.Debug($"MtRpcClient.ProcessResponse: {handle}, {commandId}, [{payload}]");
lock (_tasks) lock (tasks_)
{ {
if (_tasks.TryGetValue(commandId, out CommandTask? value)) if (tasks_.TryGetValue(commandId, out CommandTask? value))
value.SetResponse(payload); value.SetResponse(payload);
} }
} }
private void Log(string msg)
{
Console.WriteLine($"[{Environment.CurrentManagedThreadId}] {msg}");
}
public event EventHandler<EventArgs>? ConnectionFailed; public event EventHandler<EventArgs>? ConnectionFailed;
public event EventHandler<EventArgs>? Disconnected; public event EventHandler<EventArgs>? Disconnected;
public event EventHandler<MtExpertListEventArgs>? ExpertList; public event EventHandler<MtExpertListEventArgs>? ExpertList;
@@ -256,7 +259,9 @@ namespace MtClient
private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false); private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false);
private int nextCommandId = 0; private int nextCommandId = 0;
private readonly Dictionary<int, CommandTask> _tasks = []; private readonly Dictionary<int, CommandTask> tasks_ = [];
private readonly IRpcLogger logger_;
} }
internal class CommandTask internal class CommandTask
@@ -300,4 +305,23 @@ namespace MtClient
{ {
public int Expert { get; }= expert; public int Expert { get; }= expert;
} }
internal class StubLogger : IRpcLogger
{
public void Debug(string message)
{
}
public void Error(string message)
{
}
public void Info(string message)
{
}
public void Warn(string message)
{
}
}
} }