diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs index 8e73049..8088790 100644 --- a/CryptoExchange.Net/BaseClient.cs +++ b/CryptoExchange.Net/BaseClient.cs @@ -37,7 +37,7 @@ namespace CryptoExchange.Net /// protected internal AuthenticationProvider? authProvider; /// - /// Should check received objects + /// Should check objects for missing properties based on the model and the received JSON /// public bool ShouldCheckObjects { get; set; } @@ -132,8 +132,9 @@ namespace CryptoExchange.Net /// The data to deserialize /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use + /// Id of the request /// - protected CallResult Deserialize(string data, bool? checkObject = null, JsonSerializer? serializer = null) + protected CallResult Deserialize(string data, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null) { var tokenResult = ValidateJson(data); if (!tokenResult) @@ -142,7 +143,7 @@ namespace CryptoExchange.Net return new CallResult(default, tokenResult.Error); } - return Deserialize(tokenResult.Data, checkObject, serializer); + return Deserialize(tokenResult.Data, checkObject, serializer, requestId); } /// @@ -153,7 +154,7 @@ namespace CryptoExchange.Net /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use /// - protected CallResult Deserialize(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null) + protected CallResult Deserialize(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null) { if (serializer == null) serializer = defaultSerializer; @@ -166,17 +167,17 @@ namespace CryptoExchange.Net { if (obj is JObject o) { - CheckObject(typeof(T), o); + CheckObject(typeof(T), o, requestId); } else if (obj is JArray j) { if (j.HasValues && j[0] is JObject jObject) - CheckObject(typeof(T).GetElementType(), jObject); + CheckObject(typeof(T).GetElementType(), jObject, requestId); } } catch (Exception e) { - log.Write(LogVerbosity.Debug, "Failed to check response data: " + e.Message); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Failed to check response data: " + e.Message); } } @@ -184,19 +185,19 @@ namespace CryptoExchange.Net } catch (JsonReaderException jre) { - var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } catch (JsonSerializationException jse) { - var info = $"Deserialize JsonSerializationException: {jse.Message}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } catch (Exception ex) { - var info = $"Deserialize Unknown Exception: {ex.Message}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {ex.Message}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } @@ -208,8 +209,9 @@ namespace CryptoExchange.Net /// The type to deserialize into /// The stream to deserialize /// A specific serializer to use + /// Id of the request /// - protected async Task> Deserialize(Stream stream, JsonSerializer? serializer = null) + protected async Task> Deserialize(Stream stream, JsonSerializer? serializer = null, int? requestId = null) { if (serializer == null) serializer = defaultSerializer; @@ -220,8 +222,8 @@ namespace CryptoExchange.Net if (log.Level == LogVerbosity.Debug) { var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Data received: {data}"); - return Deserialize(data); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{requestId}] ": "")}Data received: {data}"); + return Deserialize(data, null, serializer, requestId); } using var jsonReader = new JsonTextReader(reader); @@ -232,7 +234,7 @@ namespace CryptoExchange.Net if(stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } catch (JsonSerializationException jse) @@ -240,7 +242,7 @@ namespace CryptoExchange.Net if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize JsonSerializationException: {jse.Message}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } catch (Exception ex) @@ -248,7 +250,7 @@ namespace CryptoExchange.Net if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize Unknown Exception: {ex.Message}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {ex.Message}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } } @@ -259,7 +261,7 @@ namespace CryptoExchange.Net return await reader.ReadToEndAsync().ConfigureAwait(false); } - private void CheckObject(Type type, JObject obj) + private void CheckObject(Type type, JObject obj, int? requestId = null) { if (type == null) return; @@ -273,7 +275,7 @@ namespace CryptoExchange.Net if (!obj.HasValues && type != typeof(object)) { - log.Write(LogVerbosity.Warning, $"Expected `{type.Name}`, but received object was empty"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Expected `{type.Name}`, but received object was empty"); return; } @@ -299,7 +301,7 @@ namespace CryptoExchange.Net { if (!(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))) { - log.Write(LogVerbosity.Warning, $"Local object doesn't have property `{token.Key}` expected in type `{type.Name}`"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object doesn't have property `{token.Key}` expected in type `{type.Name}`"); isDif = true; } continue; @@ -314,9 +316,9 @@ namespace CryptoExchange.Net if (!IsSimple(propType) && propType != typeof(DateTime)) { if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject) - CheckObject(propType.GetElementType(), (JObject)token.Value[0]); + CheckObject(propType.GetElementType(), (JObject)token.Value[0], requestId); else if (token.Value is JObject o) - CheckObject(propType, o); + CheckObject(propType, o, requestId); } } @@ -329,11 +331,11 @@ namespace CryptoExchange.Net continue; isDif = true; - log.Write(LogVerbosity.Warning, $"Local object has property `{prop}` but was not found in received object of type `{type.Name}`"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object has property `{prop}` but was not found in received object of type `{type.Name}`"); } if (isDif) - log.Write(LogVerbosity.Debug, "Returned data: " + obj); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Returned data: " + obj); } private static PropertyInfo? GetProperty(string name, IEnumerable props) diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index 0f06bb5..a171b30 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -224,6 +224,11 @@ The auth provider + + + Should check objects for missing properties based on the model and the received JSON + + The last used id @@ -259,7 +264,7 @@ The data to parse - + Deserialize a string into an object @@ -267,9 +272,10 @@ The data to deserialize Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) A specific serializer to use + Id of the request - + Deserialize a JToken into an object @@ -279,13 +285,14 @@ A specific serializer to use - + Deserialize a stream into an object The type to deserialize into The stream to deserialize A specific serializer to use + Id of the request @@ -625,12 +632,13 @@ Request factory interface - + Create a request for an uri + @@ -1588,6 +1596,11 @@ The api credentials + + + Should check objects for missing properties based on the model and the received JSON + + Proxy to use @@ -1622,23 +1635,23 @@ The time the server has to respond to a request before timing out - + - http client + Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored and should be set on the provided HttpClient instance ctor - + The base address of the API ctor - - Shared http client + The base address of the API + Shared http client instance @@ -2042,12 +2055,13 @@ Request object - + Create request object for web request + @@ -2084,7 +2098,7 @@ - + @@ -2210,7 +2224,7 @@ Whether or not the request should be authenticated Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) Where the post parameters should be placed - How array paramters should be serialized + How array parameters should be serialized @@ -2229,7 +2243,7 @@ Received data Null if not an error, Error otherwise - + Creates a request object @@ -2238,7 +2252,8 @@ The parameters of the request Whether or not the request should be authenticated Where the post parameters should be placed - How array paramters should be serialized + How array parameters should be serialized + Unique id of a request @@ -2969,148 +2984,5 @@ - - - Specifies that is allowed as an input even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that is disallowed as an input even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that a method that will never return under any circumstance. - - - - - Initializes a new instance of the class. - - - - - Specifies that the method will not return if the associated - parameter is passed the specified value. - - - - - Gets the condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Initializes a new instance of the - class with the specified parameter value. - - - The condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Specifies that an output may be even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that when a method returns , - the parameter may be even if the corresponding type disallows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter may be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter may be . - - - - - Specifies that an output is not even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that the output will be non- if the - named parameter is non-. - - - - - Gets the associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Initializes the attribute with the associated parameter name. - - - The associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Specifies that when a method returns , - the parameter will not be even if the corresponding type allows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter will not be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter will not be . - - diff --git a/CryptoExchange.Net/Interfaces/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 64a714e..1544436 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -29,7 +29,7 @@ namespace CryptoExchange.Net.Interfaces /// /// internal request id for tracing /// - string RequestId { get; } + int RequestId { get; } /// /// Set byte content /// diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs index e05b371..2779d92 100644 --- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs +++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs @@ -14,8 +14,9 @@ namespace CryptoExchange.Net.Interfaces /// /// /// + /// /// - IRequest Create(HttpMethod method, string uri); + IRequest Create(HttpMethod method, string uri, int requestId); /// /// Configure the requests created by this factory diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index 502bcce..cc3cd9b 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -85,10 +85,12 @@ namespace CryptoExchange.Net.Objects /// The api credentials /// public ApiCredentials? ApiCredentials { get; set; } + /// - /// ShoouldCheckObjects + /// Should check objects for missing properties based on the model and the received JSON /// public bool ShouldCheckObjects { get; set; } = true; + /// /// Proxy to use /// @@ -129,22 +131,24 @@ namespace CryptoExchange.Net.Objects /// The time the server has to respond to a request before timing out /// public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30); + /// - /// http client + /// Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored and should be set on the provided HttpClient instance /// - public HttpClient? HttpClient; + public HttpClient? HttpClient { get; set; } + /// /// ctor /// - /// + /// The base address of the API public RestClientOptions(string baseAddress): base(baseAddress) { } /// /// ctor /// - /// - /// Shared http client + /// The base address of the API + /// Shared http client instance public RestClientOptions(HttpClient httpClient, string baseAddress) : base(baseAddress) { HttpClient = httpClient; diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 6fcecc3..b0b669a 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -22,11 +22,12 @@ namespace CryptoExchange.Net.Requests /// /// /// - public Request(HttpRequestMessage request, HttpClient client) + /// + public Request(HttpRequestMessage request, HttpClient client, int requestId) { httpClient = client; this.request = request; - RequestId = Path.GetRandomFileName(); + RequestId = requestId; } /// @@ -48,7 +49,7 @@ namespace CryptoExchange.Net.Requests /// public Uri Uri => request.RequestUri; /// - public string RequestId { get; } + public int RequestId { get; } /// public void SetContent(string data, string contentType) diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs index 3502c61..be68097 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -36,12 +36,12 @@ namespace CryptoExchange.Net.Requests } /// - public IRequest Create(HttpMethod method, string uri) + public IRequest Create(HttpMethod method, string uri, int requestId) { if (httpClient == null) throw new InvalidOperationException("Cant create request before configuring http client"); - return new Request(new HttpRequestMessage(method, uri), httpClient); + return new Request(new HttpRequestMessage(method, uri), httpClient, requestId); } } } diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 945ff79..11b07d8 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -166,38 +166,39 @@ namespace CryptoExchange.Net /// Whether or not the request should be authenticated /// Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) /// Where the post parameters should be placed - /// How array paramters should be serialized + /// How array parameters should be serialized /// [return: NotNull] protected virtual async Task> SendRequest(Uri uri, HttpMethod method, CancellationToken cancellationToken, Dictionary? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null) where T : class { - log.Write(LogVerbosity.Debug, "Creating request for " + uri); + var requestId = NextId(); + log.Write(LogVerbosity.Debug, $"[{requestId}] Creating request for " + uri); if (signed && authProvider == null) { - log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); + log.Write(LogVerbosity.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); return new WebCallResult(null, null, null, new NoApiCredentialsError()); } - var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization); + var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization, requestId); foreach (var limiter in RateLimiters) { var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour); if (!limitResult.Success) { - log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} failed because of rate limit"); + log.Write(LogVerbosity.Debug, $"[{requestId}] Request {uri.AbsolutePath} failed because of rate limit"); return new WebCallResult(null, null, null, limitResult.Error); } if (limitResult.Data > 0) - log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}"); + log.Write(LogVerbosity.Debug, $"[{requestId}] Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}"); } string? paramString = null; if (method == HttpMethod.Post) paramString = " with request body " + request.Content; - log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")} with id {request.RequestId}"); + log.Write(LogVerbosity.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")}"); return await GetResponse(request, cancellationToken).ConfigureAwait(false); } @@ -224,7 +225,7 @@ namespace CryptoExchange.Net var data = await reader.ReadToEndAsync().ConfigureAwait(false); responseStream.Close(); response.Close(); - log.Write(LogVerbosity.Debug, $"Data for request {request.RequestId} received: {data}"); + log.Write(LogVerbosity.Debug, $"[{request.RequestId}] Data received: {data}"); var parseResult = ValidateJson(data); if (!parseResult.Success) @@ -233,12 +234,12 @@ namespace CryptoExchange.Net if (error != null) return WebCallResult.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error); - var deserializeResult = Deserialize(parseResult.Data); + var deserializeResult = Deserialize(parseResult.Data, null, null, request.RequestId); return new WebCallResult(response.StatusCode, response.ResponseHeaders, deserializeResult.Data, deserializeResult.Error); } else { - var desResult = await Deserialize(responseStream).ConfigureAwait(false); + var desResult = await Deserialize(responseStream, null, request.RequestId).ConfigureAwait(false); responseStream.Close(); response.Close(); @@ -249,7 +250,7 @@ namespace CryptoExchange.Net { using var reader = new StreamReader(responseStream); var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Error for request {request.RequestId} received: {data}"); + log.Write(LogVerbosity.Debug, $"[{request.RequestId}] Error received: {data}"); responseStream.Close(); response.Close(); var parseResult = ValidateJson(data); @@ -258,7 +259,7 @@ namespace CryptoExchange.Net } catch (HttpRequestException requestException) { - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} exception: " + requestException.Message); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request exception: " + requestException.Message); return new WebCallResult(null, null, default, new ServerError(requestException.Message)); } catch (TaskCanceledException canceledException) @@ -266,14 +267,14 @@ namespace CryptoExchange.Net if (canceledException.CancellationToken == cancellationToken) { // Cancellation token cancelled - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} cancel requested"); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request cancel requested"); return new WebCallResult(null, null, default, new CancellationRequestedError()); } else { // Request timed out - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} timed out"); - return new WebCallResult(null, null, default, new WebError($"Request {request.RequestId} timed out")); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request timed out"); + return new WebCallResult(null, null, default, new WebError($"[{request.RequestId}] Request timed out")); } } } @@ -297,9 +298,10 @@ namespace CryptoExchange.Net /// The parameters of the request /// Whether or not the request should be authenticated /// Where the post parameters should be placed - /// How array paramters should be serialized + /// How array parameters should be serialized + /// Unique id of a request /// - protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization) + protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization, int requestId) { if (parameters == null) parameters = new Dictionary(); @@ -312,7 +314,7 @@ namespace CryptoExchange.Net uriString += "?" + parameters.CreateParamString(true, arraySerialization); var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; - var request = RequestFactory.Create(method, uriString); + var request = RequestFactory.Create(method, uriString, requestId); request.Accept = Constants.JsonContentHeader; var headers = new Dictionary();