diff --git a/.travis.yml b/.travis.yml index 602a3f5..da48bb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,6 @@ deploy: provider: script skip_cleanup: true script: - - cd src/Websocket.Client && dotnet pack /p:PackageVersion=1.0.$TRAVIS_BUILD_NUMBER -c Release && cd bin/Release && dotnet nuget push **/*.1.0.$TRAVIS_BUILD_NUMBER.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json + - cd src/Websocket.Client && dotnet pack /p:PackageVersion=2.0.$TRAVIS_BUILD_NUMBER -c Release && cd bin/Release && dotnet nuget push **/*.2.0.$TRAVIS_BUILD_NUMBER.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json on: branch: master \ No newline at end of file diff --git a/README.md b/README.md index fdb93a9..7b58bf6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is a wrapper over native C# class `ClientWebSocket` with built-in reconnect * installation via NuGet ([Websocket.Client](https://www.nuget.org/packages/Websocket.Client)) * targeting .NET Standard 2.0 (.NET Core, Linux/MacOS compatible) * reactive extensions ([Rx.NET](https://github.com/Reactive-Extensions/Rx.NET)) -* integrated logging ([Serilog](https://serilog.net/)) +* integrated logging abstraction ([LibLog](https://github.com/damianh/LibLog)) ### Usage diff --git a/Websocket.Client.sln.DotSettings b/Websocket.Client.sln.DotSettings new file mode 100644 index 0000000..c232c2e --- /dev/null +++ b/Websocket.Client.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Websocket.Client/IWebsocketClient.cs b/src/Websocket.Client/IWebsocketClient.cs index 4c7cd03..540579b 100644 --- a/src/Websocket.Client/IWebsocketClient.cs +++ b/src/Websocket.Client/IWebsocketClient.cs @@ -35,6 +35,12 @@ public interface IWebsocketClient : IDisposable /// int ErrorReconnectTimeoutMs { get; set; } + /// + /// Get or set the name of the current websocket client instance. + /// For logging purpose (in case you use more parallel websocket clients and want to distinguish between them) + /// + string Name { get; set;} + /// /// Returns true if Start() method was called at least once. False if not started or disposed /// diff --git a/src/Websocket.Client/Websocket.Client.csproj b/src/Websocket.Client/Websocket.Client.csproj index 3313f36..7fd7568 100644 --- a/src/Websocket.Client/Websocket.Client.csproj +++ b/src/Websocket.Client/Websocket.Client.csproj @@ -3,11 +3,11 @@ netstandard2.0 Websocket.Client - 1.0.0 + 2.0.0 Mariusz Kotas Client for websocket API with built-in reconnection and error handling false - Initial release + Release of version 2.0 Copyright 2018 Mariusz Kotas. All rights reserved. websockets websocket client https://github.com/Marfusios/Websocket.Client/blob/master/LICENSE @@ -17,12 +17,15 @@ Git true true - 1.0.0.0 - 1.0.0.0 + 2.0.0.0 + 2.0.0.0 - + + all + runtime; build; native; contentfiles; analyzers + diff --git a/src/Websocket.Client/WebsocketClient.cs b/src/Websocket.Client/WebsocketClient.cs index aecf2a5..3854e2a 100644 --- a/src/Websocket.Client/WebsocketClient.cs +++ b/src/Websocket.Client/WebsocketClient.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Serilog; +using Websocket.Client.Logging; namespace Websocket.Client { @@ -15,6 +15,8 @@ namespace Websocket.Client /// public class WebsocketClient : IWebsocketClient { + private static readonly ILog Logger = LogProvider.GetCurrentClassLogger(); + private readonly Uri _url; private Timer _lastChanceTimer; private readonly Func _clientFactory; @@ -23,8 +25,8 @@ public class WebsocketClient : IWebsocketClient private bool _disposing = false; private ClientWebSocket _client; - private CancellationTokenSource _cancelation; - private CancellationTokenSource _cancelationTotal; + private CancellationTokenSource _cancellation; + private CancellationTokenSource _cancellationTotal; private readonly Subject _messageReceivedSubject = new Subject(); private readonly Subject _reconnectionSubject = new Subject(); @@ -50,12 +52,12 @@ public WebsocketClient(Uri url, Func clientFactory = null) public IObservable MessageReceived => _messageReceivedSubject.AsObservable(); /// - /// Stream for reconnection event (trigerred after the new connection) + /// Stream for reconnection event (triggered after the new connection) /// public IObservable ReconnectionHappened => _reconnectionSubject.AsObservable(); /// - /// Stream for disconnection event (trigerred after the connection was lost) + /// Stream for disconnection event (triggered after the connection was lost) /// public IObservable DisconnectionHappened => _disconnectedSubject.AsObservable(); @@ -71,6 +73,12 @@ public WebsocketClient(Uri url, Func clientFactory = null) /// public int ErrorReconnectTimeoutMs { get; set; } = 60 * 1000; + /// + /// Get or set the name of the current websocket client instance. + /// For logging purpose (in case you use more parallel websocket clients and want to distinguish between them) + /// + public string Name { get; set;} + /// /// Returns true if Start() method was called at least once. False if not started or disposed /// @@ -87,21 +95,21 @@ public WebsocketClient(Uri url, Func clientFactory = null) public void Dispose() { _disposing = true; - Log.Debug(L("Disposing..")); + Logger.Debug(L("Disposing..")); try { _lastChanceTimer?.Dispose(); - _cancelation?.Cancel(); - _cancelationTotal?.Cancel(); + _cancellation?.Cancel(); + _cancellationTotal?.Cancel(); _client?.Abort(); _client?.Dispose(); - _cancelation?.Dispose(); - _cancelationTotal?.Dispose(); + _cancellation?.Dispose(); + _cancellationTotal?.Dispose(); _messagesToSendQueue?.Dispose(); } catch (Exception e) { - Log.Error(e, L($"Failed to dispose client, error: {e.Message}")); + Logger.Error(e, L($"Failed to dispose client, error: {e.Message}")); } IsStarted = false; @@ -115,16 +123,16 @@ public async Task Start() { if (IsStarted) { - Log.Debug(L("Client already started, ignoring..")); + Logger.Debug(L("Client already started, ignoring..")); return; } IsStarted = true; - Log.Debug(L("Starting..")); - _cancelation = new CancellationTokenSource(); - _cancelationTotal = new CancellationTokenSource(); + Logger.Debug(L("Starting..")); + _cancellation = new CancellationTokenSource(); + _cancellationTotal = new CancellationTokenSource(); - await StartClient(_url, _cancelation.Token, ReconnectionType.Initial).ConfigureAwait(false); + await StartClient(_url, _cancellation.Token, ReconnectionType.Initial).ConfigureAwait(false); StartBackgroundThreadForSending(); } @@ -164,7 +172,7 @@ public async Task Reconnect() { if (!IsStarted) { - Log.Debug(L("Client not started, ignoring reconnection..")); + Logger.Debug(L("Client not started, ignoring reconnection..")); return; } await Reconnect(ReconnectionType.ByUser).ConfigureAwait(false); @@ -174,7 +182,7 @@ private async Task SendFromQueue() { try { - foreach (var message in _messagesToSendQueue.GetConsumingEnumerable(_cancelationTotal.Token)) + foreach (var message in _messagesToSendQueue.GetConsumingEnumerable(_cancellationTotal.Token)) { try { @@ -182,7 +190,7 @@ private async Task SendFromQueue() } catch (Exception e) { - Log.Error(L($"Failed to send message: '{message}'. Error: {e.Message}")); + Logger.Error(L($"Failed to send message: '{message}'. Error: {e.Message}")); } } } @@ -192,7 +200,7 @@ private async Task SendFromQueue() } catch (Exception e) { - if (_cancelationTotal.IsCancellationRequested || _disposing) + if (_cancellationTotal.IsCancellationRequested || _disposing) { // disposing/canceling, do nothing and exit return; @@ -206,22 +214,22 @@ private async Task SendFromQueue() private void StartBackgroundThreadForSending() { #pragma warning disable 4014 - Task.Factory.StartNew(_ => SendFromQueue(), TaskCreationOptions.LongRunning, _cancelationTotal.Token); + Task.Factory.StartNew(_ => SendFromQueue(), TaskCreationOptions.LongRunning, _cancellationTotal.Token); #pragma warning restore 4014 } private async Task SendInternal(string message) { - Log.Verbose(L($"Sending: {message}")); + Logger.Trace(L($"Sending: {message}")); var buffer = Encoding.UTF8.GetBytes(message); var messageSegment = new ArraySegment(buffer); var client = await GetClient().ConfigureAwait(false); - await client.SendAsync(messageSegment, WebSocketMessageType.Text, true, _cancelation.Token).ConfigureAwait(false); + await client.SendAsync(messageSegment, WebSocketMessageType.Text, true, _cancellation.Token).ConfigureAwait(false); } private async Task StartClient(Uri uri, CancellationToken token, ReconnectionType type) { - DeactiveLastChance(); + DeactivateLastChance(); _client = _clientFactory(); try @@ -237,7 +245,7 @@ private async Task StartClient(Uri uri, CancellationToken token, ReconnectionTyp catch (Exception e) { _disconnectedSubject.OnNext(DisconnectionType.Error); - Log.Error(e, L("Exception while connecting. " + + Logger.Error(e, L("Exception while connecting. " + $"Waiting {ErrorReconnectTimeoutMs/1000} sec before next reconnection try.")); await Task.Delay(ErrorReconnectTimeoutMs, token).ConfigureAwait(false); await Reconnect(ReconnectionType.Error).ConfigureAwait(false); @@ -261,12 +269,12 @@ private async Task Reconnect(ReconnectionType type) if(type != ReconnectionType.Error) _disconnectedSubject.OnNext(TranslateTypeToDisconnection(type)); - Log.Debug(L("Reconnecting...")); - _cancelation.Cancel(); + Logger.Debug(L("Reconnecting...")); + _cancellation.Cancel(); await Task.Delay(1000).ConfigureAwait(false); - _cancelation = new CancellationTokenSource(); - await StartClient(_url, _cancelation.Token, type).ConfigureAwait(false); + _cancellation = new CancellationTokenSource(); + await StartClient(_url, _cancellation.Token, type).ConfigureAwait(false); } private async Task Listen(ClientWebSocket client, CancellationToken token) @@ -290,7 +298,7 @@ private async Task Listen(ClientWebSocket client, CancellationToken token) } while (!result.EndOfMessage); var received = resultMessage.ToString(); - Log.Verbose(L($"Received: {received}")); + Logger.Trace(L($"Received: {received}")); _lastReceivedMsg = DateTime.UtcNow; _messageReceivedSubject.OnNext(received); @@ -302,7 +310,7 @@ private async Task Listen(ClientWebSocket client, CancellationToken token) } catch (Exception e) { - Log.Error(e, L("Error while listening to websocket stream")); + Logger.Error(e, L("Error while listening to websocket stream")); } } @@ -312,7 +320,7 @@ private void ActivateLastChance() _lastChanceTimer = new Timer(LastChance, null, timerMs, timerMs); } - private void DeactiveLastChance() + private void DeactivateLastChance() { _lastChanceTimer?.Dispose(); _lastChanceTimer = null; @@ -324,9 +332,9 @@ private void LastChance(object state) var diffMs = Math.Abs(DateTime.UtcNow.Subtract(_lastReceivedMsg).TotalMilliseconds); if (diffMs > timeoutMs) { - Log.Debug(L($"Last message received more than {timeoutMs:F} ms ago. Hard restart..")); + Logger.Debug(L($"Last message received more than {timeoutMs:F} ms ago. Hard restart..")); - DeactiveLastChance(); + DeactivateLastChance(); _client?.Abort(); _client?.Dispose(); #pragma warning disable 4014 @@ -337,12 +345,13 @@ private void LastChance(object state) private string L(string msg) { - return $"[WEBSOCKET CLIENT] {msg}"; + var name = Name ?? "CLIENT"; + return $"[WEBSOCKET {name}] {msg}"; } private DisconnectionType TranslateTypeToDisconnection(ReconnectionType type) { - // beaware enum indexes must correspond to each other + // beware enum indexes must correspond to each other return (DisconnectionType) type; } } diff --git a/test_integration/Websocket.Client.Sample.NetFramework/Program.cs b/test_integration/Websocket.Client.Sample.NetFramework/Program.cs index 3c34ce6..a308110 100644 --- a/test_integration/Websocket.Client.Sample.NetFramework/Program.cs +++ b/test_integration/Websocket.Client.Sample.NetFramework/Program.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Reflection; -using System.Runtime.Loader; using System.Threading; using System.Threading.Tasks; using Serilog; @@ -18,7 +17,6 @@ static void Main(string[] args) InitLogging(); AppDomain.CurrentDomain.ProcessExit += CurrentDomainOnProcessExit; - AssemblyLoadContext.Default.Unloading += DefaultOnUnloading; Console.CancelKeyPress += ConsoleOnCancelKeyPress; Console.WriteLine("|=======================|"); @@ -35,9 +33,14 @@ static void Main(string[] args) var url = new Uri("wss://www.bitmex.com/realtime"); using (var client = new WebsocketClient(url)) { + client.Name = "Bitmex"; client.ReconnectTimeoutMs = (int)TimeSpan.FromSeconds(30).TotalMilliseconds; client.ReconnectionHappened.Subscribe(type => Log.Information($"Reconnection happened, type: {type}")); + client.DisconnectionHappened.Subscribe(type => + Log.Warning($"Disconnection happened, type: {type}")); + + client.MessageReceived.Subscribe(msg => Log.Information($"Message received: {msg}")); client.Start(); @@ -78,12 +81,6 @@ private static void CurrentDomainOnProcessExit(object sender, EventArgs eventArg ExitEvent.Set(); } - private static void DefaultOnUnloading(AssemblyLoadContext assemblyLoadContext) - { - Log.Warning("Unloading process"); - ExitEvent.Set(); - } - private static void ConsoleOnCancelKeyPress(object sender, ConsoleCancelEventArgs e) { Log.Warning("Canceling process"); diff --git a/test_integration/Websocket.Client.Sample.NetFramework/Websocket.Client.Sample.NetFramework.csproj b/test_integration/Websocket.Client.Sample.NetFramework/Websocket.Client.Sample.NetFramework.csproj index cc941a5..0af8357 100644 --- a/test_integration/Websocket.Client.Sample.NetFramework/Websocket.Client.Sample.NetFramework.csproj +++ b/test_integration/Websocket.Client.Sample.NetFramework/Websocket.Client.Sample.NetFramework.csproj @@ -6,8 +6,8 @@ AnyCPU {56642A4E-B8CE-4399-9879-3E89C7C1FA3D} Exe - Bitmex.Client.Websocket.Sample.NetFramework - Bitmex.Client.Websocket.Sample.NetFramework + Websocket.Client.Sample.NetFramework + Websocket.Client.Sample.NetFramework v4.7.1 512 true @@ -36,7 +36,7 @@ ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll - ..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll + ..\..\packages\Serilog.2.8.0\lib\net46\Serilog.dll ..\..\packages\Serilog.Sinks.ColoredConsole.3.0.1\lib\net45\Serilog.Sinks.ColoredConsole.dll diff --git a/test_integration/Websocket.Client.Sample.NetFramework/packages.config b/test_integration/Websocket.Client.Sample.NetFramework/packages.config index 9a7f2f7..b03bb6b 100644 --- a/test_integration/Websocket.Client.Sample.NetFramework/packages.config +++ b/test_integration/Websocket.Client.Sample.NetFramework/packages.config @@ -1,7 +1,7 @@  - + diff --git a/test_integration/Websocket.Client.Sample/Program.cs b/test_integration/Websocket.Client.Sample/Program.cs index de6c311..6965cda 100644 --- a/test_integration/Websocket.Client.Sample/Program.cs +++ b/test_integration/Websocket.Client.Sample/Program.cs @@ -7,6 +7,7 @@ using Serilog; using Serilog.Events; + namespace Websocket.Client.Sample { class Program @@ -35,6 +36,7 @@ static void Main(string[] args) var url = new Uri("wss://www.bitmex.com/realtime"); using (var client = new WebsocketClient(url)) { + client.Name = "Bitmex"; client.ReconnectTimeoutMs = (int)TimeSpan.FromSeconds(30).TotalMilliseconds; client.ReconnectionHappened.Subscribe(type => Log.Information($"Reconnection happened, type: {type}")); @@ -72,7 +74,8 @@ private static void InitLogging() Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.File(logPath, rollingInterval: RollingInterval.Day) - .WriteTo.ColoredConsole(LogEventLevel.Verbose) + .WriteTo.ColoredConsole(LogEventLevel.Verbose, + outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message} {Properties}{NewLine}{Exception}") .CreateLogger(); }