Fixed several socket issues. Added backoff for reconnection.
This commit is contained in:
parent
aa9e3dbcd7
commit
ab90d47b89
@ -2,41 +2,55 @@
|
|||||||
|
|
||||||
namespace CommonSocketLibrary.Abstract
|
namespace CommonSocketLibrary.Abstract
|
||||||
{
|
{
|
||||||
public abstract class HandlerTypeManager<Client, Handler>
|
public interface ICodedOperation
|
||||||
|
{
|
||||||
|
int OperationCode { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMessageTypeManager
|
||||||
|
{
|
||||||
|
Type? GetMessageTypeByCode(int code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class MessageTypeManager<Handler> : IMessageTypeManager where Handler : ICodedOperation
|
||||||
{
|
{
|
||||||
private readonly IDictionary<int, Type> _types;
|
private readonly IDictionary<int, Type> _types;
|
||||||
public IDictionary<int, Type> HandlerTypes { get => _types; }
|
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
|
|
||||||
|
|
||||||
public HandlerTypeManager(ILogger logger, HandlerManager<Client, Handler> handlers)
|
public MessageTypeManager(IEnumerable<Handler> handlers, ILogger logger)
|
||||||
{
|
{
|
||||||
_types = new Dictionary<int, Type>();
|
_types = new Dictionary<int, Type>();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
GenerateHandlerTypes(handlers.Handlers);
|
GenerateHandlerTypes(handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Type? GetMessageTypeByCode(int code)
|
||||||
private void GenerateHandlerTypes(IDictionary<int, Handler> handlers)
|
|
||||||
{
|
{
|
||||||
foreach (var entry in handlers)
|
_types.TryGetValue(code, out Type? type);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateHandlerTypes(IEnumerable<Handler> handlers)
|
||||||
|
{
|
||||||
|
foreach (var handler in handlers)
|
||||||
{
|
{
|
||||||
if (entry.Value == null)
|
if (handler == null)
|
||||||
{
|
{
|
||||||
_logger.Error($"Failed to link websocket handler #{entry.Key} due to null value.");
|
_logger.Error($"Failed to link websocket handler due to null value.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = entry.Value.GetType();
|
var type = handler.GetType();
|
||||||
var target = FetchMessageType(type);
|
var target = FetchMessageType(type);
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
_logger.Error($"Failed to link websocket handler #{entry.Key} due to no match for {target}.");
|
_logger.Error($"Failed to link websocket handler #{handler.OperationCode} due to no match for {target}.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_types.Add(entry.Key, target);
|
_types.Add(handler.OperationCode, target);
|
||||||
_logger.Debug($"Linked websocket handler #{entry.Key} to type {target.AssemblyQualifiedName}.");
|
_logger.Debug($"Linked websocket handler #{handler.OperationCode} to type {target.AssemblyQualifiedName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,51 +1,56 @@
|
|||||||
|
using CommonSocketLibrary.Backoff;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using System.Collections;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace CommonSocketLibrary.Abstract
|
namespace CommonSocketLibrary.Abstract
|
||||||
{
|
{
|
||||||
public abstract class SocketClient<Message> : IDisposable
|
public abstract class SocketClient<Message> : IDisposable where Message : class
|
||||||
{
|
{
|
||||||
private ClientWebSocket? _socket;
|
protected ClientWebSocket? _socket;
|
||||||
private CancellationTokenSource? _cts;
|
protected CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
private readonly int ReceiveBufferSize = 8192;
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
protected readonly JsonSerializerOptions _options;
|
protected readonly JsonSerializerOptions _options;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public bool Connected { get; set; }
|
public event EventHandler<EventArgs> OnConnected;
|
||||||
public int ReceiveBufferSize { get; } = 8192;
|
public event EventHandler<SocketDisconnectionEventArgs> OnDisconnected;
|
||||||
|
|
||||||
|
|
||||||
public SocketClient(ILogger logger, JsonSerializerOptions options)
|
public SocketClient(ILogger logger, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_options = options;
|
_options = options;
|
||||||
Connected = false;
|
_disposed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ConnectAsync(string url)
|
protected async Task ConnectAsync(string url)
|
||||||
{
|
{
|
||||||
if (_socket != null)
|
if (_socket != null)
|
||||||
{
|
{
|
||||||
if (_socket.State == WebSocketState.Open) return;
|
if (_socket.State == WebSocketState.Open) return;
|
||||||
else _socket.Dispose();
|
else if (!_disposed) _socket.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_socket = new ClientWebSocket();
|
_socket = new ClientWebSocket();
|
||||||
_socket.Options.RemoteCertificateValidationCallback = (o, c, ch, er) => true;
|
_socket.Options.RemoteCertificateValidationCallback = (o, c, ch, er) => true;
|
||||||
_socket.Options.UseDefaultCredentials = false;
|
_socket.Options.UseDefaultCredentials = false;
|
||||||
|
_disposed = false;
|
||||||
if (_cts != null) _cts.Dispose();
|
if (_cts != null) _cts.Dispose();
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
await _socket.ConnectAsync(new Uri(url), _cts.Token);
|
await _socket.ConnectAsync(new Uri(url), _cts.Token);
|
||||||
await Task.Factory.StartNew(ReceiveLoop, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
await Task.Factory.StartNew(ReceiveLoop, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||||
await OnConnection();
|
OnConnected?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisconnectAsync()
|
public async Task DisconnectAsync(SocketDisconnectionEventArgs args)
|
||||||
{
|
{
|
||||||
if (_socket == null || _cts == null) return;
|
if (_disposed || _socket == null || _cts == null)
|
||||||
// TODO: requests cleanup code, sub-protocol dependent.
|
return;
|
||||||
|
|
||||||
if (_socket.State == WebSocketState.Open)
|
if (_socket.State == WebSocketState.Open)
|
||||||
{
|
{
|
||||||
_cts.CancelAfter(TimeSpan.FromMilliseconds(500));
|
_cts.CancelAfter(TimeSpan.FromMilliseconds(500));
|
||||||
@ -53,14 +58,20 @@ namespace CommonSocketLibrary.Abstract
|
|||||||
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
|
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Connected = false;
|
OnDisconnected?.Invoke(this, args);
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
_socket = null;
|
_socket = null;
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
_cts = null;
|
_cts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => DisconnectAsync().Wait();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ReceiveLoop()
|
private async Task ReceiveLoop()
|
||||||
{
|
{
|
||||||
@ -87,78 +98,84 @@ namespace CommonSocketLibrary.Abstract
|
|||||||
await ResponseReceived(outputStream);
|
await ResponseReceived(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) { }
|
catch (WebSocketException wse)
|
||||||
|
{
|
||||||
|
string data = string.Join(string.Empty, wse.Data.Cast<DictionaryEntry>().Select(e => e.Key + "=" + e.Value));
|
||||||
|
_logger.Error($"Websocket connection problem while receiving data [state: {_socket.State}][code: {wse.ErrorCode}][data: {data}]");
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
_logger.Error($"Socket's receive loop got canceled forcefully [state: {_socket.State}]");
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
outputStream?.Dispose();
|
if (_socket.State.ToString().Contains("Close") || _socket.State == WebSocketState.Aborted)
|
||||||
|
await DisconnectAsync(new SocketDisconnectionEventArgs(_socket.CloseStatus.ToString()!, _socket.CloseStatusDescription ?? string.Empty));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendRaw(string content)
|
protected async Task Reconnect(IBackoff backoff, Action reconnect)
|
||||||
{
|
{
|
||||||
if (!Connected) return;
|
while (true)
|
||||||
|
|
||||||
var bytes = new byte[1024 * 4];
|
|
||||||
var array = new ArraySegment<byte>(bytes);
|
|
||||||
var total = Encoding.UTF8.GetBytes(content).Length;
|
|
||||||
var current = 0;
|
|
||||||
|
|
||||||
while (current < total)
|
|
||||||
{
|
{
|
||||||
var size = Encoding.UTF8.GetBytes(content.Substring(current), array);
|
try
|
||||||
await _socket.SendAsync(array, WebSocketMessageType.Text, true, _cts.Token);
|
|
||||||
current += size;
|
|
||||||
}
|
|
||||||
await OnMessageSend(-1, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Send<T>(int opcode, T data)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var message = GenerateMessage(opcode, data);
|
|
||||||
var content = JsonSerializer.Serialize(message, _options);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(content);
|
|
||||||
var array = new ArraySegment<byte>(bytes);
|
|
||||||
var total = bytes.Length;
|
|
||||||
var current = 0;
|
|
||||||
|
|
||||||
while (current < total)
|
|
||||||
{
|
{
|
||||||
var size = Encoding.UTF8.GetBytes(content.Substring(current), array);
|
TimeSpan delay = backoff.GetNextDelay();
|
||||||
await _socket.SendAsync(array, WebSocketMessageType.Text, current + size >= total, _cts.Token);
|
await Task.Delay(delay);
|
||||||
current += size;
|
reconnect.Invoke();
|
||||||
|
backoff.Reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_logger.Error("Unable to reconnect to server.");
|
||||||
}
|
}
|
||||||
await OnMessageSend(opcode, content);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Connected = false;
|
|
||||||
_logger.Error(e, "Failed to send a message: " + opcode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ResponseReceived(Stream stream)
|
private async Task ResponseReceived(Stream stream)
|
||||||
{
|
{
|
||||||
|
Message? data = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = await JsonSerializer.DeserializeAsync<Message>(stream);
|
data = await JsonSerializer.DeserializeAsync<Message>(stream, _options);
|
||||||
await OnResponseReceived(data);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to read or execute a websocket message.");
|
_logger.Error(ex, "Failed to read a websocket message.");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
stream.Dispose();
|
stream.Dispose();
|
||||||
}
|
}
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Failed to read a websocket message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await OnResponseReceived(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to execute a websocket message.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Message GenerateMessage<T>(int opcode, T data);
|
|
||||||
protected abstract Task OnResponseReceived(Message? content);
|
protected abstract Task OnResponseReceived(Message? content);
|
||||||
protected abstract Task OnMessageSend(int opcode, string? content);
|
}
|
||||||
protected abstract Task OnConnection();
|
|
||||||
|
public class SocketDisconnectionEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string Status { get; }
|
||||||
|
public string Reason { get; }
|
||||||
|
|
||||||
|
public SocketDisconnectionEventArgs(string status, string reason)
|
||||||
|
{
|
||||||
|
Status = status;
|
||||||
|
Reason = reason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
32
Backoff/ExponentialBackoff.cs
Normal file
32
Backoff/ExponentialBackoff.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace CommonSocketLibrary.Backoff
|
||||||
|
{
|
||||||
|
public class ExponentialBackoff : IBackoff
|
||||||
|
{
|
||||||
|
private int _initial;
|
||||||
|
private int _current;
|
||||||
|
private int _maximum;
|
||||||
|
|
||||||
|
|
||||||
|
public ExponentialBackoff(int initial, int maximum)
|
||||||
|
{
|
||||||
|
if (maximum < initial)
|
||||||
|
throw new InvalidOperationException("Initial backoff cannot be larger than maximum backoff.");
|
||||||
|
|
||||||
|
_initial = initial;
|
||||||
|
_maximum = maximum;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TimeSpan GetNextDelay()
|
||||||
|
{
|
||||||
|
_current = Math.Min(_current * 2, _maximum);
|
||||||
|
return TimeSpan.FromMilliseconds(_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_current = _initial / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Backoff/IBackoff.cs
Normal file
8
Backoff/IBackoff.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace CommonSocketLibrary.Backoff
|
||||||
|
{
|
||||||
|
public interface IBackoff
|
||||||
|
{
|
||||||
|
TimeSpan GetNextDelay();
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,8 @@ using CommonSocketLibrary.Abstract;
|
|||||||
|
|
||||||
namespace CommonSocketLibrary.Common
|
namespace CommonSocketLibrary.Common
|
||||||
{
|
{
|
||||||
public interface IWebSocketHandler
|
public interface IWebSocketHandler : ICodedOperation
|
||||||
{
|
{
|
||||||
int OperationCode { get; }
|
|
||||||
Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data);
|
Task Execute<Data>(SocketClient<WebSocketMessage> sender, Data data);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using CommonSocketLibrary.Abstract;
|
using CommonSocketLibrary.Abstract;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -6,21 +8,22 @@ namespace CommonSocketLibrary.Common
|
|||||||
{
|
{
|
||||||
public class WebSocketClient : SocketClient<WebSocketMessage>
|
public class WebSocketClient : SocketClient<WebSocketMessage>
|
||||||
{
|
{
|
||||||
private readonly HandlerManager<WebSocketClient, IWebSocketHandler> _handlerManager;
|
protected IDictionary<int, IWebSocketHandler> _handlers;
|
||||||
private readonly HandlerTypeManager<WebSocketClient, IWebSocketHandler> _handlerTypeManager;
|
private readonly MessageTypeManager<IWebSocketHandler> _messageTypeManager;
|
||||||
|
|
||||||
public WebSocketClient(
|
public WebSocketClient(
|
||||||
ILogger logger,
|
IEnumerable<IWebSocketHandler> handlers,
|
||||||
HandlerManager<WebSocketClient, IWebSocketHandler> handlerManager,
|
MessageTypeManager<IWebSocketHandler> typeManager,
|
||||||
HandlerTypeManager<WebSocketClient, IWebSocketHandler> typeManager,
|
JsonSerializerOptions serializerOptions,
|
||||||
JsonSerializerOptions serializerOptions
|
ILogger logger
|
||||||
) : base(logger, serializerOptions)
|
) : base(logger, serializerOptions)
|
||||||
{
|
{
|
||||||
_handlerManager = handlerManager;
|
_handlers = handlers.ToDictionary(h => h.OperationCode, h => h);
|
||||||
_handlerTypeManager = typeManager;
|
_messageTypeManager = typeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebSocketMessage GenerateMessage<T>(int opcode, T data)
|
|
||||||
|
protected WebSocketMessage GenerateMessage<T>(int opcode, T data)
|
||||||
{
|
{
|
||||||
return new WebSocketMessage()
|
return new WebSocketMessage()
|
||||||
{
|
{
|
||||||
@ -29,28 +32,61 @@ namespace CommonSocketLibrary.Common
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnResponseReceived(WebSocketMessage? data)
|
protected override async Task OnResponseReceived(WebSocketMessage? message)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (message == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string content = data.Data?.ToString() ?? string.Empty;
|
string content = message.Data?.ToString() ?? string.Empty;
|
||||||
_logger.Verbose("RX #" + data.OpCode + ": " + content);
|
_logger.Verbose("RX #" + message.OpCode + ": " + content);
|
||||||
|
|
||||||
if (!_handlerTypeManager.HandlerTypes.TryGetValue(data.OpCode, out Type? type) || type == null)
|
var type = _messageTypeManager.GetMessageTypeByCode(message.OpCode);
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var obj = JsonSerializer.Deserialize(content, type, _options);
|
var data = JsonSerializer.Deserialize(content, type, _options);
|
||||||
await _handlerManager.Execute(this, data.OpCode, obj);
|
if (!_handlers.TryGetValue(message.OpCode, out IWebSocketHandler? handler) || handler == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.Execute(this, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnMessageSend(int opcode, string? content)
|
public async Task Send<T>(int opcode, T data)
|
||||||
{
|
{
|
||||||
_logger.Verbose("TX #" + opcode + ": " + content);
|
if (_socket == null)
|
||||||
}
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var message = GenerateMessage(opcode, data);
|
||||||
|
var content = JsonSerializer.Serialize(message, _options);
|
||||||
|
|
||||||
protected override async Task OnConnection()
|
var bytes = Encoding.UTF8.GetBytes(content);
|
||||||
{
|
var array = new ArraySegment<byte>(bytes);
|
||||||
|
var total = bytes.Length;
|
||||||
|
var current = 0;
|
||||||
|
|
||||||
|
while (current < total)
|
||||||
|
{
|
||||||
|
var size = Encoding.UTF8.GetBytes(content.Substring(current), array);
|
||||||
|
await _socket!.SendAsync(array, WebSocketMessageType.Text, current + size >= total, _cts!.Token);
|
||||||
|
current += size;
|
||||||
|
}
|
||||||
|
_logger.Verbose("TX #" + opcode + ": " + content);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (_socket.State.ToString().Contains("Close") || _socket.State == WebSocketState.Aborted)
|
||||||
|
{
|
||||||
|
await DisconnectAsync(new SocketDisconnectionEventArgs(_socket.CloseStatus.ToString()!, _socket.CloseStatusDescription ?? string.Empty));
|
||||||
|
_logger.Warning($"Socket state on closing = {_socket.State} | {_socket.CloseStatus?.ToString()} | {_socket.CloseStatusDescription}");
|
||||||
|
}
|
||||||
|
_logger.Error(e, $"Failed to send a websocket message [op code: {opcode}]");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,9 +4,9 @@ using Serilog;
|
|||||||
|
|
||||||
namespace CommonSocketLibrary.Socket.Manager
|
namespace CommonSocketLibrary.Socket.Manager
|
||||||
{
|
{
|
||||||
public abstract class WebSocketHandlerTypeManager : HandlerTypeManager<WebSocketClient, IWebSocketHandler>
|
public abstract class WebSocketMessageTypeManager : MessageTypeManager<IWebSocketHandler>
|
||||||
{
|
{
|
||||||
public WebSocketHandlerTypeManager(ILogger logger, HandlerManager<WebSocketClient, IWebSocketHandler> handlers) : base(logger, handlers)
|
public WebSocketMessageTypeManager(IEnumerable<IWebSocketHandler> handlers, ILogger logger) : base(handlers, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,13 +13,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MathParser.org-mXparser" Version="6.0.0" />
|
<PackageReference Include="MathParser.org-mXparser" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.1-dev-10391" />
|
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
|
||||||
<PackageReference Include="Serilog.Sinks.Trace" Version="4.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user