From 35812084d532218b231ca336f6791a14e9a054f9 Mon Sep 17 00:00:00 2001 From: Viacheslav Demydiuk Date: Sun, 25 Feb 2024 17:48:47 +0200 Subject: [PATCH] MtApi5: Added logger in RpcClient --- MtApi5/MtApi5Client.cs | 210 ++++++++++++++++++++++++---------------- MtClient/MtRpcClient.cs | 84 ++++++++++------ 2 files changed, 183 insertions(+), 111 deletions(-) diff --git a/MtApi5/MtApi5Client.cs b/MtApi5/MtApi5Client.cs index 0b41961a..8b75d0fb 100755 --- a/MtApi5/MtApi5Client.cs +++ b/MtApi5/MtApi5Client.cs @@ -41,7 +41,7 @@ namespace MtApi5 #endregion #region Public Methods - private IMtLogger? Log { get; } + private IMtLogger Log { get; } public MtApi5Client(IMtLogger? log = null) { @@ -51,7 +51,7 @@ namespace MtApi5 _mtEventHandlers[Mt5EventTypes.OnLastTimeBar] = ReceivedOnLastTimeBarEvent; _mtEventHandlers[Mt5EventTypes.OnLockTicks] = ReceivedOnLockTicksEvent; - Log = log; + Log = log ?? new StubMtLogger(); } /// @@ -61,7 +61,7 @@ namespace MtApi5 ///Port of host connection (default 8222) 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)); } @@ -71,16 +71,69 @@ namespace MtApi5 ///Port of host connection (default 8222) public void BeginConnect(int port) { - Log?.Info($"BeginConnect: port = localhost:{port}"); + Log.Info($"BeginConnect: port = 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)); + } + /// ///Disconnect from MetaTrader API. Async method. /// public void BeginDisconnect() { - Log?.Info("BeginDisconnect called."); + Log.Info("BeginDisconnect called."); Task.Factory.StartNew(() => Disconnect(false)); } @@ -121,11 +174,11 @@ namespace MtApi5 /// public bool OrderSend(MqlTradeRequest request, out MqlTradeResult? result) { - Log?.Debug($"OrderSend: request = {request}"); + Log.Debug($"OrderSend: request = {request}"); if (request == null) { - Log?.Warn("OrderSend: request is not defined!"); + Log.Warn("OrderSend: request is not defined!"); result = null; return false; } @@ -150,11 +203,11 @@ namespace MtApi5 /// public bool OrderSendAsync(MqlTradeRequest request, out MqlTradeResult? result) { - Log?.Debug($"OrderSend: request = {request}"); + Log.Debug($"OrderSend: request = {request}"); if (request == null) { - Log?.Warn("OrderSend: request is not defined!"); + Log.Warn("OrderSend: request is not defined!"); result = null; return false; } @@ -230,11 +283,11 @@ namespace MtApi5 /// public bool OrderCheck(MqlTradeRequest request, out MqlTradeCheckResult? result) { - Log?.Debug($"OrderCheck: request = {request}"); + Log.Debug($"OrderCheck: request = {request}"); if (request == null) { - Log?.Warn("OrderCheck: request is not defined!"); + Log.Warn("OrderCheck: request is not defined!"); result = null; 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) { Task.Run(() => _mtEventHandlers[(Mt5EventTypes)e.EventType](e.ExpertHandle, e.Payload)); @@ -3349,7 +3349,7 @@ namespace MtApi5 { if (experts == null || experts.Count == 0) { - Log?.Warn("ProcessExpertList: expert list invalid or empty"); + Log.Warn("ProcessExpertList: expert list invalid or empty"); return; } @@ -3382,7 +3382,7 @@ namespace MtApi5 private void ProcessExpertAdded(int handle) { - Log?.Debug($"ProcessExpertAdded: {handle}"); + Log.Debug($"ProcessExpertAdded: {handle}"); bool added; lock (_locker) @@ -3405,16 +3405,16 @@ namespace MtApi5 QuoteAdded?.Invoke(this, new Mt5QuoteEventArgs(quote)); } else - Log?.Warn($"ProcessExpertAdded: failed to get quote for expert {handle}"); + Log.Warn($"ProcessExpertAdded: failed to get quote for expert {handle}"); } else - Log?.Warn($"ProcessExpertAdded: expert handle {handle} is already exist"); + Log.Warn($"ProcessExpertAdded: expert handle {handle} is already exist"); } private void ProcessExpertRemoved(int handle) { - Log?.Debug($"ProcessExpertRemoved: {handle}"); + Log.Debug($"ProcessExpertRemoved: {handle}"); Mt5Quote? quote = null; lock (_locker) @@ -3433,7 +3433,7 @@ namespace MtApi5 private Mt5Quote? GetQuote(int expertHandle) { - Log?.Debug($"GetQuote: expertHandle = {expertHandle}"); + Log.Debug($"GetQuote: expertHandle = {expertHandle}"); var e = SendCommand(expertHandle, Mt5CommandType.GetQuote); if (e == null || string.IsNullOrEmpty(e.Instrument) || e.Tick == null) @@ -3455,13 +3455,13 @@ namespace MtApi5 private void Client_OnConnectionFailed(object? sender, EventArgs e) { - Log?.Info("Received connection failed"); + Log.Info("Received connection failed"); Disconnect(true); } private void Client_Disconnected(object? sender, EventArgs e) { - Log?.Info("Received normal disconnection"); + Log.Info("Received normal disconnection"); Disconnect(false); } @@ -3552,7 +3552,7 @@ namespace MtApi5 client?.Disconnect(); - Log?.Info(message); + Log.Info(message); ConnectionStateChanged?.Invoke(this, new Mt5ConnectionEventArgs(state, message)); } @@ -3562,33 +3562,33 @@ namespace MtApi5 var client = Client; if (client == null) { - Log?.Warn("SendCommand: No connection"); + Log.Warn("SendCommand: No connection"); throw new Exception("No connection"); } 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); - Log?.Debug($"SendCommand: received response JSON [{responseJson}]"); + Log.Debug($"SendCommand: received response JSON [{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"); } var response = JsonConvert.DeserializeObject>(responseJson); 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"); } 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); } @@ -3597,11 +3597,11 @@ namespace MtApi5 private void OnConnected() { - Log?.Debug("OnConnected: begin"); + Log.Debug("OnConnected: begin"); Client?.NotifyClientReady(); - Log?.Debug("OnConnected: finished"); + Log.Debug("OnConnected: finished"); } private void BacktestingReady() @@ -3610,4 +3610,52 @@ namespace MtApi5 } #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) + { + } + } } diff --git a/MtClient/MtRpcClient.cs b/MtClient/MtRpcClient.cs index c5f08dca..1239fb80 100755 --- a/MtClient/MtRpcClient.cs +++ b/MtClient/MtRpcClient.cs @@ -3,12 +3,21 @@ using System.Text; 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 MtRpcClient(string host, int port) + public MtRpcClient(string host, int port, IRpcLogger? logger = null) { host_ = host; port_ = port; + logger_ = logger ?? new StubLogger(); receiveThread_ = new Thread(new ThreadStart(DoReceive)); sendThread_ = new Thread(new ThreadStart(DoWrite)); @@ -16,7 +25,7 @@ namespace MtClient public async Task Connect() { - Log($"Connect: started to {host_}:{port_}"); + logger_.Debug($"MtRpcClient.Connect: started to {host_}:{port_}"); try { @@ -24,19 +33,19 @@ namespace MtClient } catch (Exception ex) { - Log($"Connect failed: {ex.Message}"); + logger_.Error($"MtRpcClient.Connect failed: {ex.Message}"); throw new Exception($"Failed connection to {host_}:{port_}"); } receiveThread_.Start(); sendThread_.Start(); - Log($"Connect: success."); + logger_.Debug($"MtRpcClient.Connect: success."); } public async void Disconnect() { - Log($"Disconnect: {host_}:{port_}"); + logger_.Debug($"MtRpcClient.Disconnect: {host_}:{port_}"); try { @@ -46,33 +55,33 @@ namespace MtClient } catch (Exception ex) { - Log($"Disconnect: {ex.Message}"); + logger_.Warn($"MtRpcClient.Disconnect: {ex.Message}"); } sendWaiter_.Set(); sendThread_.Join(); receiveThread_.Join(); - Log($"Disconnect: success"); + logger_.Debug($"MtRpcClient.Disconnect: success"); } public string? SendCommand(int expertHandle, int commandType, string payload) { CommandTask commandTask = new(); int commandId; - lock (_tasks) + lock (tasks_) { commandId = nextCommandId++; - _tasks[commandId] = commandTask; + tasks_[commandId] = commandTask; } MtCommand command = new(expertHandle, commandType, commandId, payload); Send(command); var response = commandTask.WaitResponse(10000); // 10 sec - lock (_tasks) + lock (tasks_) { - _tasks.Remove(commandId); + tasks_.Remove(commandId); } return response; @@ -113,13 +122,13 @@ namespace MtClient try { string msgStr = message.Serialize(); - Log($"DoWrite: sending message: {msgStr}"); + logger_.Debug($"MtRpcClient.DoWrite: sending message: {msgStr}"); byte[] bytes = Encoding.ASCII.GetBytes(msgStr); await ws_.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None); } 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(recvBuffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) { - Log($"DoReceive: close signal {result.CloseStatusDescription}"); - await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + logger_.Info($"MtRpcClient.DoReceive: close signal {result.CloseStatusDescription}"); Disconnected?.Invoke(this, EventArgs.Empty); break; } @@ -148,18 +156,18 @@ namespace MtClient } catch (Exception ex) { - Log($"Exception in receive - {ex.Message}"); + logger_.Warn($"MtRpcClient.DoReceive: Exception in receive - {ex.Message}"); ConnectionFailed?.Invoke(this, EventArgs.Empty); } } private void OnReceive(string msg) { - Log($"OnReceive: {msg}"); + logger_.Debug($"MtRpcClient.OnReceive: {msg}"); if (string.IsNullOrEmpty(msg)) { - Log("OnReceive: Invalid message (null or empty)"); + logger_.Warn("MtRpcClient.OnReceive: Invalid message (null or empty)"); return; } @@ -169,7 +177,7 @@ namespace MtClient || string.IsNullOrEmpty(pieces[0]) || string.IsNullOrEmpty(pieces[1])) { - Log("OnReceive: Invalid message format."); + logger_.Warn("MtRpcClient.OnReceive: Invalid message format."); return; } @@ -182,14 +190,14 @@ namespace MtClient } catch (Exception e) { - Log($"OnReceive: Parse MessageType failed. {e.Message}"); + logger_.Error($"MtRpcClient.OnReceive: Parse MessageType failed. {e.Message}"); return; } var message = MtMessageParser.Parse(msgType, (pieces[1])); if (message == null) { - Log("OnReceive: Failed parse message payload"); + logger_.Error("MtRpcClient.OnReceive: Failed parse message payload"); return; } @@ -224,20 +232,15 @@ namespace MtClient var commandId = msg.CommandId; 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); } } - private void Log(string msg) - { - Console.WriteLine($"[{Environment.CurrentManagedThreadId}] {msg}"); - } - public event EventHandler? ConnectionFailed; public event EventHandler? Disconnected; public event EventHandler? ExpertList; @@ -256,7 +259,9 @@ namespace MtClient private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false); private int nextCommandId = 0; - private readonly Dictionary _tasks = []; + private readonly Dictionary tasks_ = []; + + private readonly IRpcLogger logger_; } internal class CommandTask @@ -300,4 +305,23 @@ namespace MtClient { 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) + { + } + } } \ No newline at end of file